Spring 核心知识点全面解析

0 阅读26分钟

写在文章开头

很多 Java 开发者对 Spring 的认知停留在"会用"层面:

"Bean 怎么创建出来的?不知道" "循环依赖是怎么解决的?看过博客,过两天就忘" "AOP 为什么有时生效、有时不生效?靠试"

知其然更要知其所以然。

本文从源码实现出发,解读 Spring 框架的核心机制:

  • BeanFactory 如何一步步构建 Bean?(三级缓存机制)
  • @Autowired 注入的底层逻辑是什么?(后置处理器链)
  • Spring 事务为何会失效?(代理失效的 6 种场景)
  • @PostConstruct 为何总在 afterPropertiesSet 之前?

读完本文,你不仅能回答"怎么用",更能回答"为什么这样设计"——这才是面试官真正想听到的答案。

你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go、C 等多语言技术栈,现任某知名黑厂高级开发工程师,专注于高并发系统架构设计与性能优化。

🌟 开源项目贡献

  • mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
    🔗 github.com/shark-ctrl/…(欢迎 Star & Contribute)

📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。

👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!

详解Spring核心知识点

一、Spring 基础认知

聊聊对Spring的理解

Spring 是一个 轻量级开源框架,核心理念是集大成内置所有开发人员所需要的工具包,避免重复造轮子,专注于业务开发。

其核心功能是 DI(依赖注入)AOP(面向切面编程) ,使得对 Java Bean 的管理十分便捷。

在此基础上,Spring 生态还提供了丰富的扩展能力:

  • 数据访问JdbcTemplate 让你只需关注 SQL 逻辑,无需处理连接和异常;@Transactional 一个注解就能开启事务
  • Web 开发:只需一个 @RestController + @GetMapping,几分钟就能搞定一个 REST 接口
  • 集成能力:starter 自动装配,一行依赖即可整合 Redis、MQ、ES 等组件
  • 测试支持@SpringBootTest 让你无需启动真实容器,即可对整个应用进行集成测试

一句话总结:Spring 通过 IoC/AOP 让 Java 开发更简单、更解耦。

Spring包含那些模块

Spring 可以分为以下几大类:

分类核心模块作用
Core Containerspring-core、spring-beans、spring-context、spring-expressionIoC 容器、Bean 管理、EL 表达式
AOPspring-aop、spring-aspects面向切面编程
Webspring-web、spring-webmvc、spring-webfluxWeb 开发、MVC 模式、响应式编程
Data Accessspring-jdbc、spring-tx、spring-orm、spring-jms数据库访问、事务管理、ORM 框架、消息队列
Testspring-test单元测试、集成测试支持
Messagingspring-messaging消息通信

其中,Core Container 是 Spring 的基石,IoC/DI、AOP 等核心功能都依赖于此。

Spring,Spring MVC,Spring Boot 之间的关系

Spring 提供 IoC 容器AOP 两大核心能力,奠定了 Spring 生态的基础。

Spring MVC 是基于 Spring 的 Web 框架,简化了 Web 开发。相较于传统 Servlet

维度ServletSpring MVC
配置web.xml 硬编码注解配置
参数手动从 request 取自动绑定到方法参数
视图手动跳转视图解析器统一处理
异常try-catch@ExceptionHandler 统一处理

一句话:把 Servlet 时代那些繁琐的模板代码都封装掉了。

Spring Boot 在这设计理念之上简化了Spring的种种配置,让我们可以"偷懒":

  1. 开箱即用:引入 starter,一行依赖即可运行
  2. 自动装配:通过 @EnableAutoConfiguration 自动推断并配置所需组件
  3. 内置容器:内置 Tomcat/Jetty,无需单独部署 WAR
  4. 监控管理:actuator 提供应用监控端点

可以说 Spring Boot 通过大量的自动配置技术降低了 Spring 的使用成本。

如何根据配置动态生成 Spring 的 Bean

Spring 支持多种方式动态注册 Bean:

  1. @ConfigurationProperties + @EnableConfigurationProperties

    • application.yml 中的配置绑定到 POJO,再注册为 Bean
    • 适合:根据配置生成数据源、Redis 等组件
  2. @Conditional 系列注解

    • 满足条件时才注册 Bean,如 @ConditionalOnClass@ConditionalOnProperty
    • Spring Boot 自动装配的核心机制
  3. @Profile

    • 通过 spring.profiles.active 激活不同环境的 Bean
    • 如:@Profile("dev")@Profile("prod")
  4. FactoryBean 接口

    • 实现 FactoryBean<T>,在 getObject() 中自定义 Bean 创建逻辑
    • SqlSessionFactoryBeanRabbitMQConnectionFactoryBean

