Spring介绍
# Spring框架
Spring 是一个轻量级、开源的 Java 开发框架,提供 依赖注入(DI) 和 面向切面编程(AOP) 机制,简化 Java 企业级开发。它涵盖 Spring Core(核心容器)、Spring MVC(Web 开发)、Spring JDBC(数据访问)、Spring Security(安全框架)、Spring Boot(简化配置) 及 Spring Cloud(微服务架构),支持高效的 事务管理、REST API 开发、微服务架构 等,广泛应用于企业级 Java 应用的开发。
# Spring 的 IOC(控制反转)
**IOC(Inversion of Control,控制反转)**是 Spring 框架的核心概念之一。它的主要思想是 将对象的创建和管理的控制权交给 Spring 容器,而不是由对象自身或调用者来管理,这样可以提高程序的可维护性和扩展性。
在传统的 Java 开发中,通常是由开发者在代码中 手动创建对象,比如:
public class UserService {
private UserRepository userRepository = new UserRepository();
}
2
3
这种方式有几个问题:
- 紧耦合(Tightly Coupled):
UserService
直接依赖UserRepository
,如果要更换UserRepository
的实现,就需要修改UserService
的代码,违背了开闭原则(OCP)。 - 不易测试:由于
UserService
内部直接创建UserRepository
,在单元测试时无法轻松替换UserRepository
为 Mock 对象。
IOC 如何解决这个问题?
Spring IOC 容器会 接管对象的创建、依赖关系的管理,从而让对象变得松耦合(Loosely Coupled)。在 Spring 框架中,我们可以这样使用 IOC:
@Component
public class UserRepository {
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2
3
4
5
6
7
8
9
10
11
12
在这里:
@Component
和@Service
注解告诉 Spring 这个类应该被 Spring 容器管理。@Autowired
让 Spring 自动注入UserRepository
的实例,而不是手动创建。
这样,Spring 负责创建 UserRepository
的实例,并在 UserService
中注入它。这样 UserService
不需要自己创建 UserRepository
,也可以很方便地替换 UserRepository
为不同的实现。
# DI(依赖注入)与 IOC 的关系
DI(Dependency Injection,依赖注入) 是实现 IOC 的一种方式。IOC 是一种思想,而 DI 是一种具体的实现机制。
DI 的方式 Spring 提供了多种依赖注入的方式:
构造方法注入(Constructor Injection,推荐)
@Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
1
2
3
4
5
6
7
8
9- 推荐使用构造方法注入,保证
UserService
对象创建时必须传入UserRepository
,避免null
依赖。
- 推荐使用构造方法注入,保证
Setter 方法注入
@Service public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
1
2
3
4
5
6
7
8
9字段注入
@Service public class UserService { @Autowired private UserRepository userRepository; }
1
2
3
4
5
# 1. 什么是 Spring 容器?
在 Spring 的 IOC(控制反转) 机制下,对象的创建 由 Spring 容器 负责,而不是由开发者手动创建。Spring 通过 Bean 的管理机制 来实现对象的创建、初始化、管理和销毁,并实现 依赖注入(Dependency Injection, DI)。Spring 容器的主要作用包括:
- 解析配置文件或注解,扫描并加载 Bean 定义
- 维护 Bean 的生命周期,包括创建、依赖注入、初始化、销毁
- 提供 Bean 之间的依赖管理,如单例、懒加载、循环依赖等
Spring 容器的主要接口:
- BeanFactory:Spring 的底层容器,提供最基本的 IOC 容器功能,如
DefaultListableBeanFactory
- ApplicationContext:继承
BeanFactory
,提供更强大的功能,如事件发布、国际化支持等,如ClassPathXmlApplicationContext
、AnnotationConfigApplicationContext
等
# 2. Spring 容器的启动流程
Spring 容器的启动主要是 ApplicationContext 的创建过程,核心流程如下:
# (1)ApplicationContext 的创建
根据不同的配置方式,Spring 提供了几种 ApplicationContext
:
ClassPathXmlApplicationContext
:基于 XML 配置AnnotationConfigApplicationContext
:基于 Java 代码和注解
Spring 启动的主要入口:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
获取Bean方式:
- XML 配置方式:解析 XML 文件,获取 Bean 的定义
- 注解方式:扫描
@ComponentScan
指定的包,获取所有@Component
、@Service
、@Repository
、@Controller
标注的类 - Java 配置方式:Spring 会扫描
@Configuration
然后将其和所有@Bean标记的方法解析为BeanDefinition
,存入BeanDefinitionMap
。
# (2)BeanDefinition 解析和存储
Spring 会把所有扫描到的 Bean 解析为 BeanDefinition
,存入 BeanDefinitionMap:
Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
BeanDefinition
主要存储:
- Bean 的类信息(className)
- 作用域(单例
singleton
或多例prototype
) - 构造器参数
- 依赖关系
- 是否懒加载
# (3)实例化 Bean
- 通过
BeanFactory
遍历BeanDefinitionMap
,判断是否要创建 Bean(非懒加载和单例才创建,@Lazy // 只有 getBean("myService") 时才创建) - 反射调用构造方法创建 Bean
- 依赖注入(有一个坑,循环依赖注入后面会讲)
- 初始化(执行
@PostConstruct
或InitializingBean
) - 放入 单例池(
singletonObjects
)
# 3. 怎么创建 Bean(IoC 机制解析)
Spring 采用 IoC(控制反转) 来管理 Bean,核心是 BeanFactory.getBean()
方法,创建 Bean 的完整流程如下:
# (1)查询 Bean
Spring 通过 getBean(beanName)
方法获取 Bean:
Object bean = singletonObjects.get(beanName);
if (bean != null) {
return bean;
}
2
3
4
如果 Bean 已经存在于 singletonObjects
(单例池),直接返回,否则执行创建过程。
# (2)实例化 Bean
如果 singletonObjects
里没有该 Bean,就去 BeanDefinitionMap
中查找 BeanDefinition
并进行实例化。
实例化方式:
- 默认通过 反射调用无参构造方法 创建对象
- 该 Bean 的 依赖项 可能还是
null
- 这一步 Bean 只是被 创建,但还未进行属性填充和初始化
示例代码:
Class<?> clazz = beanDefinition.getBeanClass();
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object instance = constructor.newInstance();
2
3
# (3)属性注入(依赖注入)
Spring 进行依赖注入:
- 解析
@Autowired
或 XML 配置的依赖关系 - 递归调用
getBean()
获取依赖项 - 通过 反射调用 setter 方法或直接赋值
示例:
Field field = clazz.getDeclaredField("userService");
field.setAccessible(true);
field.set(instance, getBean(field.getType().getName()));
2
3
# (4)初始化
初始化阶段:
- 执行
InitializingBean
接口的afterPropertiesSet()
- 执行
@PostConstruct
标注的方法 - 执行
BeanPostProcessor
(前置 & 后置处理)
示例:
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
2
3
# (5)放入单例池
完成初始化后,Bean 进入 singletonObjects
(单例池):
singletonObjects.put(beanName, instance);
后续 getBean()
直接从单例池获取,无需重新创建。
# 4. Spring 解决循环依赖
在 Spring 容器中,循环依赖(Circular Dependency)指的是 多个 Bean 之间存在相互依赖,导致在实例化过程中出现递归调用,最终抛出 BeanCurrentlyInCreationException
。
Spring 默认支持 单例(singleton) Bean 的循环依赖,并通过 三级缓存(三级 Map) 解决问题,但 原型(prototype) Bean 也叫多例Bean的循环依赖默认不支持,每次调用 getBean()
都会创建一个新的实例。
# 4.1 循环依赖的类型
Spring 中的循环依赖主要分为:
- 构造器循环依赖(不支持)
- A 通过构造方法依赖 B,B 通过构造方法依赖 A
- Spring 无法解决,Spring 在实例化阶段调用构造方法,而此时对象还未放入三级缓存。因为所有 Bean 都需要先实例化,无法在构造方法中拿到对方的引用。会抛出
BeanCurrentlyInCreationException
- Setter/字段循环依赖(支持)
- A 通过
@Autowired
依赖 B,B 通过@Autowired
依赖 A - Spring 通过三级缓存解决
- A 通过
prototype
作用域循环依赖(不支持)prototype
Bean 不会 放入 Spring 容器的单例池,因此 Spring 不能缓存它,从而无法解决循环依赖。
# 4.2 Bean 的生命周期
Spring Bean 的生命周期主要包含以下四个阶段:
- 实例化(Instantiation):创建对象
- 属性赋值(Populate):注入依赖
- 初始化(Initialization):执行初始化方法
- 销毁(Destruction):对象销毁
# 4.3 什么是循环依赖?
循环依赖的概念:当两个或多个 Bean 互相依赖时,就会产生循环依赖。
示例代码:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
2
3
4
5
6
7
8
9
在上面的例子中:
A
依赖B
B
依赖A
- 形成循环依赖
# 4.4 Spring 三级缓存
Spring 采用三级缓存存放单例模式下的 Bean:
缓存层级 | 变量 | 存放内容 |
---|---|---|
一级缓存 | singletonObjects | 完全初始化好的 Bean(最终可用的对象) |
二级缓存 | earlySingletonObjects | 已实例化但未填充属性的 Bean(半成品) |
三级缓存 | singletonFactories | Bean 工厂对象,用于提前曝光 Bean |
源码示例:
/** 一级缓存:存放完全初始化好的 Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:存放原始的 Bean 对象(尚未填充属性) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 三级缓存:存放 Bean 工厂对象 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2
3
4
5
6
7
8
# 4.5 三级缓存如何解决循环依赖?
getSingleton()
方法的执行逻辑
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 尝试从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
// 2. 若一级缓存获取不到,且对象正在创建中,则尝试从二级缓存获取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 3. 若二级缓存获取不到,并且允许提早引用,则从三级缓存中获取
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 4. 将 Bean 提升到二级缓存,并移除三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- 三级缓存的核心作用: 提早暴露 Bean 的对象引用,使得依赖它的 Bean 能够在构造过程中获取到该引用,从而解决循环依赖问题。
完整流程:
实例化 Bean A(A 依赖 B)
在
singletonFactories
放入一个 A 的 ObjectFactorySpring 解析
@Autowired
,发现 A 依赖 B
实例化 Bean B(B 依赖 A)
在
singletonFactories
放入一个 B 的 ObjectFactorySpring 解析
@Autowired
,发现 B 依赖 A继续执行一遍获取A,发现三级缓存有。从
singletonFactories
获取 A 的 ObjectFactory,并返回对象对象AA 进入
earlySingletonObjects
,并移除三级缓存的A填充B对象
B 依赖注入完成,进入
singletonObjects
- B 完全初始化后放入
singletonObjects
- B 完全初始化后放入
A 继续完成属性填充,进入
singletonObjects
- A 获取到完整的 B 之后,也完成初始化,放入
singletonObjects
- A 获取到完整的 B 之后,也完成初始化,放入
# 4.6.为什么一定要三级缓存?
是否可以用二级缓存解决循环依赖?
- 理论上,二级缓存也能解决循环依赖
- 但是,二级缓存不能正确处理 AOP 代理对象
- 三级缓存的真正作用:延迟代理对象的创建,减少不必要的代理开销,代理对象的创建是在 Bean 初始化阶段
BeanPostProcessor
完成的,但在循环依赖场景下入锅要是二级缓存需要提前创建代理对象。。
示例:如果使用二级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
// 立即创建 Bean
Object bean = singletonFactory.getObject();
// 直接放入二级缓存
this.earlySingletonObjects.put(beanName, bean);
}
}
}
2
3
4
5
6
7
8
9
10
- 所有的 Bean 在实例化后,都会立即创建代理对象,而不是在初始化后再创建。
# 4.7 循环依赖的几种场景
# (1)Setter 方式(Spring 可自动解决)
@Component
class A {
@Autowired
private B b;
}
@Component
class B {
@Autowired
private A a;
}
2
3
4
5
6
7
8
9
10
11
✅ Spring 通过三级缓存解决 Setter 方式循环依赖。
# (2)构造器循环依赖(Spring 无法解决)
@Component
class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
}
@Component
class B {
private final A a;
@Autowired
public B(A a) {
this.a = a;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
⛔ Spring 无法解决 构造器注入的循环依赖,会抛出 BeanCurrentlyInCreationException
。Spring 在实例化阶段调用构造方法,而此时对象还未放入三级缓存。因为所有 Bean 都需要先实例化,无法在构造方法中拿到对方的引用。会抛出 BeanCurrentlyInCreationException
解决方法:
改用
@Lazy
@Component class A { @Autowired @Lazy private B b; } @Component class B { @Autowired private A a; }
1
2
3
4
5
6
7
8
9
10
11
12@Lazy
让 Spring 只在需要时创建 Bean,从而避免循环依赖。
改用
Setter
注入- 避免构造器直接注入对方
使用
ObjectProvider
(Spring 4.3+)@Component class A { private final B b; @Autowired public A(ObjectProvider<B> bProvider) { this.b = bProvider.getIfAvailable(); } }
1
2
3
4
5
6
7
8
9- Spring 只在需要时创建 B,从而避免循环依赖。
# (3)prototype
作用域的循环依赖
@Component
@Scope("prototype")
class A {
@Autowired
private B b;
}
@Component
@Scope("prototype")
class B {
@Autowired
private A a;
}
2
3
4
5
6
7
8
9
10
11
12
13
⛔ Spring 不支持 prototype
Bean 的循环依赖,因为 prototype
Bean 不会进入 singletonObjects
,无法通过缓存解决循环引用。
解决方案:
使用
ObjectFactory
或ObjectProvider
@Component @Scope("prototype") class A { @Autowired private ObjectProvider<B> bProvider; public B getB() { return bProvider.getIfAvailable(); } }
1
2
3
4
5
6
7
8
9
10ObjectProvider
让 Spring 延迟加载 依赖项,避免循环依赖。
使用
@Lazy
@Component @Scope("prototype") class A { @Autowired @Lazy private B b; }
1
2
3
4
5
6
7@Lazy
让 Spring 只在需要时创建 B,避免循环依赖。
# 4.8 总结
依赖类型 | Spring 解决方式 |
---|---|
构造器循环依赖 | ❌ 不支持(可用 @Lazy 、ObjectProvider 解决) |
Setter 方式循环依赖 | ✅ Spring 通过 三级缓存 解决 |
prototype 作用域循环依赖 | ❌ 不支持(可用 ObjectProvider 解决) |
✅ Spring 默认支持 singleton
Bean 的循环依赖,通过 三级缓存机制 解决
⛔ prototype
和 @Autowired
构造器注入的循环依赖无法自动解决,需用 @Lazy
或 ObjectProvider
规避。
这样,Spring 既能保证 性能(不提前创建 Bean),又能解决 Setter 循环依赖。
# 5.@Autowired和 @Resource区别
@Autowired
和 @Resource
都是 Spring 用于 依赖注入(Dependency Injection, DI) 的注解,但它们有以下主要区别:
# 5.1 @Autowired
(Spring 提供的依赖注入方式)
特点
- Spring 提供,基于 Spring 依赖注入机制(JDK 反射 + BeanFactory)。
- **默认按类型(byType)注入,然后按照名字(byName)**如果有多个同类型的 Bean,可以结合
@Qualifier
指定具体的 Bean。 - 只能用于 Spring 容器管理的 Bean,不能注入非 Spring Bean 对象。
- 可以用于构造方法、字段、Setter 方法、参数。
示例
# (1)默认按类型注入
@Component
public class UserService {
}
@Component
public class OrderService {
@Autowired // 按类型注入 UserService
private UserService userService;
}
2
3
4
5
6
7
8
# (2)配合 @Qualifier
解决多个 Bean
如果同一个类型有多个 Bean,必须使用 @Qualifier
指定:
@Component("userService1")
public class UserServiceImpl1 implements UserService { }
@Component("userService2")
public class UserServiceImpl2 implements UserService { }
@Component
public class OrderService {
@Autowired
@Qualifier("userService1") // 指定注入 userService1
private UserService userService;
}
2
3
4
5
6
7
8
9
10
11
# 5.2 @Resource(JDK javax.annotation.Resource
提供)
特点
- Java 提供,属于 JSR-250 规范,Spring 只是对它提供了支持。
- 默认按名称(byName)注入,如果找不到匹配的 Bean 才会按类型(byType)注入。
- 可以用于非 Spring Bean 的注入(如 JNDI 资源)。
- 只能用于字段或 Setter 方法,不能用于构造方法或参数。
示例
# (1)默认按名称注入
@Component("userService")
public class UserService {
}
@Component
public class OrderService {
@Resource(name = "userService") // 按名称查找 "userService" Bean
private UserService userService;
}
2
3
4
5
6
7
8
# (2)如果 name
没有指定,按字段名匹配
@Component
public class OrderService {
@Resource // 自动查找名为 "userService" 的 Bean
private UserService userService;
}
2
3
4
5
相当于:
@Resource(name = "userService")
private UserService userService;
2
# 5.3. @Autowired
vs @Resource
对比
@Autowired | @Resource | |
---|---|---|
提供方 | Spring | Java(JSR-250 规范) |
默认注入方式 | 按类型(byType) | 按名称(byName) |
支持按名称注入 | 需要 @Qualifier("beanName") | 默认按名称,找不到才按类型 |
适用范围 | 构造方法、字段、Setter、参数 | 只能用于字段和 Setter |
是否支持非 Spring Bean | ❌ 只能注入 Spring 容器管理的 Bean | ✅ 支持 JNDI 资源注入 |
是否必须在 Spring 容器中 | ✅ 是 | ❌ 不是必须 |
# 5.4 什么时候用 @Autowired
?什么时候用 @Resource
?
✅ 使用 @Autowired
- 如果你只在 Spring 容器内管理 Bean,推荐使用
@Autowired
,因为它是 Spring 官方提供的注解,支持@Qualifier
更灵活。
✅ 使用 @Resource
- 如果你的项目是 JavaEE 规范(比如 JNDI 资源),建议使用
@Resource
,因为它符合 JSR-250 规范。 - 如果你的 Bean 需要按名称注入,但你不想使用
@Qualifier
,可以直接使用@Resource(name = "beanName")
。
# 5.5 @Autowired
和 @Resource
源码解析
📌 @Autowired
底层实现
@Autowired
private UserService userService;
2
Spring 解析 @Autowired
的核心逻辑:
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName) {
return getBean(descriptor.getDependencyType());
}
2
3
它 先查找类型匹配的 Bean,找不到才报错。
📌 @Resource
底层实现
@Resource(name = "userService")
private UserService userService;
2
Spring 解析 @Resource
:
Object getResource(String name) {
if (name != null) {
return getBean(name);
} else {
return getBeanByType();
}
}
2
3
4
5
6
7
它 优先按名称(byName)查找,找不到才按类型(byType)查找。
# 5.6 总结
@Autowired
默认按类型(byType)查找,找不到可结合@Qualifier
按名称。@Resource
默认按名称(byName)查找,找不到才按类型。@Autowired
适用于所有 Spring 管理的 Bean,但@Resource
还能注入 JNDI 资源。@Autowired
可以用在构造方法、参数,@Resource
只能用于 字段和 Setter。
👉 最佳实践:
- Spring Boot 项目推荐用
@Autowired
- 老项目或 J2EE 项目可用
@Resource
(如 JNDI 注入)
🚀 Spring 默认推荐 @Autowired
,但 @Resource
在某些场景下更好用!
# 6. Bean 的生命周期
Spring Bean 的生命周期主要包括 创建、实例化、初始化、存入单例池、使用、销毁。流程如下:
# 6.1 详细流程
- 创建(Create):Spring 解析
BeanDefinition
,准备创建 Bean。 - 实例化(Instantiation):通过 反射 调用 无参构造方法 实例化 Bean(此时 Bean 还未进行属性注入)。
- 初始化(Initialization):
- BeanPostProcessor(
postProcessBeforeInitialization()
):初始化前增强。 - 执行
@PostConstruct
/ 实现InitializingBean#afterPropertiesSet()
/init-method
。 - BeanPostProcessor(
postProcessAfterInitialization()
):初始化后增强。
- BeanPostProcessor(
- 加入单例池(Singleton Pool):
- 单例模式下,Bean 存入
singletonObjects
(Spring 容器管理的单例池)。
- 单例模式下,Bean 存入
- 使用(Using):在程序运行期间被调用。
- 销毁(Destroy):
- 程序正常运行:Bean 一直存在,不会销毁。
- 调用
close()
或shutdown()
:@PreDestroy
DisposableBean#destroy()
destroy-method
配置的方法。
# 7. ApplicationContext 和 BeanFactory 的区别
# 共同点
- 都是 Spring 容器,可以管理 Bean 的生命周期。
- 都能实现 IoC(控制反转),进行依赖注入。
# 区别
对比项 | ApplicationContext | BeanFactory |
---|---|---|
定义 | 高级容器,扩展了 BeanFactory ,提供了更多功能。 | 基础容器,是 Spring IoC 容器的最底层实现。 |
Bean 预初始化 | 默认 预加载单例 Bean,启动时就创建所有单例 Bean。 | 懒加载,默认在 getBean() 时才创建 Bean。 |
支持 AOP | 直接支持 AOP,如 @Transactional | 不直接支持 AOP,需要手动添加 BeanPostProcessor 。 |
事件监听 | 支持 事件发布和监听机制(ApplicationListener )。 | 不支持。 |
国际化支持 | 内置 MessageSource ,支持多语言。 | 不支持国际化。 |
使用场景 | 一般用在 Spring Boot / Spring MVC。 | 适合 轻量级容器或测试环境。 |
# 8. AOP(面向切面编程)详解
# 8.1 什么是 AOP?
Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架提供的一种重要功能,它允许在不修改原有业务代码的情况下,为方法添加额外的行为(如日志记录、事务管理等)。Spring AOP 主要是通过 动态代理 技术来实现的,分为 JDK 动态代理 和 CGLIB 动态代理。
# 8.2 AOP 的核心概念
术语 | 作用 |
---|---|
Aspect(切面) | 具体增强的功能,如日志、事务管理。 |
JoinPoint(连接点) | 目标方法执行的具体位置(方法调用、构造方法等)。 |
Pointcut(切点) | 定义在哪些方法上应用 AOP。 |
Advice(通知) | 具体的增强逻辑,如 @Before 、@After 。 |
Weaving(织入) | 把切面逻辑动态应用到目标方法上的过程。 |
# 8.3 Spring AOP 的通知类型
通知类型 | 注解 | 作用 |
---|---|---|
前置通知(Before) | @Before("execution(切点表达式)") | 方法执行前增强 |
后置通知(After) | @After("execution(切点表达式)") | 方法执行后增强 |
返回通知(AfterReturning) | @AfterReturning("execution(切点表达式)") | 方法成功返回后增强 |
异常通知(AfterThrowing) | @AfterThrowing("execution(切点表达式)") | 方法抛出异常后增强 |
环绕通知(Around) | @Around("execution(切点表达式)") | 包裹整个方法,可以控制方法执行 |
# 8.4 AOP 示例
定义切面
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void logPointCut() {}
@Before("logPointCut()")
public void beforeAdvice() {
System.out.println("方法执行前");
}
@After("logPointCut()")
public void afterAdvice() {
System.out.println("方法执行后");
}
@AfterReturning("logPointCut()")
public void afterReturningAdvice() {
System.out.println("方法返回值后");
}
@Around("logPointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前(环绕通知)");
Object result = joinPoint.proceed();
System.out.println("方法执行后(环绕通知)");
return result;
}
}
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
配置 AOP(Spring Boot 自动生效)
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
2
3
4
# 8.5 AOP 代理模式
Spring AOP 主要使用 动态代理 来增强目标对象:
代理模式 | 适用场景 | 底层实现 |
---|---|---|
JDK 动态代理 | 目标对象 实现了接口 | java.lang.reflect.Proxy |
CGLIB 代理 | 目标对象 没有接口 | 继承目标类,使用 ASM 生成子类 |
注意:Spring Boot 2.0 以后,默认使用 CGLIB 代理(即使有接口)。
# 8.6 注解如何与 AOP 切面配合
在 Spring AOP 中,可以通过 自定义注解 结合 切面(Aspect),实现更加灵活的切面编程(如日志、权限校验、事务管理等)。
# 主要步骤
- 定义自定义注解(用于标记哪些方法需要 AOP 增强)。
- 定义 AOP 切面(拦截带有该注解的方法,实现增强逻辑)。
- 应用注解到目标方法(业务方法上加上自定义注解)。
- Spring Boot 自动扫描,切面生效。
# 代码示例
# 1 .定义自定义注解
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Target(ElementType.METHOD) // 作用于方法
public @interface LogExecutionTime {
}
2
3
4
📌 解释:
@Retention(RetentionPolicy.RUNTIME)
:让注解在 运行时可用,AOP 需要反射解析它。@Target(ElementType.METHOD)
:只作用在 方法 上。
# 2.定义 AOP 切面
@Aspect
@Component
public class LogAspect {
@Around("@annotation(com.example.annotation.LogExecutionTime)") // 拦截标注了 @LogExecutionTime 的方法
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long end = System.currentTimeMillis();
System.out.println(joinPoint.getSignature() + " 方法执行耗时: " + (end - start) + "ms");
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
📌 解释:
@Around("@annotation(全路径注解名)")
:拦截 带有@LogExecutionTime
注解的方法。joinPoint.proceed()
:执行目标方法。- 记录方法执行时间,增强业务逻辑。
# 3 .应用自定义注解
@Service
public class UserService {
@LogExecutionTime // 这个方法会被 AOP 代理
public void process() {
System.out.println("正在执行 UserService.process() 方法...");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 4 .启用 AOP(Spring Boot 自动生效)
Spring Boot 默认开启 AOP,如果手动配置:
@Configuration
@EnableAspectJAutoProxy // 开启 AOP 代理
public class AopConfig {
}
2
3
4
# 总结
✅ AOP + 自定义注解 实现了 无侵入式增强:
- 业务代码 不需要修改,只需加注解。
- 适用于 日志、权限、事务、监控 等场景。
- Spring AOP 默认使用 CGLIB 代理(非接口类),但 JDK 代理(基于接口) 也可用。
🚀 如果你要在 Spring Boot 项目中用 AOP,强烈建议结合自定义注解,这样切面逻辑清晰,代码可维护性高!
# 9. Spring Aop的底层实现原理
AOP 是 IoC(控制反转) 的扩展功能,它在 Spring 容器管理 Bean 的过程中 增加了一个扩展点,用于对 Bean 进行动态代理,实现方法级增强,如日志记录、权限校验等。
Spring Bean 的创建流程(结合 AOP 代理):
- Spring IoC 容器初始化
- 扫描并解析 Bean 定义
- 创建 Bean 实例(new)
- 执行 BeanPostProcessor
AnnotationAwareAspectJAutoProxyCreator
解析@Aspect
- 判断 Bean 是否需要 AOP 代理
- 如果需要,使用 JDK 代理或 CGLIB 代理生成代理对象
- 注册到 IoC 容器(代理对象)
- 外部调用 Bean 方法时,实际调用代理对象的方法
- 代理对象执行 AOP 逻辑 + 目标方法