<>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的构造方法的时候,也就是第一行代码,会去做一些事情:

* 解析AppConfig,得到扫描路径
*
遍历扫描路径下的所有java类,如果发现某个类上存在@Component,@Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如Map<String,Class<?>>.(实际上,Spring源码中确实存在类似的这么一个Map,叫做BeanDefinitionMap)
* Spring会根据某个规则去生成当前类对应的beanName,作为key存入Map,当前类作为value.

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

大致过程如下:

* 推断构造方法: 利用该类的构造方法实例化得到一个对象(但是如果一个类中存在多个构造方法,Spring则会进行选择)
* 依赖注入: 得到一个对象后,Spring会判断这个对象是否存在被@Autowired注解了的属性,把这些属性找到出来并且交给Spring进行赋值
* Aware回调:
依赖注入后,Spring会判断这个对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了就表示当前对象必须实现这些接口所定义的setBeanName,setBeanClassLoader,setBeanFactory方法,那Spring就会调用这些方法并传入相应的参数
* 初始化前:
Aware回调后,Spring会判断这个对象中是否存在某个方法被@PostConstruct注解了,如果存在,Spring就会调用当前对象的此方法
* 初始化:
紧接着Spring会判断这个对象是否实现了InitializingBean接口,如果实现了就表示当前对象必须实现该接口的afterPropertiesSet方法,那Spring就会调用当前对象的afterPropertiesSet方法
* 初始化后:
最后,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的设计思想是这样的

* 如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法
* 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参构造方法,因为无参构造方法本身表示了一种默认的意义
*
如果某个构造方法上加了@Autowired注解,那就表示程序员告诉Spring我采用这个构造方法进行构造,如果Spring选择了一个有参的构造方法,Spring在调用这个有参构造方法时,需要传入参数,这些参数是怎么来的呢?
Spring会根据入参的类型和入参的名字去Spring中找Bean对象,我们以单例为例,Spring会从单例池sigletonObjects中去找:
* 先根据入参类型找,如果只找到一个,那么久直接用来作为入参
* 如果根据入参类型找到多个,就会再去根据入参名字去确定唯一的一个
* 最终如果没有找到就会报错,无法创建当前Bean对象
<>AOP的大致流程

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也是.

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信