Spring 中注入 Bean 有几种方式

方式示例说明
XML 配置<bean><property name="xxx" ref="xxx"/></bean>传统方式,现在很少用
构造器注入@Autowired UserService(UserRepository)推荐,依赖不可变
Setter 注入@Autowired setXxx(Xxx xxx)可选注入
字段注入@Autowired private Xxx xxx不推荐,无法 final
@Bean 方法@Bean MyBean()用于 @Configuration
FactoryBean实现 FactoryBean 接口自定义创建逻辑

二、IoC 与 DI

谈谈你对IoC的理解

IoC即控制反转( Inversion Control)I 是反转, C是控制,通俗来说控制反转的意思就是将对象的创建交给外部管理。所以IoC并不是Spring框架特有的,它是一种软件工程设计理念,任何语言都可以基于自己的特质实现这一能力。

但我们就以Spring为例,Spring只要基于几个注解或者xml配置,就能让框架帮我自动管理对象的创建,当我们需要用到这个对象时,只需使用注解或者配置注入即可。这就使得我们后续使用某些类的时候无需关心它内部需要创建那些其他类,无需再一步一步的认为回溯将对象手动创建、管理。

介绍一下Spring的AOP

即面向切面编程对应的英文是(aspect-oriented programming),是面向对象思想(oop)的一种补充,基于切面的维度横跨多个对象进行公共逻辑织入,保证业务与非业务功能的解耦。

知道什么是Spring bean吗?

与传统 j2EE 企业规范中的 Bean 不同,Spring 中的 Bean 可以是任意 POJO,不受 JavaBean 规范约束。只要交给 Spring 容器管理的对象,就是 Spring Bean

将一个类声明为 Bean 的注解有哪些?

注解场景
@Component通用组件,无特定分层
@ControllerSpring MVC 控制层
@Service服务层,业务逻辑
@Repository持久层,DAO
@MapperMyBatis Mapper 接口

@Component 和 @Bean 的区别是什么?

维度@Component@Bean
作用对象方法
使用场景我们自己写的类第三方类,无法加注解
配置方式扫描即注册手动在 @Configuration 类中定义

如下所示,我们需要集成第三方组件时,因为无法在类上加注解,所以我们使用@Bean注解:

@Bean(name="redisTemplate")
    public RedisTemplate<StringStringredisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<StringStringtemplate = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(redisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(redisSerializer);
        //key haspmap序列化
        template.setHashKeySerializer(redisSerializer);
        //
        return template;
    }

通过注解注入 Bean 的方式有哪些

常见方式是@Autowired、@Resource、@Inject,这些注解对应来源分别是Spring框架、jdk和JSR-330

注解来源注入方式说明
@AutowiredSpringbyType → byName先匹配类型,多个时按字段名
@ResourceJDKbyName → byType先匹配名称,默认用字段名
@InjectJSR-330byType需要配合 @Named 指定名称

按照现主流开发模式@Autowired最常用,其注入规则是先匹配类型,在按照字段名,如下案例所示,UserService有两个bean实现,为了确保在@Autowired规则下能够正确注入UserServiceImplA,我们的字段名需声明为userServiceImplA:

// 接口
public interface UserService {
    void sayHello();
}

// 实现类
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void sayHello() {
        System.out.println("Hello");
    }
}

// 使用
@Autowired
private UserService userService;  // userService = 字段名

当存在多个相同类型的 Bean 时,Spring 会按字段名去匹配 Bean 的名称:

@Service
public class UserServiceImplA implements UserService {}

@Service
public class UserServiceImplB implements UserService {}

// ✅ 能匹配
@Autowired
private UserService userServiceImplA;  // 按字段名找到 UserServiceImplA

// ❌ 报错:NoUniqueBeanDefinitionException
@Autowired
private UserService userService;  // 按字段名找不到叫 userService 的 Bean

解决多 Bean 冲突:配合 @Qualifier 指定 Bean 名称

@Autowired
@Qualifier("userServiceImplA")
private UserService userService;

@Autowired 和 @Resource 的区别是什么

维度@Autowired@Resource
来源SpringJDK(JSR-250)
注入顺序byType → byNamebyName → byType
required 属性支持不支持
作用位置构造器/Setter/字段字段/Setter

常见问题:当同一接口有多个实现类时

@Service
public class A1 implements A {}

@Service
public class A2 implements A {}

