Spring
Spring
Spring
Spring介绍
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。
Spring模块
图例基于Spring Framework 4.X,Spring Framework 5.x引入了Spring WebFlux替换了Spring MVC,关于Spring WebFlux与Spring MVC相关区别参见附录

Data Access/Integration
JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
OXM 模块:提供了一个支持对象 XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类。Web
Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
Servlet 模块:包括 Spring 模型—视图—控制器 MVC 实现 Web 应用程序。
Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Web-Servlet 模块的功能。Core Container
Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。Others
AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。
Messaging 模块:是 Spring 与消息系统集成的一个扩展性的支持。它实现了从基于 JmsTemplate 的简单的使用 JMS 接口到异步接收消息的一整套完整的基础架构,Spring AMQP 提供了该协议所要求的类似的功能集。
Spring优势
方便解耦,简化开发
Spring 通过容器,将对象的创建从代码中剥离出来,交给 Spring 控制,避免直接编码造成模块之间的耦合度高,用户也不必自己编码处理对象的单例和多例控制,主要关注接口功能即可,不用关注具体使用哪个实现类和实现细节问题。
AOP编程的支持
AOP 切面编程是程序设计的一种概念,Spring 对该概念实现的比较好,通过切面编程我们可以在不修改原有代码的情况下实现功能的增加,通常用于事务控制,日志记录,性能检测,权限控制等等。
声明式事务的支持
事务的控制可以托管给 Spring,我们通过注解或者配置文件声明事务的处理方式即可,不用我们自己去编码处理。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在 Spring 里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序。
方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,相反,Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts、Hibernate、Hessian、Quartz)等的直接支持。
降低Java EE API的使用难度
Spring 对很多难用的 Java EE API(如JDBC、JavaMail、远程调用等)提供了一个薄薄的封装层,通过 Spring 的简易封装,这些 Java EE API 的使用难度大为降低。
Spring源码是经典学习范例
Spring 的源码设计巧妙,结构清晰,大量使用了设计模式,是java代码规范编写的典范,也是高级程序员面试中经常会问到的源码。
IOC
IOC容器介绍
什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。
通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JavaEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
IOC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
IOC原理分析
XML解析技术读取配置文件
将配置的Bean信息读取进程序,一个是bean的id,一个是类的全路径名
1
<bean id="teacher" class="org.spring.study.Teacher" />
反射技术实例化对象,放到容器中
1
2
3
4
5
6// 获得类的字节码
Class clazz = Class.forName("org.spring.study.Teacher")
// 通过字节码实例化对象
Object obj = clazz.newInstance();
// 将对象放到一个map集合中
map.put("teacher", obj)工厂模式getBean()方法返回Bean对象
1
2
3
4public Object getBean(String name) {
Object obj = map.get(name);
return obj;
}
关于IOC接口:
BeanFactory接口
IOC容器顶级接口,定义基本IOC容器基本功能,是Spring内部使用的接口,我们在处理业务时一般不直接使用该接口
ApplicationContext接口
BeanFactory的子接口,提供更多更强大的功能,研发人员一般使用的接口
Bean的装配
XML装配
id
bean的名字
class
bean的全限定类名,bean对象组装时通过反射构建对象使用
autowire
自动装配,配置的方式有以下几种:
default/no:不自动装配
byName:按照名字进行装配,以属性名作为id去容器中查找组件,进行赋值,如果找不到则装配null
byType:按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null
constructor:按照构造器进行装配,先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配nullscope
bean的作用域:
singleton:在容器启动完成之前就已经创建好对象,关闭的时候会销毁创建的bean,获取的所有对象都是同一个
prototype:容器启动的时候不会创建多实例bean,只有在获取对象的时候才会创建该对象,销毁的时候不会有任何的调用,每次创建都是一个新的对象
request:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP Request内有效
session:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP Session内有效
global-session:全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话lazy-init
懒加载,调用getBean的时候再去实例化对象
init-method
当前bean对象初始化时调用的方法
destroy-method
当前bean对象销毁时调用的方法
关于XML装配具体配置如下:
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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="teacher" class="org.spring.study.Teacher" autowire="byType" scope="singleton" init-method="inti()" destroy-method="destroy()" lazy-init="true">
<property name="book" ref="book"/>
</bean>
<bean id="book" class="org.spring.study.Book">
<property name="author" value="吴承恩"/>
<property name="bookName" value="《西游记》"/>
</bean>
</beans>Test
1
2
3
4
5
6
7
8
9
10public class SpringTest {
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = applicationContext.getBean("teacher", Teacher.class);
}
}
// output
// Teacher{book=Book{author='吴承恩', bookName='《西游记》'}}
Annotation装配
配置对象
@Controller:控制器:推荐给controller层添加此注解
@Service:业务逻辑:推荐给业务逻辑层添加此注解
@Repository:仓库管理:推荐给数据访问层添加此注解
@Component:给不属于以上基层的组件添加此注解注意:我们虽然人为的给不同的层添加不同的注解,但是在Spring看来,可以在任意层添加任意注解,Spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式就是给所有想交由IOC容器管理的bean对象添加@Component注解
配置属性
@Autowired:默认根据类型自动装配
@Resources:默认根据名称自动装配
@Qualifier:根据属性名称注入依赖
@Value:注入普通数据类型(8种基本数据类型+String)有关@Autowired与@Resources的区别参见附录
关于注解的扫描有两种方式,一种是基于XML文件配置,另外一种基于@ComponentScan注解,如下:
基于XML配置
1
2
3
4
5
6
7
8
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.spring.study"/>
</beans>基于@ComponentScan注解
1
2
3
public class SpringConfig {
}
自定义Bean
基于XML配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--
Spring创建第三方bean对象 - 数据库连接池
加载外部配置文件,在加载外部依赖文件的时候需要context命名空间
-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
</bean>
</beans>基于@Bean配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class AppConfig {
String jdbcUrl;
String jdbcUsername;
String jdbcPassword;
DataSource createDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(jdbcUrl);
config.setUsername(jdbcUsername);
config.setPassword(jdbcPassword);
config.addDataSourceProperty("autoCommit", "true");
config.addDataSourceProperty("connectionTimeout", "5");
config.addDataSourceProperty("idleTimeout", "60");
return new HikariDataSource(config);
}
JdbcTemplate createJdbcTemplate( DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
工厂模式创建Bean
BeanFactory与FactoryBean区别详见附录
用工厂模式创建Bean需要实现FactoryBean接口。当一个Bean实现了FactoryBean接口后,Spring会先实例化这个工厂,然后调用getObject()创建真正的Bean。getObjectType()可以指定创建的Bean的类型,因为指定类型不一定与实际类型一致,可以是接口或抽象类。
因此,如果定义了一个FactoryBean,要注意Spring创建的Bean实际上是这个FactoryBean的getObject()方法返回的Bean。为了和普通Bean区分,我们通常都以XxxFactoryBean命名。
BookFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.beans.factory.FactoryBean;
public class BookFactory implements FactoryBean<Book> {
public Book getObject() throws Exception {
Book book = new Book();
book.setBookName("《西游记》");
book.setAuthor("吴承恩");
return book;
}
public Class<?> getObjectType() {
return null;
}
}applicationContext.xml
1
2
3
4
5
6
7
8
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="book" class="org.spring.study.BookFactory" />
</beans>Test
1
2
3
4
5
6
7
8
9
10
11public class SpringTest {
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = applicationContext.getBean("book", Book.class);
System.out.println(book);
}
}
// output
// Book{author='吴承恩', bookName='《西游记》'}
Bean的依赖注入
关于Book类具体信息参见附录
set()方法注入
Teacher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Teacher {
private Book book;
public void setBook(Book book) {
this.book = book;
}
// 必须要有无参构造
public Teacher() {}
public String toString() {
return "Teacher{" +
"book=" + book +
'}';
}
}applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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.xsd">
<!-- 通过set方法注入属性(默认使用无参构造方法实例化对象) -->
<bean id="teacher" class="org.spring.study.Teacher">
<property name="book" ref="book"/>
</bean>
<bean id="book" class="org.spring.study.Book">
<property name="author" value="吴承恩"/>
<property name="bookName" value="《西游记》"/>
</bean>
</beans>Test
1
2
3
4
5
6
7
8
9
10
11public class SpringTest {
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = applicationContext.getBean("teacher", Teacher.class);
System.out.println(teacher);
}
}
// output
// Teacher{book=Book{author='吴承恩', bookName='《西游记》'}}
构造方法注入
Teacher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Teacher {
private Book book;
public Teacher(Book book) {
this.book = book;
}
public String toString() {
return "Teacher{" +
"book=" + book +
'}';
}
}applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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.xsd">
<!-- 通过构造方法注入属性(这里指定了一个参数,则需要有一个参数的构造方法)-->
<bean id="teacher" class="org.spring.study.Teacher">
<constructor-arg name="book" ref="book"/>
</bean>
<bean id="book" class="org.spring.study.Book">
<property name="author" value="吴承恩"/>
<property name="bookName" value="《西游记》"/>
</bean>
</beans>Test
1
2
3
4
5
6
7
8
9
10
11public class SpringTest {
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = applicationContext.getBean("teacher", Teacher.class);
System.out.println(teacher);
}
}
// output
// Teacher{book=Book{author='吴承恩', bookName='《西游记》'}}
Annotation注入
Teacher
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Teacher {
private Book book;
public String toString() {
return "Teacher{" +
"book=" + book +
'}';
}
}applicationContext.xml
1
2
3
4
5
6
7
8
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.spring.study"/>
</beans>Test
1
2
3
4
5
6
7
8
9
10
11public class SpringTest {
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher teacher = applicationContext.getBean("teacher", Teacher.class);
System.out.println(teacher);
}
}
// output
// Teacher{book=Book{author='null', bookName='null'}}
另外可以完全使用注解替代XML文件配置,具体如下
SpringConfig
1
2
3
public class SpringConfig {
}Test
1
2
3
4
5
6
7
8
9
10
11
12public class SpringTest {
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// 如果没有指定Bean名称默认就是类名首字母小写
Teacher teacher = applicationContext.getBean("teacher", Teacher.class);
System.out.println(teacher);
}
}
// output
// Teacher{book=Book{author='null', bookName='null'}}
Bean的生命周期

