目录
- 1、什么是Spring
- 2、说一下Spring的IOC
- 3、Spring的AOP
- 4、连接点?切入点?
- 5、Spring AOP 是通过什么实现的
- 6、Spring Bean的生命周期是怎么样的?
- 7、Spring Bean的初始化过程是怎么样
- 8、Spring的事务传播机制有哪些?
- 9、Autowired和Resource的区别
- 10、Spring中如何开启事务?
- 编程式事务管理
- 声明式事务管理
- 11、Spring中用到了哪些设计模式
- 12、什么是Spring的循环依赖问题?
- 13、Spring事务失效可能是哪些原因?
- 14、什么是Spring的三级缓存?如何解决循环依赖的问题的?
1、什么是Spring
Spring 是一个开源的Java应用程序框架,由Rod Johnson创建并在2003年发布,旨在简化企业级Java应用的开发。Spring以其轻量级、模块化和基于依赖注入(Dependency Injection, DI)的设计原则而闻名,它通过提供一套全面的解决方案,可以帮助开发者更容易地构建、测试、部署和管理Java应用程序。
Spring框架的核心特性包括:
-
核心容器(Core Container): 这是Spring框架的基础,包含了BeanFactory,ApplicationContext等组件,用于创建、配置和管理Java对象(Bean)及其依赖关系。
-
依赖注入(DI): Spring通过DI机制管理对象的依赖关系,无需对象自己去查找或创建它所依赖的对象。开发者只需要声明依赖关系,Spring容器会在运行时自动注入。
-
面向切面编程(AOP): Spring提供了面向切面编程的支持,允许开发者将横切关注点(如事务管理、日志记录、权限检查等)与业务逻辑解耦,通过切面织入实现模块化的关注点分离。
-
数据访问/集成(Data Access/Integration): 包括对JDBC、ORM框架(如Hibernate、JPA)的支持,以及事务管理抽象,简化了数据访问层的开发。
-
Web框架(Spring MVC): 提供了一个完整的MVC框架,用于构建web应用程序,支持RESTful服务的开发,并与前端技术良好集成。
-
消息传递(Messaging): 支持消息队列(如RabbitMQ、JMS)集成,便于构建分布式系统间的通信。
-
测试支持(Testing): 提供了一系列辅助工具,简化了对基于Spring的应用程序的单元测试和集成测试。
-
Spring Boot: 是Spring家族的一员,进一步简化了新Spring应用的初始搭建以及开发过程,具有自动化配置、嵌入式服务器和健康检查等功能。
Spring框架因其高度的灵活性和可扩展性,已经成为现代Java企业级应用开发的事实标准之一。通过整合众多优秀开源项目和提供统一的编程和配置模型,Spring极大地提高了开发效率和代码质量。
2、说一下Spring的IOC
Spring框架中的IOC(Inversion of Control,控制反转)是其核心机制之一,它是一种设计原则,主要应用于软件的设计和开发中,用来降低模块之间的耦合度,增强程序的可扩展性和可维护性。在Spring框架的具体实现中,IOC容器扮演着至关重要的角色。
基本概念:
- 控制反转:在传统的编程方式中,一个类往往需要自行创建它所依赖的对象。而在IOC中,这种创建和管理依赖对象的责任从应用程序本身转移到了一个专门的容器,即Spring的IOC容器。这意味着对象不再自己管理它们的依赖关系,而是由容器在运行时动态地为其注入所需要的依赖。
IOC容器的主要功能:
-
对象的生命周期管理:IOC容器负责创建对象实例,管理它们的生命周期,包括初始化、依赖注入、清理等阶段。
-
依赖注入(Dependency Injection, DI):IOC容器根据配置信息,负责将依赖关系传递给对象。注入方式包括构造器注入、setter注入或字段注入等。
-
松耦合:通过依赖注入,对象不再关心其依赖对象的创建细节,只需定义好接口,降低了不同模块之间的耦合度,使得代码更加模块化和可测试。
IOC容器的工作原理:
- 配置:开发者通过XML、Java注解或者Java配置类等方式定义Bean(即构成应用程序的各种对象)以及它们的依赖关系。
- 解析与注册:Spring IOC容器在启动时会读取这些配置信息,解析其中的Bean定义,并将Bean注册到容器中。
- 依赖注入:当应用程序请求某个Bean时,IOC容器不仅会创建这个Bean,还会根据配置找到它依赖的所有其他Bean,并将它们注入进来,建立起对象间的依赖关系。
总结来说,Spring的IOC机制实现了应用程序组件之间的解耦,使得程序组件只需要关注自身的业务逻辑,而不需要关心彼此之间的依赖是如何管理和创建的,极大地提升了代码的复用性和系统的可维护性。
3、Spring的AOP
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)是一个强大的模块,它允许开发者将横切关注点(Cross-cutting Concerns)从业务逻辑中分离出来,并以声明式的方式集中处理。横切关注点是指那些跨越多个对象或类的方法,如日志记录、事务管理、权限检查、性能监控等,这些功能在整个应用程序中往往是重复出现且与业务逻辑紧密相关的,但在传统OOP(面向对象编程)中很难做到模块化和复用。
在Spring AOP中,主要有以下几个核心概念:
-
Aspect(切面):一个切面表示一个关注点的模块化,它包含advice(通知)和pointcut(切入点)。切面可以被看作是对系统行为进行修改或增强的模块。
-
Advice(通知):通知是切面的具体实现,是在某个连接点(Join Point)执行的代码片段。通知有多种类型,包括前置通知(Before advice)、后置通知(After returning advice)、最终通知(After (finally) advice)、异常通知(After throwing advice)和环绕通知(Around advice)等。例如,前置通知就是在目标方法执行前执行的代码,环绕通知则可以完整地控制目标方法的执行过程。
-
Pointcut(切入点):切入点定义了通知应该在哪种连接点上执行,它是一个或多个连接点的集合。连接点通常是方法执行这样的程序执行点。通过表达式(如AspectJ切点表达式)来匹配感兴趣的执行点。
-
连接点(Join Point):在Spring AOP中,连接点通常指的是方法执行的时刻,它是程序执行过程中明确的点,可以插入自定义的行为(即通知)。
-
代理(Proxying):Spring AOP通过生成代理对象来实现切面的编织(Weaving),即在代理对象的方法调用过程中,根据切面定义执行相应的通知。
使用Spring AOP,开发者可以编写简洁、清晰的切面类或注解,然后由Spring容器在运行时自动将这些切面织入到目标对象中,从而达到在不影响原有业务逻辑的前提下,实现横切关注点的统一处理和模块化管理的目的。这样既简化了代码,又增强了系统的可维护性和可扩展性。
4、连接点?切入点?
当然可以,通过一个简单的例子来帮助您理解Spring AOP中的连接点(Join Point)和切入点(Pointcut)的区别:
连接点(Join Point):
想象一下,你正在开发一个在线购物系统,其中包含了各种各样的类和方法,比如OrderService
类中的placeOrder()
方法用于处理订单,UserService
类中的authenticateUser()
方法用于用户身份验证,以及PaymentService
类中的processPayment()
方法用于处理支付操作等。在Spring AOP的上下文中,每一个方法调用就是一个潜在的连接点,因为每个方法都可以是应用AOP增强的地方。
例如:
OrderService.placeOrder()
UserService.authenticateUser()
PaymentService.processPayment()
切入点(Pointcut):
而切入点则是对这些众多连接点的一个特定选择或定义,它用来指定哪些连接点应该被特定的通知(Advice)所影响。例如,你可能想在所有的业务服务层方法执行前后都记录日志,这时你可以定义一个切入点,表达式可能是 "execution(* com.example.service..*(..))"
,这个表达式会匹配所有在com.example.service
及其子包下的任何方法调用。
所以,如果我们的切面(Aspect)有一个日志记录的前置通知(Before advice),并且将其关联到上述切入点,则每当OrderService
、UserService
或PaymentService
中的任何方法被调用时,日志记录的代码就会被执行。这里的切入点就是对所有服务层方法调用这一组连接点的选择器。
5、Spring AOP 是通过什么实现的
Spring AOP 是通过代理模式实现的;
Spring AOP实现动态代理的两种方式:
-
JDK动态代理:
- 当目标类至少实现了一个接口时,Spring AOP会选择使用Java的内置
java.lang.reflect.Proxy
类来创建代理对象。这种方式下,Spring会生成一个实现了目标类所实现的所有接口的代理类,然后在代理类的方法调用时插入切面逻辑(通知)。
- 当目标类至少实现了一个接口时,Spring AOP会选择使用Java的内置
-
CGLIB代理:
-
当目标类没有实现任何接口时,Spring AOP会转而使用CGLIB库来生成一个继承自目标类的子类作为代理。CGLIB通过生成字节码技术为类创建子类,并覆盖其中的方法,在方法的前后插入切面逻辑。
-
CglibAopProxy是Spring框架中用于创建CGLIB代理的类,它同样实现了Spring的AopProxy接口,但它的getProxy方法会创建一个CGLIB代理对象,而非JDK动态代理对象。
-
这两种代理方式都是为了在不改变原始业务类代码的基础上,通过代理机制在目标方法执行前后添加额外的功能(例如事务管理、日志记录、权限检查等)。Spring框架根据目标类的特点智能选择合适的代理方式,确保AOP功能的透明实现。
6、Spring Bean的生命周期是怎么样的?
Spring框架管理的Bean(也就是Java对象)有一套完整的生命周期,涵盖了从创建到销毁的全过程。以下是Spring Bean的生命周期各阶段概述:
-
实例化(Instantiation)
- Spring IoC容器通过反射调用类的无参构造函数创建Bean的实例。
-
填充属性(Populate Properties)
- Spring容器利用BeanDefinition中的信息,通过setter方法或者构造器注入的方式为Bean实例设置属性值,即依赖注入(Dependency Injection)。
-
初始化(Initialization)
- 初始化阶段涉及多个步骤:
- 初始化前处理(Post-Process Before Initialization)
InstantiationAwareBeanPostProcessor
的postProcessBeforeInitialization
方法会被调用。
- 初始化方法调用(Invocation of init-method)
- 如果Bean定义中指定了
init-method
属性,那么Spring会在Bean实例化并填充属性之后调用该方法。
- 如果Bean定义中指定了
- 初始化后处理(Post-Process After Initialization)
InitializingBean
接口的afterPropertiesSet
方法(如果Bean实现了该接口)会被调用。SmartInitializingSingleton
接口的方法(如果Bean实现了该接口)在所有Singletons都初始化完成后被调用。BeanPostProcessor
的postProcessAfterInitialization
方法也会在此阶段被执行。
- 初始化前处理(Post-Process Before Initialization)
- 初始化阶段涉及多个步骤:
-
使用(Usage)
- 完成初始化后,Bean就可以被Spring容器或其他Bean通过ApplicationContext获取并使用了。
-
销毁(Destruction)
- 在Spring容器关闭时,Bean会经历销毁阶段:
- 预销毁处理(Pre-Destruction Callback)
- 如果Bean实现了
DisposableBean
接口,其destroy
方法会被调用。
- 如果Bean实现了
- 指定销毁方法调用(Invocation of destroy-method)
- 如果Bean定义中指定了
destroy-method
属性,则调用此方法来释放资源。
- 如果Bean定义中指定了
- 销毁后处理(Post-Destruction Callbacks)
- 虽然不在标准的生命周期内明确指出,但Spring容器可能还有其他的回调机制来清理资源。
- 预销毁处理(Pre-Destruction Callback)
- 在Spring容器关闭时,Bean会经历销毁阶段:
总结来说,Spring Bean的生命周期是一个有序的过程,允许开发者在特定的点上通过自定义方法来介入Bean的创建和销毁流程,增强了程序的灵活性和可控制性。在整个过程中,Spring容器负责管理和协调这些活动,确保Bean的生命周期行为符合预期。
7、Spring Bean的初始化过程是怎么样
Spring Bean的初始化过程可以分为以下几个步骤:
-
实例化(Instantiation)
- Spring IoC容器根据BeanDefinition信息创建Bean的实例,通常通过调用无参构造函数来实例化对象。
-
设置属性值(Population of Properties)
- Spring容器接下来会根据BeanDefinition中定义的属性注入规则,为已实例化的Bean设置属性值,包括通过构造函数注入、setter方法注入或者其他自定义注入器实现的依赖注入。
-
初始化前回调(Callback before Initialization)
- 如果Bean实现了
org.springframework.beans.factory.config.BeanPostProcessor
接口的子类,那么在其初始化之前,Spring会调用postProcessBeforeInitialization(Object bean, String beanName)
方法对Bean进行预处理。
- 如果Bean实现了
-
初始化方法调用(Invocation of Init Method)
- 如果在Bean的定义中指定了
init-method
属性,Spring会在属性设置完成后调用这个方法来初始化Bean的内容。另外,如果Bean实现了org.springframework.beans.factory.InitializingBean
接口,那么Spring会调用其afterPropertiesSet()
方法进行初始化。
- 如果在Bean的定义中指定了
-
初始化后回调(Callback after Initialization)
- 初始化方法执行完毕后,Spring再次调用
BeanPostProcessor
接口的postProcessAfterInitialization(Object bean, String beanName)
方法,允许对初始化后的Bean做进一步的处理。
- 初始化方法执行完毕后,Spring再次调用
总的来说,Spring Bean的初始化过程确保了Bean具备了运行所需的状态,并完成了所有必要的初始化逻辑,之后该Bean便可以被应用程序正常地使用了。在Bean的生命周期中,初始化是非常关键的一个阶段,它使Bean达到了可以使用的准备状态。后续,Bean还会有注册销毁回调方法、正常使用直至最终销毁的过程。
实例化与初始化更详细地说:
实例化(Instantiation):
在Spring框架中,实例化是指Spring IoC容器负责通过反射调用对应的构造函数来创建Bean对象的过程。根据配置信息(如XML配置文件、注解或Java配置类),容器会选择合适的构造器参数并传递给构造函数,从而在Java堆内存中分配空间并生成Bean实例。
初始化(Initialization):
初始化发生在实例化之后,它是对新创建的Bean对象进一步设置和准备以供应用使用的阶段。在Spring中,初始化包括但不限于以下几个步骤:
- 属性填充(Population):Spring容器会通过依赖注入(Dependency Injection)的方式,将配置好的属性值注入到Bean的相应字段或setter方法中,这一过程通常对应
populateBean
方法的执行。 - 初始化方法调用:如果Bean定义中指定了初始化方法(如通过
@PostConstruct
注解标识的方法),Spring会在Bean的所有必要依赖都注入完成后调用这些方法,确保Bean已经准备好执行其业务逻辑。 - Bean后处理器(Bean Post Processor)的应用:Spring容器还允许Bean后处理器参与到Bean的初始化流程中,它们可以在初始化前后进行额外的处理工作,比如通过
afterPropertiesSet
方法进行自定义初始化操作。
总之,在Spring容器管理Bean的生命周期中,实例化关注的是如何创建对象本身,而初始化则涉及到了对象创建后的状态设置和准备工作,确保Bean处于可使用的状态。
8、Spring的事务传播机制有哪些?
Spring的事务传播机制用于控制在多个事务方法相互调用时事务的行为。
在复杂的业务场景中,多个事务方法之间的调用可能会导致事务的不一致,如出现数据丢失、重复提交等问题,使用事务传播机制可以避免这些问题的发生,保证事务的一致性和完整性。
Spring框架提供了七种事务传播机制,用于控制事务方法在不同场景下的事务行为,特别是当事务方法互相调用时,如何管理和传播事务上下文。以下是Spring事务传播机制的详细介绍:
事务传播机制 | 描述 |
---|---|
Required | 如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。 |
Supports | 如果当前存在事务,则参与当前事务;若无事务,则不创建新事务,按非事务方式执行。 |
Mandatory | 必须在现有事务中执行,若当前无事务,则抛出异常。 |
Requires_New | 总是新建一个事务,如果当前存在事务,则挂起当前事务。新建的事务与外部事务相互独立。 |
Not_Supported | 执行时不支持事务,如果当前存在事务,则挂起当前事务直到方法执行完毕。 |
Never | 不允许在事务环境中执行,如果当前存在事务,则抛出异常。 |
Nested | 如果当前存在事务,则在嵌套事务内执行。嵌套事务可以单独提交或回滚,但外部事务失败时,所有嵌套事务都将被回滚。 |
通过在Spring中为方法注解@Transactional(propagation = Propagation.{类型})
的方式来指定事务传播行为。其中,{类型}
应替换为上述表格中的具体传播机制名称。
9、Autowired和Resource的区别
@Autowired
和 @Resource
都是用来在Spring框架中进行依赖注入的注解,但它们有一些关键的区别:
-
来源和标准:
@Autowired
是Spring框架自身提供的注解,位于org.springframework.beans.factory.annotation.Autowired
包下。@Resource
并非Spring独有,而是由Java EE规范引入的注解,它属于J2EE(现在称为Java EE)的一部分,位于javax.annotation.Resource
包下。尽管如此,Spring框架仍然支持这个注解的依赖注入功能。
-
注入策略:
@Autowired
默认按照类型(byType)进行依赖注入,也就是说,Spring容器会查找与字段或方法参数类型匹配的Bean进行注入。如果同一类型的Bean有多个候选者并且都没有明确指定Qualifier注解,那么在某些情况下可能会导致歧义,这时需要显式添加@Qualifier
注解来指定具体的Bean。@Resource
在Spring中默认按照名称(byName)进行注入,即尝试寻找与字段名或setter方法名相匹配的Bean名称进行注入。不过,当找不到与名称匹配的Bean时,@Resource
也会退而采用类型匹配的方式进行注入。
-
名称解析优先级:
@Autowired
注解在考虑名称匹配时通常需要配合@Qualifier
注解。@Resource
更倾向于名称匹配,因此在Bean名称具有唯一性的情况下,它可以简化代码,因为无需额外的@Qualifier
注解。
-
注解位置:
- 两者都可以用于字段、构造器以及setter方法上。
-
编译器支持和IDE提示:
- 对于IDEA等现代IDE而言,对于
@Autowired
和@Resource
的提示和支持可能有所不同,@Resource
由于其标准性质,可能会得到更好的跨框架支持和工具提示。
- 对于IDEA等现代IDE而言,对于
综上所述,选择使用哪一个注解主要取决于项目的具体需求和团队约定。在Spring项目中,@Autowired
使用更为普遍,但在需要强调基于名称注入或者遵循Java EE规范的项目中,@Resource
可能是一个更好的选择。同时需要注意的是,为了能够使用@Resource
,还需要确保在Spring配置中正确导入了相应的注解支持。
10、Spring中如何开启事务?
在Spring框架中开启事务主要有两种方式:编程式事务管理和声明式事务管理。
编程式事务管理
编程式事务管理要求开发人员在代码中手动管理事务的边界,包括开启、提交或回滚事务。使用编程式事务,你需要直接操作PlatformTransactionManager
接口的实现类。例如,通过TransactionTemplate
或直接调用TransactionManager
的beginTransaction()
、commit()
和rollback()
方法来控制事务。
@Autowired
private PlatformTransactionManager transactionManager;
public void someServiceMethod() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 执行业务操作
...
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
声明式事务管理
声明式事务管理则是通过在业务逻辑方法或类上添加@Transactional
注解来启用事务,Spring框架会自动根据注解的属性来管理事务的生命周期。
@Service
public class SomeService {
@Transactional
public void someTransactionalMethod() {
// 这个方法将在一个事务中执行
// 如果方法体内的代码出现未捕获的异常,事务将自动回滚
// 否则,事务将在方法结束时自动提交
...
}
}
在Spring Boot应用中,声明式事务管理的开启更为便捷,通常只需要以下几步:
- 配置数据源和事务管理器,Spring Boot通常会自动配置
DataSourceTransactionManager
或JpaTransactionManager
作为事务管理器。 - 在服务类或需要进行事务控制的方法上使用
@Transactional
注解。 - 确保Spring事务注解驱动已启用,通常在Spring Boot应用中,只要包含
spring-tx
和spring-orm
等相关的起步依赖,以及正确的组件扫描配置,就会自动启用声明式事务的支持。
注意,为了事务生效,被@Transactional
注解修饰的类必须是由Spring容器管理的Bean。此外,Spring AOP代理负责处理@Transactional
注解,所以对于类内部方法调用事务可能不会生效,因为它绕过了AOP代理。如果需要在同一个类内部的方法调用之间也保持事务,可能需要调整设计或者明确通过代理对象来调用事务方法。
11、Spring中用到了哪些设计模式
Spring框架中广泛运用了许多设计模式,这里列举出Spring中常用的设计模式及其应用场景:
-
单例模式(Singleton Pattern)
- Spring框架中的IoC容器默认将所有的Bean配置为单例模式,确保在整个应用程序中,每个Bean只有一个实例。
-
工厂模式(Factory Pattern)
- Spring通过
BeanFactory
和ApplicationContext
实现了工厂模式,它们负责创建和管理Bean对象,降低了对象间的耦合度。
- Spring通过
-
工厂方法模式(Factory Method Pattern)
- 在Spring中,Bean的创建可以通过配置元数据(如XML配置或注解)指定,类似于工厂方法模式,可以根据不同条件创建不同类型的对象。
-
抽象工厂模式(Abstract Factory Pattern)
- Spring通过一系列的子接口和实现类组成,可以根据配置创建不同类型的Bean集合,体现出抽象工厂模式的思想。
-
代理模式(Proxy Pattern)
- Spring AOP(面向切面编程)通过动态代理(JDK代理或CGLIB代理)实现切面的织入,使得代理对象可以在方法调用前后执行增强逻辑。
-
适配器模式(Adapter Pattern)
- 在Spring AOP和Spring MVC中,通过适配器模式转换接口,使得不同的类能够协同工作。例如,Spring AOP的通知(Advice)可以被适配到目标对象上,Spring MVC中控制器的适配器将HTTP请求映射到Controller方法上。
-
装饰者模式(Decorator Pattern)
- 虽然不是直接体现在Spring框架核心功能上,但在处理Filter链、HandlerInterceptor链等场景时,可以通过装饰者模式的思想来扩展功能。
-
模板方法模式(Template Method Pattern)
- 如Spring的
JdbcTemplate
类,它提供了一个执行SQL查询的基本结构,子类只需要实现特定的抽象方法来填充实际的SQL语句和处理结果。
- 如Spring的
-
策略模式(Strategy Pattern)
- 在Spring框架中,策略模式可以用在处理不同类型的事务管理策略、数据源切换策略等场景。
-
责任链模式(Chain of Responsibility Pattern)
- Spring的拦截器(Interceptor)和过滤器(Filter)可以形成一个责任链,请求依次经过链上的每一个拦截器或过滤器处理。
-
服务定位器模式(Service Locator Pattern)
- Spring的IoC容器在某种程度上也可以看作是一种服务定位器,客户端通过容器获取所需的Bean,而不是直接new出来。
-
依赖注入(Dependency Injection,DI)
- 虽然不是严格意义上的设计模式,但DI是一种设计原则,通过构造器注入、setter注入等方式实现了对象之间的解耦,体现了控制反转(Inversion of Control, IoC)思想。
这些设计模式在Spring框架中得到了深入应用,帮助构建了一个高度模块化、松耦合和易扩展的系统架构。
12、什么是Spring的循环依赖问题?
Spring的循环依赖问题是指在Spring框架的依赖注入过程中,两个或多个Bean之间相互依赖,形成了一个闭环引用的情况。具体来说,当Bean A依赖于Bean B,而Bean B反过来又依赖于Bean A,或者通过多条依赖链形成闭环(例如A->B->C->A),这样就会导致Spring容器在初始化这些Bean时陷入困境,因为每个Bean在完成初始化前都需要等待依赖的Bean被初始化,而依赖的Bean又在等待它本身的初始化完成,这就形成了循环依赖。
Spring框架对循环依赖的处理能力是有一定限制的,具体表现在以下几点:
-
构造器循环依赖:
Spring不能解决构造器注入导致的循环依赖。如果Bean A和Bean B在构造器中互相引用,Spring容器在创建Bean时发现循环依赖,将会抛出BeanCurrentlyInCreationException
异常,表示无法解决循环依赖问题。 -
setter注入或字段注入的循环依赖:
对于单例(Singleton)作用域的Bean,Spring通过三级缓存机制巧妙地解决了setter或字段注入造成的循环依赖问题。Spring在创建Bean时,会将正在创建但尚未初始化完全的Bean暂存起来,以便其他Bean在依赖注入时能够拿到。通过这种方式,Spring能够在大多数情况下处理好setter注入的循环依赖,即“一、二、二”形式的循环依赖(Bean A依赖于初始化阶段的Bean B,Bean B依赖于已实例化但未初始化完成的Bean A)。
请注意,对于原型(Prototype)作用域的Bean,Spring默认不解决循环依赖问题,同样会抛出异常。另外,如果Bean间依赖关系涉及到异步处理(如使用@Async
注解)或其他特殊场景,也可能无法正确解决循环依赖。
13、Spring事务失效可能是哪些原因?
Spring事务失效的原因多种多样,以下是一些常见的情况:
-
方法未被Spring代理:
- 如果带有
@Transactional
注解的方法所在类没有被Spring容器管理(即没有使用@Service
、@Repository
、@Component
或@Controller
等注解),则Spring不会为其生成代理对象,导致事务失效。
- 如果带有
-
方法可见性问题:
@Transactional
注解的方法必须是public的,如果是private、protected或包访问权限,Spring AOP代理无法对其进行增强,事务功能将不会生效。
-
内部方法调用:
- 在同一个类中,如果一个非
@Transactional
的方法直接调用了同类中带有@Transactional
注解的方法,事务将不会生效,因为Spring的AOP代理在这种情况下不会介入。解决办法是通过外部代理类或接口来进行调用。
- 在同一个类中,如果一个非
-
异常处理不当:
- 如果事务方法内部捕获了异常并且没有再次抛出,Spring事务就不能感知到异常的发生,进而无法触发事务的回滚。只有未被捕获的、符合事务回滚规则的异常才能导致事务回滚。
-
事务传播行为配置错误:
- 不正确的事务传播行为可能导致事务未能正确开启、挂起或加入到现有的事务中。
-
未配置事务管理器:
- 如果没有正确配置事务管理器(如
DataSourceTransactionManager
或JpaTransactionManager
),事务功能将不可用。
- 如果没有正确配置事务管理器(如
-
事务注解无效:
- 如果在方法上未启用
@Transactional
注解,或者注解被误配置(如没有指定正确的rollbackFor或noRollbackFor属性),则事务将不会生效。
- 如果在方法上未启用
-
Spring AOP代理的问题:
- 若方法被final修饰,或者使用CGLIB代理时遇到final类或final方法,由于final方法不能被重写,因此Spring无法对这些方法进行代理增强,事务功能也就无法实现。
-
数据源或连接池配置问题:
- 数据源未配置事务支持,或者连接池不支持事务,也可能导致Spring事务失效。
-
自定义切面干扰:
- 当自定义的切面处理逻辑与Spring事务切面的逻辑发生冲突,如错误地处理了异常或改变了事务状态,可能会导致事务失效。
总之,Spring事务失效通常是由于配置、注解使用不当或对AOP代理机制理解不足所引起的,针对具体失效场景,应当仔细检查以上提及的各种原因并采取相应措施修复。
14、什么是Spring的三级缓存?如何解决循环依赖的问题的?
Spring 框架中的三级缓存是用来解决对象之间的循环依赖问题的,在Spring容器进行依赖注入的过程中发挥作用。以下是三级缓存的详细介绍以及它们如何协同工作来解决循环依赖问题:
-
一级缓存 (singletonObjects):
- 这个是最常用的缓存,也被称作“单例池”。
- 存储已经完成初始化并且可以对外暴露的单例Bean实例。
- 当一个Bean的实例化、依赖注入及初始化全过程都完成后,它会被放入一级缓存中,后续请求同一Bean时,Spring容器直接从一级缓存中返回。
-
二级缓存 (earlySingletonObjects 或 singletonFactories):
- 二级缓存用于存储尚未完成初始化(也就是属性尚未注入)但已经被实例化的Bean。
- 当Spring检测到循环依赖且需要提前暴露Bean实例时,会将已实例化但尚未完成属性注入的Bean放入二级缓存中。
- 对于早期暴露的Bean,二级缓存可能存储实际的半成品对象(earlySingletonObjects),也可能存储能够产生半成品对象的工厂方法(singletonFactories)。
-
三级缓存 (prototypeFactories 或 earlySingletonPrototypeHandlers):
- 在某些特殊场景下,例如原型Bean间的循环依赖,可能需要用到三级缓存。
- 对于原型Bean,由于它们每次都新建实例,所以三级缓存的作用方式与单例Bean不同,主要用于临时保存能快速生成新实例的工厂方法。
解决循环依赖的具体步骤如下:
- 当Spring容器在实例化A时发现A依赖B,它首先尝试从一级缓存查找B是否存在,如果不存在,则继续查找二级缓存。
- 如果在二级缓存中找到了B的早期实例(半成品),则可以直接注入到A中,从而打破了循环依赖。
- 如果二级缓存中也没有B,则Spring开始实例化B,并在实例化B的过程中发现B又依赖A,这时,它将已经实例化但尚未完成初始化的A对象放入二级缓存。
- B接着从二级缓存中拿到A的早期实例,完成自己的实例化和部分依赖注入。
- 最后,当A完成了自身的初始化后,将完整实例放入一级缓存,B也可以顺利完成剩余的初始化过程,并最终进入一级缓存。
通过这样的策略,Spring确保了在单例Bean之间即使存在循环依赖也能安全、有序地进行依赖注入,避免了因循环依赖导致的死锁或其他运行时异常。对于原型Bean,虽然Spring默认不支持循环依赖,但在一些高级场景下可以通过自定义扩展解决。