Spring基础

什么是 Spring 框架

Spring是一个生态体系,其中包含了Spring Framework、Spring Boot、Spring cloud等等。我们一般常说的Spring框架指的是Spring Framework

Spring Framework所有模块

源码地址:https://github.com/spring-projects/spring-framework

模块简介
Spring AOP提供了切面编程的功能,可以对现有的类进行横向抽取代码实现统一的功能。
Spring Aspects集成了AspectJ,提供额外的切面实现,可使用AspectJ注解进行切面编程。
Spring Beans提供BeanFactory、FactoryBean等工厂类的实现,支持循环依赖解决和Bean生命周期管理等功能。
Spring Context面向应用程序的上下文,提供JNDI查找、事件传播和资源加载等企业级功能。
Spring Context Indexer用于生成Spring应用程序上下文中所有Bean的索引。
Spring Context Support提供对高级应用程序上下文特性的支持,如邮件发送和缓存管理等。
Spring Core提供Spring框架的核心组件,包括依赖注入、依赖查找、AOP抽象等。
Spring Expression提供强大的表达式语言,用于在运行时进行查询和操作对象图。
Spring Instrument提供基于代理的类和对象增强工具。
Spring JCL提供与类加载器无关的日志抽象。
Spring JDBC提供对JDBC操作的封装,支持声明式事务管理等特性。
Spring JMS提供JMS(Java消息服务)的支持。
Spring messaging提供了Spring编程模型的消息抽象。
Spring ORM提供对ORM框架(如JPA、Hibernate等)的支持。
Spring OXM提供对象/XML映射的支持。
Spring Test提供Spring测试框架和相关的支持类。
Spring TX提供事务管理抽象和实现。
Spring Web提供Spring Web MVC框架和其他Web支持功能,基于Servlet API。
Spring WebFlux提供基于Reactive Streams的Web框架。
Spring WebSocket提供对WebSocket协议的支持。

Spring入门使用案例

引入Spring依赖

创建Maven工程,在pom文件中引入依赖

1
2
3
4
5
6
7
8
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>

创建HelloWorld类

1
2
3
4
5
public class HelloWorld {
public void sayHello(){
System.out.println("helloworld");
}
}

创建Spring配置文件

在src\main\resources目录下创建Spring配置文件HelloWorld.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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.xsd">

<!--
bean标签:配置IOC容器所管理的bean
id属性:设置bean的唯一标识
class属性:设置bean所对应类型的全类名
-->

<!-- 配置HelloWorld所对应的bean,将HelloWorld的对象交给Spring的IOC容器管理 -->
<bean id="helloworld" class="入门案例.HelloWorld"></bean>

</beans>

创建测试类进行测试

注意事项:Spring 底层默认通过反射技术,调用组件类的无参构造器来创建组件对象,如果没有无参构造器,则会抛出异常

1
2
3
4
5
6
7
8
9
10
11
public class HelloWorldTest {
@Test
public void TestHelloWorld(){
// 读取指定配置文件,获取IOC容器
ApplicationContext ioc = new ClassPathXmlApplicationContext("HelloWorld.xml");
// 获取IOC容器中的Bean对象
HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");
// 调用类中方法
helloworld.sayHello();
}
}

Spring三大核心概念

IOC 控制反转

IOC 简介

控制反转IOC(Inversion of Control),是一种设计思想,把对象的创建、赋值、管理的工作都交给代码之外的容器(Spring容器)实现,而不是由自己来创建和管理依赖对象,使资源可以配置和集中管理,降低了使用资源双方的依赖程度(耦合度)

  • 自己创建和管理对象:在面向对象编程(OOP)中,对象的创建通常会采用new 类名的方式手动完成,这种方式会使得调用者与被调用者之间的耦合性增加,不利于后期项目的升级和维护
  • 容器创建和管理对象:在Spring中,通过在xml配置文件(容器)中使用标签配置对象,将对象的创建、赋值和生命周期的管理等工作全部交给容器来完成,降低组件之间的耦合度,使得应用程序更加灵活、可扩展、易于维护
1
2
3
4
5
6
7
// 不使用IOC时创建对象调用方法
User user = new User();
user.getId;
// 使用IOC时创建对象调用方法
@Autowired
private User user;
user.getId;

两大核心接口

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口

BeanFactory

BeanFactory是Spring的早期接口,称为Spring的Bean工厂,提供了最基本的Bean容器功能,包括Bean的注册、创建、依赖注入和生命周期管理等

  1. Bean的注册:可以通过BeanFactory将Java对象注册为Spring的Bean,并为其分配一个唯一的标识符,以便在需要时获取和使用。
  2. Bean的创建:BeanFactory根据配置信息和依赖关系,负责实例化并创建Bean对象。可以通过XML配置文件、注解或其他方式来描述Bean的定义和属性。
  3. 依赖注入:BeanFactory负责管理Bean之间的依赖关系,并在需要时自动将依赖的Bean注入到目标Bean中。这样可以实现解耦和灵活的组件协作。
  4. 生命周期管理:BeanFactory负责管理Bean的生命周期,包括Bean的初始化和销毁过程。可以通过配置和编程的方式定义Bean的初始化方法和销毁方法,并在适当的时机执行。
  5. 延迟加载:BeanFactory支持延迟加载方式,即只有在需要时才去实例化和创建Bean对象。这样可以提高应用程序的性能和资源利用率。
  6. 配置元数据的加载方式:BeanFactory支持多种配置元数据的加载方式,如XML配置、注解配置和基于Java的配置等。可以根据项目的需求选择最适合的配置方式。

ApplicationContext

ApplicationContext是BeanFactory的扩展,在BeanFactory基础上进行了封装,提供了更多的功能,例如自动装配、AOP支持等。

  1. BeanFactory的所有功能:Bean的注册、创建、依赖注入和生命周期管理等
  2. 自动装配:ApplicationContext在实例化Bean时,可以自动解析Bean之间的依赖关系,并自动装配到相应的属性中,省去了手动设置的步骤。
  3. AOP支持:ApplicationContext对面向切面编程(AOP)提供了支持。它可以将通用的横切逻辑(如日志、事务管理)与业务逻辑分离,提高了系统的可维护性和扩展性。
  4. 事件发布和处理:ApplicationContext可以作为事件的发布者,通过发布事件来通知其他Bean进行相应的处理。这种松耦合的方式可以方便地实现模块之间的通信和解耦。
  5. 国际化支持:ApplicationContext提供了对国际化的支持。它可以根据不同的Locale加载相应的资源文件,从而实现多语言环境的应用程序。
  6. 配置元数据的加载方式:ApplicationContext支持多种配置元数据的加载方式,如XML配置、注解配置和基于Java的配置等。这样可以根据项目的需求选择最适合的配置方式。