// 使用时报错:NoUniqueBeanDefinitionException
@Autowired
private A a;

解决方案:

  • @Primary:标记优先使用的 Bean
  • @Qualifier("beanName"):指定注入哪个 Bean

Bean 的作用域有哪些

作用域说明使用场景
singleton默认,每次获取同一实例所有 Bean
prototype多例,每次获取新实例有状态的 Bean
request每次 HTTP 请求Web 应用
session同一 SessionWeb 应用
applicationServletContext 生命周期Web 应用
websocketWebSocket 会话WebSocket

针对作用域的配置,对于 xml的话就是<bean id="..." class="..." scope="singleton"></bean>,如果是注解就加给Scope

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

BeanFactory 和 FactoryBean 的关系

概念说明
BeanFactorySpring IoC 容器的核心接口,负责 Bean 的创建和管理
FactoryBean工厂 Bean,用于创建复杂或特殊配置的 Bean

区别:

  • BeanFactory:容器的"管理员",管理所有 Bean
  • FactoryBean:某个 Bean 的"工厂",实现它可以自定义 Bean 创建逻辑

常见实现: SqlSessionFactoryBeanRabbitMQConnectionFactoryBean

单例 Bean 的线程安全问题

Spring默认的Bean是单例的,所以在多线程操作当前Bean的情况下,若有实例变量(例如全局容器等)是很可能出现线程安全问题的。常见的解决方案有以下几种:

方案一(推荐):使用无状态 Bean - 不包含可变成员变量,只包含只读操作

@Service
public class UserService {
    // 无状态Bean:所有方法只接收参数,不修改成员变量
    public User findById(Long id) {
        return userRepository.findById(id);
    }

    public List<User> findAll() {
        return userRepository.findAll();
    }
}

方案二:使用同步锁 - 根据并发场景选用合适的锁

单机场景:使用 synchronizedReentrantLock

@Service
public class CounterService {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}

分布式场景:使用 Redis/ZooKeeper 分布式锁

