Spring's underlying core principles

Overall understanding of Spring's bottom layer

  • The underlying principle of Bean's life cycle
  • The underlying principle of dependency injection
  • Initialize the underlying principle
  • Infer the underlying principles of construction
  • The underlying principle of AOP
  • The underlying principle of Spring transaction

How Spring creates an object

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
复制代码

In fact, whether it is AnnotationConfigApplicationContext or ClassPathXmlApplicationContext, we can simply understand them as being used to create java objects. For example, calling getBean() will create a pair. ==Note: This is not rigorous, getBean() may not create an object == In java, an object must be created according to a certain class. When we call context.getBean("userService") , it will create an object, but how does the getBean method know that "userService" corresponds to the UserService class? So we can analyze it, when calling the constructor of AnnotationConfigApplicationContext, that is, the first line of code, will do some things:

  1. Parse AppConfig and get the scan path
  2. Traverse all the java classes under the scan path, if it finds that @Component, @Service and other annotations exist on a class, then Spring records the class and stores it in a Map, such as Map<String,Class<?>>.( In fact, there is a similar Map in the Spring source code, called BeanDefinitionMap)
  3. Spring will generate the beanName corresponding to the current class according to a certain rule, store it in the Map as the key, and the current class as the value.

In this way, when calling context.getBean("userService"), the UserService class can be found according to "userService", so that the object can be created.

Bean creation process

The general process is as follows:

  1. 推断构造方法: 利用该类的构造方法实例化得到一个对象(但是如果一个类中存在多个构造方法,Spring则会进行选择)
  2. 依赖注入: 得到一个对象后,Spring会判断这个对象是否存在被@Autowired注解了的属性,把这些属性找到出来并且交给Spring进行赋值
  3. Aware回调: 依赖注入后,Spring会判断这个对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了就表示当前对象必须实现这些接口所定义的setBeanName,setBeanClassLoader,setBeanFactory方法,那Spring就会调用这些方法并传入相应的参数
  4. 初始化前: Aware回调后,Spring会判断这个对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring就会调用当前对象的此方法
  5. 初始化: 紧接着Spring会判断这个对象是否实现了InitializingBean接口,如果实现了就表示当前对象必须实现该接口的afterPropertiesSet方法,那Spring就会调用当前对象的afterPropertiesSet方法
  6. 初始化后: 最后,Spring就会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完成了,如果需要进行AOP,那么就会进行动态代理并且生成一个代理对象作为Bean。

通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:

  • 如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象
  • 如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的对象,而不是UserService本身所得到的对象

Bean对象创建出来后:

  • 如果当前Bean是单例Bean,那么会把这个Bean对象存入一个Map<String,Object>,Map的key为beanName,value是Bean对象.这样下次getBean的时候就可以直接从Map拿到对应的Bean对象了.
  • 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次getBean的时候会再次执行创建过程,得到一个新的Bean对象

推断构造方法

Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对象,但是如果一个类中存在多个构造方法,Spring的判断逻辑如下:

  • 如果一个类只存在一个构造方法,不管这个构造方法是无参构造还是有参构造方法,Spring都会使用这个构造方法
  • 如果一个类中存在多个构造方法
    • 这些构造方法中,存在一个无参构造方法,那么Spring就会使用这个无参构造方法
    • 这些构造方法中,不存在无参构造方法,那么Spring就会报错
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [linc.cool.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: linc.cool.service.UserService.<init>()
复制代码

Spring的设计思想是这样的

  1. 如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法
  2. 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参构造方法,因为无参构造方法本身表示了一种默认的意义
  3. 如果某个构造方法上加了@Autowired注解,那就表示程序员告诉Spring我采用这个构造方法进行构造,如果Spring选择了一个有参的构造方法,Spring在调用这个有参构造方法时,需要传入参数,这些参数是怎么来的呢?

Spring会根据入参的类型和入参的名字去Spring中找Bean对象,我们以单例为例,Spring会从单例池sigletonObjects中去找: - 先根据入参类型找,如果只找到一个,那么久直接用来作为入参 - 如果根据入参类型找到多个,就会再去根据入参名字去确定唯一的一个 - 最终如果没有找到就会报错,无法创建当前Bean对象

AOP的大致流程

Cglib原理.png AOP就是进行动态代理,在创建一个Bean的过程中,Spring在最后一步回去判断当前正在创建的这个Bean是否需要进行AOP,如果需要就会进行动态. 如何判断当前的Bean对象是否需要进行AOP:

  • 找出所有的切面Bean
  • 遍历切面中的每个方法,看看是否加了@Before、@After等注解
  • 如果写了,就判断所对应的Pointcut是否和当前的Bean对象的类是否匹配
  • 如果匹配就表示当前Bean对象有匹配的Pointcut,表示需要进行AOP

利用cglib进行AOP的大致流程

  • 生成代理类UserServiceFactory,代理类继承UserService
  • 代理类重写父类的方法,比如UserService中的test()方法
  • 代理类还会有一个target属性,该属性的值为被代理对象(也就是通过UserService类推断构造方法实例化处理的对象,进行了依赖注入,初始化等步骤的对象)
  • 代理类中的test()方法被执行时的逻辑如下
    • 执行切面逻辑
    • 调用target.test()

当我们从Spring容器中得到UserService的Bean对象时,拿到的就是UserServiceProxy所生成的对象,也就是代理对象. UserService代理对象.test()->执行切面逻辑->target.test()

Spring事务

class UserServiceProxy extends UserService{
   UserService target;
   @Override
   public void test{
       // 1.判断当前执行的方法是否存在@Transactional注解
       // 2.通过事务管理器创建一个数据库连接conn
       // 3.修改数据库连接的autocommit为false,这里就不用自动提交了,我们手动提交,默认是true
       conn.autocommit = false;
       // 4.调用被代理对象的test(),执行程序员所写的业务逻辑代码,也就是执行sql
       target.test();
       // 5.执行完了之后如果没有出现异常,则提交,否则回滚
       conn.commit(); // 如果异常 conn.rollback();
   }
}

复制代码

当我们在某个方法上加上了@Transactional注解后,就表示这个方法在调用时会开启Spring事务,而这个方法所在的类所对应的Bean对象会是该类的代理对象. Spring事务的代理对象执行某个方法时的步骤:

  • 判断当前执行的方法是否存在@Transactional注解
  • 如果存在,就利用事务管理器TransactionManager新建一个数据库连接
  • 修改数据库连接的autocommit为false
  • 执行target.test(),执行程序员所写的业务逻辑代码,也就是执行SQL
  • 执行完之后如果没有出现异常就提交事务,否则回滚

事务常见失效的场景:

  • 访问修饰符权限问题: 如果被@Transactional注解修饰的方法被定义成了private,这样会导致事务失效,spring 要求被代理方法必须是public的.

在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务. 也就是说,如果我们自定义的事务方法(目标方法),它的访问权限不是public,而是private、default或protected的话,Spring是不会提供事务功能的.

@Service
public class UserService {
    
    @Transactional
    private void add(UserModel userModel) {
         saveData(userModel);
         updateData(userModel);
    }
}
复制代码
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
}    
复制代码
  • 方法用final修饰: spring事务底层使用了AOP,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能.但是如果某个方法用final修饰了,那么在它的代理类中就不会重写该方法而去添加事务功能.同样的如果某个方法是static,同样也无法通过动态代理,变成事务方法
