Spring底层核心原理

Spring底层整体了解

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

Spring是如何创建一个对象的

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

其实不管是AnnotationConfigApplicationContext还是ClassPathXmlApplicationContext,目前我们都可以简单的将它们理解为就是用来创建java对象的,比如调用getBean()就会去创建对。 ==注意: 此处是不严谨的,getBean()可能也不会去创建对象== 在java中,肯定是根据某个类来创建一个对象的. 当我们调用context.getBean("userService")的时候,就会去创建一个对象,但是getBean方法内部怎么知道"userService"对应的是UserService这个类呢? 所以我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法的时候,也就是第一行代码,会去做一些事情:

  1. 解析AppConfig,得到扫描路径
  2. 遍历扫描路径下的所有java类,如果发现某个类上存在@Component,@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String,Class<?>>.(实际上,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<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也是.

猜你喜欢

转载自juejin.im/post/7081998594530050085