@Service
public class DistributedCounterService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void increment() {
        String lockKey = "counter:lock";
        Boolean locked = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1"Duration.ofSeconds(10));
        if (Boolean.TRUE.equals(locked)) {
            try {
                // 业务逻辑
            } finally {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

方案三:使用不可变对象 - 将可变成员变量声明为 final

@Service
public class ConfigService {
    // 不可变对象:所有成员变量都是 final
    private final Map<StringString> config;

    public ConfigService() {
        this.config = Collections.unmodifiableMap(new HashMap<>());
    }

    public String getValue(String key) {
        return config.get(key);
    }
}

方案四:使用 ThreadLocal - 管理每个线程的变量副本

@Service
public class TestBean {
    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public void doSomething() {
        try {
            threadLocal.set("test");
            // 业务逻辑...
            String value = threadLocal.get();
        } finally {
            // ⚠️ 重要:使用完毕后必须清理,否则可能导致内存泄漏
            threadLocal.remove();
        }
    }

    public String get() {
        return threadLocal.get();
    }
}

Spring Bean 的生命周期

完整生命周期包括:实例化 → 属性填充 → 初始化 → 销毁

@Service
public class UserService {

    @Autowired      // ← 属性填充阶段注入
    private UserRepository userRepository;

    @Value("${timeout:30}")  // ← 属性填充阶段注入
    private int timeout;

    @PostConstruct   // ← 初始化阶段执行
    public void init() {
        // 做一些初始化操作,比如校验配置
    }
}

执行顺序:

1. 实例化(new UserService())
         ↓
2. 属性填充(@Autowired@Value 注入)
         ↓
3. 初始化(@PostConstruct → InitializingBean.afterPropertiesSet → init-method)
         ↓
4. Bean 就绪

详细可参考:深入解析 Spring Bean 的生命周期管理

Spring默认支持循环依赖吗?如果发生如何解决

什么是循环依赖?

以 AService 和 BService 为例,它们相互依赖:

@Service
public class AService {
    @Autowired
    private BService bService;

    public void doSomething() {
        bService.doOtherThing();
    }
}

@Service
public class BService {
    @Autowired
    private AService aService;

    public void doOtherThing() {
        aService.doSomething();
    }
}

上述代码中,AService 需要注入 BService,BService 又需要注入 AService,形成了循环依赖

默认是不支持,从 Spring Boot 2.6.x 开始默认禁止循环依赖,之前的版本通过三级缓存机制支持单例Bean的字段注入循环依赖。

关键方法是 DefaultSingletonBeanRegistry#getSingleton,它依次查询三级缓存,我们以AService和BService这个互相依赖的场景介绍一下,假设AService 注入 BService时下述代码流程为:

  1. 先看一级(成品bean容器),发现不存在,执行步骤2
  2. 进入二级查看,也不存在,进入步骤3
  3. 重点,如果allowEarlyReference(允许不成熟对象提前暴露)为true,则进入步骤4
  4. 将BService先创建(可能有些依赖对象还没注入),直接暴露
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先查一级缓存(成品Bean)
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2. 查二级缓存(早期Bean)
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. 查三级缓存(工厂),创建早期引用
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 放入二级缓存,移除三级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

对应执行流程如下图,所以要想解决循环依赖问题,最基本的解决方案是将allowEarlyReference = true(配置名spring.main.allow-circular-references) ,让Spring 提前暴露未完成初始化的 Bean 引用给其他 Bean 使用,从而打破循环依赖。但这个"早期引用"存在业务风险,需要谨慎使用:

三级缓存机制流程图

三级缓存机制流程图

也就是因为上述原因,Spring Boot 2.6+ 默认禁止循环依赖——鼓励开发者通过重构解决设计问题

解决方案

方案一(不推荐) :通过配置开启循环依赖支持,也就是上文说的将不成熟的对象提前暴露:

# application.properties
spring.main.allow-circular-references=true

或者在启动类中配置:

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(Application.class);
    app.setAllowCircularReferences(true);
    app.run(args);
}

方案二:lazy注解懒加载,确保只有被使用时才会显示初始化,如下代码所示,针对需要显示初始化的类,我们首先需要通过Lazy标注声明:

/**
 * 懒加载服务示例
 * 使用@Lazy注解标记,表示该Bean在第一次被请求时才进行初始化
 */
@Service
@Slf4j
@Lazy
public class LazyService {

    public LazyService() {
        log.info("LazyService 初始化完成");
    }

    public String getMessage() {
        return "Hello from LazyService!";
    }
}

注入时也是同理,标明懒加载注解,确保只有使用的时候,才会创建bean,由此避免循环依赖:

@Lazy
    @Autowired
    private LazyService lazyService;

方案三:使用Spring容器上下文,这是笔者日常工厂时间中比较常用的一种手段,是破解循环依赖中最有效的手段。本质上,导致循环依赖的原因就是两个bean因为需要对方的能力而互相引用:

循环依赖示意图:A、B互相引用

循环依赖示意图:A、B互相引用

所以我们是否可以做到,让各个bean完成独立的加载,然后使用时,再引用对象呢?答案是使用Spring容器上下文。我们都知道Spring框架内置了一个ApplicationContext对bean容器进行封装,我们可以通过getBean方法到容器内获取指定的bean对象:

 /**
  * Spring应用上下文环境
  */
 private static ApplicationContext applicationContext;

所以使用时,我们就可以在需要用到依赖bean的地方显式调用,如下图,以互相依赖的A B两个bean为例,我们编码时不显示注入对方,避免循环依赖检测报错。 当b需要用到a时,通过ApplicationContext(笔者这里用到hutool针对ApplicationContext的封装工具)调用getBean取出依赖对象执行调用:

ApplicationContext获取Bean流程图

ApplicationContext获取Bean流程图

对应BService调用代码如下所示,这种做法避免了懒加载带来的延迟开销,本质是通过按需方式完成调用,是Spring工程的最佳实践:

 SpringUtil.getBean(AService.class).doSomething();

三、Bean 生命周期与循环依赖

Spring是如何销毁bean的

bean需要被销毁的时候,我们通过源码可以看到,对bean的销毁用了一个适配器模式DisposableBeanAdapter,感兴趣的读者可以步入看看,实际上这个方法就是判断这个bean有没有实现DisposableBean接口,若没有则看看xml配置有没有配置destroy-method,进而完成适配器的创建,着手销毁bean

@Override
 public void destroyBean(Object existingBean) {
  new DisposableBeanAdapter(existingBean, getBeanPostProcessors(), getAccessControlContext()).destroy();
 }

查看destroy的实现细节可以看到它会先走查看是否有DisposableBean接口实现,如果有则调用,后续则是查看是否xml中有配置指定的销毁发发,若有则直接调用:

public void destroy() {
  

  //如果有继承DisposableBean则调用这个bean类的destroy方法
  if (this.invokeDisposableBean) {
  .....
   try {
    if (System.getSecurityManager() != null) {
     AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
      ((DisposableBean) this.bean).destroy();
      return null;
     }, this.acc);
    }
    else {
     ((DisposableBean) this.bean).destroy();
    }
   }
  .......
  }

//反之看看xml中这个bean有没有配置destroy-method,有则调用
  if (this.destroyMethod != null) {
   invokeCustomDestroyMethod(this.destroyMethod);
  }
  else if (this.destroyMethodName != null) {
   Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
   if (methodToInvoke != null) {
    invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
   }
  }
 }