DI 依赖注入

DI 简介

依赖注入(DI)是控制反转(IOC)技术的实现方式,其核心思想是让容器来负责对象的创建和管理,并在需要使用这些对象时主动将它们注入到目标对象中,注入一个对象时,容器会自动扫描类路径下所有的组件对象,找到与该对象类型匹配的组件对象,并将其自动注入到目标对象中

配置 Bean

通过XML文件配置Bean

在 XML 文件中,我们可以使用 <bean> 标签来定义 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.xsd">

<!--
bean标签:配置IOC容器所管理的bean
id属性:设置 Bean 的唯一标识
class属性:设置 Bean 所对应类型的全类名
scope属性:设置 Bean 的作用域
-->
<bean id="xxx" class="com.xxx" scope="xxx"></bean>

</beans>

通过注解配置Bean

Spring 提供了一些内置的注解来配置 Bean,来替代 XML 配置文件

@Component:用于普通的 Java 类

1
2
3
4
@Component
public class MyUtils {

}

@Repository:用于 DAO 数据访问层的类

1
2
3
4
@Repository
public class UserDaoImpl implements UserDao {

}

@Service:用于服务层的类

1
2
3
4
@Service
public class UserServiceImpl implements UserService {

}

@Controller:用于控制层的类

1
2
3
4
@Controller
public class UserController {

}

注入 Bean

Setter 方法注入

XML配置

1
2
3
<bean id="userService" class="com.example.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>

代码中注入

1
2
3
4
5
6
7
8
9
public class UserServiceImpl implements UserService {

private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

}

构造器注入

XML配置

1
2
3
<bean id="userService" class="com.example.service.impl.UserServiceImpl">
<constructor-arg ref="userDao"/>
</bean>

代码中注入

1
2
3
4
5
6
7
8
9
public class UserServiceImpl implements UserService {

private UserDao userDao;

public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}

}

接口注入

XML配置

1
2
3
<bean id="userService" class="com.example.service.impl.UserServiceImpl">
<property name="beanName" value="userService"/>
</bean>

代码中注入

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService, BeanNameAware {

private String beanName;

@Override
public void setBeanName(String name) {
this.beanName = name;
}

}

@Autowired注解注入

  1. @Autowired是Spring框架提供的注解,用来自动装配bean。
  2. 默认情况下,@Autowired注解按照类型匹配注入bean。
  3. 如果只有一个符合类型的bean,则直接将该bean赋值给目标对象。
  4. 如果有多个符合类型的bean,则会优先按照byName方式进行注入,即根据属性名找到对应的bean对象进行注入。
  5. 如果byName注入仍存在多个可选bean,则会抛出异常。
  6. 在容器中有多个符合类型的bean时,需要结合@Qualifier注解一起使用。
  7. @Autowired注解可以应用在字段、Setter方法、构造函数上。

应用在字段上

1
2
3
4
5
6
7
8
@Service
public class UserServiceImpl implements UserService {

@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;

}

应用在Setter方法上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private UserDao userDao;

@Autowired
public void setUserDao(@Qualifier("userDaoImpl") UserDao userDao) {
this.userDao = userDao;
}

}

应用在构造函数上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private UserDao userDao;

@Autowired
public UserServiceImpl(@Qualifier("userDaoImpl") UserDao userDao) {
this.userDao = userDao;
}

}

@Resource注解注入

  1. @Resource注解是JSR-250规范中提供的注解,也可以用于自动装配Bean,相比于@Autowired注解,更加灵活
  2. 默认情况下,@Resource注解会根据名称匹配对应的bean进行注入,也可以指定name或者type属性。
  3. 如果没有指定name或者type属性,则默认使用被注释的成员变量的名称来查找对应的bean。
  4. 当指定了name属性时,会根据name的值进行匹配,找到符合条件的bean进行注入。
  5. 当指定了type属性时,会根据type的值查找相应类型的bean进行注入。
  6. 当多个bean匹配时,可以使用name属性指定要注入的具体bean。
  7. @Resource注解可以应用在字段、Setter方法上。

应用在字段上

1
2
3
4
5
6
public class UserServiceImpl implements UserService {

@Resource(name="userDaoImpl")
private UserDao userDao;

}

应用在Setter方法上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private UserDao userDao;

@Resource(name="userDaoImpl")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

}

@Inject注解注入

  1. @Inject是JSR-330规范中提供的注解,也可以用于自动装配Bean,相比于@Autowired和@Resource注解,它更加简洁和灵活
  2. 和@Autowired的作用基本相同,@Inject默认按类型匹配注入bean,但是需要引入javax.inject依赖才能使用
  3. 和@Autowired不同的是,@Inject不支持required参数,即依赖对象不存在时不会报错
  4. @Inject注解可以应用在字段、Setter方法、构造函数上
  5. @Inject注解可以结合@Named注解指定要注入的bean的名称

应用在字段上

1
2
3
4
5
6
public class UserServiceImpl implements UserService {

@Inject
private UserDao userDao;

}

应用在Setter方法上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private UserDao userDao;

@Inject
public void setUserDao(@Qualifier("userDaoImpl") UserDao userDao) {
this.userDao = userDao;
}

}

应用在构造函数上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private UserDao userDao;

@Inject
public UserServiceImpl(@Qualifier("userDaoImpl") UserDao userDao) {
this.userDao = userDao;
}

}

@Qualifier注解注入

可以和 @Autowired 注解、@Resource 注解、@Inject 注解等结合使用,用于指定需要注入的 Bean 的名称,一般用于解决多个 Bean 实例类型相同的问题

1
2
3
4
5
6
7
8
@Service
public class UserServiceImpl implements UserService {

@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;

}

@Primary注解注入

@Primary注解位于Bean定义的类上面,用于标识该Bean是首选的Bean。当存在多个类型相同的Bean时,Spring会优先选择带有@Primary注解的Bean进行装配。

1
2
3
4
5
@Primary
@Service
public class UserServiceImpl implements UserService {

}

@Value注解注入

用于注入配置项的值,可以应用在字段、Setter方法、构造函数上。需要注意的是,@Value注解只能注入基本类型和字符串类型的值

应用在字段上

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserServiceImpl implements UserService {

@Value("${jdbc.url}")
private String jdbcUrl;

@Value("${jdbc.username}")
private String jdbcUserName;

@Value("${jdbc.password}")
private String jdbcPassword;

}

应用在Setter方法上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private String username;

@Value("${db.username}")
public void setUsername(String username) {
this.username = username;
}

}

应用在构造函数上

1
2
3
4
5
6
7
8
9
10
public class UserServiceImpl implements UserService {

private String username;

@Autowired
public UserServiceImpl(@Value("${db.username}") String username) {
this.username = username;
}

}

