Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Spring Core

Spring 是一个轻量级的 Java 企业级开发框架,它的核心是 IoC(控制反转)和 AOP(面向切面编程)。通过 IoC 容器统一管理对象的创建和依赖关系,通过 AOP 将事务、日志等横切逻辑从业务代码中解耦。

IoC & DI

控制反转

什么是控制反转(IoC)?
控制反转(Inversion of Control)是一种设计思想,它将对象的创建和依赖管理的控制权从程序本身转移给外部容器。传统代码中对象自己通过 new 创建依赖,而在 IoC 中,对象的创建、组装和生命周期都由 Spring 容器负责,从而降低组件之间的耦合度。
IoC 容器的作用?
IoC 容器的核心作用是管理对象的创建、依赖关系和生命周期。它负责实例化 Bean、解析依赖、注入属性、调用初始化方法,并在容器关闭时销毁对象。同时它还支持作用域管理、AOP 代理生成以及条件装配等高级特性。
为什么需要 IoC?
使用 IoC 可以降低系统的耦合度,提高代码的可测试性和可维护性。传统模式下类与类之间强依赖,难以替换或测试;而通过 IoC,组件只依赖接口,具体实现由容器提供,从而实现解耦和更灵活的扩展能力。
BeanFactory 和 ApplicationContext 区别?
BeanFactory 是 Spring 最基础的 IoC 容器,提供最核心的 Bean 创建和依赖注入能力,默认是懒加载;而 ApplicationContext 是 BeanFactory 的高级子接口,提供更完整的企业级功能,比如国际化、事件机制、AOP 支持、自动注册 BeanPostProcessor,并且默认在容器启动时,通过在 refresh() 方法中调用 finishBeanFactoryInitialization() 进行完成单例 Bean 的初始化。
对比点BeanFactoryApplicationContext
层级最顶层接口BeanFactory 子接口
Bean 创建时机懒加载(getBean 时创建)默认启动时预实例化单例
国际化支持❌ 不支持✅ 支持
事件机制❌ 不支持✅ 支持
AOP 自动支持❌ 需要手动注册✅ 自动注册
使用场景轻量级容器企业级开发(常用)

Bean的注册

  1. 显式注册 Bean

显式注册 Bean 是通过 @Configuration + @Bean 的方式,将方法返回的对象注册到 Spring 容器中。当容器启动并执行 refresh() 时,Spring 会通过 ConfigurationClassPostProcessor 解析带有 @Configuration 的类,找到其中的 @Bean 方法,并将每个方法转换成对应的 BeanDefinition 注册到 BeanFactory。在 refresh() 的后期阶段(finishBeanFactoryInitialization()),Spring 根据这些 BeanDefinition 实例化单例 Bean,并将其放入单例缓存中,从而完成显式注册的整个流程。

@Configuration
public class AppConfig {

    @Bean
    public UserRepository userRepository() {
        return new UserRepository();
    }

    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserService(userRepository);
    }
}
@Configuration 为什么要用代理?
@Configuration 使用代理(默认通过 CGLIB 生成子类)是为了保证 配置类中多个 @Bean 方法之间互相调用时,返回的是容器中管理的同一个单例 Bean,而不是每次调用都 new 一个新对象。代理会拦截对 @Bean 方法的调用,先从 BeanFactory 中获取已存在的 Bean,如果不存在才执行原始方法创建并注册,从而保证单例语义和依赖一致性,这也被称为 inter-bean method call 语义。
  1. 组件扫描注册

Spring 通过 @ComponentScan 指定要扫描的包路径,在容器启动的 refresh() 过程中由 ConfigurationClassPostProcessor 触发扫描逻辑,使用类路径扫描器(ClassPathBeanDefinitionScanner)查找带有 @Component 及其派生注解(如 @Controller、@Service、@Repository)的类,然后为这些类生成 BeanDefinition 并注册到 BeanFactory 中,后续再统一完成实例化和依赖注入。

下面的例子中,Spring 扫描 com.example.app,发现 @Service 和 @Repository,生成对应的 BeanDefinition,在容器初始化阶段创建单例 Bean。

@Configuration
@ComponentScan("com.example.app")
public class AppConfig {
}
@Service
public class UserService {
}
@Repository
public class UserRepository {
}
  1. 导入注册

