1 Spring高级部分
1.1 spring的aop顺序
- 你肯定知道spring,那说说aop的全部通知顺序 springboot或springboot2对aop的执行顺序影响?
- 说说你使用aop中碰到的坑
1.1.1 Aop常用注解
@Before 前置通知: 目标方法之前执行
@After 后置通知: 目标方法之后执行(始终执行)
@AfterReturning 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing 异常通知: 出现异常时候执行
@Around 环绕通知: 环绕目标方法执行
1.1.2 业务类
//接口CalcService
public interface CalcService {
public int div(int x,int y);
}
//接口实现类CalcServicelmpl
import org.springframework.stereotype.Service;
@Service
public class CalcServiceImpl implements CalcService {
@Override
public int div(int x, int y) {
int result = x / y;
System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:"+result);
return result;
}
}
//想在除法方法前后各种通知,引入切面编程
新建一个切面类MyAspect并为切面类新增两个注解
- @Aspect 指定一个类为切面类
- @Component 纳入spring容器管理
@Aspect
@Component
public class MyAspect {
@Before("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
public void beforeNotify() {
System.out.println("******** @Before我是前置通知MyAspect");
}
@After("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
public void afterNotify() {
System.out.println("******** @After我是后置通知");
}
@AfterReturning("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
public void afterReturningNotify() {
System.out.println("********@AfterReturning我是返回后通知");
}
@AfterThrowing("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
public void afterThrowingNotify() {
System.out.println("********@AfterThrowing我是异常通知");
}
@Around("execution(public int com.xubh.study.service.impl.CalcServiceImpl.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object retValue = null;
System.out.println("我是环绕通知之前AAA");
retValue = proceedingJoinPoint.proceed();
System.out.println("我是环绕通知之后BBB");
return retValue;
}
}
1.1.3 Spring4+springboot1.5.x
@SpringBootTest
@RunWith(SpringRunner.class)
public class T1{
@Autowired
private CalcService service;
System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());
System.out.println();
calcService.div(10,2);
}
aop正常顺序+异常顺序
@Before
method.invode(obj, args);
@AfterReturning
{catch(
@AfterThrowing
)}
@After
正常
异常
spring4默认用的是JDK的动态代理
1.1.4 Spring5+springboot2.3.x
aop正常顺序+异常顺序
spring5--aop正常流程
spring5--aop异常流程
spring5默认动态代理用的是cglib,不再是JDK的动态代理,因为JDK必须要实现接口,但有些类它并没有实现接口,所以更加通用的话就是cglib
1.1.5 总结
1.2 spring的循环依赖
- 你解释下spring中的三级缓存?
- 三级缓存分别是什么?三个Map有什么异同?
- 什么是循环依赖?请你谈谈?看过spring源码吗?一般我们说的spring容器是什么
- 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
- 多例的情况下,循环依赖问题为什么无法解决?
1.2.1 什么是循环依赖
多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
public class T1 {
class A {
B b;
}
class B {
C c;
}
class C {
A a;
}
}
比如:A依赖于B、B依赖于C、C依赖于A
通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景
也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题
1.2.2 两种注入方式对循环依赖的影响
循环依赖官网说明
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans
我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题
1.2.3 spring容器循环依赖报错BeanCurrentlylnCreationException
循环依赖现象在Spring容器中 注入依赖的对象,有2种情况
构造器方式注入依赖
@Component
public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
/**
* 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
*
* 测试后发现,构造器循环依赖是无法解决的
*/
public class ClientConstructor {
public static void main(String[] args) {
new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
}
}
构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的
以set方式注入依赖
@Component
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("A 里面设置了B");
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("B 里面设置了A");
}
}
public class ClientSet {
public static void main(String[] args) {
//创建serviceA
ServiceA serviceA = new ServiceA();
//创建serviceB
ServiceB serviceB = new ServiceB();
//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);
}
}
code-java基础编码
public class A {
private B b;
public B getB(){
return b;
}
public void setB(B b){
this.b = b;
}
public A(){
System.out.println("---A created success");
}
}
public class B {
private A a;
public A getA(){
return a;
}
public void setA(A a){
this.a = a;
}
public B(){
System.out.println("---B created success");
}
}
public class ClientCode {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
}
默认的单例(singleton)的场景是支持循环依赖的,不报错
原型(Prototype)的场景是不支持循环依赖的,报错
spring内部通过3级缓存来解决循环依赖
DefaultSingletonBeanRegistry
只有单例的Bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所有非单例的bean是没有缓存的,不会将其放到三级缓存中。
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂
所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map
1.2.4 循环依赖Debug
- 实例化 堆内存中申请一块内存空间
- 初始化 属性填充 完成属性的各种赋值
3大Map和四大方法,总体相关对象
三级缓存+四大方法
1.getSingleton:希望从容器里面获得单例的bean,没有的话
2.doCreateBean: 没有就创建bean
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后,再添加到容器进行使用
第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
A/B两对象在三级缓存中的迁移说明
1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
1.2.5 总结
Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化
每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个
当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,
不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建
既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
Spring解决循环依赖依靠的是Bean的“中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。
实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题,使用了三级缓存
其中一级缓存为单例池〈 singletonObjects)
二级缓存为提前曝光对象( earlySingletonObjects)
三级缓存为提前曝光对象工厂( singletonFactories)。
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。
spring解决循环依赖的整个流程图
Debug的步骤---->Spring解决循环依赖过程
1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
2 在getSingleton()方法中,从一级缓存中查找,没有,返回null
3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中