为什么Spring不建议使用基于字段的依赖注入

主要有以下几点原因:

1. 违反单一职责原则(SOLID)

字段注入让依赖关系隐藏在类内部,类的职责变得不清晰。

2. 构造方法中无法使用注入的依赖

Bean 初始化顺序:实例化 → 构造方法 → 依赖注入,所以在构造方法中使用 @Autowired 注入的字段会是 null

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public UserService() {
        // ❌ userRepository 还是 null,此时无法使用
        // userRepository.findAll(); // 会报 NullPointerException
    }
}

解决方案:使用构造方法注入

@Service
public class UserService {
    private final UserRepository userRepository;

    // ✅ 构造方法注入,依赖在构造时已就绪
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
        this.userRepository.findAll(); // 正常工作
    }
}

3. 隐藏依赖关系,违背封装性

通过私有字段注入,类的依赖关系不明确,代码可读性差。

4. 单元测试困难

依赖隐藏在字段中,Mock 时需要反射或启动整个容器:

// 字段注入:测试时必须启动 Spring 容器
@Autowired
private UserService userService;

// 构造方法注入:可以直接 new
private UserRepository mockRepository;
private UserService userService = new UserService(mockRepository);

@PostConstruct、init-method和afterPropertiesSet执行顺序

从源码中可知:

  1. @PostConstruct 在 Bean 属性填充完成后、初始化阶段中调用,对应的处理器是 CommonAnnotationBeanPostProcessor,我们从该源码中看到它通过 setInitAnnotationType 方法注册了 PostConstruct 注解。
public CommonAnnotationBeanPostProcessor() {
  setOrder(Ordered.LOWEST_PRECEDENCE - 3);
  setInitAnnotationType(PostConstruct.class);
  setDestroyAnnotationType(PreDestroy.class);
  ignoreResourceType("javax.xml.ws.WebServiceContext");
 }

2. 然后就是调用InitializingBeanafterPropertiesSet 。 3. 最后是调用我们文件配置的 init-method

对应的我们可以在AbstractAutowireCapableBeanFactory的invokeInitMethods印证这一点:

protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
   throws Throwable {

  boolean isInitializingBean = (bean instanceof InitializingBean);
  if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
   
   if (System.getSecurityManager() != null) {
   //......
   }
   else {
   //调用InitializingBean接口的afterPropertiesSet
    ((InitializingBeanbean).afterPropertiesSet();
   }
  }

  //调用自定义init-method
  if (mbd != null && bean.getClass() != NullBean.class) {
   String initMethodName = mbd.getInitMethodName();
   if (StringUtils.hasLength(initMethodName) &&
     !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
     !mbd.isExternallyManagedInitMethod(initMethodName)) {
    invokeCustomInitMethod(beanName, bean, mbd);
   }
  }
 }

Spring中shutdownhook作用是什么

Spring框架所提供的关闭钩子,启动时Spring会向JVM注册一个shutdownhook,当程序关闭的时候就会触发我们所实现的DisposableBean或者配置的销毁方法完成最后清理操作。

对应我们可以在AbstractAutowireCapableBeanFactorydoCreateBean看到该方法的实现,其内部就会检查当前bean是否是DisposableBean类型,如果是则将该方法注册,在容器销毁时会调用destroy方法:

// Register bean as disposable.
  try {
   registerDisposableBeanIfNecessary(beanName, bean, mbd);
  }

四、AOP 与事务

Spring的AOP在什么场景下会失效

大体来说基本都是代理创建失败或者未能正确的调用到代理所导致:

  1. 方法私有化 - private 方法无法被代理类覆盖,AOP 无法织入
  2. 静态方法 - static 方法属于类本身,不经过 Spring 代理,AOP 无法织入
  3. final 修饰方法 - final 方法无法被子类重写,AOP 无法织入
  4. 同类内部调用(this 调用) - 通过 this.method() 调用同类方法时,绕过了代理对象,直接调用目标对象的方法,导致 AOP 增强失效
  5. 异常被 catch 吞掉 - 业务方法捕获异常后未重新抛出,代理无法感知异常,事务无法回滚(如 @Transactional 失效)

