Spring框架整理(超详细)
Spring
Spring是什么
Spring 是 Java EE 编程领域的一款轻量级的开源框架,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,它的目标就是要简化 Java 企业级应用程序的开发难度和周期。
Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。
Spring 的诞生与发展
早期的 J2EE(Java EE 平台)推崇以 EJB 为核心的开发方式,但这种开发方式在实际的开发过程中存在种种弊端,例如使用复杂、代码臃肿、代码侵入性强、开发周期长、移植难度大等。
Rod Johnson 在其 2002 年编著的畅销书《Expert One-on-One J2EE Design and Development》中,针对 EJB 各种臃肿的结构进行了逐一的分析和否定,并分别以更加简洁的方式进行了替换。
在这本书中,Rod Johnson 通过一个包含 3 万行代码的附件,展示了如何在不使用 EJB 的情况下构建一个高质量、可扩展的 Java 应用程序。在这个附件中,Rod Johnson 编写了上万行基础结构代码,其中包含了许多可重用的 Java 接口和类,例如 ApplicationContext、BeanFactory 等。这些类的根包被命名为 com.interface21,含义为:这是提供给 21 世纪的一个参考。
这本书影响甚远,后来 Rod Johnson 将 com.interface21 的代码开源,并把这个新框架并命名为“Spring”,含义为:Spring 像一缕春风一样,扫平传统 J2EE 的寒冬。
2003 年 2 月,Spring 0.9 版本发布,它采用了 Apache 2.0 开源协议;2004 年 4 月,Spring 1.0 版本正式发布。到目前为止,Spring 已经步入到了第 5 个大版本,也就是我们常说的 Spring 5。
Spring 的狭义和广义
在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。
广义的 Spring:Spring 技术栈
广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。
经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。
这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。
项目名称 | 描述 |
---|---|
Spring Data | Spring 提供的数据访问模块,对 JDBC 和 ORM 提供了很好的支持。通过它,开发人员可以使用一种相对统一的方式,来访问位于不同类型数据库中的数据。 |
Spring Batch | 一款专门针对企业级系统中的日常批处理任务的轻量级框架,能够帮助开发人员方便的开发出健壮、高效的批处理应用程序。 |
Spring Security | 前身为 Acegi,是 Spring 中较成熟的子模块之一。它是一款可以定制化的身份验证和访问控制框架。 |
Spring Mobile | 是对 Spring MVC 的扩展,用来简化移动端 Web 应用的开发。 |
Spring Boot | 是 Spring 团队提供的全新框架,它为 Spring 以及第三方库一些开箱即用的配置,可以简化 Spring 应用的搭建及开发过程。 |
Spring Cloud | 一款基于 Spring Boot 实现的微服务框架。它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。 |
狭义的 Spring:Spring Framework
狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。
Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。
Spring 有两个核心部分: IoC 和 AOP。
核心 | 描述 |
---|---|
IOC | Inverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。 |
AOP | Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。 |
Spring 是一种基于 Bean 的编程技术,它深刻地改变着 Java 开发世界。Spring 使用简单、基本的 Java Bean 来完成以前只有 EJB 才能完成的工作,使得很多复杂的代码变得优雅和简洁,避免了 EJB 臃肿、低效的开发模式,极大的方便项目的后期维护、升级和扩展。
在实际开发中,服务器端应用程序通常采用三层体系架构,分别为表现层(web)、业务逻辑层(service)、持久层(dao)。
Spring 致力于 Java EE 应用各层的解决方案,对每一层都提供了技术支持。
在表现层提供了对 Spring MVC、Struts2 等框架的整合;
在业务逻辑层提供了管理事务和记录日志的功能;
在持久层还可以整合 MyBatis、Hibernate 和 JdbcTemplate 等技术,对数据库进行访问。
这充分地体现了 Spring 是一个全面的解决方案,对于那些已经有较好解决方案的领域,Spring 绝不做重复的事情。
从设计上看,Spring 框架给予了 Java 程序员更高的自由度,对业界的常见问题也提供了良好的解决方案,因此在开源社区受到了广泛的欢迎,并且被大部分公司作为 Java 项目开发的首选框架。
Spring Framework 的特点
Spring 框架具有以下几个特点。
方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
方便程序的测试
Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。
Spring体系结构
Spring 框架基本涵盖了企业级应用开发的各个方面,它包含了 20 多个不同的模块。
上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。
1. Data Access/Integration(数据访问/集成)
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
JDBC 模块:提供了一个 JBDC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
ORM 模块:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
OXM 模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
JMS 模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
Transactions 事务模块:支持编程和声明式事务管理。
2. Web 模块
Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。
Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
Servlet 模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
WebSocket 模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
3. Core Container(Spring 的核心容器)
Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。
Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
4. AOP、Aspects、Instrumentation 和 Messaging
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
5. Test 模块
Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。
第一个Spring程序
本节介绍如何使用 Eclipse IDE 创建一个简单的 Spring 程序。在编写之前,我们必须确保已经正确搭建了 Spring 开发环境,不了解的读者请转到:Spring开发环境搭建
1. 创建 Java 项目
在 Eclipse 中创建一个简单的 Java 项目,依次单击 File -> New -> Java Project,这里将项目名称设置为 HelloSpring。
2. 添加 jar 包
这里我们需要在项目中添加 Spring 和 logging 的 jar 包,操作步骤如下。
- 鼠标右键点击 HelloSpring 项目,然后在弹出的菜单中选择 Build Path -> Add external archives,选择 Spring 的 jar 包。
2. 这里我们可以先把 Spring 基础 Jar 包以及 Commons-loggin 导入到项目中,若后续功能增加,则根据需要再导入 Spring 的其他 Jar。
org.springframework.core-5.3.13.jar
org.springframework.beans-5.3.13.jar
spring-context-5.3.13.jar
spring-expression-5.3.13.jar
commons.logging-1.2.jar
当然,我们也可以直接将所有的 Spring Jar 包都一次性导入到项目中。
3. 创建 Java 类
在 HelloSpring 中创建 com.d 包,然后在这个包下创建 HelloWorld.java 和 MainApp.java 类。
- HelloWorld.java 类的代码如下。
package com.d; public class HelloWorld { private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } }
- MainApp.java 类的代码如下。
package com.d;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); HelloWorld obj = context.getBean("helloWorld",HelloWorld.class); obj.getMessage(); }}
关于以上代码,需要注意以下两点:
创建 ApplicationContext 对象时使用了 ClassPathXmlApplicationContext 类,这个类用于加载Spring 配置文件、创建和初始化所有对象(Bean)。
ApplicationContext.getBean() 方法用来获取 Bean,该方法返回值类型为 Object,通过强制类型转换为 HelloWorld 的实例对象,调用其中的 getMessage() 方法。
4. 创建配置文件
在 src 目录下,创建一个 Spring 配置文件 Beans.xml,内容如下。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="helloWorld" class="com.d.HelloWorld"> <property name="message" value="Hello World!" /> </bean></beans>
我们也可以将该配置文件命名为其它有效的名称,但需要注意的是,该文件名必须与 MainApp.java 中读取的配置文件名称一致。
Beans.xml 用于给不同的 Bean 分配唯一的 ID,并给相应的 Bean 属性赋值。例如,在以上代码中,我们可以在不影响其它类的情况下,给 message 变量赋值。
5. 运行程序
运行 MainApp.java,Eclipse IDE 控制台中显示信息如下。
message : Hello World!
至此,我们就成功创建了第一个 Spring 应用程序。
Spring IoC(控制反转)
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。
控制反转(IoC)
在传统的 Java 应用中,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用。为了方便理解和描述,我们可以将前者称为“调用者”,将后者称为“被调用者”。也就是说,调用者掌握着被调用者对象创建的控制权。
但在 Spring 应用中,Java 对象创建的控制权是掌握在 IoC 容器手里的,其大致步骤如下。
1.开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用 标签、在 Java 类上使用 @Component 注解等。
2.Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
3.当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
IoC 带来的最大改变不是代码层面的,而是从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IoC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IoC 容器创建它所需要的对象(Bean)。
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。
依赖注入(DI)
在了解了 IoC 之后,我们还需要了解另外一个非常重要的概念:依赖注入。
依赖注入(Denpendency Injection,简写为 DI)是 Martin Fowler 在 2004 年在对“控制反转”进行解释时提出的。Martin Fowler 认为“控制反转”一词很晦涩,无法让人很直接的理解“到底是哪里反转了”,因此他建议使用“依赖注入”来代替“控制反转”。
在面向对象中,对象和对象之间是存在一种叫做“依赖”的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象。
例如,有一个名为 B 的 Java 类,它的代码如下。
public class B { String bid; A a; }
从代码可以看出,B 中存在一个 A 类型的对象属性 a,此时我们就可以说 B 的对象依赖于对象 a。而依赖注入就是就是基于这种“依赖关系”而产生的。
我们知道,控制反转核心思想就是由 Spring 负责对象的创建。在对象创建过程中,Spring 会自动根据依赖关系,将它依赖的对象注入到当前对象中,这就是所谓的“依赖注入”。
依赖注入本质上是 Spring Bean 属性注入的一种,只不过这个属性是一个对象属性而已。
IoC 的工作原理
在 Java 软件开发过程中,系统中的各个对象之间、各个模块之间、软件系统和硬件系统之间,或多或少都存在一定的耦合关系。
若一个系统的耦合度过高,那么就会造成难以维护的问题,但完全没有耦合的代码几乎无法完成任何工作,这是由于几乎所有的功能都需要代码之间相互协作、相互依赖才能完成。因此我们在程序设计时,所秉承的思想一般都是在不影响系统功能的前提下,最大限度的降低耦合度。
IoC 底层通过工厂模式、Java 的反射机制、XML 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下。
1.在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
2.我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
3.容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
4.IoC 利用 Java 的反射机制,根据类名生成相应的对象(即 Spring Bean),并根据依赖关系将这个对象注入到依赖它的对象中。
由于对象的基本信息、对象之间的依赖关系都是在配置文件中定义的,并没有在代码中紧密耦合,因此即使对象发生改变,我们也只需要在配置文件中进行修改即可,而无须对 Java 代码进行修改,这就是 Spring IoC 实现解耦的原理。
IoC 容器的两种实现
IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。
BeanFactory
BeanFactory 是 IoC 容器的基本实现,也是 Spring 提供的最简单的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory 接口定义。
BeanFactory 采用懒加载(lazy-load)机制,容器在加载配置文件时并不会立刻创建 Java 对象,只有程序中获取(使用)这个对对象时才会创建。
示例 1
下面我们通过一个实例演示,来演示下 BeanFactory 的使用。
- 在 HelloSpring 项目中,将 MainApp 的代码修改为使用 BeanFactory 获取 HelloWorld 的对象,具体代码如下。
public static void main(String[] args) { BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml"); HelloWorld obj = context.getBean("helloWorld", HelloWorld.class); obj.getMessage();}
- 运行 MainApp.java,控制台输出如下。
message : Hello World!
注意:BeanFactory 是 Spring 内部使用接口,通常情况下不提供给开发人员使用。
ApplicationContext
ApplicationContext 是 BeanFactory 接口的子接口,是对 BeanFactory 的扩展。ApplicationContext 在 BeanFactory 的基础上增加了许多企业级的功能,例如 AOP(面向切面编程)、国际化、事务支持等。
ApplicationContext 接口有两个常用的实现类,具体如下表。
实现类 | 描述 | 示例代码 |
---|---|---|
ClassPathXmlApplicationContext | 加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation); |
FileSystemXmlApplicationContext | 加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作 | ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation); |
在上表的示例代码中,参数 configLocation 用于指定 Spring 配置文件的名称和位置,如 Beans.xml。
示例 2
下面我们就通过一个实例,来演示 ApplicationContext 的使用。
- 修改 HelloSpring 项目 MainApp 类中 main() 方法的代码,具体代码如下。
public static void main(String[] args) { //使用 FileSystemXmlApplicationContext 加载指定路径下的配置文件 Bean.xml BeanFactory context = new FileSystemXmlApplicationContext("D:\\eclipe workspace\\spring workspace\\HelloSpring\\src\\Beans.xml"); HelloWorld obj = context.getBean("helloWorld", HelloWorld.class); obj.getMessage(); }
- 运行 MainApp.java,控制台输出如下。
message : Hello World!
Spring Bean定义
由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。
我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。
通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 ,该元素包含了多个子元素 。每一个 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。
例如,在《第一个Spring程序》一节中的 Beans.xml 配置文件,代码如下所示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="helloWorld" class="com.d.HelloWorld"> <property name="message" value="Hello World!"/> </bean> </beans>
在 XML 配置的 元素中可以包含多个属性或子元素,常用的属性或子元素如下表所示。
属性名称 | 描述 |
---|---|
id | Bean 的唯一标识符,Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始,可以使用字母、数字、下划线等符号。 |
name | 该属性表示 Bean 的名称,我们可以通过 name 属性为同一个 Bean 同时指定多个名称,每个名称之间用逗号或分号隔开。Spring 容器可以通过 name 属性配置和管理容器中的 Bean。 |
class | 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,即类的全限定名。 |
scope | 表示 Bean 的作用域,属性值可以为 singleton(单例)、prototype(原型)、request、session 和 global Session。默认值是 singleton。 |
constructor-arg | bean元素的子元素,我们可以通过该元素,将构造参数传入,以实现 Bean 的实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型。 |
property | bean元素的子元素,用于调用 Bean 实例中的 setter 方法对属性进行赋值,从而完成属性的注入。该元素的 name 属性用于指定 Bean 实例中相应的属性名。 |
ref | property 和 constructor-arg 等元素的子元索,用于指定对某个 Bean 实例的引用,即 bean 元素中的 id 或 name 属性。 |
value | property 和 constractor-arg 等元素的子元素,用于直接指定一个常量值。 |
list | 用于封装 List 或数组类型的属性注入。 |
set | 用于封装 Set 类型的属性注入 |
map | 用于封装 Map 类型的属性注入。 |
entry | map 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值。 |
init-method | 容器加载 Bean 时调用该方法,类似于 Servlet 中的 init() 方法 |
destroy-method | 容器删除 Bean 时调用该方法,类似于 Servlet 中的 destroy() 方法。该方法只在 scope=singleton 时有效 |
lazy-init | 懒加载,值为 true,容器在首次请求时才会创建 Bean 实例;值为 false,容器在启动时创建 Bean 实例。该方法只在 scope=singleton 时有效 |
Spring Bean属性注入
所谓 Bean 属性注入,简单点说就是将属性注入到 Bean 中的过程,而这属性既可以普通属性,也可以是一个对象(Bean)。
Spring 主要通过以下 2 种方式实现属性注入:
构造函数注入
setter 注入(又称设值注入)
构造函数注入
我们可以通过 Bean 的带参构造函数,以实现 Bean 的属性注入。
使用构造函数实现属性注入大致步骤如下:
- 在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性;
- 在 Spring 的 XML 配置文件中,通过 及其子元素 对 Bean 进行定义;
- 在 元素内使用 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 元素。
示例 1
下面我们就通过一个实例,来演示下如何构造函数注入的方式实现属性注入。
-
新建一个名为 my-spring-demo 的 Java 项目,并在 src 下创建一个名为 com.d 的包。
-
参考《第一个Spring程序》,向项目中导入所需的 Jar 包。
-
在 com.d 包下,创建一个名为 Grade 的类,代码如下。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class Grade { private static final Log LOGGER = LogFactory.getLog(Grade.class); private Integer gradeId; private String gradeName; public Grade(Integer gradeId, String gradeName) { LOGGER.info("正在执行 Course 的有参构造方法,参数分别为:gradeId=" + gradeId + ",gradeName=" + gradeName); this.gradeId = gradeId; this.gradeName = gradeName; } @Override public String toString() { return "Grade{" + "gradeId=" + gradeId + ", gradeName='" + gradeName + '\'' + '}'; }}
- 在 com.d包下,创建一个名为 Student 的类,代码如下。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class Student { private static final Log LOGGER = LogFactory.getLog(Student.class); private int id; private String name; private Grade grade; public Student(int id, String name, Grade grade) { LOGGER.info("正在执行 Course 的有参构造方法,参数分别为:id=" + id + ",name=" + name + ",grade=" + grade); this.id = id; this.name = name; this.grade = grade; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", grade=" + grade + '}'; }}
- 在 src 目录下创建 Spring 配置文件 Beans.xml,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="student" class="com.d.Student"> <constructor-arg name="id" value="2"></constructor-arg> <constructor-arg name="name" value="李四"></constructor-arg> <constructor-arg name="grade" ref="grade"></constructor-arg> </bean> <bean id="grade" class="com.d.Grade"> <constructor-arg name="gradeId" value="4"></constructor-arg> <constructor-arg name="gradeName" value="四年级"></constructor-arg> </bean> </beans>
- 在 com.d 包下,创建一个名为 MainApp 的类,代码如下。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp { private static final Log LOGGER = LogFactory.getLog(MainApp.class); public static void main(String[] args) { //获取 ApplicationContext 容器 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //获取名为 student 的 Bean Student student = context.getBean("student", Student.class); //通过日志打印学生信息 LOGGER.info(student.toString()); }}
- 执行 MainApp 中的 main() 方法,控制台输出如下。
十二月 16, 2021 4:38:42 下午 com.d.Grade <init>信息: 正在执行 Course 的有参构造方法,参数分别为:gradeId=4,gradeName=四年级十二月 16, 2021 4:38:42 下午 com.d.Student <init>信息: 正在执行 Course 的有参构造方法,参数分别为:id=2,name=李四,grade=Grade{gradeId=4, gradeName='四年级'}十二月 16, 2021 4:38:42 下午 com.d.MainApp main信息: Student{id=2, name='李四', grade=Grade{gradeId=4, gradeName='四年级'}}
setter 注入
我们可以通过 Bean 的 setter 方法,将属性值注入到 Bean 的属性中。
在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
使用 setter 注入的方式进行属性注入,大致步骤如下:
1.在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx()方法;
2.在 Spring 的 XML 配置文件中,使用 及其子元素 对 Bean 进行定义;
3.在 元素内使用 元素对各个属性进行赋值。
示例 2
下面,我们就通过一个实例,来演示如何通过 setter 注入的方式实现属性注入,步骤如下。
- 在 com.d 包下,修改 Student 类的代码。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class Student { private static final Log LOGGER = LogFactory.getLog(Student.class); private int id; private String name; private Grade grade; //无参构造方法,在没有其他带参构造方法的情况下,可以省略 public Student() { } //id 属性的 setter 方法 public void setId(int id) { LOGGER.info("正在执行 Student 类的 setId() 方法…… "); this.id = id; } //name 属性的 setter 方法 public void setName(String name) { LOGGER.info("正在执行 Student 类的 setName() 方法…… "); this.name = name; } public void setGrade(Grade grade) { LOGGER.info("正在执行 Student 类的 setGrade() 方法…… "); this.grade = grade; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", grade=" + grade + '}'; }}
- 在 com.d 包下,修改 Grade 类的代码。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class Grade { private static final Log LOGGER = LogFactory.getLog(Grade.class); private Integer gradeId; private String gradeName; /** * 无参构造函数 * 若该类中不存在其他的带参构造函数,则这个默认的无参构造函数可以省略 */ public Grade() { } public void setGradeId(Integer gradeId) { LOGGER.info("正在执行 Grade 类的 setGradeId() 方法…… "); this.gradeId = gradeId; } public void setGradeName(String gradeName) { LOGGER.info("正在执行 Grade 类的 setGradeName() 方法…… "); this.gradeName = gradeName; } @Override public String toString() { return "Grade{" + "gradeId=" + gradeId + ", gradeName='" + gradeName + '\'' + '}'; }}
- 在 src 目录下,修改配置文件 Beans.xml 的内容。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="student" class="com.d.Student"> <property name="id" value="1"></property> <property name="name" value="张三"></property> <property name="grade" ref="grade"></property> </bean> <bean id="grade" class="com.d.Grade"> <property name="gradeId" value="3"></property> <property name="gradeName" value="三年级"></property> </bean> </beans>
- 执行 MainApp 中的 main() 方法,控制台输出如下。
十二月 16, 2021 4:01:13 下午 com.d.Grade setGradeId信息: 正在执行 Grade 类的 setGradeId() 方法…… 十二月 16, 2021 4:01:13 下午 com.d.Grade setGradeName信息: 正在执行 Grade 类的 setGradeName() 方法…… 十二月 16, 2021 4:01:13 下午 com.d.Student setId信息: 正在执行 Student 类的 setId() 方法…… 十二月 16, 2021 4:01:13 下午 com.d.Student setName信息: 正在执行 Student 类的 setName() 方法…… 十二月 16, 2021 4:01:13 下午 com.d.Student setGrade信息: 正在执行 Student 类的 setGrade() 方法…… 十二月 16, 2021 4:01:13 下午 com.d.MainApp main信息: Student{id=1, name='张三', grade=Grade{gradeId=3, gradeName='三年级'}}
短命名空间注入
我们在通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 和 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置,如下表。
短命名空间 | 简化的 XML 配置 | 说明 |
---|---|---|
p命名空间 | bean元素中嵌套的 property 元素 | 是 setter 方式属性注入的一种快捷实现方式 |
c命名空间 | bean 元素中嵌套的 constructor 元素 | 是构造函数属性注入的一种快捷实现方式 |
p 命名空间注入
p 命名空间是 setter 方式属性注入的一种快捷实现方式。通过它,我们能够以 bean 属性的形式实现 setter 方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。
首先我们需要在配置文件的 元素中导入以下 XML 约束。
xmlns:p="http://www.springframework.org/schema/p"
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">
使用 p 命名空间注入依赖时,必须注意以下 3 点:
1.Java 类中必须有 setter 方法;
2.Java 类中必须有无参构造器(类中不包含任何带参构造函数的情况,无参构造函数默认存在);
3.在使用 p 命名空间实现属性注入前,XML 配置的 元素内必须先导入 p 命名空间的 XML 约束。
示例
下面我们通过一个简单的实例,演示下如何通过 p 命名空间实现属性注入。
-
参考《第一个 Spring程序》,新建一个名为 my-spring-demo6 的 Java 项目。
-
在 com.d包中,创建一个名为 Dept 的类,代码如下。
package com.d; public class Dept { //部门编号 private String deptNo; //部门名称 private String deptName; public void setDeptNo(String deptNo) { this.deptNo = deptNo; } public void setDeptName(String deptName) { this.deptName = deptName; } @Override public String toString() { return "Dept{" + "deptNo='" + deptNo + '\'' + ", deptName='" + deptName + '\'' + '}'; } }
- 在com.d包下,创建一个名为 Employee 的类,代码如下。
package com.d;public class Employee { //员工编号 private String empNo; //员工姓名 private String empName; //部门信息 private Dept dept; public void setEmpNo(String empNo) { this.empNo = empNo; } public void setEmpName(String empName) { this.empName = empName; } public void setDept(Dept dept) { this.dept = dept; } public Dept getDept() { return dept; } @Override public String toString() { return "Employee{" + "empNo='" + empNo + '\'' + ", empName='" + empName + '\'' + ", dept=" + dept + '}'; }}
- 在 src 目录下创建 Spring 配置文件 Beans.xml,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="employee" class="com.d.Employee" p:empName="小李" p:dept-ref="dept" p:empNo="22222"></bean> <bean id="dept" class="com.d.Dept" p:deptNo="1111" p:deptName="技术部"></bean></beans>
- 在 com.d 包下,创建一个名为 MainApp 的类,代码如下。
package com.d; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainApp { private static final Log LOGGER = LogFactory.getLog(MainApp.class); public static void main(String[] args) { //获取 ApplicationContext 容器 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //获取名为 employee 的 Bean Employee employee = context.getBean("employee", Employee.class); //通过日志打印信息 LOGGER.info(employee.toString()); } }
- 执行 MainApp 类中的 main() 方法,控制台输出如下。
十二月 20, 2021 3:18:54 下午 com.d.MainApp main信息: Employee{empNo='22222', empName='小李', dept=Dept{deptNo='1111', deptName='技术部'}}
c 命名空间注入
c 命名空间是构造函数注入的一种快捷实现方式。通过它,我们能够以 属性的形式实现构造函数方式的属性注入,而不再使用嵌套的 元素,以实现简化 Spring 的 XML 配置的目的。
首先我们需要在配置文件的 元素中导入以下 XML 约束。
xmlns:c="http://www.springframework.org/schema/c"
在导入 XML 约束后,我们就能通过以下形式实现属性注入。
<bean id="Bean 唯一标志符" class="包名+类名" c:普通属性="普通属性值" c:对象属性-ref="对象的引用">
使用 c 命名空间注入依赖时,必须注意以下 2 点:
1.Java 类中必须包含对应的带参构造器;
2.在使用 c 命名空间实现属性注入前,XML 配置的 元素内必须先导入 c 命名空间的 XML 约束。
示例
下面我们通过一个简单的实例,演示下如何通过 c 命名空间实现属性注入。
- 修改 Dept 中的代码,添加一个有参构造函数。
package com.d;public class Dept { //部门编号 private String deptNo; //部门名称 private String deptName; public Dept(String deptNo, String deptName) { this.deptNo = deptNo; this.deptName = deptName; } @Override public String toString() { return "Dept{" + "deptNo='" + deptNo + '\'' + ", deptName='" + deptName + '\'' + '}'; }}
- 修改 Employee 中的代码,添加一个有参构造函数。
package com.d;public class Employee { //员工编号 private String empNo; //员工姓名 private String empName; //部门信息 private Dept dept; public Employee(String empNo, String empName, Dept dept) { this.empNo = empNo; this.empName = empName; this.dept = dept; } @Override public String toString() { return "Employee{" + "empNo='" + empNo + '\'' + ", empName='" + empName + '\'' + ", dept=" + dept + '}'; }}
- 修改 Beans.xml 中的配置,使用 c 命名空间实现属性注入。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:c="http://www.springframework.org/schema/c"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="employee" class="com.d.Employee" c:empName="小胡" c:dept-ref="dept" c:empNo="999"></bean> <bean id="dept" class="com.d.Dept" c:deptNo="2222" c:deptName="测试部"></bean></beans>
- 重新执行 MainApp 中的 main() 方法,控制台输出如下。
十二月 20, 2021 3:35:33 下午 com.d.MainApp main信息: Employee{empNo='999', empName='小胡', dept=Dept{deptNo='2222', deptName='测试部'}}
Spring注入内部Bean
我们将定义在 元素的 或 元素内部的 Bean,称为“内部 Bean”。
setter 方式注入内部 Bean
我们可以通过 setter 方式注入内部 Bean。此时,我们只需要在 标签下的 元素中,再次使用 元素对内部 Bean 进行定义,格式如下。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="outerBean" class="……"> <property name="……" > <bean class="……"> <property name="……" value="……" ></property> …… </bean> </property> </bean> </beans>
注意:内部 Bean 都是匿名的,不需要指定 id 和 name 的。即使制定了,IoC 容器也不会将它作为区分 Bean 的标识符,反而会无视 Bean 的 Scope 标签。因此内部 Bean 几乎总是匿名的,且总会随着外部的 Bean 创建。内部 Bean是无法被注入到它所在的 Bean 以外的任何其他 Bean 的。
示例
下面我们就通过一个实例,演示下如何使用 setter 方法注入内部 Bean。
-
参考《第一个 Spring 程序》,新建一个名为 my-spring-demo2 的 Java 项目。
-
在 com.d 包中,创建一个名为 Dept 的类,代码如下。
package com.d;public class Dept { //部门编号 private String deptNo; //部门名称 private String deptName; public void setDeptNo(String deptNo) { this.deptNo = deptNo; } public void setDeptName(String deptName) { this.deptName = deptName; } @Override public String toString() { return "Dept{" + "deptNo='" + deptNo + '\'' + ", deptName='" + deptName + '\'' + '}'; }}
- 在 com.d 包下,创建一个名为 Employee 的类,代码如下。
package com.d;public class Employee { //员工编号 private String empNo; //员工姓名 private String empName; //部门信息 private Dept dept; public void setEmpNo(String empNo) { this.empNo = empNo; } public void setEmpName(String empName) { this.empName = empName; } public void setDept(Dept dept) { this.dept = dept; } @Override public String toString() { return "Employee{" + "empNo='" + empNo + '\'' + ", empName='" + empName + '\'' + ", dept=" + dept + '}'; }}
- 在 src 目录下创建 Spring 配置文件 Beans.xml,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="employee" class="com.d.Employee"> <property name="empNo" value="001"></property> <property name="empName" value="小王"></property> <property name="dept"> <bean class="com.d.Dept"> <property name="deptNo" value="004"></property> <property name="deptName" value="技术部"></property> </bean> </property> </bean></beans>
- 在 com.d 包下,创建一个名为 MainApp 的类,代码如下。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp { private static final Log LOGGER = LogFactory.getLog(MainApp.class); public static void main(String[] args) { //获取 ApplicationContext 容器 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //获取名为 employee 的 Bean Employee employee = context.getBean("employee", Employee.class); //通过日志打印员工信息 LOGGER.info(employee.toString()); }}
- 执行 MainApp 中的 main() 方法,控制台输出如下。
十二月 17, 2021 10:46:06 上午 com.d.MainApp main信息: Employee{empNo='001', empName='小王', dept=Dept{deptNo='004', deptName='技术部'}}
构造函数方式注入内部 Bean
我们可以通过构造方法注入内部 Bean。此时,我们只需要在 标签下的 元素中,再次使用 元素对内部 Bean 进行定义,格式如下。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="……" class="……"> <constructor-arg name="……"> <bean class="……"> <constructor-arg name="……" value="……"></constructor-arg> …… </bean> </constructor-arg> </bean></beans>
示例
下面我们就通过一个实例,演示下如何在通过构造方法的方式注入内部 Bean。
-
参考《第一个 Spring 程序》,新建一个名为 my-spring-demo3 的 Java 项目。
-
在 com.d 包中,创建一个名为 Dept 的类,代码如下。
package com.d;public class Dept { //部门编号 private String deptNo; //部门名称 private String deptName; public Dept(String deptNo, String deptName) { this.deptNo = deptNo; this.deptName = deptName; } @Override public String toString() { return "Dept{" + "deptNo='" + deptNo + '\'' + ", deptName='" + deptName + '\'' + '}'; }}
- 在com.d 包下,创建一个名为 Employee 的类,代码如下。
package com.d;public class Employee { //员工编号 private String empNo; //员工姓名 private String empName; //部门信息 private Dept dept; public Employee(String empNo, String empName, Dept dept) { this.empNo = empNo; this.empName = empName; this.dept = dept; } @Override public String toString() { return "Employee{" + "empNo='" + empNo + '\'' + ", empName='" + empName + '\'' + ", dept=" + dept + '}'; }}
- 在 src 目录下创建 Spring 配置文件 Beans.xml,配置如下。
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="employee" class="com.d.Employee"> <constructor-arg name="empNo" value="002"></constructor-arg> <constructor-arg name="empName" value="小李"></constructor-arg> <constructor-arg name="dept"> <bean class="com.d.Dept"> <constructor-arg name="deptNo" value="005"></constructor-arg> <constructor-arg name="deptName" value="运维部"></constructor-arg> </bean> </constructor-arg> </bean></beans>
- 在 com.d 包下,创建一个名为 MainApp 的类,代码如下。
package com.d;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class MainApp { private static final Log LOGGER = LogFactory.getLog(MainApp.class); public static void main(String[] args) { //获取 ApplicationContext 容器 ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); //获取名为 employee 的 Bean Employee employee = context.getBean("employee", Employee.class); //通过日志打印员工信息 LOGGER.info(employee.toString()); }}
- 执行 MainApp 中的 main() 方法,控制台输出如下。
十二月 17, 2021 10:56:36 上午 com.d.MainApp main信息: Employee{empNo='002', empName='小李', dept=Dept{deptNo='005', deptName='运维部'}}
阅读世界,共赴山海
423全民读书节,邀你共读美国云服务器