AOP面向切面编程

AOP 是什么?

AOP(面向切面编程)是一种编程范式。通过将的代码逻辑封装成切面(Aspect),并在运行时通过动态代理技术将这些切面织入到目标方法中,从而实现不修改源代码的情况下对目标方法进行增强的效果,常见的应用场景有日志记录、事务管理、缓存优化、安全控制、异常处理等。

AOP怎么实现?

编译期间实现AOP

在编译期间可以使用AspectJ框架实现AOP,它能够在编译阶段将切面功能编织到目标类中。通过使用AspectJ注解或XML配置文件,可以在编译时通过特定的编译器将切面代码插入到目标类中。

运行期间实现AOP

在运行期间可以使用动态代理和反射技术实现AOP,通过使用Java的Proxy类或CGLIB库,在运行时创建一个动态代理对象,并在方法调用前后执行切面逻辑

  • JDK动态代理要求目标类实现接口,是基于接口的代理方式。利用反射机制,在运行时动态地创建实现目标对象接口的代理类实例,并通过代理对象来调用目标对象的方法,在代理对象的方法执行前后,添加额外的逻辑,实现AOP。
  • CGLIB动态代理可以代理任意类,不要求目标类实现接口,是基于继承的代理方式。利用CGLIB字节码生成技术,通过继承目标类,动态生成目标对象的子类,在子类中重写父类的方法时,添加切面逻辑,实现AOP。

Spring AOP是什么?

Spring AOP(Aspect-Oriented Programming)是Spring框架中的一个模块,用于实现AOP(面向方面编程)。

概念简介
切面(Aspect)切面(Aspect) = 切入点(Pointcut) + 通知(Advice)
切入点(Pointcut)用于定义哪些方法会被增强(切入点一定是连接点,连接点不一定是切入点)
连接点(JoinPoint)目标对象的所属类中,定义的所有方法均为连接点
织入(Weaving)将切面代码插入到目标对象上,从而扩展目标对象的功能
通知(Advice)在连接点前后执行的代码,用于增强功能
目标对象(Target)需要被增强的对象
代理对象(Proxy)向目标对象应用通知之后创建的对象,可以作为目标对象的替身使用

Spring AOP怎么实现?

在Spring框架中,提供了JDK动态代理和CGLIB动态代理两种方式来实现AOP(面向切面编程)。具体使用哪种代理方式取决于被代理的对象是否实现了接口。

  • JDK动态代理如果被代理的对象实现了接口,Spring AOP会使用JDK动态代理来创建代理对象。JDK动态代理是通过反射机制,在运行时动态生成一个实现被代理接口的代理类,并将方法的调用委托给目标对象执行。
  • CGLIB动态代理如果被代理的对象没有实现接口,Spring AOP会使用CGLIB动态代理来生成一个被代理对象的子类作为代理对象。CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它通过在运行时生成目标类的子类来实现代理。
  • AspectJ框架:除了JDK动态代理和CGLIB动态代理,Spring还支持使用AspectJ框架来实现AOP。AspectJ是一个独立的AOP框架,它使用编译时或者运行时的字节码增强,可以更灵活地实现切面功能。

基于JDK动态代理(依赖接口)

基于 JDK 动态代理实现的AOP,代理类和目标类都必须实现共同的接口。Spring利用JDK的反射机制,在运行时动态地创建目标对象的代理类,代理类实现了目标对象的接口,并在方法调用前后添加逻辑代码,实现对目标方法的增强。

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
59
60
61
62
63
64
65
// 目标接口
interface TargetInterface {
void doSomething();
}

// 目标方法(被代理类)
class TargetClass implements TargetInterface {
@Override
public void doSomething() {
System.out.println("被代理类中的方法被执行...");
}
}

