Spring底层整体了解

  • Bean的生命周期底层原理
  • 依赖注入底层原理
  • 初始化底层原理
  • 推断构造底层原理
  • AOP底层原理
  • Spring事务底层原理

    Spring如何创建一个对象

    1. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    2. UserService userService = (UserService) context.getBean("userService");
    3. userService.test();
    其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前我们都可以简单的将它们理解为就是用来创建java对象的,比如调用getBean()就会去创建对象。
    注意:此处是不严谨的,getBean()可能也不会去创建对象
    在java中,肯定是根据某个类来创建一个对象的. 当我们调用context.getBean(“userService”)的时候,就会去创建一个对象,但是getBean方法内部怎么知道”userService”对应的是UserService这个类呢? 所以我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法的时候,也就是第一行代码,会去做一些事情:
  1. 解析AppConfig,得到扫描路径
  2. 遍历扫描路径下的所有java类,如果发现某个类上存在@Component,@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map>.(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap)
  3. Spring会根据某个规则去生成当前类对应的beanName,作为key存入Map,当前类作为value.

这样,当调用context.getBean(“userService”)时,就可以根据”userService”找到UserService类,从而就可以去创建对象了.

Bean的创建过程

大致过程如下:

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

    推断构造方法

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

  • 如果一个类只存在一个构造方法,不管这个构造方法是无参构造还是有参构造方法,Spring都会使用这个构造方法

  • 如果一个类中存在多个构造方法
    • 这些构造方法中,存在一个无参构造方法,那么Spring就会使用这个无参构造方法
    • 这些构造方法中,不存在无参构造方法,那么Spring就会报错
      1. 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的大致流程

Spring底层核心原理 - 图1
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事务

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

当我们在某个方法上加上了@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是不会提供事务功能的。

    1. @Service
    2. public class UserService {
    3. @Transactional
    4. private void add(UserModel userModel) {
    5. saveData(userModel);
    6. updateData(userModel);
    7. }
    8. }
    1. protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    2. // Don't allow no-public methods as required.
    3. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    4. return null;
    5. }
    6. }

    方法用final修饰

    spring事务底层使用了AOP,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能.但是如果某个方法用final修饰了,那么在它的代理类中就不会重写该方法而去添加事务功能.同样的如果某个方法是static,同样也无法通过动态代理,变成事务方法

    1. @Service
    2. public class UserService {
    3. @Transactional
    4. public final void add(UserModel userModel){
    5. saveData(userModel);
    6. updateData(userModel);
    7. }
    8. }

    方法内部调用

    在同一个类中的方法直接内部调用,会导致事务失效

    1. @Service
    2. public class UserService {
    3. @Autowired
    4. private UserMapper userMapper;
    5. public void add(UserModel userModel) {
    6. userMapper.insertUser(userModel);
    7. updateStatus(userModel); //并不是代理类调用,因此事务失效
    8. }
    9. @Transactional
    10. public void updateStatus(UserModel userModel) {
    11. doSameThing();
    12. }
    13. }

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

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

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

    如何解决事务失效

    采用编程式事务

    1. @Autowired
    2. private TransactionTemplate transactionTemplate;
    3. transactionTemplate.execute((status) -> {
    4. ...
    5. return Boolean.TRUE;
    6. })

    自己注入自己

    1. @Component
    2. public class UserService {
    3. @Autowired
    4. private UserService userService;
    5. @Transactional
    6. public void update(){
    7. userService.update2();
    8. }
    9. @Transactional(rollbackFor = Exception.class)
    10. public void update2() {
    11. }
    12. }

    Spring上下文

    1. @Component
    2. public class UserService implements ApplicationContextAware {
    3. ApplicationContext context;
    4. @Transactional
    5. public void update(){
    6. UserService userService = (UserService)context.getBean("userService");
    7. userService.update2();
    8. }
    9. @Transactional(rollbackFor = Exception.class)
    10. public void update2() {
    11. }
    12. @Override
    13. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    14. context = applicationContext;
    15. }
    16. }

    获取它的代理类,直接调用代理类

    1. @Component
    2. public class UserService {
    3. @Autowired
    4. private UserService userService;
    5. @Transactional
    6. public void update(){
    7. ((UserService) AopContext.currentProxy()).update2();
    8. }
    9. @Transactional(rollbackFor = Exception.class)
    10. public void update2() {
    11. }
    12. }

    问题:为什么要加上@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也是。