详解Spring技术细节

Spring的事务在多线程下是否生效

如果异步且通过注解Transactional注解标记的,因为事务是通过threadLocal来维护当前线程的内部map记录connection信息,这意味着异步提交到别的线程后connection信息就取不到即无法回滚错误事务了。 对此我们给出DataSourceTransactionManager的事务开启方法doBegin

@Override
 protected void doBegin(Object transaction, TransactionDefinition definition) {
  DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  Connection con = null;

  try {
   //......
   //拿到当前线程持有的连接
   txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
   con = txObject.getConnectionHolder().getConnection();

   //......
   //设置自动提交为false
   if (con.getAutoCommit()) {
    txObject.setMustRestoreAutoCommit(true);
    if (logger.isDebugEnabled()) {
     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
    }
    con.setAutoCommit(false);
   }

   //......

   //通过threadLocal绑定到当前线程的map中
   if (txObject.isNewConnectionHolder()) {
    TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
   }
  }

  catch (Throwable ex) {
  //......
  }
 }

针对该问题,我们要从三种情况考虑:

情况一: @Transactional@Async在同一个代理类的方法上

@Async
@Transactional(rollbackFor = Exception.class)
public Integer saveOrderInfo(Order order, TUser user) {
    userMapper.insert(user);
    return orderMapper.insert(order);
}

这种情况被外部调用时,本质上就是通过异步线程池调用代理类增强后的方法,所以代理类还是生效的:

情况二: @Transactional 调用 @Async

@Transactional(rollbackFor = Exception.class)
public void save(Order order, TUser user) {
    SpringUtil.getBean(OrderService.class).saveOrderInfo(orderuser);
}

@Async
public Integer saveOrderInfo(Order order, TUser user) {
    userMapper.insert(user);
    return orderMapper.insert(order);
}

这会使得saveOrderInfo方法被提交到异步线程池,对应的connection信息对save方法无感知无法进行回滚,事务不生效:

情况三: @Async 调用 @Transactional

@Async
public void save(Order order, TUser user) {
  //.....其他SQL操作
    SpringUtil.getBean(OrderService.class).saveOrderInfo(orderuser);
}

@Transactional(rollbackFor = Exception.class)
public Integer saveOrderInfo(Order order, TUser user) {
    userMapper.insert(user);
    return orderMapper.insert(order);
}

这种情况下,事务仅仅针对在saveOrderInfo方法中,所以 save 方法中的操作不受事务保护,但如果 saveOrderInfo 方法内抛异常,该方法内的操作会回滚:

Spring的事务传播机制有哪些

Spring 提供了 7 种传播行为:

传播行为说明
REQUIRED(默认)如果当前有事务,加入该事务;否则新建事务
REQUIRES_NEW总是新建事务,挂起当前事务
SUPPORTS如果当前有事务,加入事务;否则非事务执行
NOT_SUPPORTED非事务执行,挂起当前事务
MANDATORY必须在事务中执行,否则抛异常
NEVER必须非事务执行,否则抛异常
NESTED如果当前有事务,在嵌套事务中执行

详细可参考:探索 Spring 事务的奥秘

Spring在业务中常见的使用方式

  1. 基于IOC完成策略模式
  2. 基于AOP进行日志采集、逻辑校验、异常捕获。
  3. 事务管理数据持久化保证多表操作ACID。
  4. 通过Event异步解耦功能。

Spring中如何开启事务

  1. 编程式事务:用TransactionTemplateexecute方法提交逻辑完成编程式事务。
  2. 声明式事务:用Transactional注解。

Spring中用到了哪些设计模式

详情可以参考笔者这篇文章,论 Spring 框架所运用的设计模式:mp.weixin.qq.com/s/L82TPwjtA…

详解Spring MVC核心知识点

五、Spring MVC

详解MVC的设计理念

MVC 是 Web 开发经典的分层模式:

层级职责
Model数据和业务逻辑
View用户界面展示
Controller接收请求,调用 Model 处理,响应 View

SpringMVC是如何将不同的Request路由到不同Controller中的

深入解读 Spring MVC:Web 开发的得力助手 :mp.weixin.qq.com/s/gzW1Mhw06…

详解Spring Boot核心知识点

六、Spring Boot 专题

Spring Boot 如何让你的 bean 在其他 bean 之前加载