// 用于处理代理逻辑:实现InvocationHandler接口,重写invoke()方法
class JdkDynamicProxyHandler implements InvocationHandler {
// 将被代理的目标对象声明为成员变量
private final Object target;

// 用于初始化被代理的目标对象
public JdkDynamicProxyHandler(Object target) {
this.target = target;
}

/**
* 处理代理逻辑:实现InvocationHandler接口,重写invoke()方法
*
* @param proxy 代理对象
* @param method 代理对象需要实现的方法,即其中需要重写的方法
* @param args method所对应方法的参数
* @return 根据加载到内存中的被代理类,动态的创建一个代理类及其对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("[JDK动态代理][日志] " + "执行方法前的逻辑");// 在调用目标方法前进行逻辑增强
result = method.invoke(target, args);// 调用目标方法
System.out.println("[JDK动态代理][日志] " + "执行方法后的逻辑");// 在调用目标方法后进行逻辑增强
} catch (Exception e) {
e.printStackTrace();
System.out.println("[JDK动态代理][日志] " + "方法名称:" + method.getName() + ",出现异常:" + e.getMessage());
} finally {
System.out.println("[JDK动态代理][日志] " + "方法名称:" + method.getName() + ",执行完毕...");
}
return result;
}
}

public class JdkDynamicProxyTest {
public static void main(String[] args) {
TargetClass target = new TargetClass();// 创建被代理的目标对象
ClassLoader classLoader = target.getClass().getClassLoader(); // 代理类的类加载器
Class<?>[] interfaces = target.getClass().getInterfaces();// 代理类实现的接口列表
JdkDynamicProxyHandler invocationHandler = new JdkDynamicProxyHandler(target);// 自定义的处理器对象

// Proxy.newProxyInstance():创建一个代理实例,其中有三个参数:
// 1、classLoader:代理类的类加载器
// 2、interfaces:代理类要实现的接口列表
// 3、invocationHandler:实现了 InvocationHandler 接口的对象,即自定义的处理器对象。
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

// 调用代理对象的方法
proxy.doSomething();
}
}

基于CGLIB动态代理(不依赖接口)

基于CGLIB动态代理实现的AOP,不要求代理类和目标类实现相同的接口,而是通过继承来实现 AOP。CGLIB动态代理是通过修改目标类的字节码来实现的,可以对任意类进行代理,包括没有实现接口的类。当目标对象没有实现接口时,Spring会在运行时使用CGLIB库来生成目标对象的子类,该子类会重写目标对象中的非final方法,并将代理逻辑织入其中,从而实现了对目标方法的代理和增强。当调用目标类的方法时,实际上是调用了子类中重写的代理方法。

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
// 目标接口
interface TargetInterface {
void doSomething();
}

// 目标方法(被代理类)
class TargetClass implements TargetInterface {
@Override
public void doSomething() {
System.out.println("被代理类中的方法被执行...");
}
}

// 用于处理代理逻辑:自定义类实现MethodInterceptor接口,重写接口的intercept方法(方法拦截器)
class CglibDynamicProxyHandler implements MethodInterceptor {
/**
* intercept方法用于拦截并增强被代理类的方法,类似JDK动态代理中的invoke方法
*
* @param proxy 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
System.out.println("[CGLIB动态代理][日志] " + "方法名称:" + method.getName() + ",方法参数:" + Arrays.toString(args));
result = methodProxy.invokeSuper(proxy, args);
System.out.println("[CGLIB动态代理][日志] " + "方法名称:" + method.getName() + ",方法结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[CGLIB动态代理][日志] " + "方法名称:" + method.getName() + ",出现异常:" + e.getMessage());
} finally {
System.out.println("[CGLIB动态代理][日志] " + "方法名称:" + method.getName() + ",执行完毕...");
}
return result;
}
}

public class CglibDynamicProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();// 创建动态代理增强类
enhancer.setClassLoader(TargetClass.class.getClassLoader());// 设置类加载器
enhancer.setSuperclass(TargetClass.class);// 设置被代理类
enhancer.setCallback(new CglibDynamicProxyHandler());// 设置方法拦截器
TargetClass proxy = (TargetClass)enhancer.create();// 创建代理类对象
proxy.doSomething();
}
}

基于AspectJ框架(不依赖接口)

AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,并不是 Spring 组成部分,是一款独立的 AOP 框架(不需要Spring也能独立使用),它使用编译时或者运行时的字节码增强,可以更灵活地实现切面功能。一般会把AspectJ和Spring框架一起使用,去进行一些AOP操作

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
// 目标接口
interface TargetInterface {
void doSomething();
}

// 目标方法(被代理类)
class TargetClass implements TargetInterface {
@Override
public void doSomething() {
System.out.println("被代理类中的方法被执行...");
}
}
@Aspect
@Component
public class AspectJProxy {
/**
* 定义切点,匹配 TargetClass 类中的所有方法
*/
@Pointcut("execution(* com.wen.aop.aspectj.TargetClass.*(..))")
public void pointcut() {
}

/**
* 环绕通知,在切点方法执行前后执行
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = null;
try {
System.out.println("[AspectJ][日志] " + "方法名称:" + proceedingJoinPoint.getSignature().getName() + ",方法参数:" + Arrays.toString(proceedingJoinPoint.getArgs()));
result = proceedingJoinPoint.proceed();
System.out.println("[AspectJ][日志] " + "方法名称:" + proceedingJoinPoint.getSignature().getName() + ",方法结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[AspectJ][日志] " + "方法名称:" + proceedingJoinPoint.getSignature().getName() + ",出现异常:" + e.getMessage());
} finally {
System.out.println("[AspectJ][日志] " + "方法名称:" + proceedingJoinPoint.getSignature().getName() + ",执行完毕...");
}
return result;
}
}

Spring基于AspectJ实现AOP

Spring中AspectJ使用步骤

  1. 引入AOP相关依赖:在项目的构建配置文件中,添加 AOP模块的依赖 以及 AspectJ 相关的依赖。
  2. 创建切面类:创建一个切面类,该类包含了横切关注点的实现
  3. 定义切入点:在切面类中使用 AspectJ 的切入点表达式语法来指定在哪些连接点上应用通知。
  4. 定义通知:根据需要,在切面类中定义需要的通知。
  5. 编写通知代码:根据需要,在切面类中编写通知代码。

AspectJ五种通知类型

AspectJ提供了几种类型的通知,用于在目标方法执行前后以及发生异常时执行特定的逻辑

通知对应XML对应注解简介
前置通知<before>@Before在被代理的目标方法执行之前执行通知
返回通知<after-returning>@AfterReturning在被代理的目标方法成功返回结果后执行通知
异常通知<after-throwing>@AfterThrowing在被代理的目标方法抛出异常后执行通知
后置通知<after>@After在被代理的目标方法执行之后(无论是否抛出异常)执行通知
环绕通知<around>@Around包括上面四种通知对应的所有位置
通过使用try…catch…finally结构围绕整个被代理的目标方法进行增强

切入点表达式

每个通知都可以使用切入点表达式来定义切入的目标方法,切入点表达式语法如下

1
execution(modifier-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)throws-pattern?)
  • execution:用于声明切入点类型。
  • modifier-pattern:表示目标方法的访问修饰符。例如,publicprivateprotected等。使用 * 表示任意修饰符。
  • ret-type-pattern:表示目标方法的返回类型。例如,voidintjava.lang.String等。使用 * 表示任意返回类型。
  • declaring-type-pattern:表示方法所在类的全限定名。例如,com.wen.service.UserService。使用 * 表示任意包或类名。
  • name-pattern:表示目标方法的名称。例如,addUserfindBy*等。可使用通配符 * 表示任意方法名。
  • param-pattern:表示目标方法的参数列表。例如,(int) 表示只有一个 int 类型参数,(String, int) 表示第一个参数为 String 类型,第二个参数为 int 类型,(..) 表示任意参数列表。注意,参数模式中不能使用通配符 *
  • throws-pattern:表示目标方法可能抛出的异常类型。例如,java.lang.Exceptionjava.io.IOException等。使用 * 表示任意异常类型。

下面是一些常见的切入点表达式示例:

  • 切入所有类中的所有方法: execution(* *(..))
  • 切入指定包下的所有方法: execution(* com.example.service.*.*(..))
  • 切入指定类中的所有方法: execution(* com.example.service.UserService.*(..))
  • 切入指定方法名的方法: execution(* com.example.service.UserService.add*(..))
  • 切入指定参数类型的方法: execution(* com.example.service.UserService.findByAge(int))
  • 切入指定参数个数的方法: execution(* com.example.service.UserService.*(.., *))

引入AOP相关依赖

在Spring中基于AspectJ实现AOP,需要引入以下两个依赖

  • spring-aop:这是Spring框架提供的AOP模块的核心依赖。它包含了Spring AOP的相关功能和类。
  • aspectjweaver:这是AspectJ框架提供的依赖,用于支持AspectJ注解和切点表达式等特性。
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Spring AOP依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version><!-- Spring版本号 --></version>
</dependency>
<!-- AspectJ 依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version><!-- AspectJ版本号 --></version>
</dependency>

如果使用Spring Boot,只需引入 spring-boot-starter-aop 依赖即可,它会自动包含所需的 spring-aopaspectjweaver 依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version><!-- Spring Boot 版本号 --></version>
</dependency>

XML配置实现AOP

(1)在 XML 配置文件中导入 Spring aop 命名空间的约束

1
2
3
4
5
6
7
8
9
10
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
...
</beans>

(2)在 XML 配置文件中,使用 <aop:aspect> 元素定义切面类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 配置切面类 -->
<bean id="logAspec" class="com.wen.aop.LogAspec"/>

<!-- 定义切面和通知 -->
<aop:config>
<!-- 将IOC容器的某个bean设置为切面类并设置优先级 -->
<aop:aspect id="myAspect" ref="logAspec" order="1">
...
</aop:aspect>
</aop:config>
</beans>

(3)在 XML 配置文件中,定义切入点 <aop:pointcut>,用来表示对哪个类中的那个方法进行增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 配置切面类 -->
<bean id="logAspec" class="com.wen.aop.LogAspec"/>

<!-- 定义切面和通知 -->
<aop:config>
<!-- 将IOC容器的某个bean设置为切面类并设置优先级 -->
<aop:aspect id="myAspect" ref="logAspec" order="1">
<!-- 设置公共的切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(* 基于xml的AOP.CalculatorImpl.*(..))"/>
...
</aop:aspect>
</aop:config>
</beans>

(4)在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
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 配置切面类 -->
<bean id="logAspec" class="com.wen.aop.LogAspec"/>

<!-- 定义切面和通知 -->
<aop:config>
<!-- 将IOC容器的某个bean设置为切面类并设置优先级 -->
<aop:aspect id="myAspect" ref="logAspec" order="1">
<!-- 设置公共的切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(* 基于xml的AOP.CalculatorImpl.*(..))"/>
<!-- 前置通知 -->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"></aop:before>
<!-- 后置通知 -->
<aop:after method="afterAdvice" pointcut-ref="pointCut"></aop:after>
<!-- 返回通知 -->
<aop:after-returning method="afterReturningAdvice" returning="result" pointcut-ref="pointCut"></aop:after-returning>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingAdvice" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
<!-- 环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>

</beans>

(5)根据需要,在切面类中编写通知代码

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
59
60
61
62
63
64
65
66
67
68
69
public class LogAspec {
/**
* 前置通知方法:记录目标方法的名称、参数和开始执行时间。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]前置通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 后置通知方法
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]后置通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 返回通知方法:记录目标方法的名称、参数和结束执行时间,并打印执行结果。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param result 目标对象方法的返回值
*/
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]返回通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
}