@Import 用于将指定的类直接注册到 Spring 容器中,它是比组件扫描更底层、更灵活的注册方式。Spring 在解析 @Configuration 类时,会处理 @Import 注解,根据导入类型的不同,可能直接注册普通类、通过 ImportSelector 动态选择要导入的类,或者通过 ImportBeanDefinitionRegistrar 手动向容器注册 BeanDefinition。Spring Boot 的自动装配机制本质上就是基于 @Import + ImportSelector 实现的。

@Configuration
@Import(UserService.class)
public class AppConfig {
}
public class UserService {
}
public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        return new String[] {
            "com.example.UserService",
            "com.example.UserRepository"
        };
    }
}
@Configuration
@Import(MyImportSelector.class)
public class AppConfig {
}
public class MyRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {

        RootBeanDefinition beanDefinition =
                new RootBeanDefinition(UserService.class);

        registry.registerBeanDefinition("userService", beanDefinition);
    }
}
@Configuration
@Import(MyRegistrar.class)
public class AppConfig {
}
  1. 条件注册

条件注册是通过 @Conditional 机制,在 Bean 注册前进行条件判断,只有条件成立才会将 Bean 注册到容器中。其底层核心是 Condition 接口,所有条件判断最终都会实现该接口,并通过 matches(ConditionContext context, AnnotatedTypeMetadata metadata) 方法决定是否注册。Spring 在解析配置类时会读取 @Conditional,调用 matches():返回 true 则注册 Bean,返回 false 则不注册。

Spring Boot 在此基础上提供了派生注解,例如 @ConditionalOnMissingBean 等,本质仍然是对 @Conditional 的封装。@ConditionalOnMissingBean 的含义是:当容器中不存在指定类型(或名称)的 Bean 时才会注册当前 Bean。它的作用是避免重复注册,允许用户自定义 Bean 覆盖默认配置,是 Spring Boot 自动装配“可覆盖”机制的关键。

@Configuration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public User user() {
        return new User("默认用户");
    }
}
@Configuration
public class UserConfig {

    @Bean
    public User user() {
        return new User("自定义用户");
    }
}
  1. FactoryBean注册

FactoryBean 是一种特殊的 Bean 注册方式,它本身是一个工厂类,用来创建另外一个对象交给 Spring 容器管理。当一个类实现 FactoryBean 接口时,Spring 注册到容器中的并不是这个工厂对象本身,而是通过其 getObject() 方法返回的对象。也就是说,默认通过 getBean(“xxx”) 获取到的是 getObject() 生成的实例,而不是 FactoryBean 本身。这种机制常用于创建复杂对象(如代理对象)。

getBean(“&xxx”) 和 getBean(“xxx”) 区别?
getBean(“xxx”)获取的是 FactoryBean 通过 getObject() 方法生产的对象。
getBean(“&xxx”)获取的是 FactoryBean 本身(加 & 表示获取工厂对象)。

作用域 & 懒加载

@Scope
@Scope 用来指定 Bean 在 Spring 容器中的作用范围。默认是 singleton(单例),即整个容器中只有一个实例。除了单例外,还可以定义为 prototype(多例),以及 Web 环境下的 request、session 等。不同作用域决定了 Bean 的创建时机、实例数量以及生命周期范围。
作用域含义实例个数创建时机适用场景
singleton单例容器中唯一容器启动时(默认)无状态、通用组件
prototype多例每次获取一个新对象每次 getBean 时有状态对象
request请求级每个 HTTP 请求一个每次请求创建Web 请求数据
session会话级每个 Session 一个Session 存在期间用户会话数据
@Lazy
@Lazy 用于控制 Bean 是否在容器启动时立即创建。默认情况下,singleton Bean 会在容器启动时实例化;加上 @Lazy 后,会在第一次被获取或注入时才创建。它只影响创建时机,不改变作用域本身。
单例 Bean 是线程安全的吗?
Spring 只保证 singleton Bean 在容器中只有一个实例,但并不保证线程安全。如果单例 Bean 是无状态的(不保存可变成员变量),通常是线程安全的;如果包含可变成员变量,在多线程环境下可能存在线程安全问题,需要自行加锁或避免共享状态。
三级缓存如何解决循环依赖?
三级缓存是 Spring 在创建单例 Bean 过程中为了解决循环依赖问题而设计的内部机制,属于 Bean 生命周期中“实例化 + 属性填充阶段”的知识点。本质上,它发生在单例 Bean 创建流程里,当 Bean A 依赖 Bean B,Bean B 又依赖 Bean A 时,Spring 需要在 A 尚未完全初始化完成时,提前暴露一个“早期引用”给 B 使用,从而打破循环依赖。
它具体存在于 Spring 的单例注册流程中(DefaultSingletonBeanRegistry),通过三级缓存机制分别保存不同阶段的单例对象:一级缓存是单例池,用于存放完全创建好的 Bean;二级缓存用于存放早期暴露的对象,也就是尚未完成初始化的半成品 Bean;三级缓存存放的是对象工厂,用于在需要时生成早期引用,并且支持 AOP 代理的创建。这个机制的核心目的,是在支持循环依赖的同时,还能兼容 AOP 代理对象的创建。