- Bean 容器找到配置文件中
Spring Bean的定义。 - Bean 容器利用
Java Reflection API创建一个 Bean 的实例。 - 如果涉及到一些属性值,利用
set()方法设置一些属性值。 - 如果 Bean 实现了
BeanNameAware接口,调用setBeanName()方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 - 如果 Bean 实现了
BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。 - 与上面的类似,如果实现了其他
*.Aware接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。 - 如果 Bean 实现了
InitializingBean接口,执行afterPropertiesSet()方法。 - 如果 Bean 在配置文件中的定义包含
init-method属性,执行指定的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessAfterInitialization()方法。 - 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean接口,执行destroy()方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含
destroy-method属性,执行指定的方法。
关于BeanPostProcesser与InitializingBean的用法详解参见附录
AOP
动态代理
JDK动态代理
基于接口的动态代理。利用拦截器(必须实现InvocationHandler接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvocationHandler.invoke()方法来处理。
Proxy.newProxyInstance()参数说明:
- ClassLoader loader:被代理类的ClassLoader
- Class<?>[] interfaces:被代理类实现的接口
- InvocationHandler h:代理类需要处理的实际业务逻辑
- retrun Object:返回的代理对象
1 | public class Proxy implements java.io.Serializable { |
InvocationHandler.invoke()参数说明:
- Object proxy:代理对象
- Method method:被代理的方法
- Object[] args:被代理方法运行时的实参
- return Object:被代理方法的返回值
1 | public interface InvocationHandler { |
使用:
在内存中生成的代理类$Proxy0参见附录
1 | public class TankProxy { |
CGLib动态代理
基于父类的动态代理。基于ASM的字节码生成库,它允许在运行时对字节码进行修改和动态生成。CGLIB通过继承的方式实现代理,它可以在运行期扩展Java类与实现Java接口,比反射更加高效。
MethodInterceptor.intercept()参数说明:
- Object obj:代理对象
- Method method:被代理的方法,即父类中的方法
- Object[] args:被代理方法运行时的实参
- MethodProxy proxy:代理对象的方法,即子类中重写的父类的方法
- return Object:被代理方法的返回值
1 | public interface MethodInterceptor extends Callback { |
使用:
1 | <!-- 需要导入cglib的包 --> |
1 | public class CglibProxy { |
JDK与CGLib区别
-
JDK动态代理基于接口,CGLIB动态代理基于父类。因为JDK动态代理生成的代理类需要继承java.lang.reflect.Proxy,所以只能基于接口;CGLIB动态代理是根据父类创建此类的子类,所以此父类不能被final修饰。 -
JDK和CGLIB动态代理都是在运行期生成字节码。而JDK是直接写Class字节码;而CGLIB使用ASM框架写Class字节码(不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉)。JDK通过反射调用方法,CGLIB通过FastClass机制直接调用方法。所以,CGLIB执行的效率较高。 -
JDK动态代理是利用反射机制生成一个实现代理接口的类(这个类看不见摸不着,在 jvm 内存中有这个类),在调用具体方法前调用InvocationHandler.invoke()来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知;CGLIB动态代理是利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。
AOP介绍
面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。通俗点说的话就是在程序运行期间,将某段代码动态切入到指定方法的指定位置进行运行的这种编程方式。
- AOP(Aspect Oriented Programming) :面向切面编程
- OOP(Object Oriented Programming) :面向对象编程
AOP术语
切面(Aspect)
指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。
连接点(Join Point)
在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
通知(Advice)
在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before”, “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
切点(Pointcut)
匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
引入(Introduction)
声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
目标对象(Target Object)
被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
AOP代理(Aop Proxy)
AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
织入(Weaving)
把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
AOP通知类型
前置通知(Before advice)
@Before在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
后置通知(After advice)
@After当连接点退出的时候执行的通知(无论是正常返回还是异常退出),总是会执行。
后置返回通知(After returning advice)
@AfterReturning在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
后置异常通知(After throwing advice)
@AfterThrowing在方法抛出异常退出时执行的通知。
环绕通知(Around advice)
@Around环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
AOP配置
需要导入以下Spring AOP依赖包:
1 | <dependency> |
基于注解
注解说明
@Pointcut
切点,定义要切入的方法表达式
@Before
前置通知,在目标方法之前运行
这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了。
@After
后置通知,在目标方法之后运行
这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行。
@AfterReturning
返回通知,在目标方法正常返回之后
和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码。
@AfterThrowing
异常通知,在目标方法抛出异常后开始运行
和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码。
@Around
环绕通知,可指定目标方法的执行,且可在目标方法执行前后增加自定义逻辑
能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
execution表达式
访问修饰符 返回值类型 方法全称
精准匹配
1
execution(public int com.dream.study.aop.ComCalculator.add(int,int))
精简匹配
1
execution(* *(..)) 或者 execution(* *.*(..))
匹配字符
匹配一个或者多个字符
1
execution(public int com.dream.study.aop.*Calculator.*(int,int))
匹配参数
匹配任意一个参数
1
execution(public int com.dream.study.aop.ComCalculator.*(int,*))
匹配多个参数,任意类型参数
1
execution(public int com.dream.study.aop.ComCalculator.*(..))
匹配路径
只能匹配一层路径,如果项目路径下有多层目录,那么*只能匹配一层路径
1
execution(public int com.dream.*.*.*Calculator.*(int,*))
匹配任意多层路径
1
execution(public int com.dream..ComCalculator.*(..))
匹配返回值
1
execution(public * com.dream.study.aop.ComCalculator.*(int,*))
不能够匹配访问修饰符
不能够匹配访问修饰符,如果不确定访问修饰符是什么,可以直接省略不写
1
execution(int com.dream.study.aop.ComCalculator.*(int,*))
其他表达式
&&:两个表达式同时满足
1
execution(public int com.dream.study.aop.ComCalculator.*(..)) && execution(* *.*(int,int) )
||:任意满足一个表达式即可
1
execution(public Integer com.dream.study.aop.ComCalculator.*(..)) || execution(* *(..))
!:只要不是这个位置都可以进行切入
1
!execution(public int com.dream.study.aop.ComCalculator.add(int,int))
JoinPoint对象
JoinPoint对象封装了Spring AOP中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
| 方法名 | 功能 |
|---|---|
| Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
| Object[] getArgs(); | 获取传入目标方法的参数对象 |
| Object getTarget(); | 获取被代理的对象 |
| Object getThis(); | 获取代理对象 |
ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中。
| 方法名 | 功能 |
|---|---|
| Object proceed() throws Throwable; | 执行目标方法 |
| Object proceed(Object[] var1) throws Throwable; | 传入的新的参数去执行目标方法 |
注解代码
关于Calculator以及ComCalculator类参见附录
在Spring中如果用到AOP,需要在配置类中添加@EnableAspectJAutoProxy(SpringBoot不需要,因为SpringBoot有自动配置)
SpringConfig
1
2
3
4
5
public class SpringConfig {
}LogUtil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class LogUtil {
public void calPoint() {
}
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
System.out.println(name + " 前置通知,参数是:" + Arrays.asList(args));
}
private void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " 后置通知");
}
public void afterReturning(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " 返回通知,结果是:" + result);
}
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " 异常通知:" + exception.getMessage());
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) {
Object[] args = proceedingJoinPoint.getArgs();
String name = proceedingJoinPoint.getSignature().getName();
Object proceed = null;
try {
System.out.println("环绕前置通知:" + name + " 方法开始,参数是:" + Arrays.asList(args));
// 利用反射调用目标方法,就是method.invoke()
proceed = proceedingJoinPoint.proceed(args);
System.out.println("环绕后置通知:" + name + " 方法结束");
} catch (Throwable e) {
System.out.println("环绕异常通知:" + name + " 方法出现异常,异常信息是:" + e);
} finally {
System.out.println("环绕返回通知:" + name + " 方法返回,返回值是:" + proceed);
}
return proceed;
}
}
// output
// 环绕前置通知:add 方法开始,参数是:[1, 1]
// add 前置通知,参数是:[1, 1]
// add 返回通知,结果是:2
// add 后置通知
// 环绕后置通知:add 方法结束
// 环绕返回通知:add 方法返回,返回值是:2
基于XML
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.dream.study"/>
<bean id="logUtil" class="com.dream.study.aop.LogUtil"/>
<bean id="comCalculator" class="com.dream.study.aop.ComCalculator"/>
<aop:config>
<aop:pointcut id="globalPoint" expression="execution( public int com.dream.*.*.*Calculator.*(..))"/>
<aop:aspect ref="logUtil">
<!-- 也可以定义在里面 -->
<!--<aop:pointcut id="globalPoint" expression="execution( public int com.dream.*.*.*Calculator.*(..))"/>-->
<aop:around method="around" pointcut-ref="globalPoint"/>
<aop:before method="before" pointcut-ref="globalPoint"/>
<aop:after method="after" pointcut-ref="globalPoint"/>
<aop:after-returning method="afterReturning" pointcut-ref="globalPoint" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="globalPoint" throwing="exception"/>
</aop:aspect>
</aop:config>
</beans>
Transaction
介绍
在事务控制方面,主要有两个分类:
编程式事务
在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction(),commit(),rollback()等事务管理相关的方法
声明式事务
在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。Spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
事务的特性
事务处理可以确保除非事务性序列内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的序列,可以简化错误恢复并使应用程序更加可靠。
但并不是所有的操作序列都可以称为事务,这是因为一个操作序列要成为事务,必须满足事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这四个特性简称为ACID特性。
原子性(Atomicity)
原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体。
使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行。通常,某个事务中的操作都具有共同的目标,并且是相互依赖的。如果数据库系统只执行这些操作中的一部分,则可能会破坏事务的总体目标,而原子性消除了系统只处理部分操作的可能性。
一致性(Consistency)
一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的。
例如:在转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配。
隔离性(Isolation)
隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。也就是说:并发执行的事务之间既不能看到对方的中间状态,也不能相互影响。
例如:在转账时,只有当A账户中的转出和B账户中转入操作都执行成功后才能看到A账户中的金额减少以及B账户中的金额增多。并且其他的事务对于转账操作的事务是不能产生任何影响的。
持久性(Durability)
持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。但如果是由于外部原因导致的数据库故障,如硬盘被损坏,那么之前提交的数据则有可能会丢失。
事务的并发问题
脏读(Dirty Read)
当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
| 时间点 | 事务A | 事务B |
|---|---|---|
| 1 | 开启事务A | |
| 2 | 开启事务B | |
| 3 | 余额增加至150 | |
| 4 | 查询余额为150 | |
| 5 | 事务回滚 |
不可重复读(Unrepeatable Read)
指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
| 时间点 | 事务A | 事务B |
|---|---|---|
| 1 | 开启事务A | |
| 2 | 开启事务B | |
| 3 | 查询余额为100 | |
| 4 | 余额增加至150 | |
| 5 | 事务提交 | |
| 6 | 查询余额为150 |
幻读(Phantom Read)
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
| 时间点 | 事务A | 事务B |
|---|---|---|
| 1 | 开启事务A | |
| 2 | 开启事务B | |
| 3 | 查询id<3的数据记录,共3条 | |
| 4 | 插入一条记录id=2 | |
| 5 | 事务提交 | |
| 6 | 查询id<3的数据记录,共4条 |
事务的隔离级别
事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题。
事务的隔离级别从低到高依次为【Read Uncommitted -> Read Committed -> Repeatable Read -> Serializable】,隔离级别越低,越能支持高并发的数据库操作。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted | √ | √ | √ |
| Read Committed | × | √ | √ |
| Repeatable Read | × | × | √ |
| Serializable | × | × | × |
读未提交(Read Uncommitted)
可以读取未提交的数据,该隔离级别会产生脏读,不可重复读,幻读的问题。
读已提交(Read Committed)
可以读取已提交的数据,该隔离级别解决了脏读,但会产生不可重复读,幻读的问题。
可重复读(Repeatable Read)
MySQL InnoDB 默认支持的隔离级别
可以重复读取数据,该隔离级别解决了脏读,不可重复读,但会产生幻读的问题。(MySQL使用间隙锁来解决幻读的问题)。
序列化(Serializable)
最严格的隔离级别,该隔离级别解决了脏读,不可重复读,幻读的问题,但是效率最低。
事务的传播特性
一个事务方法,如何获知当前是否存在事务?答案是使用ThreadLocal。Spring总是把JDBC相关的Connection和TransactionStatus实例绑定到ThreadLocal。如果一个事务方法从ThreadLocal未取到事务,那么它会打开一个新的JDBC连接,同时开启一个新的事务,否则,它就直接使用从ThreadLocal获取的JDBC连接以及TransactionStatus。换句话说,事务只能在当前线程传播,无法跨线程传播。
REQUIRED
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
REQUIRES_NEW
表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行。
NESTED
表示如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
MANDATORY
表示如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用。
SUPPORTS
表示如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务。
NOT_SUPPORTED
表示以非事务方式运行,如果当前存在事务,则把当前事务挂起,等这个方法执行完成后,再恢复执行。
NEVER
表示以非事务方式运行,如果当前存在事务,则抛出异常。
关于REQUIRES_NEW与NESTED区别
事务的传播特性是相较于多个嵌套事务而言的,针对于多个嵌套事务之间的事务处理机制。
1 |
|
- 事务传播级别是REQUIRED,当 updateAccount() 被调用时,如果 updateAccount() 中的代码抛出异常,即便被捕获,updateBook() 方法也会进行 roll back。
- 事务传播级别是REQUIRES_NEW,当 updateAccount() 代码抛出异常,并且被捕获,updateBook() 不会进行 roll back;如果是 updateBook() 抛出异常,而且没有被捕获,不会导致 updateAccount() 进行 roll back。
- 事务传播级别是NESTED,当 updateAccount() 代码抛出异常,并且被捕获,updateBook() 不会进行 roll back;如果是 updateBook() 抛出异常,而且没有被捕获,会导致 updateAccount() 进行 roll back。
PROPAGATION_REQUIRES_NEW 启动一个新的,不依赖于环境的 “内部” 事务。 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围,自己的锁,等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行。
另一方面,PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务。嵌套事务开始执行时,它将取得一个 savepoint。如果这个嵌套事务失败,我们将回滚到此 savepoint。 嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。
由此可见,PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务,而 PROPAGATION_NESTED 则是外部事务的子事务,如果外部事务 commit,嵌套事务也会被 commit, 这个规则同样适用于 roll back。
事务的配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
基于注解
注解说明
propagation
设置事务的传播特性
传播行为 解释 PROPAGATION_REQUIRED 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 PROPAGATION_REQUIRES_NEW 表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行。 PROPAGATION_NESTED 表示如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 PROPAGATION_MANDATORY 表示如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用。 PROPAGATION_SUPPORTS 表示如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务。 PROPAGATION_NOT_SUPPORTED 表示以非事务方式运行,如果当前存在事务,则把当前事务挂起,等这个方法执行完成后,再恢复执行。 PROPAGATION_NEVER 表示以非事务方式运行,如果当前存在事务,则抛出异常。 isolation
设置事务的隔离级别
隔离级别 解释 ISOLATION_DEFAULT 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。 ISOLATION_READ_UNCOMMITTED 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。 ISOLATION_READ_COMMITTED 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 ISOLATION_REPEATABLE_READ 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。 ISOLATION_SERIALIZABLE 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 readOnly
设置事务是否为只读事务
rollbackFor
设置哪些异常事务需要回滚
rollbackForClassName
设置哪些异常事务需要回滚(填写的参数是类名)
noRollbackFor
设置哪些异常事务可以不回滚
noRollbackForClassName
设置哪些异常事务可以不回滚(填写的参数是类名)
timeout
设置事务超出指定执行时长后自动终止并回滚,单位是秒
注解代码
Spring对一个声明式事务的方法的原理仍然是AOP代理,即通过自动创建Bean的Proxy实现,具体参见附录
AppConfig
1
2
3
4
5
6
7
// 启用声明式
public class AppConfig {
...
}UserService
1
2
3
4
5
6
7
8
9
10
// @Transactional 表示所有public方法都具有事务支持
public class UserService {
// 此public方法自动具有事务支持
public User register(String email, String password, String name) {
...
}
}
基于XML
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.dream.study"/>
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClassName}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<!--事务控制-->
<!--配置事务管理器的bean-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启基于注解的事务控制模式,依赖tx名称空间-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--配置通知-->
<tx:advice id="txAdvice">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="transMoney" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.dream.study.AccountService.transMoney(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
附录
Spring WebFlux
Spring WebFlux 是 Spring Framework 5.0中引入的新的响应式 web 框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步且非阻塞的,并且通过 Reactor 项目实现了 Reactive Streams 规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。
- Spring-WebMVC + Servlet + Tomcat:命令式的、同步阻塞的
- Spring-WebFlux + Reactor + Netty:响应式的、异步非阻塞的

BeanFactory与FactoryBean区别
两者一个比较形象的比喻就是BeanFactory就是一家工厂,我们可以通过提供物品名字,从工厂中得到各式各样的物品,比如桌椅板凳,键盘鼠标等等。而除此之外,我们还能获取一种比较特殊的物品——生产线(FactoryBean),一般情况下,我们获取生产线当然不是为了它本身,而是为了利用生产线生产出产品,所以当你提供生产线的名字,得到的其实是生产线生产的产品。当然,如果你就是想取这个生产线本身,那你提供的名字就得是 “&” + 生产线名。

BeanFactory
BeanFactory 就是我们常说的Spring容器,其内包含着大量的Bean,我们可以从BeanFactory获取到想要的Bean,或者查询Bean的一些信息。

接口定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}使用说明:
1
2ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml"});
BeanFactory factory = (BeanFactory) context;FactoryBean
FactoryBean则是众多Bean里的一种,只不过这种Bean是一种辅助Bean或者说中间人,它的作用是为你提供另一个或一些Bean。如果要获取FactoryBean对象,需要在id前面加一个&符号来获取。
接口定义:
1
2
3
4
5public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {return true;}
}使用说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class BookFactory implements FactoryBean<Book> {
public Book getObject() throws Exception {
Book book = new Book();
book.setBookName("《西游记》");
book.setAuthor("吴承恩");
return book;
}
public Class<?> getObjectType() {
return null;
}
}1
2
3
4
5
6
7
8
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="book" class="org.spring.study.BookFactory" />
</beans>
Book
1 | // 如果是注解注入需加上@Component |
@Autowired与@Resources的区别
| 类型 | 提供方 | 默认注入方式 | 多实现类处理 |
|---|---|---|---|
| @Autowired | Spring提供 | byType(根据类型进行匹配) | 通过 @Qualifier 注解来显式指定名称 |
| @Resources | JDK提供 | byName(根据名称进行匹配) | 通过 name 属性来显式指定名称 |
BeanPostProcesser
如果容器实现了BeanPostProcesser并重写了两个方法,则在bean对象初始化前后需要执行重写的方法
BeanPostProcessor也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法。
配置BeanPostProcesser,是对容器中的所有Bean对象添加后置处理器的生命周期
1 | public class MyBeanProcesser implements BeanPostProcessor { |
作用:
如果我们想在Spring容器中完成bean实例化,配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。
注意:
- 接口中的两个方法都要将传入的bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标
- ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法
InitialzingBean
当一个类实现这个接口之后,Spring启动后,初始化Bean时,若该Bean实现InitialzingBean接口,会自动调用afterPropertiesSet()方法,完成一些用户自定义的初始化操作。
配置InitialzingBean,只对当前实现类的Bean对象添加InitialzingBean的生命周期
1 | public class Book implements InitializingBean { |
注意:
在Spring初始化Bean的时候,如果该Bean是实现了InitializingBean接口,并且同时配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法。
JDK生成的动态代理类$Proxy0
1 | final class $Proxy0 extends Proxy implements Movable { |
Calculator
1 | public interface Calculator { |
ComCalculator
1 |
|
声明式事务原理
1 | public class UserService$$EnhancerBySpringCGLIB extends UserService { |
参考网站
- Spring Framework官网
- Spring Framework下载地址
- GitHub Spring中文翻译
- GitHub Spring源码地址
- 廖雪峰Spring开发
- Spring 常见知识点&面试题总结
- Spring Bean的生命周期(非常详细)
- BeanFactory 和 FactoryBean 的关联与区别