/**
* 异常通知方法:记录目标方法的名称、参数和结束执行时间,并打印异常信息。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param ex 可能抛出的异常,当目标方法执行正常返回时,ex 参数应该是 null
*/
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]异常通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + ex.getMessage());
}

/**
* 环绕通知方法:记录目标方法的名称、参数和开始执行时间,并在执行前后打印日志。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
result = joinPoint.proceed();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + throwable.getMessage());
} finally {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行完毕,时间是 " + new Date());
}
return result;
}
}

XML配置+注解实现AOP

(1)在 XML 配置文件中,开启 Spring 对注解 AOP 的支持

1
2
3
4
5
6
7
8
9
10
<?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: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">

<!--开启Spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>

</beans>

(2)使用@Aspect注解定义切面类

1
2
3
4
5
@Aspect
@Component
public class LogAspec {
...
}

(3)定义切入点,用来表示对哪个类中的那个方法进行增强

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

...

}

(4)根据需要,在切面类中定义需要的通知。

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
59
60
61
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

/**
* 前置通知方法:记录目标方法的名称、参数和开始执行时间。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint) {
...
}

/**
* 后置通知方法
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@AfterReturning(pointcut = "pointCut()")
public void afterAdvice(JoinPoint joinPoint, Object result) {
...
}

/**
* 返回通知方法:记录目标方法的名称、参数和结束执行时间,并打印执行结果。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param result 目标对象方法的返回值
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
...
}

/**
* 异常通知方法:记录目标方法的名称、参数和结束执行时间,并打印异常信息。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param ex 可能抛出的异常,当目标方法执行正常返回时,ex 参数应该是 null
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
...
}

/**
* 环绕通知方法:记录目标方法的名称、参数和开始执行时间,并在执行前后打印日志。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
...
}
}

(5)根据需要,在切面类中编写通知代码。

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

/**
* 前置通知方法:记录目标方法的名称、参数和开始执行时间。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]前置通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 后置通知方法
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@AfterReturning(pointcut = "pointCut()")
public void afterAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]后置通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 返回通知方法:记录目标方法的名称、参数和结束执行时间,并打印执行结果。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param result 目标对象方法的返回值
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]返回通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
}

/**
* 异常通知方法:记录目标方法的名称、参数和结束执行时间,并打印异常信息。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param ex 可能抛出的异常,当目标方法执行正常返回时,ex 参数应该是 null
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]异常通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + ex.getMessage());
}

/**
* 环绕通知方法:记录目标方法的名称、参数和开始执行时间,并在执行前后打印日志。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
result = joinPoint.proceed();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + throwable.getMessage());
} finally {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行完毕,时间是 " + new Date());
}
return result;
}
}

使用纯注解实现AOP

(1)使用@EnableAspectJAutoProxy注解开启Spring对注解AOP的⽀持

1
2
3
4
5
6
@Configuration
@ComponentScan("com.wen.aop")
@EnableAspectJAutoProxy // 开启Spring对注解AOP的⽀持
public class SpringConfiguration {
// Spring启动类配置
}

(2)使用@Aspect注解定义切面类

1
2
3
4
5
@Aspect
@Component
public class LogAspec {
...
}

(3)定义切入点,用来表示对哪个类中的那个方法进行增强

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

...

}

(4)根据需要,在切面类中定义需要的通知。

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
59
60
61
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

/**
* 前置通知方法:记录目标方法的名称、参数和开始执行时间。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint) {
...
}

/**
* 后置通知方法
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@AfterReturning(pointcut = "pointCut()")
public void afterAdvice(JoinPoint joinPoint, Object result) {
...
}

/**
* 返回通知方法:记录目标方法的名称、参数和结束执行时间,并打印执行结果。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param result 目标对象方法的返回值
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
...
}

/**
* 异常通知方法:记录目标方法的名称、参数和结束执行时间,并打印异常信息。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param ex 可能抛出的异常,当目标方法执行正常返回时,ex 参数应该是 null
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
...
}

/**
* 环绕通知方法:记录目标方法的名称、参数和开始执行时间,并在执行前后打印日志。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
...
}
}

(5)根据需要,在切面类中编写通知代码。

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Aspect
@Component
public class LogAspec {
/**
* 定义切入点表达式
*/
@Pointcut("execution(* com.wen.aop.Calculator.*(..))")
public void pointCut() {}

