Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的开源容器框架,它是由 Rod Johnson(音乐学博士)所创建,其核心就是为了解决企业应用开发的复杂性。
Spring 是一款目前主流的 Java EE 轻量级开源框架,是 Java 世界最为成功的框架之一,自 2004 年 4 月,Spring 1.0 版本正式发布以来,Spring 已经步入到了第 5 个大版本,也就是我们常说的 Spring 5。本教程使用版本为 Spring 5.3.22。
Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。
Spring 框架不局限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何 Java 应用都可以从 Spring 中受益。Spring 框架还是一个超级粘合平台,除了自己提供功能外,还提供粘合其他技术和框架的能力。
Spring 致力于提供一个以统一的、高效的方式构造整个应用,并且可以将单层框架以最佳的组合揉和在一起建立一个连贯的体系。例如,通过 Spring 框架,整合 MyBatis 、SpringMVC等,也就是传说中的大杂烩。其主要具有以下特点:
-
方便解耦,简化开发:管理所有对象的创建和依赖的关系维护。
-
AOP编程的支持:方便的实现对程序进行权限拦截、运行监控等功能。
-
声明式事务的支持:通过配置完成对事务的管理,而无需手动编程。
-
方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
-
方便集成各种优秀框架:内部提供了对各种优秀框架的直接支持。
-
降低JavaEE API的使用难度:封装JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等)。
1.2、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 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。
-
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 绝不做重复的事情,这一理念即为“轮子理论”。
“轮子理论”,也即“不要重复发明轮子”,这是西方国家的一句谚语,意思是别人已经做过,我们需要用的时候,直接拿来用即可,而不要重新制造。将已出现的各种IT技术比作一个个“轮子”,当我们进行项目开发的时候,若已有的技术能满足我们的开发需求,我们不需要在去创造新的技术,只需要把现有的技术拿过来用就可以了。若已有的技术不能满足我们的开发需求,这时,我们就要去创造新的“轮子”。
1.3、Spring 的体系结构
Spring 框架基本涵盖了企业级应用开发的各个方面,它包含了 20 多个不同的模块。下图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍。
1.3.1、Core Container(Spring 的核心容器)Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下。
-
spring-core模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。。
-
spring-beans模块:提供了BeanFactory与Bean的装配,使Spring成为一个容器,也就是提供了框架的基本组成部分,包括控制反转(IOC)和依赖注入(DI)功能
-
spring-context模块:应用上下文,建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等,提供一个框架式的对象访问方式,是访问定义和配置任何对象的媒介,使Spring成为一个框架。ApplicationContext 接口是上下文模块的焦点。
-
spring-context-support模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
-
spring-expression(SpELl)模块:Spring 表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:
-
spring-aop模块:提供了一个符合 AOP 要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
-
spring-aspects模块:提供了与 AspectJ 的集成功能,AspectJ是 一个功能强大且成熟的面向切面编程(AOP) 框架。
-
spring-instrument模块:提供了类工具支持和类加载器的实现,可以在特定的应用服务器中使用。
-
spring-messaging模块:Spring 4.0 以后新增了消息模块,该模块提供了对消息传递体系结构和协议的支持。
数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
-
spring-jdbc模块:提供了一个 JBDC 的样例模板,,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,而且能享受到 Spring 管理事务的好处。
-
spring-orm模块:提供一个对象关系映射(Object-Relational Mapping)API 框架,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
-
spring-oxm模块:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
-
spring-jms模块:指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
-
spring-tx模块:事务模块,支持用于实现特殊接口和所有POJO类的编程和声明式事务管理。
Spring 的 Web 层包括 Web、Servlet、WebSocket 和 Portlet 组件,具体介绍如下。
-
spring-web模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
-
spring-webmvc模块:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
-
spring-websocket模块:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
-
spring-portlet模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。
1.4 Spring Hello World
首先我们需要创建一个 Maven 项目,创建成功后,我们需要导入 Spring 的相关依赖,这里推荐直接导入 spring-webmvc,这个 jar 包默认会为我们导入其他的依赖包,导入如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>JavaEE</artifactId>
<groupId>com.loner</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Spring</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
</dependency>
</dependencies>
</project>
依赖导入成功后,等待 Maven 拉取即可,接下来我们需要创建一个 HelloWorld 实体类,此类只有一个字符串了类型的字段,生成其 getter 和 setter 以及 toString()方法即可,实体类实现如下:
public class HelloWorld {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "HelloWorld{" + "message='" + message + '\'' + '}';
}
}
实体类创建成功后,我们就需要使用 Spring 的特有方式来装配 Bean,这也是 Spring 的核心思想,将对象的创建和销毁完全交付给 Spring 进行管理,我们只需要进行简单的配置即可使用。首先我们需要在 resource 目录下创建一个 Spring 的 xml 配置文件,名称随意,推荐使用 applicationContext.xml。创建成功后,需要引入 Spring 各个模块的约束,然后通过 <bean></bean>
进行 Bean 的装配,具体实现如下:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 装配Bean,相当于new一个HelloWorld对象,id等同于变量名,class就是类路径 -->
<bean id="helloWorld" class="com.loner.mj.pojo.HelloWorld">
<!-- 设置属性及其对应的值,name为实体的属性名,value为属性的值 -->
<property name="message" value="第一个Spring程序" />
</bean>
</beans>
注意,这里的 Bean 装配是通过 xml 配置文件的方式实现的,还有注解的实现方式以及 Java 配置类的实现方式等。
Bean 装配后,我们需要通过 Spring 的入口进行应用的启动,此时我们需要实例化 ClassPathXmlApplicationContext 对象,此对象需要传入一个或多个 xml 配置文件,他是 ApplicationContext 接口的一个实现类,此接口的实现类有很多,这里我们只使用 ClassPathXmlApplicationContext 对象,实现方式如下:
public class HelloTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// getBean的属性值就是Bean标签的id值
HelloWorld bean = (HelloWorld) applicationContext.getBean("helloWorld");
System.out.println(bean.getMessage());
}
}
到此为止,整个 Spring 应用即编写完成,启动程序后,控制台输出“第一个Spring程序”。从以上程序可以看出,我们从未手动的实例化过对象,我们只是在 Spring 的配置文件中,进行了 Bean 的装配,然后就可以在程序中使用此 Bean,这也印证了,Spring 框架无需我们考虑和关心 Bean 在何时实例化,我们只需要关心自己的核心业务即可,所有的 Bean 的创建和销毁都是 Spring 框架帮我们完成,因此接下来我们需要对这个框架的使用以及相关的原理进行探讨。
2、Spring IoC 2.1、IoC 和 DI 概述 2.1.1、IoC(控制反转) 的概念
IoC 是 Inversion of Control 的简写,译为“控制反转”,于1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中提出。它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。因此,IoC 的作用就是完成对象的创建和依赖的管理注入。
在传统的 Java 应用中,一个类想要调用另一个类中的属性或方法,通常会先在其代码中通过 new Object() 的方式将后者的对象创建出来,然后才能实现属性或方法的调用。为了方便理解和描述,我们可以将前者称为“调用者”,将后者称为“被调用者”,也就是说,调用者掌握着被调用者对象创建的控制权。
但在 Spring 应用中,Java 对象创建的控制权是掌握在 IoC 容器手里的,其大致步骤如下。
-
开发人员通过 XML 配置文件、注解、Java 配置类等方式,对 Java 对象进行定义,例如在 XML 配置文件中使用
<bean>
标签、在 Java 类上使用 @Component 注解等。 -
Spring 启动时,IoC 容器会自动根据对象定义,将这些对象创建并管理起来。这些被 IoC 容器创建并管理的对象被称为 Spring Bean。
-
当我们想要使用某个 Bean 时,可以直接从 IoC 容器中获取(例如通过 ApplicationContext 的 getBean() 方法),而不需要手动通过代码(例如 new Obejct() 的方式)创建。
IoC 带来的最大改变不是代码层面的,而是从思想层面上发生了“主从换位”的改变。原本调用者是主动的一方,它想要使用什么资源就会主动出击,自己创建;但在 Spring 应用中,IoC 容器掌握着主动权,调用者则变成了被动的一方,被动的等待 IoC 容器创建它所需要的对象(Bean)。
这个过程在职责层面发生了控制权的反转,把原本调用者通过代码实现的对象的创建,反转给 IoC 容器来帮忙实现,因此我们将这个过程称为 Spring 的“控制反转”。简单的理解就是原调用这相当于程序员,而 IoC 相当于用户,用户根据市场的变化随时修改程序,程序提供给用户以配置的方式来实现,而不需要大量的修改源代码来实现
2.1.2、DI(依赖注入) 的概念依赖注入(Denpendency Injection,简写为 DI)是 Martin Fowler 在 2004 年在对“控制反转”进行解释时提出的。Martin Fowler 认为“控制反转”一词很晦涩,无法让人很直接的理解“到底是哪里反转了”,因此他建议使用“依赖注入”来代替“控制反转”。
在面向对象中,对象和对象之间是存在一种叫做“依赖”的关系。简单来说,依赖关系就是在一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象,这种依赖一般都组合关系,还有一种就是继承关系。
总结来说,依赖注入就是在 IoC 容器运行期间,动态地将某种依赖关系注入到对象之中,也就是说获得依赖对象的过程被反转了。依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦。
例如,有一个名为 A 的 Java 类,它的代码如下:
public class A {
String bid;
B b;
}
从代码可以看出,A 中存在一个 B 类型的对象属性 b,此时我们就可以说 A 的对象依赖于对象 b(A 和 B 是组合关系),而依赖注入就是基于这种“依赖关系”而产生的。
我们知道,控制反转核心思想就是由 Spring 负责对象的创建。在对象创建过程中,Spring 会自动根据依赖关系,将它依赖的对象注入到当前对象中,这就是所谓的“依赖注入”。
依赖注入本质上是 Spring Bean 属性注入的一种,只不过这个属性是一个对象属性而已。
2.1.3、IoC 的工作原理在 Java 软件开发过程中,系统中的各个对象之间、各个模块之间、软件系统和硬件系统之间,或多或少都存在一定的耦合关系。
若一个系统的耦合度过高,那么就会造成难以维护的问题,但完全没有耦合的代码几乎无法完成任何工作,这是由于几乎所有的功能都需要代码之间相互协作、相互依赖才能完成。因此我们在程序设计时,所秉承的思想一般都是在不影响系统功能的前提下,最大限度的降低耦合度。
IoC 底层通过工厂模式、Java 的反射机制、XML 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下:
-
首先在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
-
我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
-
容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
-
IoC 利用 Java 的反射机制,根据类名生成相应的对象(即 Spring Bean),并根据依赖关系将这个对象注入到依赖它的对象中。
由于对象的基本信息、对象之间的依赖关系都是在配置文件中定义的,并没有在代码中紧密耦合,因此即使对象发生改变,我们也只需要在配置文件中进行修改即可,而无须对 Java 代码进行修改,这就是 Spring IoC 实现解耦的原理。
Spring 通过“轮子理论”进行设计,将传统的多个对象之间的复杂依赖关系,通过 IOC 容器进行解耦,把对象的创建和依赖交给 Spring 处理,使得各个对象之间的依赖由主动变为被动,把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IoC 相当于“粘合剂”,其整个实现思想如下图所示:
2.1.4、IoC 容器的两种实现IoC 思想基于 IoC 容器实现的,IoC 容器底层其实就是一个 Bean 工厂。Spring 框架为我们提供了两种不同类型 IoC 容器,它们分别是 BeanFactory 和 ApplicationContext。
BeanFactory 是 IoC 容器的基本实现,也是 Spring 提供的最简单的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory
接口定义。最典型的Bean工厂,定义了IoC容器的基本功能规范。
BeanFactory 采用懒加载(lazy-load)机制,容器在加载配置文件时并不会立刻创建 Java 对象,只有程序中获取(使用)这个对象时才会创建,具体实现如下:
public static void main(String[] args) {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloWorld", HelloWorld.class);
obj.getMessage();
}
注意:BeanFactory 是 Spring 内部使用接口,通常情况下不提供给开发人员使用,同时不建议使用此方式。
ApplicationContext 是 BeanFactory 接口的子接口,是对 BeanFactory 的扩展。ApplicationContext 在 BeanFactory 的基础上增加了许多企业级的功能,例如 AOP(面向切面编程)、国际化、事务支持等。ApplicationContext 接口有两个常用的实现类,具体如下:
-
ClassPathXmlApplicationContext:加载类路径 ClassPath 下指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作。
-
FileSystemXmlApplicationContext:加载指定的文件系统路径中指定的 XML 配置文件,并完成 ApplicationContext 的实例化工作。
例如,修改上述第一个 Spring 程序中的实例化方法,使其能够加载指定文件系统路径中的配置文件,具体实现如下:
public static void main(String[] args) {
//使用 FileSystemXmlApplicationContext 加载指定路径下的配置文件 Bean.xml
ApplicationContext context = new FileSystemXmlApplicationContext("D:\\eclipe workspace\\spring workspace\\HelloSpring\\src\\Beans.xml");
HelloWorld obj = context.getBean("helloWorld", HelloWorld.class);
obj.getMessage();
}
2.2、Spring Bean 2.2.1、Bean 的定义
由 Spring IoC 容器管理的对象称为 Bean,Bean 根据 Spring 配置文件中的信息创建。我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。如果希望这个大工厂生产和管理 Bean,就需要告诉容器需要哪些 Bean,以哪种方式装配。
Spring 配置文件支持两种格式,即 XML 文件格式和 Properties 文件格式。
-
Properties 配置文件主要以 key-value 键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
-
XML 配置文件采用树形结构,结构清晰,相较于 Properties 文件更加灵活。但是 XML 配置比较繁琐,适用于大型的复杂的项目。
通常情况下,Spring 的配置文件都是使用 XML 格式的。XML 配置文件的根元素是 <beans>
,该元素包含了多个子元素 <bean>
。每一个 <bean>
元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。例如在Spring Hello World中的配置一样。
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloWorld" class="com.loner.mj.pojo.HelloWorld">
<property name="message" value="第一个Spring程序" />
</bean>
</beans>
在 XML 配置的 <beans>
元素中可以包含多个属性或子元素,常用的属性或子元素如下所示:
-
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、set、map:用于封装 List 或数组类型、Set类型以及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 时有效
2.2.2、Bean 的依赖注入
Spring 主要有三种属性注入的方式,分贝是构造函数注入、setter注入(又称属性注入)以及拓展注入方式。在演示三种注入方式之前,我们首先创建一个用于测试的 Bean。
public class Student {
private String name; //基本类型
private Classes classes; // 对象类型
private String[] array; // 数组类型
private List<String> list; // List集合类型
private Map<String, String> map; // Map集合类型
private Set<Integer> set; // Set集合类型
private Properties properties; // 配置文件类型
// 此处省略getter、setter、有参构造以及toString
}
构造函数注入
通过 Bean 的带参构造函数注入时,首先要在 Bean 中添加一个有参构造函数,构造函数内的每一个参数代表一个需要注入的属性,其次需要在 <bean>
元素内使用 <constructor-arg>
元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 <constructor-arg>
元素,此标签的属性和<property>
一样,其实现主要如下:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 构造函数注入 -->
<bean id="student" class="com.loner.mj.pojo.Student">
<constructor-arg name="name" value="张三" />
</bean>
</beans>
setter 注入(属性注入)
在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
使用 setter 注入的方式进行属性注入,首先需要在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法,其次需要在 xml 配置文件使用 <bean>
元素内使用 <property>
元素对各个属性进行赋值,其实现主要如下:
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 依赖注入 -->
<bean id="classes" class="com.loner.mj.pojo.Classes">
<property name="name" value="一年级1班" />
</bean>
<!-- setter注入(属性注入) -->
<bean id="student2" class="com.loner.mj.pojo.Student">
<!-- 基本类型 -->
<property name="name" value="张三" />
<!-- 对象类型 -->
<property name="classes" ref="classes" />
<!-- 数组类型 -->
<property name="array">
<array>
<value>元素1</value>
<value>元素2</value>
</array>
</property>
<!-- list集合类型 -->
<property name="list">
<list>
<value>元素1</value>
<value>元素1</value>
</list>
</property>
<!-- set集合类型 -->
<property name="set">
<set>
<value>1</value>
<value>2</value>
</set>
</property>
<!-- map集合类型 -->
<property name="map">
<map>
<entry key="键" value="值" />
</map>
</property>
<!-- 配置文件类型 -->
<property name="properties">
<props>
<prop key="键">值</prop>
</props>
</property>
</bean>
</beans>
程序运行之后,输出的结果为:Student{name='张三', classes=Classes{name='一年级1班'}, array=[元素1, 元素2], list=[元素1, 元素1], map={键=值}, set=[1, 2], properties={键=值}}
拓展方式注入
我们在通过构造函数或 setter 方法进行属性注入时,通常是在 <bean>
元素中嵌套 <property> 和 <constructor-arg>
元素来实现的,这种方式虽然结构清晰,但书写较繁琐。
Spring 给我们提供了两种拓展方式来实现依赖注入,分别是p 命名空间和c命名空间
,其中p命名空间是 setter 方式属性注入的一种快捷实现方式,c命名空间是构造函数注入的一种快捷实现方式。
通过它两,我们能够以 bean 属性的形式实现 setter 方式的属性注入和构造函数注入,而不再使用嵌套的 <property> 和 <constructor-arg>
元素,以实现简化 Spring 的 XML 配置的目的。
使用两种拓展方式之前,首先要确保 Bean 生成了 setter 方法,并且使用c命名空间时,需要确保具有无参和有参构造才可以,然后我们需要在 xml 配置文件引入两者的约束,其约束如下:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
引入约束后,通过c:参数名和p:属性名
的方式就可以给 Bean 注入依赖了,其实现如下:
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 拓展方式注入 -->
<bean id="Bean 唯一标志符" class="包名+类名" p:普通属性="普通属性值" p:对象属性-ref="对象的引用">
<bean id="student3" class="com.loner.mj.pojo.Student" c:name="刘备"></bean>
<bean id="student4" class="com.loner.mj.pojo.Student" p:name="张三"></bean>
</beans>
2.2.3、Bean 的作用域
默认情况下,所有的 Spring Bean 都是单例的,也就是说在整个 Spring 应用中, Bean 的实例只有一个。
我们可以在 <bean>
元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
Spring 5 共提供了 6 种 scope 作用域,如下表。
注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。
singleton(单例模式)
singleton 是 Spring 容器默认的作用域。当 Bean 的作用域为 singleton 时,Spring IoC 容器中只会存在一个共享的 Bean 实例。这个 Bean 实例将存储在高速缓存中,所有对于这个 Bean 的请求和引用,只要 id 与这个 Bean 定义相匹配,都会返回这个缓存中的对象实例。
如果一个 Bean 定义的作用域为 singleton ,那么这个 Bean 就被称为 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的默认创建方式,可以更好地重用对象,节省重复创建对象的开销。
Singleton 是单例类型,就是在创建起容器时就同时自动创建了一个 Bean 的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,singleton 作用域是 Spring 中的缺省作用域
在 Spring 配置文件中,可以使用 <bean>
元素的 scope 属性,将 Bean 的作用域定义成 singleton,其配置方式如下所示:<bean id="..." class="..." scope="singleton"/>
,原理图如下所示:
prototype(原型模式)
如果一个 Bean 定义的作用域为 prototype,那么这个 Bean 就被称为 prototype bean。对于 prototype bean 来说,Spring 容器会在每次请求该 Bean 时,都创建一个新的 Bean 实例。
从某种意义上说,Spring IoC 容器对于 prototype bean 的作用就相当于 Java 的 new 操作符。它只负责 Bean 的创建,至于后续的生命周期管理则都是由客户端代码完成的,详情请参看《Spring Bean 生命周期》。
prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取 Bean 的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 Bean 应该使用 prototype 作用域,而对无状态的 Bean 则应该使用 singleton 作用域。
在 Spring 配置文件中,可以使用 <bean>
元素的 scope 属性将 Bean 的作用域定义成 prototype,其配置方式如下所示:<bean id="..." class="..." scope="prototype"/>
,原理图如下所示:
2.2.4、Bean 的生命周期
Spring Bean 的生命周期指的是从一个普通的Java类变成Bean的过程。当一个 Bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 Bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。
尽管还有一些在 Bean 实例化和销毁之间发生的活动,但是本章将只讨论两个重要的生命周期回调方法,它们在 Bean 的初始化和销毁的时候是必需的。
为了定义安装和拆卸一个 Bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 Bean 即可。init-method 属性指定一个方法,在实例化 Bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 Bean 之后,才能调用该方法。
Bean的生命周期可以简单的表达为:实例化(Instantiation)->属性赋值(Populate)->初始化(Initialization)->销毁(Destruction)
接下来对 Bean 的整个生命周期做一个详细的流程,其主要包括:实例化 Bean、依赖注入、注入 Aware 接口、BeanPostProcessor、InitializingBean 与 init-method 以及 DisposableBean 和 destroy-method。其实现实例化过程如下:
-
实例化 Bean:对于 BeanFactory 容器,当客户向容器请求一个尚未初始化的 Bean 时,或初始化 Bean 的时候需要注入另一个尚未初始化的依赖时,容器就会调用 createBean 进行实例化。而对于 ApplicationContext 容器,当容器启动结束后,便实例化所有的 Bean。容器通过获取 BeanDefinition 对象中的信息进行实例化,并且这一步仅仅是简单的实例化,并未进行依赖注入。实例化对象被包装在 BeanWrapper 对象中,BeanWrapper 提供了设置对象属性的接口,从而避免了使用反射机制设置属性。
-
依赖注入:实例化后的对象被封装在 BeanWrapper 对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。紧接着,Spring 根据 BeanDefinition 中的信息进行依赖注入,并且通过 BeanWrapper 提供的设置属性的接口完成依赖注入。
-
注入 Aware 接口:紧接着,Spring 会检测该对象是否实现了 xxxAware 接口,并将相关的 xxxAware 实例注入给Bean。
-
如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的名称传给 setBeanName() 方法;
-
如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 实例传进来;
-
如果 Bean 实现了 ApplicationContextAware 接口,它的 setApplicationContext() 方法将被调用,将应用上下文的引用传入到 Bean 中;
-
-
BeanPostProcessor:当经过上述几个步骤后,Bean 对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过 BeanPostProcessor 接口实现。该接口提供了两个函数:
-
postProcessBeforeInitialzation( Object bean, String beanName ):当前正在初始化的 Bean 对象会被传递进来,我们就可以对这个 Bean 作任何处理,这个函数会先于 InitialzationBean 执行,因此称为前置处理。所有Aware接口的注入就是在这一步完成的。
-
postProcessAfterlnitialzation( Object bean, String beanName ):当前正在初始化的 Bean 对象会被传递进来,我们就可以对这个 Bean 作任何处理,这个函数会在 InitialzationBean 完成后执行,因此称为后置处理。
-
-
InitializingBean 与 init-method:当 BeanPostProcessor 的前置处理完成后就会进入本阶段。InitializingBean 接口只有一个函数:afterPropertiesSet()。
-
这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前 Bean 对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。若要使用它,我们需要让 Bean 实现该接口,并把要增加的逻辑写在该函数中,然后 Spring 会在前置处理完成后检测当前 Bean 是否实现了该接口,并执行 afterPropertiesSet 函数。
-
当然,Spring 为了降低对客户代码的侵入性,给 Bean 的配置提供了 init-method 属性,该属性指定了在这一阶段需要执行的函数名。Spring 便会在初始化阶段执行我们设置的函数。init-method 本质上仍然使用了 InitializingBean 接口。
-
-
DisposableBean 和 destroy-method:和 init-method 一样,通过给 destroy-method 指定函数,就可以在 Bean 销毁前执行指定的逻辑。若 Bean 实现了 DisposableBean 接口,Spring 将调用它的 distroy() 接口方法。同样的,如果 Bean 使用了 destroy-method 属性声明了销毁方法,则该方法被调用;
这里特别说明一下 Awar e接口,Spring 的依赖注入最大亮点就是所有的 Bean 对 Spring 容器的存在是没有意识的。但是在实际项目中,我们有时不可避免的要用到 Spring 容器本身提供的资源,这时候要让 Bean 主动意识到 Spring 容器的存在,才能调用 Spring 所提供的资源,这就是 Spring 的 Aware 接口。
Aware 接口是个标记接口,标记这一类接口是用来“感知”属性的,Aware 的众多子接口则是表征了具体要“感知”什么属性。例如 BeanNameAware 接口用于“感知”自己的名称,ApplicationContextAware 接口用于“感知”自己所处的上下文。
其实 Spring 的 Aware 接口是 Spring设 计为框架内部使用的,在大多数情况下,我们不需要使用任何 Awar e接口,除非我们真的需要它们,实现了这些接口会使应用层代码耦合到 Spring 框架代码中。
接下来我们通过一个例子来演示一下 Spring Bean 的生命周期。首先通过在实体类中定义两个方法,一个初始化方法和一个销毁方法,然后在 xml 配置文件中,对 Bean 进行装配,并且通过 init-method="初始化方法" destroy-method="销毁方法"
两个属性指定我们自定义的初始化和销毁方法,实现代码如下:
public class SpringBeanSmzq {
private String massage;
public void init() {
System.out.println("初始化Bean");
}
public void destroy() {
System.out.println("销毁Bean");
}
public String getMassage() {
return massage;
}
public void setMassage(String massage) {
this.massage = massage;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="smzq" class="com.loner.mj.pojo.SpringBeanSmzq"
init-method="init" destroy-method="destroy">
<property name="massage" value="测试Spring Bean的生命周期" />
</bean>
</beans>
测试程序程序如下,最终输出结果为:初始化Bean -> 测试Spring Bean的生命周期 -> 销毁Bean
public class SpringBeanSmzqTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("smzq.xml");
SpringBeanSmzq smzq = (SpringBeanSmzq) applicationContext.getBean("smzq");
System.out.println(smzq.getMassage());
// 调用销毁方法
applicationContext.registerShutdownHook();
}
}
2.2.5、Bean 的自动装配 3、Spring AOP