这一点我们查看依赖注入的源码即可知晓,一般来说主动注入的时候这些bean都会优先加载,所以要想实现这一点无非用以下几种手段:

  1. 直接使用Autowired注解,如下所示,一旦容器扫描到TDataService发现其依赖TDataMapper 时,若发现容器还未初始化TDataMapper ,则会优先将这个Bean加载:
@Service
public class TDataService {
    @Autowired
    private TDataMapper tDataMapper;

2. 我们同样可以通过DependsOn注解,明确告知配置类在加载TDataService前先加载tDataMapper

 @Bean
    @DependsOn("tDataMapper")
    public TDataService tDataService() {
        return new TDataService();
    }

3. 基于Spring拓展点,即在实现BeanFactoryPostProcessorpostProcessBeanFactory,通过BeanFactory完全加载BeanDefinition之后,通过该方法找到容器找到bean的定义通过反射完成创建beanInstance并返回:

 @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanA bean = beanFactory.getBean(BeanA.class);
    }

如何用Spring Boot统计一个Bean中的方法调用次数

针对某个请求方法通过AOP切面配合原子类计数:

@Aspect
@Component
@Slf4j
public class CalculateAspect {

    private AtomicInteger requestCount = new AtomicInteger(0);

    // 定义切点:拦截所有 controller 包下的 public 方法
    @Pointcut("execution(public * com.*.*(..))")
    public void controllerPointcut() {}

    // 获取调用次数
    public int getRequestCount() {
        return requestCount.get();
    }

    // 环绕通知:方法执行前后都计数
    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 方法执行前计数
        requestCount.incrementAndGet();
        // 执行目标方法
        return joinPoint.proceed();
    }
}

Springboot是如何实现自动配置的

Spring Boot自动装配原理以及实践:mp.weixin.qq.com/s/GYlMV53Ua…

SpringBoot是如何实现main方法启动Web项目的

  1. 调用run方法启动时,Spring会自动扫描所有Spring组件完成web相关的依赖加载。
  2. 基于默认配置将内嵌的tomcat服务器启动。
  3. 完成启动处理所有web请求。

在Spring中如何使用Spring Event做事件驱动

  1. 继承Event类实现事件,
  2. 基于EventListener注解编写监听方法监听指定的事件
  3. 发布自定义实现的事件。
// 1. 定义事件:继承 ApplicationEvent
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
}

// 2. 定义监听器:@EventListener 监听指定事件
@Component
public class OrderEventListener {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("收到订单创建事件:" + event.getOrder().getId());
        // 发短信、发邮件等后续操作
    }
}

// 3. 发布事件:ApplicationEventPublisher
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void createOrder(Order order) {
        // 创建订单逻辑...
        // 发布事件
        publisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}
收到订单创建事件:ORDER-20240101-001

SpringBoot中的事务事件如何使用

以笔者为例最常用到i的就是事务监听注解TransactionalEventListener,当事务触发回滚时就可以通过事件监听感知回滚,基于这个拓展点,我们就可以感知事务回滚进行针对性的处理。

参考:事务监听详解

为什么不建议直接使用SpringBoot的@Async

默认情况下通过@Async提交异步任务会通过SimpleAsyncTaskExecutor处理,查看doExecute方法可以看到,其本质上就是创建一个全新的线程处理任务,若频繁调用则会导致创建大量线程,导致操作系统上下文切换开销增大:

protected void doExecute(Runnable task) {
  Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
  thread.start();
 }

Spring中@Service 、@Component、@Repository等注解区别是什么

本质上都是对于bean的描述,只是语义上的区别:

  1. @Service :标记为服务层组件。

  2. @Component:表示该类为Spring的组件

  3. @Repository:标记为持久层组件,会触发异常转换功能

    • JPA 场景:Spring 会自动注册 PersistenceExceptionTranslationPostProcessor,将底层数据库异常(如 SQLException)转换为 Spring 的统一 DataAccessException 体系,让业务层无需关心具体数据库的异常类型
    • MyBatis 场景:MyBatis 自带异常转换,无需依赖 @Repository 的异常转换功能,此时 @Repository 更多起语义化作用

这里笔者特别针对Repository源码进行特殊说明,被Repository标注的持久层组件,会被PersistenceExceptionTranslationPostProcessor处理,通过PersistenceExceptionTranslationAdvisor增强即设置PersistenceExceptionTranslationInterceptor拦截器,一旦感知异常则抛出DataAccessException:

@Repository异常转换拦截器流程图

@Repository异常转换拦截器流程图

与上图对应,我们给出织入@Repository的拦截器PersistenceExceptionTranslationInterceptor核心代码段,可以看到其内部通过try块执行调用,针对运行时异常统一通过DataAccessUtils抛出DataAccessException