/**
* 前置通知方法:记录目标方法的名称、参数和开始执行时间。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Before("pointCut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]前置通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 后置通知方法
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@AfterReturning(pointcut = "pointCut()")
public void afterAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]后置通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
}

/**
* 返回通知方法:记录目标方法的名称、参数和结束执行时间,并打印执行结果。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param result 目标对象方法的返回值
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]返回通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
}

/**
* 异常通知方法:记录目标方法的名称、参数和结束执行时间,并打印异常信息。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
* @param ex 可能抛出的异常,当目标方法执行正常返回时,ex 参数应该是 null
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[LogAspect]异常通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + ex.getMessage());
}

/**
* 环绕通知方法:记录目标方法的名称、参数和开始执行时间,并在执行前后打印日志。
*
* @param joinPoint 连接点对象,可以获取到目标对象、目标方法和方法参数等信息
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 开始执行,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date());
result = joinPoint.proceed();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",执行结果是 " + result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行结束,参数是 " + Arrays.toString(args) + ",执行时间是 " + new Date() + ",抛出异常信息:" + throwable.getMessage());
} finally {
System.out.println("[LogAspect]环绕通知:方法 " + methodName + " 执行完毕,时间是 " + new Date());
}
return result;
}
}

AOP应用案例(记录日志)

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version><!-- Spring Boot 版本号 --></version>
</dependency>

自定义注解

自定义注解,注解中定义参数用于指定日志的描述信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 自定义注解,用于打印日志
*/
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
/**
* @return 业务名
*/
String businessName() default "";

/**
* @return 操作类型
*/
String businessType() default "";
}

定义切面类

  1. 定义切面类,使用@Aspect注解标识
  2. 在切面类中,使用自定义的注解作为切点,用于拦截注解标识的方法
  3. 在切面类中,编写环绕通知方法,在方法执行前后分别打印日志
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/**
* 切面类,对需要打印日志的接口做增强
*/
@Slf4j// 用于生成日志对象
@Aspect// 标识切面类
@Component// 标识普通组件,保证这个切面类能够放入IOC容器
public class LogAspect {

@Autowired
private OperationLogMapper operationLogMapper;

/**
* 定义切点,使用@Log注解的方法会被拦截
* 注解@Pointcut:用于定义切点,即需要拦截的连接点
* 切点函数@annotation: 用于匹配带有指定注解的方法
*/
@Pointcut("@annotation(com.wen.aop.log.Log)")
public void pt() {

}

/**
* 在切点方法执行前后进行处理
* 注解@Around:环绕通知,使用try...catch...finally结构围绕整个被代理的目标方法
*
* @param pjp 切点方法的连接点
* @return 切点方法的返回结果
*/
@Around("pt()")
public Object printLot(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
log.info("<==========Start==========>");
proceed = pjp.proceed();// 执行目标方法
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
recordOperationLog(proceed, pjp, request);// 记录操作日志信息
saveOperationLog(proceed, pjp, request);// 保存操作日志信息
} catch (Throwable e) {
e.printStackTrace();
log.error("[抛出异常] - {}", e.getMessage());// 异常通知
} finally {
log.info("<==========End==========>" + System.lineSeparator());// 后置通知,System.lineSeparator()表示拼接换行
}
return proceed;
}

