目录
10.BeanFactory 和 FactoryBean的区别
1.SpringIOC的构建流程
1.获取一个新的bean工厂,通常是ApplicationContext。
2.加载并解析spring的配置,解析bean对象,将解析的bean对象封装成BeanDefinition,并放入缓存
3.实例化和调用BeanFactoryPostProcessor(在加载完 Bean 定义之后,创建 Bean 实例之前被触发)
4.实例化BeanPostProcessor放入BeanFactory,但是现在不触发,具体是在bean对象初始化前后触发。也就是说这个接口我们可以扩展,在bean对象初始化前后,对对象进行操作。
5.实例化所以的bean(懒加载除外),bean对象的创建,属性填充,bean初始化
6.完成容器的刷新,推送上下文刷新完成事件到监听器
2.Spring Bean的生命周期
1.bean实例化,创建一个bean对象
2.bean属性填充
3.bean对象的初始化,可以BeanPostProcessor的两个方法进行bean对象的初始化前和初始化后进行操作
4.bean对象正常使用
5.bean对象销毁
3.Spring 的 AOP 是怎么实现的
本质是通过动态代理来实现的,主要有以下几个步骤。
1、获取增强器,例如被 Aspect 注解修饰的类。
2、在创建每一个 bean 时,匹配是否有增强器与之对应。如果有,则将增强器作为拦截器参数,使用动态代理创建 bean 的代理对象实例。
3、当我们调用被增强过的 bean 时,就会走到代理类中,从而可以触发增强器,本质跟拦截器类似
4.JDK 动态代理和 Cglib 代理的区别
1、JDK 动态代理本质上是实现了被代理对象的接口,而 Cglib 本质上是继承了被代理对象,覆盖其中的方法。
2、JDK 动态代理只能对实现了接口的类生成代理,Cglib 则没有这个限制。但是 Cglib 因为使用继承实现,所以 Cglib 无法代理被 final 修饰的方法或类。
3、在调用代理方法上,JDK 是通过反射机制调用,Cglib是通过FastClass 机制直接调用。FastClass 简单的理解,就是使用 index 作为入参,可以直接定位到要调用的方法直接进行调用。
5.JDK 动态代理为什么只能对实现了接口的类生成代理
根本原因是通过 JDK 动态代理生成的类已经继承了 Proxy 类,所以无法再使用继承的方式去对类实现代理。
6.Spring中哪些情况会导致事务失效
1.如果我们自定义方法,它的访问权限不是public,而是protected、default或者private,spring则不会提供事务功能。
2.方法用final修饰,因为我们事务底层也是使用Aop,Aop的原理是动态代理。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而无法添加事务功能。某个方法是static修饰的同样无法代理
3.方法内部调用,@Transactional注解修饰的方法拥有事务的原因是Aop生成了代理对象,在方法内部调用的时候是通过this对象调用。
4.未被Spring容器管理的service会失效
5.多线程调用的时候,多线程调用的时候获取到的数据库连接是不一样的。但是spring的事务管理又是通过数据库连接保证的。如果同样会失效。
6.错误的传播特性,比如@Transactional(propagation = Propagation.NOT_SUPPORTED)Propagation.NOT_SUPPORTED。
7.自己捕获异常又不抛出来。
8.在事务方法中,手动抛了其它异常。spring默认是RuntimeException,如果要抛其它异常,请@Transactional(rollbackFor = Exception.class)这样设置。
9.自己定义的一些其它异常@Transactional(rollbackFor = BusinessException.class)。所以我们最好设置为@Transactional(rollbackFor = Exception.class)或者@Transactional(rollbackFor = Throwable.class)
10.事务嵌套,如果内部的事务出现回滚,会导致整个事务回滚。如果只希望回滚嵌套的事务,就需要用try{}catch{}来捕获异常。
7.Spring 的事务隔离级别
Spring 的事务隔离级别底层其实是基于数据库的,Spring 并没有自己的一套隔离级别。
DEFAULT:使用数据库的默认隔离级别。
1.READ_UNCOMMITTED:读未提交,最低的隔离级别,会读取到其他事务还未提交的内容,存在脏读。
2.READ_COMMITTED:读已提交,读取到的内容都是已经提交的,可以解决脏读,但是存在不可重复读。
3.REPEATABLE_READ:可重复读,在一个事务中多次读取时看到相同的内容,可以解决不可重复读,但是存在幻读。
4.SERIALIZABLE:串行化,最高的隔离级别,对于同一行记录,写会加写锁,读会加读锁。在这种情况下,只有读读能并发执行,其他并行的读写、写读、写写操作都是冲突的,需要串行执行。可以防止脏读、不可重复度、幻读,没有并发事务问题
8.Spring 事务的实现原理
Spring 事务的底层实现主要使用的技术:AOP(动态代理) + ThreadLocal + try/catch。
动态代理:基本所有要进行逻辑增强的地方都会用到动态代理,AOP 底层也是通过动态代理实现。
ThreadLocal:主要用于线程间的资源隔离,以此实现不同线程可以使用不同的数据源、隔离级别等等。
try/catch:最终是执行 commit 还是 rollback,是根据业务逻辑处理是否抛出异常来决定
spring事务的主要流程:
1.事务拦截器被触发,调用invokeWithinTransaction
2.判断当前线程是否绑定了事务资源,如果没有绑定则走第3步,如有则直接跳到第7步。
3.使用数据源,建立数据库连接
4.根据事务参数修改事务状态
5.需要的话修改当前事务隔离级别,保存之前的事务隔离级别
6.修改事务为手动提交,同时将事务资源绑定到当前线程
7.将当前的事务状态、属性,封装成TransactionInfo保存到当前线程,将原来的事务信息保存到oldTransactionInfo
8.执行被代理的方法,事务提交或者事务回滚
9.将事务恢复成之前的样子也就是oldTransactionInfo绑定到当前线程
10.清理同步状态,释放连接,释放当前线程事务资源
9.Spring的事务的传播行为有哪些?
事务的传播行为:当一个事务方法被另外一个事务方法调用的时候,这个事务方法会怎样执行。
在spring中我们通过@Transactional(propagation = Propagation.XXX)表示,事务的某一个传播行为。A方法是我们被调用方@Transactional(propagation = Propagation.XXX),XXX是我们以下的七种情况之一,B方法是我们的事务调用方默认是@Transactional(propagation = Propagation.required)
1.required(必要):说明我们的方法A就是一个事务方法。Spring默认的传播行为
如果方法A被@Transactional(propagation = Propagation.required)修饰,在调用方法A的时候,发现A的调用方B,B方法没有事务则为A方法开启一个事务。如果B方法有事务,A就加入到B方法的事务。
2.supports(支持事务):如果调用方B有事务,A方法就加入到这个事务,如果B方法没有事务,那么A方法就以无事务执行。单独调A方法也是无事务执行
3.mandatory(强制):调用方B方法在调用A方法的时候,要求B方法必须要有事务,如果没有事务则会报错,如果单独调用A方法也会报错。
4.required_news(新增一个独立的事务):A方法在被B方法调用的时候,会自己单独新建一个事务,两个事务是隔离的,当A方法的事务提交后,B方法的事务成功与否都不影响A方法的事务提交。
5.supports_no(不支持事务):A方法在被B方法调用的时候,A方法还是以非事务方法执行
6.never(不允许事务):A方法在被B方法调用的时候,如果B方法存在事务,则会报错
7.nested(嵌套的子事务):A方法在被B方法调用的时候,外层事务失败会引起内层事务回滚,而内层事务失败,不会引起外层事务回滚。
10.BeanFactory 和 FactoryBean的区别
BeanFactory:Spring 容器最核心也是最基础的接口,本质是个工厂类,用于管理 bean 的工厂,最核心的功能是加载 bean,也就是 getBean 方法,通常我们不会直接使用该接口,而是使用其子接口
FactoryBean:该接口以 bean 样式定义,但是它不是一种普通的 bean,它是个工厂 bean,实现该接口的类可以自己定义要创建的 bean 实例,只需要实现它的 getObject 方法即可
11.Spring 怎么解决循环依赖的问题
Spring 是通过提前暴露 bean 的引用来解决的,具体如下。
Spring 首先使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存).
通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,从而避免出现循环引用导致的死循环。
这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。
12.Spring 三级缓存
Spring 的三级缓存其实就是解决循环依赖时所用到的三个缓存。
一级缓存:singletonObjects:正常情况下的 bean 被创建完毕后会被放到该缓存,key:beanName,value:完整bean 实例。
二级缓存:singletonFactories:一个不完整的bean,提前曝光的 ObjectFactory 就会被放到该缓存中,key:beanName,value:ObjectFactory。
三级缓存:earlySingletonObjects:该缓存用于存放 ObjectFactory 返回的 bean,也就是说对于一个 bean,ObjectFactory 只会被用一次,之后就通过 earlySingletonObjects 来获取,key:beanName,早期 bean 实例。
13.Spring 中的常见扩展点有哪些
1、ApplicationContextInitializer
initialize 方法,在 Spring 容器刷新前触发,也就是 refresh 方法前被触发。
2、BeanFactoryPostProcessor
postProcessBeanFactory 方法,在加载完 Bean 定义之后,创建 Bean 实例之前被触发,通常使用该扩展点来加载一些自己的 bean 定义。
3、BeanPostProcessor
postProcessBeforeInitialization 方法,执行 bean 的初始化方法前被触发;postProcessAfterInitialization 方法,执行 bean 的初始化方法后被触发。
4、@PostConstruct
该注解被封装在 CommonAnnotationBeanPostProcessor 中,具体触发时间是在 postProcessBeforeInitialization 方法。
5、InitializingBean
afterPropertiesSet 方法,在 bean 的属性填充之后,初始化方法(init-method)之前被触发,该方法的作用基本等同于 init-method,主要用于执行初始化相关操作。
6、ApplicationListener,事件监听器
onApplicationEvent 方法,根据事件类型触发时间不同,通常使用的 ContextRefreshedEvent 触发时间为上下文刷新完毕,通常用于 IoC 容器构建结束后处理一些自定义逻辑。
7、@PreDestroy
该注解被封装在 DestructionAwareBeanPostProcessor 中,具体触发时间是在 postProcessBeforeDestruction 方法,也就是在销毁对象之前触发。
8、DisposableBean
destroy 方法,在 bean 的销毁阶段被触发,该方法的作用基本等同于
destroy-method,主用用于执行销毁相关操作。