依赖注入

@Autowired
@Autowired 是 Spring 提供的自动注入注解,默认 按类型(byType)注入。当容器中存在唯一匹配类型的 Bean 时即可注入成功;如果存在多个同类型 Bean,就会发生冲突。required=true 是默认值,表示必须注入成功,否则启动报错;设置 required=false 时,如果找不到匹配 Bean,则注入 null 不报错。注入顺序上:先按类型匹配 → 如果有多个候选,再按名称匹配 → 仍无法确定则报错。
@Service
public class UserService {
}

@RestController
public class UserController {

    @Autowired
    private UserService userService;  // 按类型注入
}
@Qualifier 和 @Primary
当容器中有 两个相同类型的实现类 时,单纯使用 @Autowired 会报错,因为按类型无法唯一确定。解决方式有两种:@Primary:标记某个 Bean 为首选;@Qualifier:明确指定要注入的 Bean 名称。
public interface PaymentService {
    void pay();
}

@Service
@Primary
public class AlipayService implements PaymentService {
    public void pay() {}
}

@Service
public class WechatPayService implements PaymentService {
    public void pay() {}
}
@Autowired
private PaymentService paymentService;
@Autowired
@Qualifier("wechatPayService")
private PaymentService paymentService;
@Resource
@Resource 是 JSR-250 标准注解(不是 Spring 原生),默认 按名称(byName)注入,如果找不到同名 Bean,才会按类型匹配。它没有 required 属性。
@Service
public class UserService {
}

@RestController
public class UserController {

    @Resource
    // @Resource(name = "userService")
    private UserService userService;  // 默认按名称 userService 注入
}
字段注入、Setter注入和构造器注入比较
见下表
对比项构造器注入 ✅(推荐)Setter 注入字段注入 ❌(不推荐)
注入方式通过构造方法传入通过 set 方法注入直接在成员变量上加 @Autowired
依赖是否强制强制依赖(必须传入)可选依赖容器控制,不明显
是否可用 final✅ 可以❌ 不可以❌ 不可以
依赖是否清晰✅ 明确较明确❌ 隐式依赖
是否利于单元测试✅ 非常方便一般❌ 不方便(依赖容器)
是否符合设计原则✅ 符合(依赖不可变)一般❌ 破坏封装
Spring 官方推荐✅ 推荐可用❌ 不推荐
  1. 构造器注入:依赖是强制的(必须传入),可以配合 final,更安全、更清晰,方便单元测试,Spring 4.3 以后,只有一个构造器时可以省略 @Autowired

@Service
public class UserService {

    private final OrderService orderService;

    public UserService(OrderService orderService) {
        this.orderService = orderService;
    }
}
  1. 通过 set 方法注入:适合可选依赖,依赖不是必须的,不如构造器注入严谨

@Service
public class UserService {

    private OrderService orderService;

    @Autowired
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}
  1. 字段注入:直接在成员变量上使用 @Autowired,破坏封装,无法使用 final,不利于单元测试(必须依赖 Spring 容器),依赖关系不明显

@Service
public class UserService {

    @Autowired
    private OrderService orderService;
}
属性注入
属性注入是指将外部配置(如 properties 文件)中的值注入到 Spring Bean 中,常用 @Value 实现。@Value 支持两种写法:${} 占位符用于读取配置文件中的属性值,#{}(SpEL)用于表达式计算。@PropertySource 用于指定额外的 properties 文件加载到环境中。Spring 启动时会将配置文件加载到 Environment,再通过属性解析器解析 ${} 占位符,最终完成字段注入。
  1. ${} 占位符

# application.properties
user.name=zhangsan
user.age=18
@Component
public class User {

    @Value("${user.name}")
    private String name;

    @Value("${user.age}")
    private int age;
}
  1. SpEL 表达式

