1 Spring高級(jí)部分
1.1 spring的aop順序
- 你肯定知道spring,那說說aop的全部通知順序 springboot或springboot2對(duì)aop的執(zhí)行順序影響?
- 說說你使用aop中碰到的坑
1.1.1 Aop常用注解
@Before 前置通知: 目標(biāo)方法之前執(zhí)行
@After 后置通知: 目標(biāo)方法之后執(zhí)行(始終執(zhí)行)
@AfterReturning 返回后通知: 執(zhí)行方法結(jié)束前執(zhí)行(異常不執(zhí)行)
@AfterThrowing 異常通知: 出現(xiàn)異常時(shí)候執(zhí)行
@Around 環(huán)繞通知: 環(huán)繞目標(biāo)方法執(zhí)行
1.1.2 業(yè)務(wù)類
//接口CalcService
public interface CalcService {
public int div(int x,int y);
}
//接口實(shí)現(xiàn)類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被調(diào)用了,我們的計(jì)算結(jié)果:"+result);
return result;
}
}
//想在除法方法前后各種通知,引入切面編程
新建一個(gè)切面類MyAspect并為切面類新增兩個(gè)注解
- @Aspect 指定一個(gè)類為切面類
- @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("我是環(huán)繞通知之前AAA");
retValue = proceedingJoinPoint.proceed();
System.out.println("我是環(huán)繞通知之后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默認(rèn)用的是JDK的動(dòng)態(tài)代理
1.1.4 Spring5+springboot2.3.x
aop正常順序+異常順序
spring5--aop正常流程
spring5--aop異常流程
spring5默認(rèn)動(dòng)態(tài)代理用的是cglib,不再是JDK的動(dòng)態(tài)代理,因?yàn)镴DK必須要實(shí)現(xiàn)接口,但有些類它并沒有實(shí)現(xiàn)接口,所以更加通用的話就是cglib
1.1.5 總結(jié)
1.2 spring的循環(huán)依賴
- 你解釋下spring中的三級(jí)緩存?
- 三級(jí)緩存分別是什么?三個(gè)Map有什么異同?
- 什么是循環(huán)依賴?請(qǐng)你談?wù)?看過spring源碼嗎?一般我們說的spring容器是什么
- 如何檢測(cè)是否存在循環(huán)依賴?實(shí)際開發(fā)中見過循環(huán)依賴的異常嗎?
- 多例的情況下,循環(huán)依賴問題為什么無法解決?
1.2.1 什么是循環(huán)依賴
多個(gè)bean之間相互依賴,形成了一個(gè)閉環(huán)。 比如: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容器內(nèi)部如何解決循環(huán)依賴, 一定是指默認(rèn)的單例Bean中,屬性互相引用的場(chǎng)景
也就是說,Spring的循環(huán)依賴,是Spring容器注入時(shí)候出現(xiàn)的問題
1.2.2 兩種注入方式對(duì)循環(huán)依賴的影響
循環(huán)依賴官網(wǎng)說明
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans
我們AB循環(huán)依賴問題只要A的注入方式是setter且singleton, 就不會(huì)有循環(huán)依賴問題
1.2.3 spring容器循環(huán)依賴報(bào)錯(cuò)BeanCurrentlylnCreationException
循環(huán)依賴現(xiàn)象在Spring容器中 注入依賴的對(duì)象,有2種情況
構(gòu)造器方式注入依賴
@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;
}
}
/**
* 通過構(gòu)造器的方式注入依賴,構(gòu)造器的方式注入依賴的bean,下面兩個(gè)bean循環(huán)依賴
*
* 測(cè)試后發(fā)現(xiàn),構(gòu)造器循環(huán)依賴是無法解決的
*/
public class ClientConstructor {
public static void main(String[] args) {
new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
}
}
構(gòu)造器注入沒有辦法解決循環(huán)依賴, 你想讓構(gòu)造器注入支持循環(huán)依賴,是不存在的
以set方式注入依賴
@Component
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("A 里面設(shè)置了B");
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("B 里面設(shè)置了A");
}
}
public class ClientSet {
public static void main(String[] args) {
//創(chuàng)建serviceA
ServiceA serviceA = new ServiceA();
//創(chuàng)建serviceB
ServiceB serviceB = new ServiceB();
//將serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//將serviceB注入到serviceA中
serviceA.setServiceB(serviceB);
}
}
code-java基礎(chǔ)編碼
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);
}
}
默認(rèn)的單例(singleton)的場(chǎng)景是支持循環(huán)依賴的,不報(bào)錯(cuò)
原型(Prototype)的場(chǎng)景是不支持循環(huán)依賴的,報(bào)錯(cuò)
spring內(nèi)部通過3級(jí)緩存來解決循環(huán)依賴
DefaultSingletonBeanRegistry
只有單例的Bean會(huì)通過三級(jí)緩存提前暴露來解決循環(huán)依賴的問題,而非單例的bean,每次從容器中獲取都是一個(gè)新的對(duì)象,都會(huì)重新創(chuàng)建,所有非單例的bean是沒有緩存的,不會(huì)將其放到三級(jí)緩存中。
第一級(jí)緩存〈也叫單例池)singletonObjects:存放已經(jīng)經(jīng)歷了完整生命周期的Bean對(duì)象
第二級(jí)緩存: earlySingletonObjects,存放早期暴露出來的Bean對(duì)象,Bean的生命周期未結(jié)束(屬性還未填充完整)
第三級(jí)緩存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工廠
所謂的三級(jí)緩存其實(shí)就是spring容器內(nèi)部用來解決循環(huán)依賴問題的三個(gè)map
1.2.4 循環(huán)依賴Debug
- 實(shí)例化 堆內(nèi)存中申請(qǐng)一塊內(nèi)存空間
- 初始化 屬性填充 完成屬性的各種賦值
3大Map和四大方法,總體相關(guān)對(duì)象
三級(jí)緩存+四大方法
1.getSingleton:希望從容器里面獲得單例的bean,沒有的話
2.doCreateBean: 沒有就創(chuàng)建bean
3.populateBean: 創(chuàng)建完了以后,要填充屬性
4.addSingleton: 填充完了以后,再添加到容器進(jìn)行使用
第一層singletonObjects存放的是已經(jīng)初始化好了的Bean,
第二層earlySingletonObjects存放的是實(shí)例化了,但是未初始化的Bean,
第三層singletonFactories存放的是FactoryBean。假如A類實(shí)現(xiàn)了FactoryBean,那么依賴注入的時(shí)候不是A類,而是A類產(chǎn)生的Bean
A/B兩對(duì)象在三級(jí)緩存中的遷移說明
1 A創(chuàng)建過程中需要B,于是A將自己放到三級(jí)緩存里面,去實(shí)例化B
2 B實(shí)例化的時(shí)候發(fā)現(xiàn)需要A,于是B先查一級(jí)緩存,沒有,再查二級(jí)緩存,還是沒有,再查三級(jí)緩存,找到了A
然后把三級(jí)緩存里面的這個(gè)A放到二級(jí)緩存里面,并刪除三級(jí)緩存里面的A
3 B順利初始化完畢,將自己放到一級(jí)緩存里面(此時(shí)B里面的A依然是創(chuàng)建中狀態(tài))
然后回來接著創(chuàng)建A,此時(shí)B已經(jīng)創(chuàng)建結(jié)束,直接從一級(jí)緩存里面拿到B,然后完成創(chuàng)建,并將A自己放到一級(jí)緩存里面。
@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 總結(jié)
Spring創(chuàng)建bean主要分為兩個(gè)步驟,創(chuàng)建原始bean對(duì)象,接著去填充對(duì)象屬性和初始化
每次創(chuàng)建bean之前,我們都會(huì)從緩存中查下有沒有該bean,因?yàn)槭菃卫荒苡幸粋€(gè)
當(dāng)我們創(chuàng)建 beanA的原始對(duì)象后,并把它放到三級(jí)緩存中,接下來就該填充對(duì)象屬性了,這時(shí)候發(fā)現(xiàn)依賴了beanB,接著就又去創(chuàng)建beanB,同樣的流程,創(chuàng)建完 beanB填充屬性時(shí)又發(fā)現(xiàn)它依賴了beanA又是同樣的流程,
不同的是:
這時(shí)候可以在三級(jí)緩存中查到剛放進(jìn)去的原始對(duì)象beanA,所以不需要繼續(xù)創(chuàng)建,用它注入beanB,完成beanB的創(chuàng)建
既然 beanB創(chuàng)建好了,所以beanA就可以完成填充屬性的步驟了,接著執(zhí)行剩下的邏輯,閉環(huán)完成
Spring解決循環(huán)依賴依靠的是Bean的“中間態(tài)"這個(gè)概念,而這個(gè)中間態(tài)指的是已經(jīng)實(shí)例化但還沒初始化的狀態(tài)……>半成品。
實(shí)例化的過程又是通過構(gòu)造器創(chuàng)建的,如果A還沒創(chuàng)建好出來怎么可能提前曝光,所以構(gòu)造器的循環(huán)依賴無法解決。
Spring為了解決單例的循環(huán)依賴問題,使用了三級(jí)緩存
其中一級(jí)緩存為單例池〈 singletonObjects)
二級(jí)緩存為提前曝光對(duì)象( earlySingletonObjects)
三級(jí)緩存為提前曝光對(duì)象工廠( singletonFactories)。
假設(shè)A、B循環(huán)引用,實(shí)例化A的時(shí)候就將其放入三級(jí)緩存中,接著填充屬性的時(shí)候,發(fā)現(xiàn)依賴了B,同樣的流程也是實(shí)例化后放入三級(jí)緩存,接著去填充屬性時(shí)又發(fā)現(xiàn)自己依賴A,這時(shí)候從緩存中查找到早期暴露的A,沒有AOP代理的話,直接將A的原始對(duì)象注入B,完成B的初始化后,進(jìn)行屬性填充和初始化,這時(shí)候B完成后,就去完成剩下的A的步驟,如果有AOP代理,就進(jìn)行AOP處理獲取代理后的對(duì)象A,注入B,走剩下的流程。
spring解決循環(huán)依賴的整個(gè)流程圖
Debug的步驟---->Spring解決循環(huán)依賴過程
1 調(diào)用doGetBean()方法,想要獲取beanA,于是調(diào)用getSingleton()方法從緩存中查找beanA
2 在getSingleton()方法中,從一級(jí)緩存中查找,沒有,返回null
3 doGetBean()方法中獲取到的beanA為null,于是走對(duì)應(yīng)的處理邏輯,調(diào)用getSingleton()的重載方法(參數(shù)為ObjectFactory的)
4 在getSingleton()方法中,先將beanA_name添加到一個(gè)集合中,用于標(biāo)記該bean正在創(chuàng)建中。然后回調(diào)匿名內(nèi)部類的creatBean方法
5 進(jìn)入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調(diào)用構(gòu)造器創(chuàng)建出beanA的實(shí)例,然后判斷。是否為單例、是否允許提前暴露引用(對(duì)于單例一般為true)、是否正在創(chuàng)建中〈即是否在第四步的集合中)。判斷為true則將beanA添加到【三級(jí)緩存】中
6 對(duì)beanA進(jìn)行屬性填充,此時(shí)檢測(cè)到beanA依賴于beanB,于是開始查找beanB
7 調(diào)用doGetBean()方法,和上面beanA的過程一樣,到緩存中查找beanB,沒有則創(chuàng)建,然后給beanB填充屬性
8 此時(shí)beanB依賴于beanA,調(diào)用getsingleton()獲取beanA,依次從一級(jí)、二級(jí)、三級(jí)緩存中找,此時(shí)從三級(jí)緩存中獲取到beanA的創(chuàng)建工廠,通過創(chuàng)建工廠獲取到singletonObject,此時(shí)這個(gè)singletonObject指向的就是上面在doCreateBean()方法中實(shí)例化的beanA
9 這樣beanB就獲取到了beanA的依賴,于是beanB順利完成實(shí)例化,并將beanA從三級(jí)緩存移動(dòng)到二級(jí)緩存中
10 隨后beanA繼續(xù)他的屬性填充工作,此時(shí)也獲取到了beanB,beanA也隨之完成了創(chuàng)建,回到getsingleton()方法中繼續(xù)向下執(zhí)行,將beanA從二級(jí)緩存移動(dòng)到一級(jí)緩存中