@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        return mi.proceed();
    }
    catch (RuntimeException ex) {
        //......
        //统一针对异常抛出DataAccessException
        throw DataAccessUtils.translateIfNecessary(ex, translator);
    }
}

如何在SpringBoot启动过程中做缓存预热

CommandLineRunnerrun 方法会在容器初始化完成后执行,适合做缓存预热。

SpringApplicationrun 方法可以看到,完成所有前置工作后会调用 callRunners,这就是 CommandLineRunner 的执行时机:

public ConfigurableApplicationContext run(String... args) {
    // ... 前置准备工作
    callRunners(context, applicationArguments);
    return context;
}

代码示例:

@Component
public class CacheWarmUpRunner implements CommandLineRunner {

    @Autowired
    private DictMapper dictMapper;

    @Autowired
    private JedisPool jedisPool;

    private static final String DICT_CACHE_KEY = "sys:dict:";

    @Override
    public void run(String... args) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 查询数据库字典数据
            List<Dict> dictList = dictMapper.selectAll();
            // 写入 Redis 缓存
            for (Dict dict : dictList) {
                String key = DICT_CACHE_KEY + dict.getType() + ":" + dict.getCode();
                jedis.set(key, dict.getValue());
            }
            System.out.println("缓存预热完成,共加载 " + dictList.size() + " 条字典数据");
        }
    }
}

SpringBoot中如何实现多环境配置

编写多个配置文件,再通过application的配置-Dspring.profiles.active决定,例如:

-Dspring.profiles.active=prod

如何开发一个spring boot starter脚手架

深入解读 Spring MVC:Web 开发的得力助手 :mp.weixin.qq.com/s/gzW1Mhw06…

介绍下@Scheduled的实现原理以及用法

该注解注明的任务会被扩展点ScheduledAnnotationBeanPostProcessor处理,如下postProcessAfterInitialization所示,本质上就是扫描到该注解注明的所有方法生成annotatedMethods ,然后调用processScheduled(其内部就是调用scheduleFixedDelayTask)生成延迟任务并提交:

@Override
 public Object postProcessAfterInitialization(Object bean, String beanName) {
  //......

  Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
  if (!this.nonAnnotatedClasses.contains(targetClass) &&
    AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
    //获取所有添加注解的方法
   Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
     (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
      Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
        method, Scheduled.class, Schedules.class);
      return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);
     });
     
   if (annotatedMethods.isEmpty()) {
   //......
   }
   else {
    //遍历这些方法生成任务提交到延迟队列中
    annotatedMethods.forEach((method, scheduledAnnotations) ->
      scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));
    //......
   }
  }
  return bean;
 }

小结

通过对 Spring 核心知识点的全面解析,我们对其有了更深入的理解。

Spring 的控制反转(IoC)实现了对象之间依赖关系的灵活管理,极大地降低了代码的耦合度。依赖注入方式让组件的装配更加便捷高效。Spring 的面向切面编程(AOP)为横切关注点的处理提供了强大支持,能够在不影响业务代码的情况下实现诸如日志记录、事务管理、安全控制等功能。其 Bean 的生命周期管理确保了 Bean 在不同阶段的正确处理和行为。配置管理方面,无论是 XML 配置还是基于注解的配置,都为项目的搭建和定制提供了丰富的手段。Spring 还提供了对数据访问、事务管理等方面的良好集成和支持,使开发更加顺畅。

你好,我是 SharkChili ,Java Guide 核心维护者之一,对 Redis、Nightingale 等知名开源项目有深度源码研究经验。熟悉 Java、Go、C 等多语言技术栈,现任某知名黑厂高级开发工程师,专注于高并发系统架构设计与性能优化。

🌟 开源项目贡献

  • mini-redis:教学级 Redis 精简实现,助力分布式缓存原理学习
    🔗 github.com/shark-ctrl/…(欢迎 Star & Contribute)

📚 公众号价值 分享企业级架构设计、性能优化、源码解析等核心技术干货,涵盖分布式系统、微服务治理、大数据处理等实战领域,并探索面向AI的vibe coding等现代开发范式。

👥 加入技术社群 关注公众号,回复 【加群】 获取联系方式,与众多技术爱好者交流分布式架构、微服务等前沿技术!

参考

Spring 常见面试题总结:javaguide.cn/system-desi…

Spring Bean的生命周期(非常详细):www.cnblogs.com/zrtqsk/p/37…

Spring Bean的作用域:blog.csdn.net/kongmin_123…