Spring中如何开启事务?

典型回答 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。 编程式事务 基于底层的API,如PlatformTransactionManager、TransactionDefinition 和 TransactionTemplate 等核心接口,开发者完全可以通过编程的方式来进行事务管理。 编程式事务方式需要是开发者在代码中手动的管理事务的开启、提交、回滚等操作。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void test() { TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try { // 事务操作 // 事务提交 transactionManager.commit(status); } catch (DataAccessException e) { // 事务回滚 transactionManager.rollback(status); throw e; } } 当然,我们也可以使用Spring中提供的TransactionTemplate来实现编程式事务。 ...

March 22, 2026 · 1 min · santu

Spring中用到了哪些设计模式

典型回答 Spring有着非常优雅的设计,很多地方都遵循SOLID原则,里面的设计模式更是数不胜数。大概有以下几种: 工厂模式 所谓的工厂模式,核心是屏蔽内部的实现,直接由client使用即可。定义可以参考: ✅三种工厂模式的区别和特点 Spring的IOC就是一个非常好的工厂模式的例子。Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。 ✅介绍一下Spring的IOC 组合模式 组合模式在SpringMVC中用的非常多,其中的参数解析,响应值处理等模块就是使用了组合模式。拿参数解析模块举例: 类图如下: 可以发现,整体的参数解析模块中,由一个接口HandlerMethodArgumentResolver负责。其中父节点会实现该接口,同时对所有的具体的子接口进行聚合。 其实这个里面不止用了组合模式,接口还提供了#supportsParamerter方法,去判断是否执行该resolver,这也是策略模式的一种。 适配器模式 适配器模式简而言之就是上游为了适应下游,而要做一些适配,承担适配工作的模块,就叫做适配器。常见的场景是甲方因为话语权很高,提供了一套交互模型,而所有对接甲方模型的乙方,就需要通过适配器模式来适配甲方的模型和自己已有的系统。 在SpringMVC中,HandlerAdapter就是典型的适配器模式。参考其注释我们可以发现: MVC framework SPI, allowing parameterization of the core MVC workflow. Interface that must be implemented for each handler type to handle a request. This interface is used to allow the DispatcherServlet to be indefinitely extensible. The DispatcherServlet accesses all installed handlers through this interface, meaning that it does not contain code specific to any handler type. ...

March 22, 2026 · 3 min · santu

Spring解决循环依赖一定需要三级缓存吗?

典型回答 其实,使用二级缓存也能解决循环依赖的问题,但是如果完全依靠二级缓存解决循环依赖,意味着当我们依赖了一个代理类的时候,就需要在Bean实例化之后完成AOP代理。而在Spring的设计中,为了解耦Bean的初始化和代理,是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理的。 但是,在Spring的初始化过程中,他是不知道哪些Bean可能有循环依赖的,那么,这时候Spring面临两个选择: 不管有没有循环依赖,都提前把代理对象创建出来,并将代理对象缓存起来,出现循环依赖时,其他对象直接就可以取到代理对象并注入。 不提前创建代理对象,在出现循环依赖时,再生成代理对象。这样在没有循环依赖的情况下,Bean就可以按着Spring设计原则的步骤来创建。 第一个方案看上去比较简单,只需要二级缓存就可以了。但是他也意味着,Spring需要在所有的bean的创建过程中就要先生成代理对象再初始化;那么这就和spring的aop的设计原则(前文提到的:在Spring的设计中,为了解耦Bean的初始化和代理,是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理的)是相悖的。 而Spring为了不破坏AOP的代理设计原则,则引入第三级缓存,在三级缓存中保存对象工厂,因为通过对象工厂我们可以在想要创建对象的时候直接获取对象。有了它,在后续发生循环依赖时,如果依赖的Bean被AOP代理,那么通过这个工厂获取到的就是代理后的对象,如果没有被AOP代理,那么这个工厂获取到的就是实例化的真实对象。 扩展知识 其实,只用二级缓存,也是可以解决循环依赖的问题的,如下图,没有三级缓存,只有二级缓存的话,也是可以解决循环依赖的。 那么,为什么还需要引入三级缓存呢?我们看下两个过程的区别主要是什么呢? 我给大家标出来了,如果使用三级缓存,在实例化之后,初始化之前,向三级缓存中保存的是ObjectFactory。而如果使用二级缓存,那么在这个步骤中保存的就是具体的Object。 这里如果我们只用二级缓存,对于普通对象的循环依赖问题是都可以正常解决的,但是如果是代理对象的话就麻烦多了,并且AOP又是Spring中很重要的一个特性,代理又不能忽略。 我们都知道,我们是可以在一个ServiceA中注入另外一个ServiceB的代理对象的,那么在解决循环依赖过程中,如果需要注入ServiceB的代理对象,就需要把ServiceB的代理对象创建出来,但是这时候还只是ServiceB的实例化阶段,代理对象的创建要等到初始化之后,在后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理的。 那怎么办好呢?Spring想到了一个好的办法,那就是使用三级缓存,并且在这个三级缓存中,并没有存入一个实例化的对象,而是存入了一个匿名类ObjectFactory(其实本质是一个函数式接口() -> getEarlyBeanReference(beanName, mbd, bean)),具体代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { .... // 如果允许循环引用,且beanName对应的单例bean正在创建中,则早期暴露该单例bean,以便解决潜在的循环引用问题 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 向singletonFactories添加该beanName及其对应的提前引用对象,以便解决潜在的循环引用问题 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ... } }

March 22, 2026 · 1 min · santu

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

典型回答 默认支不支持要看版本,在SpringBoot 2.6以前,是默认支持的,但是在 SpringBoot 2.6 开始,默认已经不开启对循环依赖的支持了。 在SpringBoot 2.6及以后版本中,如果代码中出现Spring的Bean的循环依赖,启动会报错,如以下是我的数藏项目中关于出现循环依赖时的报错: 提示是有一个循环依赖的问题,即 PayApplicationService -> PayChannelServiceFactory -> MockPayChannelService -> PayApplicationService 也就是说,Spring虽然引入了三级缓存来解决循环依赖,但是Spring依然认为循环依赖时不合理的,所以他默认关闭了对循环依赖的支持。 ✅三级缓存是如何解决循环依赖的问题的? 如果想要开启对循环依赖的支持,有以下几种办法: 1、在配置文件中加入spring.main.allow-circular-references=true 2、用@Lazy 注解,在@Autowired 地方增加即可。 1 2 3 @Autowired @Lazy private PayChannelServiceFactory payChannelServiceFactory; ✅@Lazy注解能解决循环依赖吗?

March 22, 2026 · 1 min · santu

三级缓存是如何解决循环依赖的问题的?

典型回答 Spring中Bean的创建过程其实可以分成两步,第一步叫做实例化,第二步叫做初始化。 实例化的过程只需要调用构造函数把对象创建出来并给他分配内存空间,而初始化则是给对象的属性进行赋值。 而Spring之所以可以解决循环依赖就是因为对象的初始化是可以延后的,也就是说,当我创建一个Bean ServiceA的时候,会先把这个对象实例化出来,然后再初始化其中的serviceB属性。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class ServiceA{ @Autowired private ServiceB serviceB; } @Service public class ServiceB{ @Autowired private ServiceA serviceA; } 而当一个对象只进行了实例化,但是还没有进行初始化时,我们称之为半成品对象。所以,所谓半成品对象,其实只是 bean 对象的一个空壳子,还没有进行属性注入和初始化。 当两个Bean在初始化过程中互相依赖的时候,如初始化A发现他依赖了B,继续去初始化B,发现他又依赖了A,那这时候怎么办呢?大致流程如下: 通过以上方式,就通过引入三级缓存,解决了循环依赖的问题,在上述流程执行完之后,ServiceA和ServiceB都被成功的完成了实例化和初始化。 以下是DefaultSingletonBeanRegistry#getSingleton方法,代码中,包括一级缓存、二级缓存、三级缓存的处理逻辑,该方法是获取bean的单例实例对象的核心方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 首先从一级缓存中获取bean实例对象,如果已经存在,则直接返回 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 如果一级缓存中不存在bean实例对象,而且当前bean正在创建中,则从二级缓存中获取bean实例对象 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 如果二级缓存中也不存在bean实例对象,并且允许提前引用,则需要在锁定一级缓存之前, // 先锁定二级缓存,然后再进行一系列处理 synchronized (this.singletonObjects) { // 进行一系列安全检查后,再次从一级缓存和二级缓存中获取bean实例对象 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 如果二级缓存中也不存在bean实例对象,则从三级缓存中获取bean的ObjectFactory,并创建bean实例对象 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 将创建好的bean实例对象存储到二级缓存中 this.earlySingletonObjects.put(beanName, singletonObject); // 从三级缓存中移除bean的ObjectFactory this.singletonFactories.remove(beanName); } } } } } } return singletonObject; } 扩展知识 ✅Spring解决循环依赖一定需要三级缓存吗? ...

March 22, 2026 · 1 min · santu

为什么SpringBoot 3中移除了spring.factories

典型回答 在SpringBoot 2.7中,官方已经明确的说了,使用spring.factories这种自动配置的方式已经过时了,并且将在SpringBoot 3.0中彻底移除。 官方文档以及网上有很多资料也提到了,可以使用org.springframework.boot.autoconfigure.AutoConfiguration.imports文件替代。具体参考: ✅如何自定义一个starter? 但是我并没有找到有人说为啥要这么做!!! 但是Spring这么做肯定不是无缘无故的吧,总要有理由啊。虽然官方没说,网上也没人写,那我就尝试着分析一下。 在Spring开始提出要废弃spring.factories文件的时候,有且仅有这样一句话: Using spring.factories to find auto-configuration classes is causing problems with our native work. We initially used it because the code was already available, but we want to offer an alternative in 2.7 and stop using spring.factories for auto-configuration in 3.0 这句话我很早就看到了,但是一直没明白是啥意思,但是当SpringBoot 3.0提出来之后我知道了,一切都是为了云原生!(也就是上面这段英文中提到的native,我以前以为他是"本地"的意思,就一直不理解。原来是cloud native中的native) 下面这篇文章中我介绍过SpringBoot 3.0中新增了很多对云原生的支持。 ✅Spring 6.0和SpringBoot 3.0有什么新特性? 我们知道,云原生时代中,最重要的是什么?启动速度。那Java如何提升的启动速度?AOT编译+云原生镜像 所以,SpringBoot 3.0中支持基于 GraalVM 将 Spring 应用程序编译成原生镜像。重点是编译,所以这个动作需要在编译期进行。 可是,传统方法使用spring.factories依赖于运行时扫描和加载自动配置类,这这么做效率肯定不高。不适合云原生! 相比之下,使用org.springframework.boot.autoconfigure.AutoConfiguration.imports这种方式,允许在编译时确定自动配置类,减少了运行时开销,并使得像GraalVM这样的工具更容易分析和编译Spring Boot应用到原生映像。这种方法可以带来更快的启动时间和更低的内存消耗,这对于从可扩展性和效率受益的云原生应用至关重要。 ...

March 22, 2026 · 1 min · santu

什么是MVC

典型回答 MVC是指Model-View-Controller,是一种软件设计模式,它将应用程序分为三个部分:模型、视图和控制器。这个模式的目的是将应用程序的表示(视图)与处理(控制器)分开,以及将应用程序的数据和业务逻辑(模型)与表示和处理分开。 ✅请简述MVC模式的思想 具体来说,MVC模式的思想如下: 模型(Model):表示应用程序的核心业务逻辑和数据。模型通常包含一些数据结构和逻辑规则,用于处理数据的输入、输出、更新和存储等操作。模型并不关心数据的显示或用户的交互方式,它只关注数据本身以及对数据的操作。 视图(View):表示应用程序的用户界面,用于显示模型中的数据。视图通常包含一些控件和元素,用于展示数据,并且可以与用户进行交互。视图并不关心数据的处理或存储方式,它只关注如何呈现数据以及如何与用户进行交互。 控制器(Controller):表示应用程序的处理逻辑,用于控制视图和模型之间的交互。控制器通常包含一些事件处理和动作触发等操作,用于响应用户的输入或视图的变化,并对模型进行操作。控制器通过将用户的输入转化为对模型的操作,从而实现了视图和模型之间的解耦。 MVC模式的核心思想是将应用程序的表示和处理分离开来,从而使得应用程序更加灵活、易于维护和扩展。这种模式可以提高代码的可读性和可维护性,同时也可以促进代码的复用和分工,使得多人协作开发变得更加容易。 扩展知识 MVC和三层架构 ✅MVC和三层架构有什么区别?

March 22, 2026 · 1 min · santu

什么是Spring的循环依赖问题?

典型回答 在Spring框架中,循环依赖是指两个或多个bean之间相互依赖,形成了一个循环引用的情况。如果不加以处理,这种情况会导致应用程序启动失败。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Service public class ServiceA{ @Autowired private ServiceB serviceB; } @Service public class ServiceB{ @Autowired private ServiceA serviceA; } 如以上情况,两个Bean就发生了互相依赖。 在Spring中,解决循环依赖的方式就是引入了三级缓存。 ✅什么是Spring的三级缓存 但是,Spring解决循环依赖是有一定限制的: 首先就是要求互相依赖的Bean必须要是单例的Bean 另外就是依赖注入的方式不能都是构造函数注入的方式 扩展知识 为什么只支持单例 Spring循环依赖的解决方案主要是通过对象的提前暴露来实现的。当一个对象在创建过程中需要引用到另一个正在创建的对象时,Spring会先提前暴露一个尚未完全初始化的对象实例,以解决循环依赖的问题。这个尚未完全初始化的对象实例就是半成品对象。 在 Spring 容器中,单例对象的创建和初始化只会发生一次,并且在容器启动时就完成了。这意味着,在容器运行期间,单例对象的依赖关系不会发生变化。因此,可以通过提前暴露半成品对象的方式来解决循环依赖的问题。 相比之下,原型对象的创建和初始化可以发生多次,并且可能在容器运行期间动态地发生变化。因此,对于原型对象,提前暴露半成品对象并不能解决循环依赖的问题,因为在后续的创建过程中,可能会涉及到不同的原型对象实例,无法像单例对象那样缓存并复用半成品对象。 ...

March 22, 2026 · 1 min · santu

介绍一下Spring的AOP

典型回答 AOP(Aspect-Oriented Programming),即面向切面编程,用人话说就是把公共的逻辑抽出来,让开发者可以更专注于业务逻辑开发。 和IOC一样,AOP也指的是一种思想。AOP思想是OOP(Object-Oriented Programming)的补充。OOP是面向类和对象的,但是AOP则是面向不同切面的。一个切面可以横跨多个类和对象去操作,极大的丰富了开发者的使用方式,提高了开发效率。 譬如,一个订单的创建,可能需要以下步骤: 权限校验 事务管理 创建订单 日志打印 如果使用AOP思想,我们就可以把这四步当成四个“切面”,让业务人员专注开发第三个切面,其他三个切面则是基础的通用逻辑,统一交给AOP封装和管理。 Spring AOP有如下概念(列举下,不用刻意记): 术语 翻译 释义 Aspect 切面 切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。 JoinPoint 连接点 连接点是程序在运行时的执行点,这个点可以是正在执行的方法,或者是正在抛出的异常。因为Spring只支持方法类型的连接点,所以在Spring中连接点就是运行时刻被拦截到的方法。连接点由两个信息确定:+ 方法(表示程序执行点,即在哪个目标方法)+ 相对点(表示方位,即目标方法的什么位置,比如调用前,后等) PointCut 切入点 切入点是对连接点进行拦截的条件定义,决定通知应该作用于截哪些方法。(充当where角色,即在哪里做) Advice 通知 通知定义了通过切入点拦截后,应该在连接点做什么,是切面的具体行为。(充当what角色,即做什么) Target 目标对象 目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。 Weaving 织入 织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。 对于通知类型来说: Before Advice 连接点执行前执行的逻辑 After returning advice 连接点正常执行(未抛出异常)后执行的逻辑 After throwing advice 连接点抛出异常后执行的逻辑 After finally advice 无论连接点是正常执行还是抛出异常,在连接点执行完毕后执行的逻辑 Around advice 该通知可以非常灵活的在方法调用前后执行特定的逻辑 来一段代码示例: 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 import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; @Aspect @Component public class LoggingAspect { // 定义切点(匹配 service 包下所有方法) @Pointcut("execution(* com.hollis.service.*.*(..))") public void serviceMethods() {} // 前置通知 @Before("serviceMethods()") public void beforeAdvice() { System.out.println("Before method execution..."); } // 后置通知 @AfterReturning("serviceMethods()") public void afterReturningAdvice() { System.out.println("After method execution..."); } // 环绕通知 @Around("serviceMethods()") public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable { System.out.println("Around before..."); Object result = pjp.proceed(); // 执行目标方法 System.out.println("Around after..."); return result; } } 扩展知识 AOP是如何实现的? Spring 提供两种实现方式: ...

March 22, 2026 · 4 min · santu

介绍一下Spring的IOC

典型回答 所谓的IOC(inversion of control),就是控制反转的意思。何为控制反转? 在传统的程序设计中,应用程序代码通常控制着对象的创建和管理。例如,一个对象需要依赖其他对象,那么它会直接new出来对象。这样的设计通常被称为 “控制流程”。 而在IOC 中,控制关系发生了反转。控制权被转移到Spring容器中,容器负责创建和管理对象,并在需要的时候将它们注入到应用程序中。 所以,原来这个对象的控制权在我们的代码中,我们自己new的对象,在Spring中,应用程序不再控制对象的创建,而是被动地接受由容器注入的对象。 我们拿代码来举个例子: 下面是一个没有IOC的例子 1 2 3 4 5 6 7 8 9 10 11 class A {} class B { // B需要将A的实例new出来,也就是我们说的控制 private A a = new A(); public void use() { System.out.print(a); } } 当有了IOC之后 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component // 说明A自己控制自己,把自己初始化出来,注入给了容器 class A {} class B { // B不需要控制a,直接使用。如果A没有把自己注入给容器,B就不能使用 @Resource private A a; public void use() { System.out.print(a); } } 也就是说,没有Spring的话,我们要使用的对象,需要我们自己创建,而有了Spring的IOC之后,对象由IOC容器创建并管理,我们只需要在想要使用的时候从容器中获取就行了。 ...

March 22, 2026 · 1 min · santu

留言给博主