Spring面试题
1、Spring IOC是什么
Spring
中的org.springframework.beans
包和 org.springframework.context
包构成了 Spring 框架 IoC 容器的基础。BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。ApplicationContex 接口对 BeanFactory(是一个子接口)进行了扩展,在 BeanFactory的基础上添加了其他功能,比如与 Spring 的 AOP 更容易集成,也提供了处理 message resource的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对 Web 应用的WebApplicationContext。org.springframework.beans.factory.BeanFactory
是 Spring IoC 容器的具体实现,用来包装和管理前面提到的各种 bean。BeanFactory 接口是 Spring IoC 容器的核心接口。IOC:把对象的创建、初始化、销毁交给 spring 来管理,而不是由开发者控制,实现控制反转。
2、什么是依赖注入DI
依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件?
在 Java 中依赖注入有以下三种实现方式:
- 构造器注入
- Setter 方法注入
- 接口注入
哪种依赖注入方式你建议使用,构造器注入,还是 Setter 方法注入?
构造器注入和 Setter 方法注入。最好的解决方案是用构造器参数实现强制依赖,setter 方法实现可选依赖。
3、BeanFactory 和 ApplicationContext 有什么区别
BeanFactory 可以理解为含有 bean 集合的工厂类。BeanFactory 包含了种 bean 的定义,以便在接收到客户端请求时将对应的 bean 实例化。
BeanFactory 还能在实例化对象的时生成协作类之间的关系。此举将 bean 自身与 bean 客户端的配置中解放出来。BeanFactory 还包含 了 bean 生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。从表面上看,application context 如同 bean factory 一样具有 bean 定义、bean 关联关系的设置,根据请求分发 bean 的功能。但 applicationcontext 在此基础上还提供了其他的功能。
- 提供了支持国际化的文本消息
- 统一的资源文件读取方式
- 已在监听器中注册的 bean 的事件
以下是几种较常见的 ApplicationContext 实现方式:
ClassPathXmlApplicationContext:从 classpath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中
javaApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
FileSystemXmlApplicationContext :由文件系统中的 XML 配置文件读取上下文。
javaApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文
AnnotationConfigApplicationContext(基于 Java 配置启动容器)
4、 Spring Bean 的生命周期
- Spring 容器从 XML 文件中读取 bean 的定义,并实例化 bean。
- Spring 根据 bean 的定义填充所有的属性。
- 如果 bean 实现了 BeanNameAware 接口,Spring 传递 bean 的 ID 到 setBeanName 方法。
- 如果 Bean 实现了 BeanFactoryAware 接口,Spring 传递 beanfactory 给 setBeanFactory 方法。
- 如果有任何与 bean 相关联的 BeanPostProcessors,Spring 会在postProcesserBeforeInitialization()方法内调用它们。
- 如果 bean 实现 IntializingBean 了,调用它的 afterPropertySet 方法,如果 bean 声明了初始化方法,调用此初始化方法。
- 如果有 BeanPostProcessors 和 bean 关联,这些 bean 的postProcessAfterInitialization()方法将被调用。
- 如果 bean 实现了 DisposableBean,它将调用 destroy()方法。
bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:
- 处理名称,检查缓存
- 处理父子容器
- 处理 dependsOn
- 选择 scope 策略
- 创建 bean
- 类型转换处理
- 销毁 bean
划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情
1. 处理名称,检查缓存
- 这一步会处理别名,将别名解析为实际名称
- 对 FactoryBean 也会特殊处理,如果以 & 开头表示要获取 FactoryBean 本身,否则表示要获取其产品
- 这里针对单例对象会检查一级、二级、三级缓存
- singletonFactories 三级缓存,存放单例工厂对象
- earlySingletonObjects 二级缓存,存放单例工厂的产品对象
- 如果发生循环依赖,产品是代理;无循环依赖,产品是原始对象
- singletonObjects 一级缓存,存放单例成品对象
2. 处理父子容器
- 如果当前容器根据名字找不到这个 bean,此时若父容器存在,则执行父容器的 getBean 流程
- 父子容器的 bean 名称可以重复
3. 处理 dependsOn
- 如果当前 bean 有通过 dependsOn 指定了非显式依赖的 bean,这一步会提前创建这些 dependsOn 的 bean
- 所谓非显式依赖,就是指两个 bean 之间不存在直接依赖关系,但需要控制它们的创建先后顺序
4. 选择 scope 策略
- 对于 singleton scope,首先到单例池去获取 bean,如果有则直接返回,没有再进入创建流程
- 对于 prototype scope,每次都会进入创建流程
- 对于自定义 scope,例如 request,首先到 request 域获取 bean,如果有则直接返回,没有再进入创建流程
5.1 创建 bean - 创建 bean 实例
要点 | 总结 |
---|---|
有自定义 TargetSource 的情况 | 由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回 |
Supplier 方式创建 bean 实例 | 为 Spring 5.0 新增功能,方便编程方式创建 bean 实例 |
FactoryMethod 方式 创建 bean 实例 | ① 分成静态工厂与实例工厂;② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency;③ 如果有多个工厂方法候选者,还要进一步按权重筛选 |
AutowiredAnnotationBeanPostProcessor | ① 优先选择带 @Autowired 注解的构造;② 若有唯一的带参构造,也会入选 |
mbd.getPreferredConstructors | 选择所有公共构造,这些构造之间按权重筛选 |
采用默认构造 | 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的 |
5.2 创建 bean - 依赖注入
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor | 识别 @Autowired 及 @Value 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
CommonAnnotationBeanPostProcessor | 识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
resolveDependency | 用来查找要装配的值,可以识别:① Optional;② ObjectFactory 及 ObjectProvider;③ @Lazy 注解;④ @Value 注解(${ }, #{ }, 类型转换);⑤ 集合类型(Collection,Map,数组等);⑥ 泛型和 @Qualifier(用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义) |
AUTOWIRE_BY_NAME | 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员 |
AUTOWIRE_BY_TYPE | 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues |
applyPropertyValues | 根据 mbd 的 propertyValues 进行依赖注入(即xml中 `<property name ref |
5.3 创建 bean - 初始化
要点 | 总结 |
---|---|
内置 Aware 接口的装配 | 包括 BeanNameAware,BeanFactoryAware 等 |
扩展 Aware 接口的装配 | 由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization |
@PostConstruct | 由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization |
InitializingBean | 通过接口回调执行初始化 |
initMethod | 根据 BeanDefinition 得到的初始化方法执行初始化,即 <bean init-method> 或 @Bean(initMethod) |
创建 aop 代理 | 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization |
5.4 创建 bean - 注册可销毁 bean
在这一步判断并登记可销毁 bean
- 判断依据
- 如果实现了 DisposableBean 或 AutoCloseable 接口,则为可销毁 bean
- 如果自定义了 destroyMethod,则为可销毁 bean
- 如果采用 @Bean 没有指定 destroyMethod,则采用自动推断方式获取销毁方法名(close,shutdown)
- 如果有 @PreDestroy 标注的方法
- 存储位置
- singleton scope 的可销毁 bean 会存储于 beanFactory 的成员当中
- 自定义 scope 的可销毁 bean 会存储于对应的域对象当中
- prototype scope 不会存储,需要自己找到此对象销毁
- 存储时都会封装为 DisposableBeanAdapter 类型对销毁方法的调用进行适配
6. 类型转换处理
- 如果 getBean 的 requiredType 参数与实际得到的对象类型不同,会尝试进行类型转换
7. 销毁 bean
- 销毁时机
- singleton bean 的销毁在 ApplicationContext.close 时,此时会找到所有 DisposableBean 的名字,逐一销毁
- 自定义 scope bean 的销毁在作用域对象生命周期结束时
- prototype bean 的销毁可以通过自己手动调用 AutowireCapableBeanFactory.destroyBean 方法执行销毁
- 同一 bean 中不同形式销毁方法的调用次序
- 优先后处理器销毁,即 @PreDestroy
- 其次 DisposableBean 接口销毁
- 最后 destroyMethod 销毁(包括自定义名称,推断名称,AutoCloseable 接口 多选一)
5、哪些是重要的 bean 生命周期方法?你能重载它们吗?
有两个重要的 bean 生命周期方法,第一个是 setup,它是在容器加载 bean 的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。 Thebean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。
6、Spring初始化bean的流程
Spring初始化Bean的流程如下:
- Spring通过BeanFactory或ApplicationContext加载配置文件,并且解析配置文件中的Bean定义。
- 对于每个Bean定义,Spring会根据Bean定义信息创建一个BeanDefinition对象。BeanDefinition对象包含了Bean的名称、类型、作用域、依赖关系等信息。
- Spring根据BeanDefinition对象中的信息,创建Bean实例。如果Bean的作用域是singleton,则只创建一个实例;如果Bean的作用域是prototype,则每次请求都会创建一个新的实例。
- 如果Bean实现了BeanNameAware接口,则调用BeanNameAware接口的setBeanName()方法,将Bean的名称传递给Bean。
- 如果Bean实现了BeanFactoryAware接口,则调用BeanFactoryAware接口的setBeanFactory()方法,将BeanFactory实例传递给Bean。
- 如果Bean实现了ApplicationContextAware接口,则调用ApplicationContextAware接口的setApplicationContext()方法,将ApplicationContext实例传递给Bean。
- 如果Bean实现了BeanPostProcessor接口,则调用BeanPostProcessor接口的postProcessBeforeInitialization()方法,对Bean进行预处理。
- 如果Bean实现了InitializingBean接口,则调用InitializingBean接口的afterPropertiesSet()方法,对Bean进行初始化。
- 如果Bean配置了init-method属性,则调用Bean的init-method方法,对Bean进行初始化。
- 如果Bean实现了BeanPostProcessor接口,则调用BeanPostProcessor接口的postProcessAfterInitialization()方法,对Bean进行后处理。
- 将初始化后的Bean实例添加到Spring容器中。
在整个初始化过程中,Spring使用了反射、依赖注入、AOP等技术,实现了Bean的自动装配和管理。这些技术使得Bean的创建和管理变得非常便捷和灵活,大大提高了应用程序的开发效率和质量。
7、Spring的循环依赖
循环依赖是指两个或多个Bean互相依赖,形成了一个环路的情况。在Spring中,如果发生循环依赖,会抛出BeanCurrentlyInCreationException异常,提示循环依赖错误。
Spring通过三级缓存来解决循环依赖问题,三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories。
- 当Spring需要创建一个Bean时,首先会检查singletonObjects缓存中是否存在该Bean的实例,如果存在则直接返回该实例。
- 如果singletonObjects缓存中不存在该Bean的实例,但是earlySingletonObjects缓存中存在该Bean的“早期”实例(即未完成依赖注入的实例),则将该“早期”实例返回,避免出现循环依赖。
- 如果singletonObjects和earlySingletonObjects缓存中都不存在该Bean的实例,则调用createBean()方法创建该Bean的实例,同时将该实例存储到singletonFactories缓存中。
- 在创建Bean实例的过程中,如果发现依赖的Bean还未创建,则先创建依赖的Bean实例,并将其存储到earlySingletonObjects缓存中。如果依赖的Bean已经存在,则直接从singletonObjects缓存中获取该Bean的实例。
- 当创建完所有的Bean实例后,Spring会执行依赖注入操作,将依赖的Bean注入到目标Bean中。
- 完成依赖注入后,Spring会将该Bean实例从singletonFactories缓存中移除,并将其存储到singletonObjects缓存中,以供后续使用。
需要注意的是,循环依赖可能会导致程序逻辑混乱,因此应该尽量避免出现循环依赖的情况。如果无法避免循环依赖,可以考虑使用构造函数注入代替属性注入,或者通过使用代理对象来解决循环依赖问题。
循环依赖的产生
- 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
- set 方法(包括成员变量)的循环依赖如图所示
- 可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
- 【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
- a 的顺序,及 b 的顺序都能得到保障
- 构造方法的循环依赖如图所示,显然无法用前面的方法解决
构造循环依赖的解决
- 思路1
- a 注入 b 的代理对象,这样能够保证 a 的流程走通
- 后续需要用到 b 的真实对象时,可以通过代理间接访问
- 思路2
- a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
- 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问
- 示例1:用 @Lazy 为构造方法参数生成代理
public class App60_1 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println();
}
}
- 示例2:用 ObjectProvider 延迟依赖对象的创建
public class App60_2 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.debug("A({})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.getObject());
System.out.println(context.getBean(B.class));
}
}
- 示例3:用 @Scope 产生代理
public class App60_3 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
scanner.scan("com.itheima.app60.sub");
context.refresh();
System.out.println();
}
}
@Component
class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
- 示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
public class App60_4 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private Provider<B> b;
public A(Provider<B> b) {
log.debug("A({}})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({}})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.get());
System.out.println(context.getBean(B.class));
}
}
解决 set 循环依赖的原理
一级缓存
作用是保证单例对象仅被创建一次
- 第一次走
getBean("a")
流程后,最后会将成品 a 放入 singletonObjects 一级缓存 - 后续再走
getBean("a")
流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建
一级缓存与循环依赖
一级缓存无法解决循环依赖问题,分析如下
- 无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走
getBean("a")
- 当 a 的实例对象创建,接下来执行
a.setB()
时,需要走getBean("b")
流程,红色箭头 1 - 当 b 的实例对象创建,接下来执行
b.setA()
时,又回到了getBean("a")
的流程,红色箭头 2 - 但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环
二级缓存
解决思路如下:
- 再增加一个 singletonFactories 缓存
- 在依赖注入前,即
a.setB()
以及b.setA()
将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存 - 执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程
对于上面的图
a = new A()
执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即factories.put(a)
- 接下来执行
a.setB()
,走入getBean("b")
流程,红色箭头 3 - 这回再执行到
b.setA()
时,需要一个 a 对象,有没有呢?有! factories.get()
在 singletonFactories 缓存中就可以找到,红色箭头 4 和 5- b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6
二级缓存与创建代理
二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下
- spring 默认要求,在
a.init
完成之后才能创建代理pa = proxy(a)
- 由于 a 的代理创建时机靠后,在执行
factories.put(a)
向 singletonFactories 中放入的还是原始对象 - 接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象
三级缓存
简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:
- 图中
factories.put(fa)
放入的既不是原始对象,也不是代理对象而是工厂对象 fa - 当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a
- 假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5
- 这回
b.setA()
注入的就是代理对象,保证了正确性,红色箭头 7 - 还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6
a.init
完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9
当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可
8、Spring 支持的事务管理类型
Spring支持以下几种事务管理类型:
- 编程式事务管理:在代码中显式地开启、提交或回滚事务。
- 声明式事务管理:使用AOP技术,将事务管理代码与业务逻辑代码分离开来,通过配置文件声明事务增强器(TransactionInterceptor),自动将事务管理代码织入到业务逻辑代码中。
- 注解式事务管理:使用注解的方式声明事务增强器(@Transactional),自动将事务管理代码织入到被注解的方法中。
- JTA事务管理:使用Java Transaction API (JTA)实现分布式事务管理,支持多个数据源的事务管理。
其中,声明式事务管理和注解式事务管理是Spring事务管理的两种主要方式,它们都通过AOP技术实现了事务管理代码与业务逻辑代码的分离,使得业务逻辑代码更加简洁清晰。在实际开发中,通常使用声明式事务管理或注解式事务管理来管理事务,具体选择哪种方式取决于具体的业务需求和开发习惯。
9、Spring事务7种传播机制
Spring事务的传播机制描述了在一个事务方法调用另一个事务方法时,事务应该如何传播的规则。Spring支持以下7种事务传播机制:
- REQUIRED:如果当前没有事务,则创建一个新的事务;如果当前有事务,则加入该事务。
- SUPPORTS:如果当前有事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。
- MANDATORY:如果当前有事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前有事务,则挂起当前事务。
- NOT_SUPPORTED:以非事务的方式执行操作,如果当前有事务,则挂起当前事务。
- NEVER:以非事务的方式执行操作,如果当前有事务,则抛出异常。
- NESTED:如果当前有事务,则在该事务中嵌套一个新的事务;如果当前没有事务,则创建一个新的事务。
10、事务传播类型说明
- REQUIRED:如果当前没有事务,则创建一个新的事务;如果当前有事务,则加入该事务。这是最常用的事务传播类型,它保证了方法在一个事务中运行,如果当前存在事务,则加入该事务,否则创建一个新的事务。
- SUPPORTS:如果当前有事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。该传播类型适用于只读操作,如果当前有事务则加入该事务,否则以非事务的方式执行。
- MANDATORY:如果当前有事务,则加入该事务;如果当前没有事务,则抛出异常。该传播类型要求当前必须存在一个事务,否则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前有事务,则挂起当前事务。该传播类型会创建一个新的事务,并且挂起当前事务,直到新的事务完成。
- NOT_SUPPORTED:以非事务的方式执行操作,如果当前有事务,则挂起当前事务。该传播类型会以非事务的方式执行,如果当前存在事务,则挂起当前事务,直到非事务执行完毕。
- NEVER:以非事务的方式执行操作,如果当前有事务,则抛出异常。该传播类型会以非事务的方式执行,如果当前存在事务,则抛出异常。
- NESTED:如果当前有事务,则在该事务中嵌套一个新的事务;如果当前没有事务,则创建一个新的事务。该传播类型会在当前事务中嵌套一个新的事务,如果当前没有事务,则创建一个新的事务。
需要注意的是,在选择事务传播类型时,应该根据具体的业务需求和数据一致性要求进行选择,以确保事务管理的正确性和一致性。同时,在使用嵌套事务时,应该特别注意事务的回滚和提交规则,以避免出现不一致的情况。
11、Spring事务为何失效
Spring事务失效的原因有多种可能,以下是一些常见的原因:
- 未开启事务管理:如果没有正确配置Spring事务管理器或没有在业务方法上添加@Transactional注解,就可能导致事务无法生效。
- 事务传播类型不正确:如果使用了错误的事务传播类型,可能会导致事务无法正确传播或嵌套,从而导致事务失效。
- 异常处理不当:如果事务方法中发生异常,但未正确处理或抛出异常,可能会导致事务无法正确回滚或提交,从而导致事务失效。
- 数据库引擎不支持事务:如果使用的数据库引擎不支持事务或事务隔离级别,则无法使用Spring事务管理器进行事务管理。
- 事务超时或锁等待:如果事务超时或锁等待时间过长,可能会导致事务失败或回滚,从而导致事务失效。
- 多线程并发操作:如果多个线程同时访问同一个事务,可能会导致事务无法正确提交或回滚,从而导致事务失效。
- 事务方法中包含非事务方法:如果事务方法中调用了非事务方法,可能会导致事务无法正确传播或嵌套,从而导致事务失效。
针对以上问题,可以通过正确配置事务管理器、选择合适的事务传播类型、正确处理异常、优化数据库性能、设置合适的事务超时时间和锁等待时间、使用线程安全的事务管理方式等方式来避免Spring事务失效。
1、抛出检查异常导致事务不能正确回滚
package com.xx.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xx.common.Result;
import com.xx.entity.Order;
import java.io.FileNotFoundException;
/**
* @Author: xueqimiao
* @Date: 2024/5/10 16:20
*/
public interface OrderService extends IService<Order> {
Result saveOrder(Order order) throws FileNotFoundException;
}
@Override
@Transactional
public Result saveOrder(Order order) throws FileNotFoundException {
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U"+SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
if(order != null){
new FileInputStream("aaa");
}
return Result.ok("操作成功");
}
package com.xx.controller;
import com.xx.common.Result;
import com.xx.entity.Order;
import com.xx.service.OrderService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.FileNotFoundException;
/**
* @Author: xueqimiao
* @Date: 2024/5/10 16:31
*/
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/saveOrder")
public Result saveOrder() throws FileNotFoundException {
return orderService.saveOrder(new Order());
}
}
原因:Spring 默认只会回滚非检查异常
默认情况下,
@Transactional
注解只会回滚RuntimeException
异常及其子类。如果方法中抛出的异常不是RuntimeException
的子类,那么 Spring 就不会回滚事务,这可能导致数据不一致的问题。因此,为了确保在方法中抛出任何异常时都能够回滚事务,可以在
@Transactional
注解中使用rollbackFor
属性来指定需要回滚的异常类型。例如,@Transactional(rollbackFor = Exception.class)
表示在方法中抛出任何异常时都会回滚事务。
@Transactional(rollbackFor = Exception.class)
@Override
@Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order) throws FileNotFoundException {
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U"+SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
if(order != null){
new FileInputStream("aaa");
}
return Result.ok("操作成功");
}
测试完还原代码
java@GetMapping("/saveOrder") public Result saveOrder(){ return orderService.saveOrder(new Order()); } Result saveOrder(Order order); @Override @Transactional(rollbackFor = Exception.class) public Result saveOrder(Order order){ String orderNo = "R" + SnowFlakeIdUtil.generateId(); order.setOrderNo(orderNo); order.setUserId("U"+SnowFlakeIdUtil.generateId()); order.setOrderPrice(new BigDecimal(1)); save(order); return Result.ok("操作成功"); }
2、业务方法内自己 try-catch 异常导致事务不能正确回滚
@Override
@Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U"+SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
if(order != null){
try {
new FileInputStream("aaa");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
return Result.ok("操作成功");
}
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解法1:异常原样抛出
- 在 catch 块添加
throw new RuntimeException(e);
- 在 catch 块添加
解法2:手动设置 TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
- 在 catch 块添加
@Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U"+SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
if(order != null){
try {
new FileInputStream("aaa");
} catch (FileNotFoundException e) {
// throw new RuntimeException(e);
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
}
}
return Result.ok("操作成功");
}
3、调用本类方法导致失效
void saveOrder2();
// 注意这里注释了
// @Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
saveOrder2();
return Result.ok("操作成功");
}
@Transactional(rollbackFor = Exception.class)
public void saveOrder2() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
- 原因:本类方法调用不经过代理,因此无法增强
1、解决方式一
在saveOrder()
也加事务注解
@Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
saveOrder2();
return Result.ok("操作成功");
}
@Transactional(rollbackFor = Exception.class)
public void saveOrder2() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
2、解决方式二
@Resource
private OrderService orderService;
@Override
// 注意这里注释了
// @Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
orderService.saveOrder2();
return Result.ok("操作成功");
}
@Transactional(rollbackFor = Exception.class)
public void saveOrder2() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
注意会出现循环依赖的问题:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
orderController
┌─────┐
| orderServiceImpl
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
spring:
main:
allow-circular-references: true
3、解决方案三
package com.xx.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @Author: xueqimiao
* @Date: 2024/5/10 19:26
*/
@Component
public class SpringApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext param) throws BeansException {
applicationContext = param;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
}
SpringApplicationContextUtil.getBean(OrderService.class).saveOrder2();
@Override
// 注意这里注释了
// @Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
// orderService.saveOrder2();
SpringApplicationContextUtil.getBean(OrderService.class).saveOrder2();
return Result.ok("操作成功");
}
@Transactional(rollbackFor = Exception.class)
public void saveOrder2() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
4、解决方案四
将 saveOrder2()
写到另一个类中去
package com.xx.service;
/**
* @Author: xueqimiao
* @Date: 2024/5/10 19:29
*/
public interface OrderService2 {
void saveOrder2();
}
package com.xx.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xx.entity.Order;
import com.xx.mapper.OrderMapper;
import com.xx.service.OrderService2;
import com.xx.utils.SnowFlakeIdUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
/**
* @Author: xueqimiao
* @Date: 2024/5/10 17:09
*/
@Service
public class OrderService2Impl extends ServiceImpl<OrderMapper, Order> implements OrderService2 {
@Transactional(rollbackFor = Exception.class)
public void saveOrder2() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
}
@Resource
private OrderService2 orderService2;
@Override
// 注意这里注释了
// @Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
orderService2.saveOrder2();
return Result.ok("操作成功");
}
4、非 public 方法导致的事务失效
1、SpringBoot2
SpringBoot2
和SpringBoot3
有所区别,以下操作业务逻辑代码不变,以下列出SpringBoot2
的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>xx-spring-tx-boot2</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.6.4</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mysql.version>8.0.33</mysql.version>
<mybatisplus.version>3.5.3.2</mybatisplus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xx</groupId>
<artifactId>xx-common-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
</dependencies>
</project>
我们到OrderService2Impl
加一个非public的方法
@Transactional(rollbackFor = Exception.class)
void saveOrder3() {
Order order = new Order();
String orderNo = "R" + SnowFlakeIdUtil.generateId();
order.setOrderNo(orderNo);
order.setUserId("U" + SnowFlakeIdUtil.generateId());
order.setOrderPrice(new BigDecimal(1));
save(order);
int n = 10 / 0;
}
OrderServiceImpl
修改如下
// 注意这里注释了
// @Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
orderService2Impl.saveOrder3();
return Result.ok("操作成功");
}
- 原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
- 解法1:改为 public 方法
- 解法2:添加 bean 配置如下
同时启动类也要加@EnableTransactionManagement
,需要自定义事务管理的行为,例如指定不同的事务管理器或配置其他事务相关的属性,那么你需要显式地添加 @EnableTransactionManagement
注解,并在配置类中进行自定义配置。
@Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false);
}
yaml也要配置允许bean覆盖
spring:
main:
allow-bean-definition-overriding: true
2、SpringBoot3
在SpringBoot3(也就是Spring6.0)中默认是可以代理非public方法的,所以这一点在SpringBoot3中是不会失效的。
5、Aop 切面顺序导致导致事务不能正确回滚
@Transactional(rollbackFor = Exception.class)
public Result saveOrder(Order order){
save(order);
int n = 10 / 0;
return Result.ok("操作成功");
}
package com.xx.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @Author: xueqimiao
* @Date: 2024/5/14 14:04
*/
@Aspect
@Component
public class MyAspect {
@Around("execution(* saveOrder(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (Throwable e) {
return null;
}
}
}
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…
解法1:异常原样抛出
- 在 catch 块添加
throw new RuntimeException(e);
- 在 catch 块添加
解法2:手动设置 TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
- 在 catch 块添加
解法3:调整切面顺序,在 MyAspect 上添加
@Order(Ordered.LOWEST_PRECEDENCE - 1)
(不推荐)
12、AOP到底如何去支持事务
AOP是Spring事务模块实现事务管理的核心技术之一,它通过在目标方法周围添加事务增强(Transaction Advice)来实现事务管理。
具体来说,Spring事务管理器通过AOP将事务增强织入到带有@Transactional注解的业务方法中,从而实现事务管理。当业务方法被调用时,AOP会先判断当前是否已经有事务在进行中,如果有,则使用当前事务,否则创建一个新的事务,并将其与当前线程绑定。
在业务方法执行完毕后,AOP会根据事务执行结果进行提交或回滚操作。如果业务方法正常执行完成,则事务增强会将事务状态设置为可提交状态,当方法返回时,事务管理器会自动提交事务。如果业务方法抛出异常,则事务增强会将事务状态设置为可回滚状态,当方法返回时,事务管理器会自动回滚事务。
需要注意的是,在使用AOP实现事务管理时,应该避免在事务方法中调用非事务方法,因为这样会导致事务无法正确传播或嵌套,从而导致事务失效。
总之,AOP是Spring事务模块实现事务管理的关键技术之一,它通过将事务增强织入到业务方法中,实现了事务的自动管理、提交和回滚等功能,从而大大简化了事务管理的复杂度。
13、ACID,数据库事务的四大基本特性
- 原子性(Atomicity):事务是一个原子操作,要么全部执行成功,要么全部回滚失败。当事务执行失败时,所有已经执行的操作都必须回滚到初始状态,保证数据的一致性和完整性。
- 一致性(Consistency):事务执行前后,数据库必须保持一致性状态。即,事务执行后,数据库中的数据必须符合所有的约束和规则,不破坏数据完整性和正确性。
- 隔离性(Isolation):事务之间是相互隔离的,一个事务执行过程中所做的修改在提交之前对其他事务是不可见的,保证了并发事务的正确性和一致性。
- 持久性(Durability):事务提交后,对数据库的修改必须永久保存,即使发生系统故障或重启也不能丢失。
这四个特性是数据库事务的基本特性,保证了数据库操作的正确性、一致性和可靠性。如果数据库不支持ACID特性,则可能会导致数据不一致、丢失或损坏等问题,因此ACID是数据库事务的基本保障和标准。
14、Spring怎么保证线程安全
Spring框架本身并没有直接提供线程安全的机制,但是通过一些最佳实践和设计模式可以在Spring应用程序中实现线程安全。以下是一些保证Spring应用程序线程安全的方法:
- 无状态Bean:Spring容器中的Bean默认是单例的,因此要尽量避免在Bean中存储状态或共享状态,而应该使Bean无状态,这样可以避免线程安全问题。
- Synchronized关键字:在多线程环境下,可以使用Java的Synchronized关键字来同步共享资源的访问,从而保证线程安全。
- ThreadLocal变量:ThreadLocal变量是一种线程本地变量,每个线程都有自己的变量副本,因此可以避免多线程访问的竞争和冲突。
- 使用线程安全的集合和对象:Spring框架提供了一些线程安全的集合和对象,如ConcurrentHashMap、AtomicInteger等,可以在多线程环境下安全地使用。
- 使用AOP切面:可以使用Spring AOP切面来实现线程安全,通过在切面中添加事务管理、锁定共享资源等操作,可以保证多线程环境下的操作安全性。
- 使用注解:Spring提供了一些注解,如@Scope("prototype")、@Async等,可以用来控制Bean的作用域和异步执行,从而实现线程安全。
总之,在Spring应用程序中保证线程安全需要综合考虑多种因素,在程序设计和实现中遵循最佳实践和设计模式,并且在必要的时候使用Java的同步机制、线程安全的集合和对象、AOP切面等技术来保证线程安全。
15、Spring框架中的单例Beans是线程安全的么?
在Spring框架中,单例Beans默认是线程安全的,因为Spring容器在创建单例Bean时只会创建一个Bean实例,并且在每个请求中返回这个实例的引用。这意味着多个线程可以共享同一个Bean实例,并且对Bean实例的修改会在所有线程中反映出来。因此,Spring容器会确保单例Beans的线程安全性。
具体来说,Spring容器会在创建单例Beans时,使用synchronized关键字来确保Bean实例的创建是线程安全的。一旦Bean实例创建完成,Spring容器就不再使用synchronized关键字,因此对于线程安全的Bean实例,多个线程可以同时访问它们,而不会有线程安全问题。
需要注意的是,如果单例Beans中存储了共享状态或可变状态,那么就需要考虑线程安全的问题。此时,可以使用线程安全的集合和对象来确保线程安全,或者使用Spring AOP切面来实现多线程环境下的安全访问。
总之,Spring框架中的单例Beans默认是线程安全的,但是如果单例Beans中存储了共享状态或可变状态,则需要考虑线程安全的问题。
16、Spring Bean前置处理器与后置处理器
Spring Bean前置处理器和后置处理器是Spring框架的两个扩展点,可以在Bean实例化、依赖注入和销毁等生命周期阶段进行自定义处理。下面分别介绍一下Bean前置处理器和后置处理器的作用和用法。
- Bean前置处理器
Bean前置处理器是在Bean实例化之前调用的扩展点,它可以在Bean实例化之前对Bean进行自定义处理,例如修改Bean属性、验证Bean状态等。Bean前置处理器是通过实现org.springframework.beans.factory.config.BeanPostProcessor接口来实现的。
Bean前置处理器的使用步骤如下:
(1)创建一个类并实现org.springframework.beans.factory.config.BeanPostProcessor接口。
(2)在实现的方法中进行自定义处理,例如在postProcessBeforeInitialization方法中修改Bean属性或验证状态。
(3)将自定义的Bean前置处理器注册到Spring容器中。
- Bean后置处理器
Bean后置处理器是在Bean实例化之后、依赖注入之前和Bean销毁之前调用的扩展点,它可以在Bean实例化后对Bean进行自定义处理,例如修改Bean属性、初始化Bean状态等。Bean后置处理器是通过实现org.springframework.beans.factory.config.BeanPostProcessor接口来实现的。
Bean后置处理器的使用步骤如下:
(1)创建一个类并实现org.springframework.beans.factory.config.BeanPostProcessor接口。
(2)在实现的方法中进行自定义处理,例如在postProcessAfterInitialization方法中修改Bean属性或初始化状态。
(3)将自定义的Bean后置处理器注册到Spring容器中。
需要注意的是,Bean前置处理器和后置处理器都是通过实现BeanPostProcessor接口来实现的,因此如果有多个Bean前置处理器或后置处理器,它们的调用顺序是不确定的。如果需要保证处理器的执行顺序,则可以使用@Order注解或实现Ordered接口来指定处理器的执行顺序。
总之,Spring Bean前置处理器和后置处理器是Spring框架提供的扩展点,可以在Bean实例化、依赖注入和销毁等生命周期阶段进行自定义处理,是Spring框架的重要特性之一。
17、Bean的创建为什么要采用三级缓存
在Spring框架中,Bean的创建过程是一个非常复杂的过程,其中包括Bean的实例化、属性注入、初始化等多个步骤。为了提高Bean的创建效率和减少内存开销,Spring框架采用了三级缓存机制来管理Bean的创建过程。
具体来说,Spring框架中的三级缓存机制包括:
- singletonObjects缓存:用于缓存已经创建完成的单例Bean实例。
- earlySingletonObjects缓存:用于缓存正在创建中的单例Bean实例。
- singletonFactories缓存:用于缓存创建单例Bean实例的工厂方法。
这种缓存机制的设计是为了在Bean的创建过程中,尽可能地复用已经创建完成或正在创建中的Bean实例,从而提高创建效率和减少内存开销。具体来说,当获取一个Bean实例时,Spring框架会首先从singletonObjects缓存中查找是否已经创建完成该Bean实例,如果没有,则会从earlySingletonObjects缓存中查找是否正在创建该Bean实例,如果也没有,则会从singletonFactories缓存中查找创建该Bean实例的工厂方法,并将工厂方法执行结果放入earlySingletonObjects缓存中。当Bean实例创建完成后,会将Bean实例放入singletonObjects缓存中,并从earlySingletonObjects缓存中移除。
需要注意的是,虽然三级缓存机制可以提高Bean的创建效率和减少内存开销,但是也会带来一些问题。例如,当一个Bean实例依赖另一个正在创建的Bean实例时,就可能会出现循环依赖的问题。为了解决这个问题,Spring框架采用了AOP技术和提前暴露对象的方式来处理循环依赖问题。
总之,Spring框架采用三级缓存机制来管理Bean的创建过程,可以提高Bean的创建效率和减少内存开销,但是也会带来一些问题,需要注意处理。
18、Spring 面向切面编程(AOP)
面向切面的编程,或 AOP,是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。
1、Aspect 切面
AOP 核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组 API 提供横切功能。比如,一个日志模块可以被称作日志的 AOP 切面。根据需求的不同,一个应用程序可以有若干切面。在 SpringAOP 中,切面通过带有@Aspect 注解的类实现。
2、在 SpringAOP 中,关注点和横切关注的区别是什么?
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
3、连接点
连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个 AOP 切面,它实际上是个应用程序执行 SpringAOP 的位置。
4、通知
通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过 SpringAOP 框架触发的代码段。 Spring 切面可以应用五种类型的通知:
- before:前置通知,在一个方法执行前被调用。
- after:在方法执行之后调用的通知,无论方法执行是否成功。
- after-returning:仅当方法成功完成后执行的通知。
- after-throwing:在方法抛出异常退出时执行的通知。
- around:在方法执行之前和之后调用的通知。
5、切点
切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
6、什么是引入?
引入允许我们在已存在的类中增加新的方法和属性。
7、什么是目标对象?
被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。
8、什么是代理?
代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。
9、有几种不同类型的自动代理?
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
Metadataautoproxying
10、什么是织入。什么是织入应用的不同点?
织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。 织入可以在编译时,加载时,或运行时完成。
19、Spring Bean 的作用域之间有什么区别?
- singleton:这种 bean 范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean 的实例,单例的模式由 bean factory 自身来维护。
- prototype:原形范围与单例范围相反,为每一个 bean 请求提供一个实例。
- request:在请求 bean 范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收。
- Session:与请求范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean会随之失效。
- global- session:global-session 和 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果 你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。全局作用域与 Servlet 中的 session 作用域效果相同
20、Spring 中的设计模式
1. Spring 中的 Singleton
请大家区分 singleton pattern 与 Spring 中的 singleton bean
- 根据单例模式的目的 Ensure a class only has one instance, and provide a global point of access to it
- 显然 Spring 中的 singleton bean 并非实现了单例模式,singleton bean 只能保证每个容器内,相同 id 的 bean 单实例
- 当然 Spring 中也用到了单例模式,例如
- org.springframework.transaction.TransactionDefinition#withDefaults
- org.springframework.aop.TruePointcut#INSTANCE
- org.springframework.aop.interceptor.ExposeInvocationInterceptor#ADVISOR
- org.springframework.core.annotation.AnnotationAwareOrderComparator#INSTANCE
- org.springframework.core.OrderComparator#INSTANCE
2. Spring 中的 Builder
定义 Separate the construction of a complex object from its representation so that the same construction process can create different representations
它的主要亮点有三处:
- 较为灵活的构建产品对象
- 在不执行最后 build 方法前,产品对象都不可用
- 构建过程采用链式调用,看起来比较爽
Spring 中体现 Builder 模式的地方:
- org.springframework.beans.factory.support.BeanDefinitionBuilder
- org.springframework.web.util.UriComponentsBuilder
- org.springframework.http.ResponseEntity.HeadersBuilder
- org.springframework.http.ResponseEntity.BodyBuilder
3. Spring 中的 Factory Method
定义 Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses
根据上面的定义,Spring 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法,它隐藏了 bean (产品)的创建过程和具体实现
Spring 中其它工厂:
- org.springframework.beans.factory.FactoryBean
- @Bean 标注的静态方法及实例方法
- ObjectFactory 及 ObjectProvider
前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题
4. Spring 中的 Adapter
定义 Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces
典型的实现有两处:
- org.springframework.web.servlet.HandlerAdapter – 因为控制器实现有各种各样,比如有
- 大家熟悉的 @RequestMapping 标注的控制器实现
- 传统的基于 Controller 接口(不是 @Controller注解啊)的实现
- 较新的基于 RouterFunction 接口的实现
- 它们的处理方法都不一样,为了统一调用,必须适配为 HandlerAdapter 接口
- org.springframework.beans.factory.support.DisposableBeanAdapter – 因为销毁方法多种多样,因此都要适配为 DisposableBean 来统一调用销毁方法
5. Spring 中的 Composite
定义 Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly
典型实现有:
- org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
- org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
- org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
- org.springframework.web.servlet.view.ViewResolverComposite
composite 对象的作用是,将分散的调用集中起来,统一调用入口,它的特征是,与具体干活的实现实现同一个接口,当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成
6. Spring 中的 Decorator
定义 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality
典型实现:
- org.springframework.web.util.ContentCachingRequestWrapper
7. Spring 中的 Proxy
定义 Provide a surrogate or placeholder for another object to control access to it
装饰器模式注重的是功能增强,避免子类继承方式进行功能扩展,而代理模式更注重控制目标的访问
典型实现:
- org.springframework.aop.framework.JdkDynamicAopProxy
- org.springframework.aop.framework.ObjenesisCglibAopProxy
8. Spring 中的 Chain of Responsibility
定义 Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it
典型实现:
- org.springframework.web.servlet.HandlerInterceptor
9. Spring 中的 Observer
定义 Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
典型实现:
- org.springframework.context.ApplicationListener
- org.springframework.context.event.ApplicationEventMulticaster
- org.springframework.context.ApplicationEvent
10. Spring 中的 Strategy
定义 Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it
典型实现:
- org.springframework.beans.factory.support.InstantiationStrategy
- org.springframework.core.annotation.MergedAnnotations.SearchStrategy
- org.springframework.boot.autoconfigure.condition.SearchStrategy
11. Spring 中的 Template Method
定义 Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure
典型实现:
- 大部分以 Template 命名的类,如 JdbcTemplate,TransactionTemplate
- 很多以 Abstract 命名的类,如 AbstractApplicationContext
21、Spring refresh 流程
Spring refresh 概述
refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。它的内部主要会调用 12 个方法,我们把它们称为 refresh 的 12 个步骤:
- prepareRefresh
- obtainFreshBeanFactory
- prepareBeanFactory
- postProcessBeanFactory
- invokeBeanFactoryPostProcessors
- registerBeanPostProcessors
- initMessageSource
- initApplicationEventMulticaster
- onRefresh
- registerListeners
- finishBeanFactoryInitialization
- finishRefresh
功能分类
- 1 为准备环境
- 2 3 4 5 6 为准备 BeanFactory
- 7 8 9 10 12 为准备 ApplicationContext
- 11 为初始化 BeanFactory 中非延迟单例 bean
1. prepareRefresh
- 这一步创建和准备了 Environment 对象,它作为 ApplicationContext 的一个成员变量
- Environment 对象的作用之一是为后续 @Value,值注入时提供键值
- Environment 分成三个主要部分
- systemProperties - 保存 java 环境键值
- systemEnvironment - 保存系统环境键值
- 自定义 PropertySource - 保存自定义键值,例如来自于 *.properties 文件的键值
2. obtainFreshBeanFactory
- 这一步获取(或创建) BeanFactory,它也是作为 ApplicationContext 的一个成员变量
- BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化,bean 的各项特征由 BeanDefinition 定义
- BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
- BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加
- 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合
3. prepareBeanFactory
- 这一步会进一步完善 BeanFactory,为它的各项成员变量赋值
- beanExpressionResolver 用来解析 SpEL,常见实现为 StandardBeanExpressionResolver
- propertyEditorRegistrars 会注册类型转换器
- 它在这里使用了 ResourceEditorRegistrar 实现类
- 并应用 ApplicationContext 提供的 Environment 完成 ${ } 解析
- registerResolvableDependency 来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入
- beanPostProcessors 是 bean 后处理器集合,会工作在 bean 的生命周期各个阶段,此处会添加两个:
- ApplicationContextAwareProcessor 用来解析 Aware 接口
- ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean
4. postProcessBeanFactory
- 这一步是空实现,留给子类扩展。
- 一般 Web 环境的 ApplicationContext 都要利用它注册新的 Scope,完善 Web 下的 BeanFactory
- 这里体现的是模板方法设计模式
5. invokeBeanFactoryPostProcessors
- 这一步会调用 beanFactory 后处理器
- beanFactory 后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition
- 常见的 beanFactory 后处理器有
- ConfigurationClassPostProcessor – 解析 @Configuration、@Bean、@Import、@PropertySource 等
- PropertySourcesPlaceHolderConfigurer – 替换 BeanDefinition 中的 $
- MapperScannerConfigurer – 补充 Mapper 接口对应的 BeanDefinition
6. registerBeanPostProcessors
- 这一步是继续从 beanFactory 中找出 bean 后处理器,添加至 beanPostProcessors 集合中
- bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段,常见的有:
- AutowiredAnnotationBeanPostProcessor 功能有:解析 @Autowired,@Value 注解
- CommonAnnotationBeanPostProcessor 功能有:解析 @Resource,@PostConstruct,@PreDestroy
- AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理
7. initMessageSource
- 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能
- 去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现
8. initApplicationContextEventMulticaster
- 这一步为 ApplicationContext 添加事件广播器成员,即 applicationContextEventMulticaster
- 它的作用是发布事件给监听器
- 去 beanFactory 找名为 applicationEventMulticaster 的 bean 作为事件广播器,若没有,会创建默认的事件广播器
- 之后就可以调用 ApplicationContext.publishEvent(事件对象) 来发布事件
9. onRefresh
- 这一步是空实现,留给子类扩展
- SpringBoot 中的子类在这里准备了 WebServer,即内嵌 web 容器
- 体现的是模板方法设计模式
10. registerListeners
- 这一步会从多种途径找到事件监听器,并添加至 applicationEventMulticaster
- 事件监听器顾名思义,用来接收事件广播器发布的事件,有如下来源
- 事先编程添加的
- 来自容器中的 bean
- 来自于 @EventListener 的解析
- 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中 onApplicationEvent(E e) 方法即可
11. finishBeanFactoryInitialization
- 这一步会将 beanFactory 的成员补充完毕,并初始化所有非延迟单例 bean
- conversionService 也是一套转换机制,作为对 PropertyEditor 的补充
- embeddedValueResolvers 即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能
- singletonObjects 即单例池,缓存所有单例对象
- 对象的创建都分三个阶段,每一阶段都有不同的 bean 后处理器参与进来,扩展功能
12. finishRefresh
- 这一步会为 ApplicationContext 添加 lifecycleProcessor 成员,用来控制容器内需要生命周期管理的 bean
- 如果容器中有名称为 lifecycleProcessor 的 bean 就用它,否则创建默认的生命周期管理器
- 准备好生命周期管理器,就可以实现
- 调用 context 的 start,即可触发所有实现 LifeCycle 接口 bean 的 start
- 调用 context 的 stop,即可触发所有实现 LifeCycle 接口 bean 的 stop
- 发布 ContextRefreshed 事件,整个 refresh 执行完成
22、Spring声明式事务使用步骤
1、注解
用@Transactional
注解实现事务要加rollbackFor
@Transactional(rollbackFor = Exception.class)
默认情况下,
@Transactional
注解只会回滚RuntimeException
异常及其子类。如果方法中抛出的异常不是RuntimeException
的子类,那么 Spring 就不会回滚事务,这可能导致数据不一致的问题。因此,为了确保在方法中抛出任何异常时都能够回滚事务,可以在
@Transactional
注解中使用rollbackFor
属性来指定需要回滚的异常类型。例如,@Transactional(rollbackFor = Exception.class)
表示在方法中抛出任何异常时都会回滚事务。
2、全局事务
配置全局事务后就不需要每个方法都添加
@Transactional
注解了,在开发过程中更注重于业务开发。
package com.xx.config;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: xueqimiao
* @Date: 2024/1/17 09:45
*/
@Configuration
public class TransactionConfiguration {
/**
* 配置全局事务的切点为service层的所有方法
* 设置service层所在位置
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xx.service..*.*(..))";
/**
* 注入事务管理器
*/
@Autowired
private TransactionManager transactionManager;
/**
* 配置事务拦截器
*/
@Bean
public TransactionInterceptor txAdvice() {
RuleBasedTransactionAttribute txAttrRequired = new RuleBasedTransactionAttribute();
txAttrRequired.setName("REQUIRED事务");
//设置事务传播机制,默认是PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
txAttrRequired.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//设置异常回滚为Exception 默认是RuntimeException
List<RollbackRuleAttribute> rollbackRuleAttributes = new ArrayList<>();
rollbackRuleAttributes.add(new RollbackRuleAttribute(Exception.class));
txAttrRequired.setRollbackRules(rollbackRuleAttributes);
RuleBasedTransactionAttribute txAttrRequiredReadOnly = new RuleBasedTransactionAttribute();
txAttrRequiredReadOnly.setName("SUPPORTS事务");
//设置事务传播机制,PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
txAttrRequiredReadOnly.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
//设置异常回滚为Exception 默认是RuntimeException
txAttrRequiredReadOnly.setRollbackRules(rollbackRuleAttributes);
txAttrRequiredReadOnly.setReadOnly(true);
/*事务管理规则,声明具备事务管理的方法名*/
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
//方法名规则限制,必须以下列开头才会加入事务管理当中
source.addTransactionalMethod("add*", txAttrRequired);
source.addTransactionalMethod("save*", txAttrRequired);
source.addTransactionalMethod("create*", txAttrRequired);
source.addTransactionalMethod("insert*", txAttrRequired);
source.addTransactionalMethod("submit*", txAttrRequired);
source.addTransactionalMethod("del*", txAttrRequired);
source.addTransactionalMethod("remove*", txAttrRequired);
source.addTransactionalMethod("update*", txAttrRequired);
source.addTransactionalMethod("exec*", txAttrRequired);
source.addTransactionalMethod("set*", txAttrRequired);
//对于查询方法,根据实际情况添加事务管理 可能存在查询多个数据时,已查询出来的数据刚好被改变的情况
source.addTransactionalMethod("get*", txAttrRequiredReadOnly);
source.addTransactionalMethod("select*", txAttrRequiredReadOnly);
source.addTransactionalMethod("query*", txAttrRequiredReadOnly);
source.addTransactionalMethod("find*", txAttrRequiredReadOnly);
source.addTransactionalMethod("list*", txAttrRequiredReadOnly);
source.addTransactionalMethod("count*", txAttrRequiredReadOnly);
source.addTransactionalMethod("is*", txAttrRequiredReadOnly);
return new TransactionInterceptor(transactionManager, source);
}
/**
* 设置切面
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
23、Spring常用注解
事务注解
- @EnableTransactionManagement,会额外加载 4 个 bean
- BeanFactoryTransactionAttributeSourceAdvisor 事务切面类
- TransactionAttributeSource 用来解析事务属性
- TransactionInterceptor 事务拦截器
- TransactionalEventListenerFactory 事务监听器工厂
- @Transactional
核心
- @Order
切面
- @EnableAspectJAutoProxy
- 会加载 AnnotationAwareAspectJAutoProxyCreator,它是一个 bean 后处理器,用来创建代理
- 如果没有配置 @EnableAspectJAutoProxy,又需要用到代理(如事务)则会使用 InfrastructureAdvisorAutoProxyCreator 这个 bean 后处理器
组件扫描与配置类
@Component
@Controller
@Service
@Repository
@ComponentScan
@Conditional
@Configuration
- 配置类其实相当于一个工厂, 标注 @Bean 注解的方法相当于工厂方法
- @Bean 不支持方法重载, 如果有多个重载方法, 仅有一个能入选为工厂方法
- @Configuration 默认会为标注的类生成代理, 其目的是保证 @Bean 方法相互调用时, 仍然能保证其单例特性
- @Configuration 中如果含有 BeanFactory 后处理器, 则实例工厂方法会导致 MyConfig 提前创建, 造成其依赖注入失败,解决方法是改用静态工厂方法或直接为 @Bean 的方法参数依赖注入, 针对 Mapper 扫描可以改用注解方式
@Bean
@Import
四种用法
① 引入单个 bean
② 引入一个配置类
③ 通过 Selector 引入多个类
④ 通过 beanDefinition 注册器
解析规则
- 同一配置类中, @Import 先解析 @Bean 后解析
- 同名定义, 默认后面解析的会覆盖前面解析的
- 不允许覆盖的情况下, 如何能够让 MyConfig(主配置类) 的配置优先? (虽然覆盖方式能解决)
- 采用 DeferredImportSelector,因为它最后工作, 可以简单认为先解析 @Bean, 再 Import
@Lazy
- 加在类上,表示此类延迟实例化、初始化
- 加在方法参数上,此参数会以代理方式注入
@PropertySource
依赖注入
- @Autowired
- @Qualifier
- @Value
mvc mapping
- @RequestMapping,可以派生多个注解如 @GetMapping 等
mvc rest
- @RequestBody
- @ResponseBody,组合 @Controller => @RestController
- @ResponseStatus
mvc 统一处理
- @ControllerAdvice,组合 @ResponseBody => @RestControllerAdvice
- @ExceptionHandler
mvc 参数
- @PathVariable
mvc ajax
- @CrossOrigin
boot auto
- @SpringBootApplication
- @EnableAutoConfiguration
- @SpringBootConfiguration
boot condition
- @ConditionalOnClass,classpath 下存在某个 class 时,条件才成立
- @ConditionalOnMissingBean,beanFactory 内不存在某个 bean 时,条件才成立
- @ConditionalOnProperty,配置文件中存在某个 property(键、值)时,条件才成立
boot properties
- @ConfigurationProperties,会将当前 bean 的属性与配置文件中的键值进行绑定
- @EnableConfigurationProperties,会添加两个较为重要的 bean
- ConfigurationPropertiesBindingPostProcessor,bean 后处理器,在 bean 初始化前调用下面的 binder
- ConfigurationPropertiesBinder,真正执行绑定操作