使用 #{},支持表达式计算、方法调用等。

@Value("#{10 * 2}")
private int num;

@Value("#{systemProperties['os.name']}")
private String osName;
  1. @PropertySource

@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
}
Aware 接口机制
xxxAware 是 Spring 提供的一组“感知接口”,用于让 Bean 感知容器内部对象。当一个 Bean 实现了某个 Aware 接口(如 BeanNameAware、ApplicationContextAware),Spring 在创建该 Bean 时,会在初始化阶段自动调用对应的回调方法,把相关对象注入进来。这是一种由容器主动回调、传递内部资源给 Bean 的机制。常见接口包括:BeanNameAware获取当前 Bean 在容器中的名字、ApplicationContextAware获取容器对象。
下面的例子中,当 Spring 创建 MyBean 时,会自动调用setBeanName()、setApplicationContext(),把对应对象传进来。
@Component
public class MyBean implements BeanNameAware, ApplicationContextAware {

    private String beanName;
    private ApplicationContext applicationContext;

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

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}
@Profile
@Profile 用于标识 Bean 在特定环境下才会生效。Spring 启动时会根据当前激活的环境(active profile)决定是否注册对应的 Bean。常见环境有 dev、test、prod。当某个 Bean 标注了 @Profile(“dev”),只有在激活 dev 环境时才会被注册到容器中,从而实现多环境隔离。
下面的例子中,启动后只会加载 DevDataSource,不会加载 ProdDataSource。
spring.profiles.active=dev
@Service
@Profile("dev")
public class DevDataSource {
}
@Service
@Profile("prod")
public class ProdDataSource {
}

Bean的生命周期

生命周期 8 步
见下表
步骤阶段名称说明常见扩展点 / 对应机制
1️⃣实例化创建 Bean 对象(new 出对象)构造方法
2️⃣属性填充依赖注入,给字段/属性赋值@Autowired@Value
3️⃣Aware 回调容器将内部对象传给 BeanBeanNameAwareApplicationContextAware
4️⃣BeanPostProcessor before初始化前增强postProcessBeforeInitialization()
5️⃣初始化方法执行初始化逻辑@PostConstructafterPropertiesSet()initMethod
6️⃣BeanPostProcessor after初始化后增强postProcessAfterInitialization()(AOP 在此生成代理)
7️⃣使用Bean 对外提供服务业务方法调用
8️⃣销毁容器关闭时回收资源@PreDestroydestroyMethod
初始化与销毁方法
Spring 提供三类初始化扩展点:@PostConstruct、InitializingBean#afterPropertiesSet()、以及 @Bean(initMethod=...) 指定的方法。它们在初始化阶段的执行顺序是:先执行 @PostConstruct,再执行 afterPropertiesSet(),最后执行 initMethod 指定的方法。也就是说,从注解方式到接口方式,再到显式指定的方法,依次执行。
销毁阶段同样有对应扩展点:@PreDestroy、DisposableBean#destroy()(如果实现),以及 @Bean(destroyMethod=...) 指定的方法。常见执行顺序是:先执行 @PreDestroy,再执行 destroy(),最后执行 destroyMethod 指定的方法。
使用场景初始化方式销毁方式说明
✅ 我能修改源码(普通业务 Bean)@PostConstruct@PreDestroy最推荐方式,标准注解,侵入性小
✅ 我要和 Spring 生命周期强绑定afterPropertiesSet()destroy()实现接口,Spring 侵入性较强
✅ 第三方类,不能改源码@Bean(initMethod=...)@Bean(destroyMethod=...)在配置类中指定方法名
@Configuration
public class AppConfig {
    @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    public LifeCycleBean lifeCycleBean() {
        return new LifeCycleBean();
    }
}
public class LifeCycleBean implements InitializingBean {

    @PostConstruct
    public void postConstruct() {
        System.out.println("1 @PostConstruct");
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("2 afterPropertiesSet");
    }

    public void initMethod() {
        System.out.println("3 @Bean initMethod");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("1 @PreDestroy");
    }

    @Override
    public void destroy() {
        System.out.println("2 DisposableBean destroy(若实现)");
    }

