biancheng-Spring
目录http://c.biancheng.net/spring/spring-abc.html
1Spring是什么
2Spring体系结构
3Spring开发环境搭建
4第一个Spring程序
5Spring IoC
6Spring Bean定义
7Spring Bean属性注入
8Spring注入内部Bean
9Spring注入集合
10Spring注入其他类型的属性
11Spring Bean作用域
12Spring Bean生命周期
13Spring后置处理器
14Spring Bean继承
15Spring自动装配
16Spring自动装配(基于注解)
17AOP面向切面编程
18Spring AOP
19Spring集成AspectJ
20基于XML的AspectJ AOP开发
21基于注解的AspectJ AOP开发
22Spring JdbcTemplate
23Spring事务(Transaction)
24Spring基于XML实现事务管理
25Spring基于注解实现事务管理
26Spring整合日志框架Log4j2
27Spring SpEL表达式语言
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-aop spring-context-indexer spring-instrument spring-orm spring-web spring-aspects spring-context-support spring-jcl spring-oxm spring-webflux spring-beans spring-core spring-jdbc spring-r2dbc spring-webmvc spring-context spring-expression spring-jms spring-test spring-websocket spring-messaging spring-tx
上图中包含了 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 IoC(控制反转)
IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。
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 的“控制反转”。
依赖注入(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 解析等技术,将代码的耦合度降低到最低限度,其主要步骤如下。
- 在配置文件(例如 Bean.xml)中,对各个对象以及它们之间的依赖关系进行配置;
- 我们可以把 IoC 容器当做一个工厂,这个工厂的产品就是 Spring Bean;
- 容器启动时会加载并解析这些配置文件,得到对象的基本信息以及它们之间的依赖关系;
- 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 的使用。
1. 在 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();
- }
2. 运行 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 的使用。
1. 修改 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();
- }
2. 运行 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 配置文件的根元素是 <beans>,该元素包含了多个子元素 <bean>。每一个 <bean> 元素都定义了一个 Bean,并描述了该 Bean 是如何被装配到 Spring 容器中的。
例如,在《第一个Spring程序》一节中的 Beans.xml 配置文件,代码如下所示:
- <?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
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
- <bean id="helloWorld" class="net.biancheng.c.HelloWorld">
- <property name="message" value="Hello World!"/>
- </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 | 用于封装 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 配置文件中,通过 <beans> 及其子元素 <bean> 对 Bean 进行定义;
- 在 <bean> 元素内使用 <constructor-arg> 元素,对构造函数内的属性进行赋值,Bean 的构造函数内有多少参数,就需要使用多少个 <constructor-arg> 元素。
- <bean id="student" class="net.biancheng.c.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="net.biancheng.c.Grade">
- <constructor-arg name="gradeId" value="4"></constructor-arg>
- <constructor-arg name="gradeName" value="四年级"></constructor-arg>
- </bean>
setter 注入
在 Spring 实例化 Bean 的过程中,IoC 容器首先会调用默认的构造方法(无参构造方法)实例化 Bean(Java 对象),然后通过 Java 的反射机制调用这个 Bean 的 setXxx() 方法,将属性值注入到 Bean 中。
使用 setter 注入的方式进行属性注入,大致步骤如下:
- 在 Bean 中提供一个默认的无参构造函数(在没有其他带参构造函数的情况下,可省略),并为所有需要注入的属性提供一个 setXxx() 方法;
- 在 Spring 的 XML 配置文件中,使用 <beans> 及其子元素 <bean> 对 Bean 进行定义;
- 在 <bean> 元素内使用 <property> 元素对各个属性进行赋值。
- <bean id="student" class="net.biancheng.c.Student">
- <!--使用 property 元素完成属性注入
- name: 类中的属性名称,例如 id,name
- value: 向属性注入的值 例如 学生的 id 为 1,name 为张三
- -->
- <property name="id" value="1"></property>
- <property name="name" value="张三"></property>
- <property name="grade" ref="grade"></property>
- </bean>
Spring注入集合
我们还可以在 Bean 标签下的 <property> 元素中,使用以下元素配置 Java 集合类型的属性和参数,例如 List、Set、Map 以及 Properties 等。
<bean id="javaCollection" class="net.biancheng.c.JavaCollection">
<!--数组类型-->
<property name="courses">
<array>
<value>Java</value>
<value>PHP</value>
<value>C 语言</value>
</array>
</property>
<!--List 类型-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王五</value>
<value>赵六</value>
</list>
</property>
<!--Map 类型-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--Set 类型-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
Spring Bean作用域
我们可以在 <bean> 元素中添加 scope 属性来配置 Spring Bean 的作用范围。例如,如果每次获取 Bean 时,都需要一个新的 Bean 实例,那么应该将 Bean 的 scope 属性定义为 prototype,如果 Spring 需要每次都返回一个相同的 Bean 实例,则应将 Bean 的 scope 属性定义为 singleton。
Spring 5 共提供了 6 种 scope 作用域,如下表。
作用范围 | 描述 |
---|---|
singleton | 默认值,单例模式,表示在 Spring 容器中只有一个 Bean 实例 |
prototype | 原型模式,表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。 |
request | 每次 HTTP 请求,容器都会创建一个 Bean 实例。该作用域只在当前 HTTP Request 内有效。 |
session | 同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在当前 HTTP Session 内有效。 |
application | 同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。 |
websocket | websocket 的作用域是 WebSocket ,即在整个 WebSocket 中有效。 |
注意:在以上 6 种 Bean 作用域中,除了 singleton 和 prototype 可以直接在常规的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基于 Web 的 ApplicationContext 实现(例如 XmlWebApplicationContext)中才能使用,否则就会抛出一个 IllegalStateException 的异常。
Spring Bean生命周期
相比之下,Spring 中 Bean 的生命周期较复杂,大致可以分为以下 5 个阶段:
- Bean 的实例化
- Bean 属性赋值
- Bean 的初始化
- Bean 的使用
- Bean 的销毁
Spring 根据 Bean 的作用域来选择 Bean 的管理方式,
- 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
- 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。
Spring 生命周期流程
Spring Bean 的完整生命周期从创建 Spring IoC 容器开始,直到最终 Spring IoC 容器销毁 Bean 为止,其具体流程如下图所示。
Bean 生命周期的整个执行过程描述如下。
- Spring 启动,查找并加载需要被 Spring 管理的 Bean,对 Bean 进行实例化。
- 对 Bean 进行属性注入。
- 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
- 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
- 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
- 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
- 如果在 <bean> 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
- 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
自定义 Bean 的生命周期
我们可以在 Spring Bean 生命周期的某个特定时刻,指定一些生命周期回调方法完成一些自定义的操作,对 Bean 的生命周期进行管理。
Bean 的生命周期回调方法主要有两种:
- 初始化回调方法:在 Spring Bean 被初始化后调用,执行一些自定义的回调操作。
- 销毁回调方法:在 Spring Bean 被销毁前调用,执行一些自定义的回调操作。
我们可以通过以下 3 种方式自定义 Bean 的生命周期回调方法:
- 通过接口实现
- 通过 XML 配置实现
- 使用注解实现
如果一个 Bean 中有多种生命周期回调方法时,优先级顺序为:注解 > 接口 > XML 配置。
通过接口实现
我们可以在 Spring Bean 的 Java 类中,通过实现 InitializingBean 和 DisposableBean 接口,指定 Bean 的生命周期回调方法。
回调方式 | 接口 | 方法 | 说明 |
---|---|---|---|
初始化回调 | InitializingBean | afterPropertiesSet() | 指定初始化回调方法,这个方法会在 Spring Bean 被初始化后被调用,执行一些自定义的回调操作。 |
销毁回调 | DisposableBean | destroy() | 指定销毁回调方法,这个方法会在 Spring Bean 被销毁前被调用,执行一些自定义的回调操作。 |
注意:通常情况下,我们不建议通过这种方式指定生命周期回调方法,这是由于这种方式会导致代码的耦合性过高。
示例
下面我们就通过一个实例,来演示下如何通过接口指定 Bean 的生命周期回调方法。
Spring后置处理器(BeanPostProcessor)
BeanPostProcessor 接口源码如下:
- public interface BeanPostProcessor {
- Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
- Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
- }
该接口中包含了两个方法:
- postProcessBeforeInitialization() 方法:在 Bean 实例化、属性注入后,初始化前调用。
- postProcessAfterInitialization() 方法:在 Bean 实例化、属性注入、初始化都完成后调用。
当需要添加多个后置处理器实现类时,默认情况下 Spring 容器会根据后置处理器的定义顺序来依次调用。也可以通过实现 Ordered 接口的 getOrder 方法指定后置处理器的执行顺序。该方法返回值为整数,默认值为 0,取值越大优先级越低。
Spring Bean继承
Spring Bean 的定义中可以包含很多配置信息,例如构造方法参数、属性值。子 Bean 既可以继承父 Bean 的配置数据,也可以根据需要重写或添加属于自己的配置信息。
在 Spring XML 配置中,我们通过子 Bean 的 parent 属性来指定需要继承的父 Bean,配置格式如下。
<!--父Bean-->
<bean id="parentBean" class="xxx.xxxx.xxx.ParentBean" >
<property name="xxx" value="xxx"></property>
<property name="xxx" value="xxx"></property>
</bean>
<!--子Bean-->
<bean id="childBean" class="xxx.xxx.xxx.ChildBean" parent="parentBean"></bean>
Spring自动装配
Spring 的 IOC 容器虽然功能强大,但它本身不过只是一个空壳而已,它自己并不能独自完成装配工作。需要我们主动将 Bean 放进去,并告诉它 Bean 和 Bean 之间的依赖关系,它才能按照我们的要求完成装配工作。
在前面的学习中,我们都是在 XML 配置中通过 <constructor-arg>和 <property> 中的 ref 属性,手动维护 Bean 与 Bean 之间的依赖关系的。
自动装配规则
Spring 共提供了 5 中自动装配规则,它们分别与 autowire 属性的 5 个取值对应,具体说明如下表。
属性值 | 说明 |
---|---|
byName | 按名称自动装配。 Spring 会根据的 Java 类中对象属性的名称,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。 |
byType | 按类型自动装配。 Spring 会根据 Java 类中的对象属性的类型,在整个应用的上下文 ApplicationContext(IoC 容器)中查找。若某个 Bean 的 class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。 |
constructor | 与 byType 模式相似,不同之处在与它应用于构造器参数(依赖项),如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。 其实就是根据构造器参数的数据类型,进行 byType 模式的自动装配。 |
default | 表示默认采用上一级元素 <beans> 设置的自动装配规则(default-autowire)进行装配。 |
no | 默认值,表示不使用自动装配,Bean 的依赖关系必须通过 <constructor-arg>和 <property> 元素的 ref 属性来定义。 |
示例
<!--部门 Dept 的 Bean 定义-->
<bean id="dept" class="net.biancheng.c.Dept"></bean>
<!--雇员 Employee 的 Bean 定义,通过 autowire 属性设置自动装配的规则-->
<bean id="employee" class="net.biancheng.c.Employee" autowire="byName">
</bean>
Spring自动装配(基于注解)
pring 通过注解实现自动装配的步骤如下:
- 引入依赖
- 开启组件扫描
- 使用注解定义 Bean
- 依赖注入
1. 引入依赖
使用注解的第一步,就是要在项目中引入以下 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-aop-5.3.13.jar
注意,除了 spring 的四个基础 jar 包和 commons-logging-xxx.jar 外,想要使用注解实现 Spring 自动装配,还需要引入Spring 提供的 spring-aop 的 Jar 包。
2. 开启组件扫描
Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 <context:component-scan> 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中
<!--开启组件扫描功能--><context:component-scan base-package="net.biancheng.c"></context:component-scan>
注意:在使用 <context:component-scan> 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 <beans> 中添加 context 相关的约束。
3. 使用注解定义 Bean
Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。
注解 | 说明 |
---|---|
@Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 |
@Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
@Controller | 该注解通常作用在控制层(如 Struts2 的 Action、SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 |
4. 基于注解方式实现依赖注入
我们可以通过以下注解将定义好 Bean 装配到其它的 Bean 中。
注解 | 说明 |
---|---|
@Autowired | 可以应用到 Bean 的属性变量、setter 方法、非 setter 方法及构造函数等,默认按照 Bean 的类型进行装配。 @Autowired 注解默认按照 Bean 的类型进行装配,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它的 required 属性为 false。如果我们想使用按照名称(byName)来装配,可以结合 @Qualifier 注解一起使用 |
@Resource | 作用与 Autowired 相同,区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 的名称进行装配。 @Resource 中有两个重要属性:name 和 type。
|
@Qualifier | 与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。 |
AOP面向切面编程
除了控制反转(IoC)和依赖注入(DI)外,Spring 框架还提供了对面向切面编程(AOP)的支持。本节,我们就对 AOP 面向切面编程进行讲解。
AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。
目前最流行的 AOP 实现(框架)主要有两个,分别为 Spring AOP 和 AspectJ。
AOP 框架 | 说明 |
---|---|
Spring AOP | 是一款基于 AOP 编程的框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。 Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring AOP 支持 2 种代理方式,分别是基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。 |
AspectJ | 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。 AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。 |
AOP 术语
与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。
名称 | 说明 |
---|---|
Joinpoint(连接点) | AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。 |
Pointcut(切入点) | 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 |
Advice(通知) | 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。 |
Target(目标) | 指代理的目标对象,通常也被称为被通知(advised)对象。 |
Weaving(织入) | 指把增强代码应用到目标对象上,生成代理对象的过程。 |
Proxy(代理) | 指生成的代理对象。 |
Aspect(切面) | 切面是切入点(Pointcut)和通知(Advice)的结合。 |
Advice 直译为通知,也有人将其翻译为“增强处理”,共有 5 种类型,如下表所示。
通知 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法封装起来 |
Spring AOP编程(十分详细)
Spring AOP 的代理机制
Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。
Spring AOP 的底层是通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。
代理技术 | 描述 |
---|---|
JDK 动态代理 | Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。 |
CGLIB 动态代理 | 若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。 |
注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。
Spring AOP 连接点
Spring AOP 并没有像其他 AOP 框架(例如 AspectJ)一样提供了完成的 AOP 功能,它是 Spring 提供的一种简化版的 AOP 组件。其中最明显的简化就是,Spring AOP 只支持一种连接点类型:方法调用。您可能会认为这是一个严重的限制,但实际上 Spring AOP 这样设计的原因是为了让 Spring 更易于访问。
方法调用连接点是迄今为止最有用的连接点,通过它可以实现日常编程中绝大多数与 AOP 相关的有用的功能。如果需要使用其他类型的连接点(例如成员变量连接点),我们可以将 Spring AOP 与其他的 AOP 实现一起使用,最常见的组合就是 Spring AOP + ApectJ。
Spring AOP 通知类型
AOP 联盟为通知(Advice)定义了一个 org.aopalliance.aop.Interface.Advice 接口。
Spring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口,如下表。
通知类型 | 接口 | 描述 |
---|---|---|
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强。 |
后置通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行后实施增强。 |
后置返回通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行完成,并返回一个返回值后实施增强。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强。 |
引入通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性。 |
Spring AOP 切面类型
Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。
在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。
切面类型 | 接口 | 描述 |
---|---|---|
一般切面 | org.springframework.aop.Advisor | Spring AOP 默认的切面类型。 由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。 这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。 |
切点切面 | org.springframework.aop.PointcutAdvisor | Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。 使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。 |
引介切面 | org.springframework.aop.IntroductionAdvisor | Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。 |
本节我们着重对 Advisor 和 PointcutAdvisor 进行讲解,至于 IntroductionAdvisor,我们会在后面的教程中进行介绍。
一般切面的 AOP 开发
当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。
示例 1
下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。
1. 创建一个名为 my-spring-aop-demo 的 Java 工程,并将以下依赖引入到工程中。
- 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-aop-5.3.13.jar
<!-- 定义目标(target)对象 -->
<bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>
<!--通过配置生成代理 UserDao 的代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置目标对象 -->
<property name="target" ref="userDao"/>
<!-- 设置实现的接口 ,value 中写接口的全路径 -->
<property name="proxyInterfaces" value="net.biancheng.c.dao.UserDao"/>
<!-- 需要使用value:增强 Bean 的名称 -->
<property name="interceptorNames" value="beforeAdvice"/>
</bean>
Spring集成AspectJ
但由于 AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。
使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。
- spring-aop-xxx.jar
- spring-aspects-xxx.jar
- aspectjweaver-xxxx.jar
Spring使用AspectJ进行AOP开发(基于XML)
Spring 项目中通过 XML 配置,对切面(Aspect 或 Advisor)、切点(PointCut)以及通知(Advice)进行定义和管理,以实现基于 AspectJ 的 AOP 开发。
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 <aop:config> 元素。
- 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 <aop:config> 元素中;
- 在 Spring 配置中,可以使用多个 <aop:config>。
- 每一个 <aop:config> 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect
定义切面<aop:aspect>
在 Spring 配置文件中,使用 <aop:aspect> 元素定义切面。该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean。
- <aop:config>
- <aop:aspect id="myAspect" ref="aBean">
- ...
- </aop:aspect>
- </aop:config>
其中,id 用来定义该切面的唯一标识名称,ref 用于引用普通的 Spring Bean。
定义切入点<aop:pointcut>
<aop:pointcut> 用来定义一个切入点,用来表示对哪个类中的那个方法进行增强。它既可以在 <aop:pointcut> 元素中使用,也可以在 <aop:aspect> 元素下使用。
- 当 <aop:pointcut>元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
- 当 <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效。
- <aop:config>
- <aop:pointcut id="myPointCut"
- expression="execution(* net.biancheng.service.*.*(..))"/>
- </aop:config>
其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。
execution 的语法格式格式为:
execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表])
其中:
- 返回值类型、方法名、参数列表是必须配置的选项,而其它参数则为可选配置项。
- 返回值类型:
*
表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。 - 类的完全限定名:指定包名 + 类名。
- 方法名:
*
代表所有方法,set*
代表以 set 开头的所有方法。 - 参数列表:
(..)
代表所有参数;(*)
代表只有一个参数,参数类型为任意类型;(*,String)
代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。
举例 1:对 net.biancheng.c 包下 UserDao 类中的 add() 方法进行增强,配置如下。
execution(* net.biancheng.c.UserDao.add(..))
举例 2:对 net.biancheng.c 包下 UserDao 类中的所有方法进行增强,配置如下。
execution(* net.biancheng.c.UserDao.*(..))
举例 3:对 net.biancheng.c 包下所有类中的所有方法进行增强,配置如下。
execution(* net.biancheng.c.*.*(..))
定义通知
AspectJ 支持 5 种类型的 advice,如下。
- <aop:aspect id="myAspect" ref="aBean">
- <!-- 前置通知 -->
- <aop:before pointcut-ref="myPointCut" method="..."/>
- <!-- 后置通知 -->
- <aop:after-returning pointcut-ref="myPointCut" method="..."/>
- <!-- 环绕通知 -->
- <aop:around pointcut-ref="myPointCut" method="..."/>
- <!-- 异常通知 -->
- <aop:after-throwing pointcut-ref="myPointCut" method="..."/>
- <!-- 最终通知 -->
- <aop:after pointcut-ref="myPointCut" method="..."/>
- ....
- </aop:aspect>
示例
下面我们通过一个示例来演示下 Spring 集成 AspectJ 基于 XML 实现 AOP 开发。
1. 新建一个名为 my-spring-asepctj-demo 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。
- commons-logging-1.2.jar
- spring-aop-5.3.13.jar
- spring-aspects-5.3.13.jar
- spring-beans-5.3.13.jar
- spring-context-5.3.13.jar
- spring-core-5.3.13.jar
- spring-expression-5.3.13.jar
- aspectjweaver-1.9.7.jar
Spring使用AspectJ进行AOP开发(基于注解)
为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。
关于注解的介绍如表 1 所示。
名称 | 说明 |
---|---|
@Aspect | 用于定义一个切面。 |
@Pointcut | 用于定义一个切入点。 |
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
启用 @AspectJ 注解支持
在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。
我们可以通过以下 2 种方式来启用 @AspectJ 注解。
1)使用 Java 配置类启用
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。
- @Configuration
- @ComponentScan(basePackages = "net.biancheng.c") //注解扫描
- @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
- public class AppConfig {
- }
2)基于 XML 配置启用
在 Spring 的 XML 配置文件中,添加以下内容启用 @AspectJ 注解支持。
- <!-- 开启注解扫描 -->
- <context:component-scan base-package="net.biancheng.c"></context:component-scan>
- <!--开启AspectJ 自动代理-->
- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
定义切面 @Aspect
我们可以通过 @Aspect 注解将一个 Bean 定义为切面。
在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。
我们可以在 XML 配置中通过一些配置将这个类定义为一个 Bean,如下。
- <bean id = "myAspect" class = "net.biancheng.c.MyAspect">
- ...
- </bean>
在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。
- package net.biancheng.c;
- import org.aspectj.lang.annotation.*;
- @Aspect //定义为切面
- public class MyAspect {
- }
全注解方式定义切面
我们也可以在 Java 类上使用以下 2 个注解,使用全注解方式定义切面。
- package net.biancheng.c;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
- @Component // 定义成 Bean
- @Aspect //定义为切面
- public class MyAspect {
- }
在以上代码中共使用两个注解:
- @Component 注解:将这个类的对象定义为一个 Bean;
- @Aspect 注解:则是将这个 Bean 定义为一个切面。
定义切点 @Pointcut
在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,示例代码如下。
- // 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
- @Pointcut("execution(*net.biancheng..*.*(..))")
- private void myPointCut() {
- }
@Pointcut 注解中有一个 value 属性,这个属性的值就是切入点表达式。有关切入点表达式的具体介绍请参考《使用AspectJ实现AOP(基于XML)》中的 execution 语法格式介绍。
值得注意的是,我除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、“||”和“!”等表示“与”、“或”、“非”的含义,示例代码如下。
- /**
- * 将 net.biancheng.c.dao包下 UserDao 类中的 get() 方法定义为一个切点
- */
- @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.get(..))")
- public void pointCut1(){
- }
- /**
- * 将 net.biancheng.c.dao包下 UserDao 类中的 delete() 方法定义为一个切点
- */
- @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.delete(..))")
- public void pointCut2(){
- }
- /**
- * 除了 net.biancheng.c.dao包下 UserDao 类中 get() 方法和 delete() 方法外,其他方法都定义为切点
- *
- * ! 表示 非 ,即 "不是" 的含义,求补集
- * * && 表示 与,即 ”并且“ ,求交集
- * || 表示 或,即 “或者”,求并集
- */
- @Pointcut(value ="!pointCut1() && !pointCut2()")
- public void pointCut3(){
- }
定义通知
AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。
注解 | 说明 |
---|---|
@Before | 用于定义前置通知,相当于 BeforeAdvice。 |
@AfterReturning | 用于定义后置通知,相当于 AfterReturningAdvice。 |
@Around | 用于定义环绕通知,相当于 MethodInterceptor。 |
@AfterThrowing | 用于定义抛出通知,相当于 ThrowAdvice。 |
@After | 用于定义最终通知,不管是否异常,该通知都会执行。 |
@DeclareParents | 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。 |
以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。
- @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.get(..))")
- public void pointCut1(){
- }
- @Pointcut(value ="execution(* net.biancheng.c.dao.UserDao.delete(..))")
- public void pointCut2(){
- }
- @Pointcut(value ="!pointCut1() && !pointCut2()")
- public void pointCut3(){
- }
- //使用切入点引用
- @Before("MyAspect.pointCut3()")
- public void around() throws Throwable {
- System.out.println("环绕增强……");
- }
- //使用切入点表达式
- @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.get(..))" ,returning = "returnValue")
- public void afterReturning(Object returnValue){
- System.out.println("方法返回值为:"+returnValue);
- }
示例
下面,我们就通过一个完整的实例,来演示下如何通过注解的方式实现 AspectJ AOP 开发。
1. 新建一个名为 my-spring-asepctj-demo2 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。
- commons-logging-1.2.jar
- spring-aop-5.3.13.jar
- spring-aspects-5.3.13.jar
- spring-beans-5.3.13.jar
- spring-context-5.3.13.jar
- spring-core-5.3.13.jar
- spring-expression-5.3.13.jar
- aspectjweaver-1.9.7.jar
Spring JdbcTemplate(使用详解)
JdbcTemplate
JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。
JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。
方法 | 说明 |
---|---|
public int update(String sql) | 用于执行新增、更新、删除等语句;
|
public int update(String sql,Object... args) | |
public void execute(String sql) | 可以执行任意 SQL,一般用于执行 DDL 语句;
|
public T execute(String sql, PreparedStatementCallback action) | |
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) | |
用于执行查询语句;
|
|
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) | |
public int[] batchUpdate(String sql, List<Object[]> batchArgs, final int[] argTypes) | 用于批量执行新增、更新、删除等语句;
|
示例 1
<!--定义数据源 Bean-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--数据库的密码-->
<property name="password" value="${jdbc.password}"/>
<!--数据库驱动-->
<property name="driverClassName" value="${jdbc.driver}"/>
</bean>
<!--定义JdbcTemplate Bean-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--将数据源的 Bean 注入到 JdbcTemplate 中-->
<property name="dataSource" ref="dataSource"></property>
</bean>
Spring事务(Transaction)
Spring 支持以下 2 种事务管理方式。
事务管理方式 | 说明 |
---|---|
编程式事务管理 | 编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。 |
声明式事务管理 | Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。 |
选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。
- 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
- 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。
事务管理器
Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下。
- public interface PlatformTransactionManager extends TransactionManager {
- TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
- void commit(TransactionStatus status) throws TransactionException;
- void rollback(TransactionStatus status) throws TransactionException;
- }
该接口中各方法说明如下:
名称 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefinition definition) | 用于获取事务的状态信息 |
void commit(TransactionStatus status) | 用于提交事务 |
void rollback(TransactionStatus status) | 用于回滚事务 |
Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。
实现类 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用 Spring JDBC 或 iBatis 进行持久化数据时使用。 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用 Hibernate 3.0 及以上版本进行持久化数据时使用。 |
org.springframework.orm.jpa.JpaTransactionManager | 使用 JPA 进行持久化时使用。 |
org.springframework.jdo.JdoTransactionManager | 当持久化机制是 Jdo 时使用。 |
org.springframework.transaction.jta.JtaTransactionManager | 使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。 |
这些事务管理器的使用方式十分简单,我们只要根据持久化框架(或平台)选用相应的事务管理器实现,即可实现对事物的管理,而不必关心实际事务实现到底是什么。
TransactionDefinition 接口
Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。TransactionDefinition 接口提供了获取事务相关信息的方法,接口定义如下。
- public interface TransactionDefinition {
- int getPropagationBehavior();
- int getIsolationLevel();
- String getName();
- int getTimeout();
- boolean isReadOnly();
- }
该接口中方法说明如下。
方法 | 说明 |
---|---|
String getName() | 获取事务的名称 |
int getIsolationLevel() | 获取事务的隔离级别 |
int getPropagationBehavior() | 获取事务的传播行为 |
int getTimeout() | 获取事务的超时时间 |
boolean isReadOnly() | 获取事务是否只读 |
事务的隔离级别
事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。在实际应用中,经常会出现多个事务同时对同一数据执行不同操作,来实现各自的任务的情况。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。
在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。
Spring 中提供了以下隔离级别,我们可以根据自身的需求自行选择合适的隔离级别。
方法 | 说明 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读 |
ISOLATION_READ_COMMITTED | Oracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读 |
ISOLATION_REPEATABLE_READ | MySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读 |
ISOLATION_SERIALIZABLE | 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读 |
关于事务隔离级别、脏读、幻读、不可重复度等概念的详细介绍,请阅读《数据库事务隔离级别》一节。
事务的传播行为
事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。Spring 提供了以下 7 种不同的事务传播行为。事务方法指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法。
名称 | 说明 |
---|---|
PROPAGATION_MANDATORY | 支持当前事务,如果不存在当前事务,则引发异常。 |
PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行。 |
PROPAGATION_NEVER | 不支持当前事务,如果当前事务存在,则引发异常。 |
PROPAGATION_NOT_SUPPORTED | 不支持当前事务,始终以非事务方式执行。 |
PROPAGATION_REQUIRED | 默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。 |
PROPAGATION_REQUIRES_NEW | 创建新事务,如果已经存在事务则暂停当前事务。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在事务,则以非事务方式执行。 |
TransactionStatus 接口
TransactionStatus 接口提供了一些简单的方法,来控制事务的执行、查询事务的状态,接口定义如下。- public interface TransactionStatus extends SavepointManager {
- boolean isNewTransaction();
- boolean hasSavepoint();
- void setRollbackOnly();
- boolean isRollbackOnly();
- boolean isCompleted();
- }
该接口中各方法说明如下。
名称 | 说明 |
---|---|
boolean hasSavepoint() | 获取是否存在保存点 |
boolean isCompleted() | 获取事务是否完成 |
boolean isNewTransaction() | 获取是否是新事务 |
boolean isRollbackOnly() | 获取事务是否回滚 |
void setRollbackOnly() | 设置事务回滚 |
Spring基于XML实现事务管理
Spring 实现声明式事务管理主要有 2 种方式:
- 基于 XML 方式的声明式事务管理。
- 通过 Annotation 注解方式的事务管理。
<!--配置事务管理器,以 JDBC 为例-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="tx-advice" transaction-manager="transactionManager">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" timeout="10"/>
</tx:attributes>
</tx:advice>
事务属性配置
对于<tx:advice> 来说,事务属性是被定义在<tx:attributes> 中的,该元素可以包含一个或多个 <tx:method> 元素。
<tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
4. 配置切点切面
<tx:advice> 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 <tx:advice> 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。
在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。
- <!--配置切点和切面-->
- <aop:config>
- <!--配置切点-->
- <aop:pointcut id="tx-pt" expression="execution(* net.biancheng.c.service.impl.OrderServiceImpl.*(..))"/>
- <!--配置切面-->
- <aop:advisor advice-ref="tx-advice" pointcut-ref="tx-pt"></aop:advisor>
- </aop:config>
在以上配置中用到了 aop 命名空间,这就是我们为什么在给工程导入依赖时要引入 spring-aop 等 Jar 包的原因。
Spring基于注解实现事务管理
1. 开启注解事务
tx 命名空间提供了一个 <tx:annotation-driven> 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。
<tx:annotation-driven> 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。
- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
与 <tx:advice> 元素一样,<tx:annotation-driven> 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。
- <tx:annotation-driven/>
通过 <tx:annotation-driven> 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。
2. 使用 @Transactional 注解
@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。
- @Transactional
- public class XXX {
- @Transactional
- public void A(Order order) {
- ……
- }
- public void B(Order order) {
- ……
- }
- }
若 @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。
Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。
@Transactional 注解包含多个属性,其中常用属性如下表。
事务属性 | 说明 |
---|---|
propagation | 指定事务的传播行为。 |
isolation | 指定事务的隔离级别。 |
read-only | 指定是否为只读事务。 |
timeout | 表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。 |
rollback-for | 指定事务对于那些类型的异常应当回滚,而不提交。 |
no-rollback-for | 指定事务对于那些异常应当继续运行,而不回滚。 |
Spring整合日志框架Log4j2
对下载完成的压缩包进行解压,并将以下 3 个依赖包导入到 my-spring-log4j-demo 项目中。
- log4j-api-2.17.1.jar
- log4j-core-2.17.1.jar
- log4j-slf4j18-impl-2.17.1.jar
在 src 目录下,创建一个名为 log4j2.xml 的配置文件,配置内容如下。
- <?xml version="1.0" encoding="UTF-8"?>
- <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
- <!-- Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,当设置成 trace 时,可以看到 log4j2 内部各种详细输出-->
- <configuration status="INFO">
- <!--先定义所有的 appender-->
- <appenders>
- <!--输出日志信息到控制台-->
- <console name="Console" target="SYSTEM_OUT">
- <!--控制日志输出的格式-->
- <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
- </console>
- </appenders>
- <!--然后定义 logger,只有定义了 logger 并引入的 appender,appender 才会生效-->
- <!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为默认的日志输出-->
- <loggers>
- <root level="info">
- <appender-ref ref="Console"/>
- </root>
- </loggers>
- </configuration>
Spring SpEL表达式语言
SpEL 提供了以下接口和类:
- Expression interface:该接口负责评估表达式字符串
- ExpressionParser interface:该接口负责解析字符串
- EvaluationContext interface:该接口负责定义上下文环境
SpEL 支持如下表达式:
1. 基本表达式
字面量表达式、关系、逻辑与算术运算表达式、字符串连接及截取表达式、三目运算表达式、正则表达式、括号优先级表达式;
2. 类相关表达式
类类型表达式、类实例化、instanceof 表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean 引用;
3. 集合相关表达式
内联 List、内联数组、集合、字典访问、列表、字典、数组修改、集合投影、集合选择;不支持多维内联数组初始化;不支持内联字典定义;
4. 其他表达式
模板表达式。