@Service
public class UserService {

    @Transactional
    public final void add(UserModel userModel){
        saveData(userModel);
        updateData(userModel);
    }
}
复制代码
  • 方法内部调用: 在同一个类中的方法直接内部调用,会导致事务失效
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

复制代码

Spring事务是否会失效的标准:

@Component
public class UserService {
    @Transactional
    public void test() {
       jdbcTemplate.execute("INSERT INTO `t1` (`id`, `a`, `b`) VALUES (10001, 9999, 9999);");
       invalid();
    }
    
    // 事务失效 因为最终是普通对象调用了这个方法,并不是这个方法的代理对象执行代理逻辑
    // 想要解决这个问题,我们得去考虑要让这个代理对象区调用这个方法即可
    @Transactional(propagation = Propagation.NEVER)
    public void invalid() {
    }
    ...
}
复制代码

某个加了@Transaction注解的方法被调用时,要判断到底是不是直接被代理对象调用的,如果是直接调用的事务就不会生效,否则会失效

如何解决事务失效?

  • 我们可以采用编程式事务
   @Autowired
   private TransactionTemplate transactionTemplate;
   
   transactionTemplate.execute((status) -> {
          ...
            return Boolean.TRUE;
         })
复制代码
  • 自己注入自己
@Component
public class UserService {
   @Autowired
    private UserService userService;
    
    @Transactional
    public void update(){
        userService.update2();
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void update2() {

    }
}

复制代码
  • Spring上下文
@Component
public class UserService implements ApplicationContextAware {
	ApplicationContext context;

    @Transactional
    public void update(){
       UserService userService = (UserService)context.getBean("userService");
       userService.update2();
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void update2() {

    }

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		context = applicationContext;
	}
}
复制代码
  • 获取它的代理类,直接调用代理类
@Component
public class UserService {
    @Autowired
    private UserService userService;
    
    @Transactional
    public void update(){
    ((UserService) AopContext.currentProxy()).update2();
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void update2() {

    }
}

复制代码

为什么我们要加上@Configuration,事务才会生效

@Configuration
@EnableTransactionManagement
@ComponentScan("linc.cool")
public class AppConfig {
	@Bean
	public JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(dataSource());
	}

	@Bean
	public PlatformTransactionManager transactionManager() {
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource());
		return transactionManager;
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
		sessionFactoryBean.setDataSource(dataSource());
		return sessionFactoryBean.getObject();
	}

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/linc?characterEncoding=utf-8&useSSL=false");
		dataSource.setUsername("root");
		dataSource.setPassword("123456");
		return dataSource;
	}
}

复制代码

因为没有加@Configuration注解的话,JdbcTemplate里面和TransactionManager里面持有的是两个不同的DataSource,所以在我们执行代理逻辑的时候,会通过TransactionManager里面的DataSource去建立连接以及设置它的属性. 然后JdbcTemplate会用它自己的DataSource去建立一个新的连接去执行SQL,它此时的连接和TransactionManager的连接时两个不同的连接,所以就导致JdbcTemplate执行的SQL就自动提交了. 而对于TransactionManager会设置自动提交为false,这个JdbcTemplate却没有设置. 而加上了@Configuration注解后,就能保证两个DataSource是同一个,然后再加上一些逻辑,就可以保证事务生效了. 这里也和Spring的代理模式是有联系的,这个加上@Configuration的类也是一个代理对象. 它会先去看DataSource有没有,没有我们就先去创建,然后TransactionManager直接从Spring容器中拿出来用,JdbcTemplate也是.

Guess you like

Origin juejin.im/post/7081998594530050085