    public void destroyMethod() {
        System.out.println("3 @Bean destroyMethod");
    }
}
BeanPostProcessor
BeanPostProcessor 是 Spring 提供的生命周期扩展机制,它允许我们在 Bean 初始化前后对 Bean 进行统一处理。它的作用可以概括为三个方面:扩展、增强、基础设施。所谓扩展,是指在不修改业务代码的情况下,对容器中的 Bean 进行统一逻辑处理;所谓增强,是指可以修改、包装甚至替换 Bean(例如返回代理对象);所谓基础设施,是指 Spring 中很多核心功能(如 AOP、依赖注入相关处理器等)都是基于它实现的,因此它是整个扩展体系的核心支撑点。
AOP 的目标是对方法进行增强,而增强的方式是通过代理对象替代原始 Bean 对外提供服务。为了保证代理对象是基于一个“完整可用的目标对象”生成的,必须等到 Bean 完成依赖注入和初始化逻辑之后再进行包装。如果在 before 阶段创建代理,可能会影响初始化过程或导致代理对象参与初始化,增加复杂性。因此,Spring 通常在 postProcessAfterInitialization 阶段获取已经完成初始化的 Bean,然后将其包装成代理对象并返回,容器最终保存和对外暴露的就是这个代理对象,从而实现 AOP 增强。
before 更偏向“加工”,after 更偏向“包装”,具体总结如下:
阶段执行时机Bean 状态适合做什么
postProcessBeforeInitialization初始化方法之前已实例化、已完成依赖注入做校验、补充属性、预处理逻辑
postProcessAfterInitialization初始化方法之后已完成初始化,是完整可用对象包装 Bean、创建代理、替换原始对象
@Component
public class MyBpp implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if ("lifeCycleBean".equals(beanName)) {
            System.out.println("4 BPP before: " + beanName);
        }
        return bean; // 注意:必须返回bean(或替换后的bean)
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if ("lifeCycleBean".equals(beanName)) {
            System.out.println("6 BPP after: " + beanName);
            // AOP 的“生成代理并返回”就发生在这里(本质:return proxy)
        }
        return bean;
    }
}

AOP

动态代理
在 Java 中实现动态代理主要有两种方式:JDK 动态代理和 CGLIB 代理。JDK 动态代理要求目标类必须实现接口,它的原理是基于接口在运行时生成一个实现同样接口的代理类,然后在方法调用时通过 InvocationHandler 进行拦截和增强,因此本质是“基于接口生成代理对象”;而 CGLIB 则不要求目标类实现接口,它通过在运行时生成目标类的子类,并重写其中的方法来实现增强,本质是“通过继承目标类生成子类代理”。Spring Boot 默认使用 CGLIB,是为了避免 JDK 动态代理带来的类型转换问题,保证代理对象与目标类类型一致,同时统一代理行为。在现代 JVM 下性能差距很小,因此默认选择兼容性更强的 CGLIB。
名词解释
切面通过切入点选择连接点,在织入阶段生成代理对象,实现对目标对象的增强。
术语含义例子(以日志 AOP 为例)
Aspect(切面)封装横切逻辑的类LogAspect 类,专门写日志增强逻辑
Advice(通知)具体增强逻辑(方法)@Before 标注的 logBefore() 方法
JoinPoint(连接点)可以被增强的方法UserService.save() 方法
Pointcut(切入点)过滤哪些方法要增强execution(* com.xxx.service.*.*(..))
Target(目标对象)原始业务对象UserServiceImpl 实例
Proxy(代理对象)增强后的对象容器中实际获取到的 UserServiceImpl$$EnhancerBySpringCGLIB
Weaving(织入)把增强应用到目标对象的过程Spring 在 Bean 初始化后生成代理对象并应用日志逻辑
通知类型
常见的通知类型见下表:
类型注解执行时机
前置通知@Before方法执行前
后置通知@After方法执行后(无论异常)
返回通知@AfterReturning方法正常返回后
异常通知@AfterThrowing方法抛异常时
环绕通知@Around自己控制整个执行过程
@Service
public class UserService {