/**
* 记录操作日志信息
*
* @param proceed 目标方法的返回结果
* @param pjp 切点方法的连接点
* @param request HTTP请求对象
*/
private void recordOperationLog(Object proceed, ProceedingJoinPoint pjp, HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 获取登录用户信息
log.info("User ID: : {}", loginUser.getUser() != null ? loginUser.getUser().getId() : -1L);// 获取用户ID
log.info("NickName: : {}", loginUser.getUser() != null ? loginUser.getUser().getNickName() : "用户未登录");// 获取用户昵称

log.info("BusinessName : {}", getLogAnnotation(pjp).businessName());// 获取业务名称
log.info("BusinessType : {}", getLogAnnotation(pjp).businessType());// 获取业务类型

log.info("Module: : {}", getApiAnnotation(pjp).tags()[0]);// 获取模块名称
log.info("Class : {}", pjp.getSignature().getDeclaringTypeName());// 获取切点方法所在的类名
log.info("Method : {}", ((MethodSignature) pjp.getSignature()).getName());// 获取切点方法的方法名
log.info("Description: : {}", getApiOperation(pjp).value());// 获取操作描述

log.info("IP Address: : {}", IpUtils.getIpAddress(request));// 获取请求的IP地址
log.info("Ip Location: : {}", IpUtils.getIpLocation(IpUtils.getIpAddress(request)));// 获取请求IP的归属地
log.info("DeviceName: : {}", IpUtils.getDeviceName(request));// 获取请求IP的访问设备
log.info("Request URL: : {}", request.getRequestURI());// 获取请求URL
log.info("Request Method: : {}", request.getMethod());// 获取请求方法
log.info("Request Param: : {}", JSON.toJSONString(pjp.getArgs()));// 获取请求参数,并转换为JSON字符串
log.info("Response Data: : {}", JSON.toJSONStringWithDateFormat(proceed, "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteDateUseDateFormat));// 获取返回结果,并转换为JSON字符串
}


/**
* 保存操作日志信息
*
* @param proceed 目标方法的返回结果
* @param pjp 切点方法的连接点
* @param request HTTP请求对象
*/
private void saveOperationLog(Object proceed, ProceedingJoinPoint pjp, HttpServletRequest request) {
OperationLog operationLog = new OperationLog();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 获取登录用户信息
operationLog.setUserId(loginUser.getUser() != null ? loginUser.getUser().getId() : -1L);// 设置用户ID
operationLog.setNickName(loginUser.getUser() != null ? loginUser.getUser().getNickName() : "用户未登录");// 设置用户昵称
operationLog.setBusinessName(getLogAnnotation(pjp).businessName());// 设置业务名称
operationLog.setBusinessType(getLogAnnotation(pjp).businessType());// 设置业务类型
operationLog.setModule(getApiAnnotation(pjp).tags()[0]);// 设置模块名称
operationLog.setClassName(pjp.getTarget().getClass().getName());// 设置切点方法所在的类名
operationLog.setMethodName(pjp.getSignature().getName());// 设置切点方法的方法名
operationLog.setDescription(getApiOperation(pjp).value());// 设置操作描述
operationLog.setIpAddress(IpUtils.getIpAddress(request));// 设置请求的IP地址
operationLog.setIpLocation(IpUtils.getIpLocation(IpUtils.getIpAddress(request)));// 设置请求IP的归属地
operationLog.setDeviceName(IpUtils.getDeviceName(request));// 设置请求IP的访问设备
operationLog.setRequestUrl(request.getRequestURI());// 设置请求URL
operationLog.setRequestMethod(request.getMethod());// 设置请求方法
operationLog.setRequestParam(JSON.toJSONString(pjp.getArgs()));// 设置请求参数,并转换为JSON字符串
operationLog.setResponseData(JSON.toJSONStringWithDateFormat(proceed, "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteDateUseDateFormat));// 设置返回结果,并转换为JSON字符串
operationLogMapper.insert(operationLog);
}

/**
* 获取方法上的@Log注解
*
* @param pjp 切点方法的连接点
* @return 方法上的@Log注解
*/
private Log getLogAnnotation(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
return method.getAnnotation(Log.class);
}

/**
* 获取类上的@Api注解
*
* @param pjp 切点方法的连接点
* @return 类上的@Api注解
*/
private Api getApiAnnotation(ProceedingJoinPoint pjp) {
Class<?> targetClass = pjp.getTarget().getClass();
return targetClass.getAnnotation(Api.class);
}

/**
* 获取方法上的@ApiOperation注解
*
* @param pjp 切点方法的连接点
* @return 方法上的@ApiOperation注解
*/
private ApiOperation getApiOperation(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
return method.getAnnotation(ApiOperation.class);
}

}

使用方式

在需要打印日志的表现层接口中加上自定义的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/user")
public class UserController {

@Autowired
private UserService userService;

@Log(businessName = "getUserListLog", businessType = "SELECT")
@GetMapping("/")
public List<User> getUserList() {
return userService.getUserList();
}

}

Spring Bean对象

Spring Bean 代指的就是那些被 IoC 容器所管理的对象

Bean 作用域

默认情况下,Spring Bean 是单例的,对于同一个 Bean 定义,只会创建一个实例,需要时重复使用这个实例

  1. Singleton(单例):这是默认的作用域。在整个应用程序中,只会存在一个 Bean 实例。无论何时都会返回相同的实例引用。
  2. Prototype(原型):每次通过容器获取该 Bean 时,都会创建一个新的实例。因此,每次获取的实例都是独立的。
  3. Request(请求):在每次 HTTP 请求到达时,都会创建一个新的 Bean 实例。该实例仅在当前 HTTP 请求范围内有效,不同请求之间不共享。
  4. Session(会话):在每个用户会话期间只创建一个 Bean 实例。即使是同一用户的不同请求,也会共享同一个 Bean 实例。
  5. Global Session(全局会话):在基于 Portlet 的 web 应用中,表示全局会话。在使用非 Portlet 环境时,它与 Session 作用域完全相同。
  6. Custom(自定义):除了上述内置的作用域外,Spring 还允许用户自定义作用域。可以通过实现 org.springframework.beans.factory.config.Scope 接口来定义自定义作用域。

Bean 生命周期

实例化—>依赖注入(属性赋值)—>前置处理(初始化前的操作)—>初始化—>后置处理(初始化后的操作)—>使用—>销毁

  1. 实例化(Instantiation):在这个阶段,Spring 容器根据配置信息创建 Bean 的实例,通常使用构造函数来实例化对象。
  2. 依赖注入(Dependency Injection):在实例创建完成后,Spring 容器会通过 setter 方法或构造函数来注入所需的依赖项,即属性赋值。
  3. 前置处理(Post Process Before Initialization):在初始化之前,Spring 提供了一些扩展点,可以在 Bean 初始化之前对其进行自定义操作。例如,可以通过实现 BeanPostProcessor 接口来创建前置处理器,以在初始化之前对 Bean 进行自定义修改。
  4. 初始化(Initialization):在这个阶段,Spring 容器会对 Bean 进行初始化,包括调用初始化方法和设置其他相应的配置信息。可以通过实现 InitializingBean 接口或在配置文件中指定 init-method 属性来定义初始化方法。
  5. 后置处理(Post Process After Initialization):在初始化完成后,Spring 提供了另一个扩展点,可以在 Bean 初始化之后对其进行自定义操作。与前置处理类似,可以通过实现 BeanPostProcessor 接口来创建后置处理器。
  6. 使用(Bean in Use):在初始化完成后,Bean 可以被应用程序使用。在这个阶段,Bean 将服务于它所属的对象或应用程序。可以调用 Bean 的方法、访问其属性等。
  7. 销毁(Destroy):当应用程序关闭或销毁 Spring 容器时,Spring 容器会触发 Bean 的销毁过程。可以通过实现 DisposableBean 接口或在配置文件中指定 destroy-method 属性来定义销毁方法。

Bean 生命周期案例

定义User类

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
public class User {
private int id;
private String name;

public User() {
System.out.println("1. 实例化 User Bean");
}

public void setName(String name) {
this.name = name;
System.out.println("2. 给 User Bean 的 name 属性赋值:" + name);
}

public String getName() {
return name;
}

public void init() {
System.out.println("4. 调用 User Bean 的自定义初始化方法");
}

public void destroy() {
System.out.println("7. 调用 User Bean 的自定义销毁方法");
}
}

MyBeanPostProcessor类

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
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* Bean初始化之前的处理
* @param bean 正在被初始化的Bean
* @param beanName Bean的名称
* @return 处理后的Bean实例
* @throws BeansException 如果处理失败,则抛出异常
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof User) {
System.out.println("3. BeanPostProcessor 前置处理");
}
return bean;
}

/**
* Bean初始化之后的处理
* @param bean 已经初始化完成的Bean
* @param beanName Bean的名称
* @return 处理后的Bean实例
* @throws BeansException 如果处理失败,则抛出异常
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof User) {
System.out.println("5. BeanPostProcessor 后置处理");
}
return bean;
}
}

XML配置文件

1
2
3
4
5
6
<!--使用init-method属性指定初始化方法-->
<!--使用destroy-method属性指定销毁方法-->
<bean id="user" class="com.wen.spring.pojo.User" init-method="init" destroy-method="destroy">
<property name="id" value="1"></property>
<property name="username" value="小明"></property>
</bean>

测试方法

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testBeanLifecycle() {
// 创建IOC容器
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("bean的生命周期.xml");

// 从IOC容器中获取User Bean实例
User user = context.getBean(User.class);
System.out.println("6. 调用UserService的sayHello方法,Hi " + user.getName());

// 关闭IOC容器
context.close();
}

控制台输出

1
2
3
4
5
6
7
1. 实例化 User Bean
2. 给 User Bean 的 name 属性赋值:默认名称
3. BeanPostProcessor 前置处理
4. 调用 User Bean 的自定义初始化方法
5. BeanPostProcessor 后置处理
6. 调用UserService的sayHello方法,Hi 小明
7. 调用 User Bean 的自定义销毁方法

Spring循环依赖问题

什么是循环依赖?

循环依赖是指在Spring容器中,单个或多个Bean之间相互依赖,形成环形依赖结构的情况

自己调用自己

1
2
3
4
5
6
7
@Service
public class A {

@Autowired
private A a;

}

两个对象互相调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class A {

@Autowired
private B b;

}

@Service
public class B {

@Autowired
private A a;

}

多个对象互相调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class A {

@Autowired
private B b;

}

@Service
public class B {

@Autowired
private C c;

}

@Service
public class C {

@Autowired
private A a;

}

循环依赖出现的原因

当Bean A实例化并开始初始化时,发现依赖了Bean B,于是Bean B也开始进行实例化和初始化。

但是在Bean B的初始化过程中,又依赖了Bean A,由此形成了循环依赖的问题

三级缓存解决循环依赖

大部分的循环依赖问题Spring内部会使用三级缓存进行解决

1
2
3
4
5
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
}

第一级缓存

一级缓存用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用

  1. 当Spring容器需要获取某个Bean时,会首先从一级缓存中查找是否已经存在该Bean实例
  2. 如果存在则直接返回;
  3. 如果不存在,则会进入后续的创建流程。

第二级缓存

二级缓存用于存放早期单例对象,保存已经实例化但尚未填充属性完成初始化的Bean实例

  1. 创建Bean对象A,在实例化后将半成品的Bean对象A先放入二级缓存earlySingletonObjects中;
  2. 在创建过程中,发现需要依赖Bean对象B,而依赖的Bean对象B还没有完成创建,因此需要从二级缓存earlySingletonObjects中获取到Bean对象B的半成品对象;
  3. 如果二级缓存中Bean对象B的半成品对象不存在,则继续创建Bean对象B,并将其半成品对象放入二级缓存earlySingletonObjects中;
  4. 创建Bean对象B,在创建过程中发现需要依赖Bean对象A,此时可以从二级缓存earlySingletonObjects中获取半成品对象A,并利用其进行依赖注入以及其他操作,最终完成Bean对象B的创建并将其放入一级缓存singletonObjects中;
  5. 继续创建Bean对象A,在创建过程中发现需要依赖Bean对象B,此时可以从一级缓存singletonObjects中获取Bean对象B,并利用其进行依赖注入以及其他操作,最终完成Bean对象A的创建,将其放入一级缓存singletonObjects中。

需要注意的是,这种方式只适用于单例Bean,因为在单例模式下,才能保证从缓存中获取的Bean对象是同一个实例

第三级缓存

三级缓存用于保存bean创建工厂,以便后面有机会创建代理对象

  1. 创建Bean对象A,在实例化后将半成品的Bean对象A先放入二级缓存earlySingletonObjects中,同时将该Bean对象的ObjectFactory存储到三级缓存singletonFactories中;
  2. 在创建过程中,发现需要依赖Bean对象B,而依赖的Bean对象B还没有完成创建,因此会从一级缓存singletonObjects中获取Bean对象A的完整实例,并使用它来创建Bean对象B;
  3. 当Bean对象B创建完成后,将其放入到一级缓存singletonObjects中;
  4. 继续创建Bean对象A,在创建过程中如果需要依赖Bean对象B,则会从一级缓存singletonObjects中获取Bean对象B的完整实例。如果一级缓存中不存在Bean对象B,则会从二级缓存earlySingletonObjects中获取Bean对象B的半成品实例进行创建。如果二级缓存中也不存在,则从三级缓存singletonFactories中获取Bean对象B的ObjectFactory,并使用它来创建Bean对象B;
  5. 当Bean对象A创建完成后,将其放入到一级缓存singletonObjects中。

Spring事务

Spring提供了两种管理事务的方式:声明式事务和编程式事务。

编程式事务

编程式事务是通过编写代码,显式地控制事务的提交或回滚,相对于声明式事务更加灵活,适用于对事务控制需求较复杂的场景

(1)配置事务管理器:在Spring配置文件中配置一个事务管理器(例如DataSourceTransactionManager),用于管理事务的创建、提交或回滚等操作。

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

(2)配置TransactionTemplate:在Spring配置文件中配置TransactionTemplate,并将事务管理器注入到TransactionTemplate中。

1
2
3
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>

(3)编写业务逻辑代码:在需要进行事务控制的方法中,编写具体的业务逻辑代码。

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
public class TransactionalService {

@Autowired
private TransactionTemplate transactionTemplate;

public void performTransactionalOperation() {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

// 获取事务状态
TransactionStatus status = transactionTemplate.getTransactionManager().getTransaction(def);

try {
// 事务逻辑代码
// ...

// 手动提交事务
transactionTemplate.getTransactionManager().commit(status);

} catch (Exception ex) {
// 异常处理,手动回滚事务
transactionTemplate.getTransactionManager().rollback(status);
throw ex;
}
}
}

声明式事务

声明式事务通过在配置文件或注解中定义事务规则,将事务管理的责任交给Spring框架来处理,开发者只需要通过配置或注解的方式声明哪些方法需要进行事务管理,而不需要编写额外的代码来处理事务。Spring提供了以下几种常用的方式:

  • 基于XML:通过在XML配置文件中定义事务管理器和事务切面,然后在需要事务管理的方法上添加事务通知(Advice)来实现声明式事务。
  • 基于AspectJ:使用AspectJ提供的切面编程能力,在代码中直接定义事务切面,将事务规则与具体的方法进行绑定,类似于基于XML的配置方式。
  • 基于注解:通过在方法或类上添加注解,如@Transactional,来指定需要进行事务管理的方法。可以设置事务的传播行为、隔离级别、超时时间等属性。
1
2
3
4
5
6
7
8
9
@Service
public class MyService {

@Transactional
public void performTransactionalOperation() {
// 事务逻辑代码
// ...
}
}

事务的七种传播行为

事务的传播行为:指的就是当一个事务方法,被另一个事务方法调用时,这个事务方法应该如何运行

事务传播行为名称描述
PROPAGATION_REQUIRED必须有事务如果当前存在一个事务,则支持当前事务,否则新建一个事务。
PROPAGATION_SUPPORTS支持当前事务如果当前存在一个事务,则支持当前事务,否则不使用事务。
PROPAGATION_MANDATORY必须有当前事务如果当前存在一个事务,则支持当前事务,否则抛出异常。
PROPAGATION_REQUIRES_NEW无论如何都要开启一个新事务新建一个全新的事务,如果当前存在一个事务,则将当前事务挂起。
PROPAGATION_NOT_SUPPORTED不支持当前事务以非事务方式执行操作,如果当前存在一个事务,则将当前事务挂起。
PROPAGATION_NEVER没有当前事务以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
PROPAGATION_NESTED嵌套事务如果当前存在一个事务,则在嵌套事务内执行。如果当前没有事务,则相当于 PROPAGATION_REQUIRED。