Spring 相关知识点
本文主要整理 Spring 相关知识点,包括 Spring bean 的生命周期,Spring 事务的原理,Srping 事务的实现方式等等
一、Spring Bean 的生命周期
@service
public class TestService implements ApplicationContextAware, InitializingBean {
@Autowired
private TestInfo testInfo;
private TestDAO testDao;
public TestService(TestDAO testDao) {
this testDao = testDao;
}
@PostConstruct
public void test() {
System.out.println("init exexute test function");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init after test function");
}
}
1、推断构造方法
在一个 Bean 中,如果使用了构造方法,Spring 要去推断构造方法,为什么要推断构造方法,假如一个 Bean 中,使用了多个构造方法,那么 Spring 应该使用哪个构造方法?
- 如果类中只有一个构造方法,那么会 spring 会使用这一个构造方法
- 如果类中有多个构造方法,如果两个里面有一个使用 @Autowired 注解,那么 Spring 就会使用这个构造方。
- 如果两个都没有使用 @Autowired 注解,如果存在无参的构造方法,那么会使用无参的构造方法
- 如果两个都没有使用 @Autowired 注解,并且不存在无参的构造方法,那么在运行时会报错
- 如果两个都加了 @Autowired 注解,那么 Spring 会报错
2、实例化
通过构造方法得到一个对象,如果选择无参的构造方法,那么对象中的属性 testDao 是没有值的
3、填充属性
实例化一个对象之后,Spring 就会进行依赖注入,比如在上面代码中,会判断加了 @Autowired 注解或者 @Resource 注解的属性,利用返回找到这些属性的 Bean 对象,然后赋值给这些属性。
4、处理 Aware 回调
填充属性之后,会进一步判断当前的类中是否定义了 Aware 回调,比如上面实现了 ApplicationContextAware, 会将当前的 Spring 容器传给当前的方法,可以利用容器做些其他的事情。
5、初始化
初始化分为初始化前、初始化中和初始化后。
初始化前,处理 @PostConstruct 接口。如果被加载的类中有使用 @PostConstruct 注解修饰的方法,那么 spring 会自动执行这个方法。
初始化中,处理 InitializingBean 接口,如果被加载的类中有实现 InitializingBean 的接口,那么会实现对应方法 - afterPropertiesSet。
执行的先后顺序:setApplicationContext() -> @PostConstruct 修饰的方法 -> 执行实现 InitializingBean 接口对应的方法
初始化后,进行 AOP。前面的过程构造的对象(经过依赖注入,回调,经过初始化前,初始化中执行的方法)生成一个代理对行,AOP 的结果就是返回一个代理对象。
6、容器关闭时,会对 Bean 进行销毁
二、Spring 事务管理
1、Spring 事务传播机制
Spring 事务传播是指在两个事务方法中,其中一个方法调用另外一个方法,这个时候希望另外一个方法应该如何去执行事务逻辑,具体代码如下所示:
public class testTraction{
@Traction
public void testA() {
testB();
}
public void testB() {
}
}
共有七种事务传播机制:
spring 事务实现是使用 AOP 进行实现的,但是事务的本质还是使用数据库的链接,比如新创建一个事务,本质还是新打开一个数据库链接,并且基于新的数据库链接进行提交以及回滚事务。
2、声明式事务与编程式事务
声明式编程事务使用 @Transactional 来进行管理。示例如下:
@Service
public class TransactionDemo {
@Transactional
public void declarativeUpdate() {
updateOperation1();
updateOperation2();
}
}
这样的写法相当于执行 declarativeUpdate() 方法前使用 BEGIN 开启事务,在方法调用结束后使用 COMMIT 进行提交事务。也可以将 @Transactional 放在类上,这样默认对象中的 public 方法都开启了事务。
相比之下,使用编程式事务管理相对来说比较复杂一些,编程式事务管理是指通过代码来控制事务,使用 TransactionTemplate 来实现。示例如下所示:
@Service
public class TransactionDemo {
@Autowired
private TransactionTemplate transactionTemplate;
public void programmaticUpdate() {
// 这里也可以使用Lambda表达式
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
}
}
两种方式的对比如下:
3、事务失效条件
方法内部调用
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
//@Transactional
public void add(UserModel userModel) {
// do something...
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
这是一个非常常见的错误,在方法内部进行调用,但是我们都知道,spring 事务实现方式是基于 AOP 进行实现的,上面的代码逻辑相当于是调用了 this.updateStatus(userModel)。这时候 this 并不是代理之后的对象,自然而然就会失效。
解决的方法如下:
- 新增一个类进行方法调用
- 在本类中注入自己
- 使用 AopContext.currentProxy() 进行调用
使用 private 进行修饰
会判断方法是否使用 private 进行修饰,如果使用 private 进行修饰,则会直接返回 null,即代表不支持事务
方法使用 final 进行修饰
如果一个事务方法被定义成了final,也会导致事务不生效,这是因为通过cglib是通过生成子类来方式生成代理类。如果方法被定义为final,则意味着该方法无法被重写,无法添加事务功能。
未被 spring 进行管理
如果在类注视中没有使用 @Component、@Service、@Repository 等,则事务将不会生效
多线程调用
在多线程之间调用,会导致不同的线程获取不同的链接,这两个方法就会在不同的事务中。
转载自:https://juejin.cn/post/7383017171179372598