    public String save(String name) {
        System.out.println("执行保存用户:" + name);
        return "success";
    }
}
@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.xxx.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        // 1️⃣ 方法执行前
        System.out.println("方法开始执行:" + pjp.getSignature().getName());

        long start = System.currentTimeMillis();

        // 2️⃣ 执行目标方法
        Object result = pjp.proceed();

        long end = System.currentTimeMillis();

        // 3️⃣ 方法执行后
        System.out.println("方法执行结束");
        System.out.println("耗时:" + (end - start) + "ms");

        return result;  // ⚠️ 一定要返回
    }
}
AspectJ 切入点表达式
Spring AOP 使用的是 AspectJ 切入点表达式语法 来匹配需要增强的方法,其中最常见的写法是 execution(...)。它的基本结构是:execution(访问修饰符 返回值 包名.类名.方法名(参数)),用于精确或模糊匹配某一类方法。例如:execution(* com.xxx.service.*.*(..)) 表示匹配 com.xxx.service 包下所有类的所有方法,其中第一个 * 表示任意返回值,第一个 * 表示任意类名,第二个 * 表示任意方法名,(..) 表示任意参数列表。表达式中常用的通配符包括:*(匹配任意字符或单个层级)和 ..(匹配任意参数或多层包路径)。通过这种表达式,Spring 可以灵活地筛选出需要进行 AOP 增强的目标方法。
@Pointcut
在 Spring AOP 中,可以使用 @Pointcut 将切入点表达式单独抽取出来,避免在多个通知中重复书写复杂的 execution(...) 表达式。例如,通过 @Pointcut(“execution(* com.xxx.service.*.*(..))”) 定义一个公共的切入点方法,然后在不同的通知中通过 @Before(“servicePointcut()”) 等方式直接引用。这样做的好处是切入点表达式可以被多个通知复用,当匹配规则需要修改时只需改一处即可,显著提升了代码的可维护性和可读性。
@Pointcut("execution(* com.xxx.service.*.*(..))")
public void servicePointcut(){}
@Before("servicePointcut()")
通知执行流程总结
正常情况下是 Before → 方法 → AfterReturning → After;异常情况下是 Before → 方法 → AfterThrowing → After;@After 类似 finally,而 @AfterReturning 只在方法正常返回时执行。
  1. 正常情况

执行顺序通知类型说明
1@Before方法执行前执行
2目标方法业务方法执行
3@AfterReturning方法正常返回后执行
4@After方法最终执行(类似 finally)
  1. 异常情况

执行顺序通知类型说明
1@Before方法执行前执行
2目标方法抛出异常
3@AfterThrowing捕获异常后执行
4@After最终执行(类似 finally)
@After和@AfterReturning比较
见下表
对比点@After@AfterReturning
执行时机方法结束后(无论是否异常)方法正常返回后
类似语义类似 finally类似 try 中成功分支
是否能获取返回值❌ 默认不能✅ 可以获取返回值

JoinPoint : 在 Spring AOP 中,JoinPoint 表示当前被增强的方法执行点,通过在通知方法中声明 JoinPoint 参数,可以获取当前方法的关键信息,例如方法名、方法参数、目标对象以及代理对象等。它常用于日志记录、权限校验、参数打印等场景。比如在一个前置通知中,可以通过 jp.getSignature().getName() 获取方法名,通过 jp.getArgs() 获取参数列表,从而在方法执行前打印调用信息。

@Before("execution(* com.xxx.service.*.*(..))")
public void before(JoinPoint jp) {

    // 获取方法名
    String methodName = jp.getSignature().getName();

    // 获取参数
    Object[] args = jp.getArgs();

    System.out.println("调用方法:" + methodName);
    System.out.println("参数列表:" + Arrays.toString(args));
}
多切面执行顺序
在 Spring AOP 中,如果存在多个切面同时作用于同一个方法,默认情况下它们的执行顺序是不可控的。为了控制多个切面的执行顺序,可以在切面类上使用 @Order(n) 注解,数字越小优先级越高。对于 @Around 通知来说,执行顺序呈现出类似“栈结构”的嵌套效果:优先级高的切面会先进入(执行前半部分),最后退出(执行后半部分)。
@Aspect
@Component
@Order(1)
public class LogAspect {

    @Around("execution(* com.xxx.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("日志切面 - 前");
        Object result = pjp.proceed();
        System.out.println("日志切面 - 后");
        return result;
    }
}
@Aspect
@Component
@Order(2)
public class TxAspect {

    @Around("execution(* com.xxx.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("事务切面 - 前");
        Object result = pjp.proceed();
        System.out.println("事务切面 - 后");
        return result;
    }
}

当调用目标方法时,执行顺序为:

日志切面 - 前
  事务切面 - 前
    目标方法
  事务切面 - 后
日志切面 - 后

事务

什么是 Spring 事务?
Spring 事务是基于 AOP 实现的声明式事务管理机制,本质上是在方法执行前通过代理开启事务,在方法正常完成时提交事务,在出现异常时进行回滚,从而保证数据的一致性。Spring 提供两种事务管理方式:编程式事务和声明式事务。编程式事务由开发者手动控制事务的开启、提交与回滚,虽然控制粒度更精细,但代码侵入性强、耦合度高,因此在实际项目中较少使用;声明式事务则通过 @Transactional 注解定义事务边界,由 Spring 借助 AOP 自动完成事务的创建与管理,代码更加简洁、业务逻辑与事务逻辑解耦,因此在实际开发和面试中通常指的都是声明式事务。
有哪些传播行为?
传播行为决定了当前方法调用时,如何处理已有事务。REQUIRED 是共用一个事务,REQUIRES_NEW 是强制独立事务。
REQUIREDREQUIRES_NEW
外部有事务加入外部事务挂起外部事务,新开事务
回滚影响一起回滚互不影响
使用场景普通业务日志、审计、独立操作
REQUIRED 调用 REQUIRES_NEW,内部方法异常会不会影响外部事务?
当 A 方法使用 REQUIRED、内部调用使用 REQUIRES_NEW 的 B 方法时,B 会开启一个全新的独立事务并挂起 A 的事务;如果 B 抛出异常,它自己的事务一定会回滚,但是否影响 A 取决于异常是否继续向外传播:如果异常被 A 捕获并处理,A 的事务可以正常提交;如果异常继续抛出到 A 的事务边界之外,A 也会因为异常而回滚。因此,REQUIRES_NEW 本身是独立事务,但异常传播仍然可能影响外层事务。
四种隔离级别
MySQL默认REPEATABLE_READ
隔离级别解决问题含义说明
READ_UNCOMMITTED可能脏读允许读取其他事务未提交的数据,事务之间几乎没有隔离,问题最多,性能最高
READ_COMMITTED避免脏读只能读取其他事务已提交的数据,但同一事务中多次读取可能结果不同
REPEATABLE_READ避免不可重复读同一事务中多次读取同一行数据结果一致(即使其他事务已提交修改)
SERIALIZABLE完全串行所有事务按顺序串行执行,强制加锁,避免所有并发问题,但性能最低
rollbackFor回滚规则
在 Spring 声明式事务中,默认的回滚规则是:只有运行时异常(RuntimeException)和 Error 才会触发事务回滚,而受检异常(Checked Exception)默认不会导致事务回滚。例如,如果方法中抛出的是 IOException 这类受检异常,事务通常仍然会提交而不是回滚。如果希望在出现受检异常时也进行回滚,可以通过在 @Transactional 注解中显式指定 rollbackFor 属性来定义需要回滚的异常类型,例如指定对 Exception 或某个具体异常类进行回滚控制,从而改变默认行为。
超时回滚
timeout 属性用于设置事务的最大执行时间,例如设置 @Transactional(timeout = 5) 表示该事务必须在 5 秒内执行完成,否则 Spring 会回滚事务并抛出超时异常,以防止长时间占用数据库资源。
事务管理器原理
Spring 事务管理的底层核心接口是 PlatformTransactionManager,不同的数据访问技术对应不同的实现,例如 JDBC 使用 DataSourceTransactionManager,JPA 使用 JpaTransactionManager,Hibernate 使用 HibernateTransactionManager。在执行过程中,Spring 通过 AOP 拦截被 @Transactional 标注的方法,然后调用事务管理器开启事务,获取数据库连接并将自动提交设置为 false,接着执行目标方法;如果方法正常结束则提交事务(commit),如果出现异常则回滚事务(rollback)。其本质就是利用 AOP 对方法进行包装,从而统一控制数据库连接的提交与回滚。
为什么private方法事务和同类方法调用不生效?
Spring 声明式事务是基于 AOP 代理机制实现的,而代理对象只能拦截对外暴露的可被代理的方法。在默认情况下,Spring 通过代理对象对 public 方法进行增强,如果方法是 private 的,就无法被代理类重写或拦截,因此事务增强逻辑不会织入进去,最终导致事务不生效。本质原因在于:事务控制发生在代理对象上,而不是目标对象本身。
同类方法调用事务不生效的原因也与代理机制有关。当在同一个类内部通过 this.methodB() 调用另一个带有 @Transactional 注解的方法时,调用是直接发生在目标对象内部,并没有经过 Spring 生成的代理对象,因此事务拦截器不会生效。换句话说,事务是通过代理对象拦截外部调用实现的,而内部调用绕过了代理,所以事务不会被触发。