深入分析大廠面試題四

1 Spring高級部分

1.1 spring的aop順序

  • 你肯定知道spring,那說說aop的全部通知順序 springboot或springboot2對aop的執(zhí)行順序影響皱蹦?
  • 說說你使用aop中碰到的坑

1.1.1 Aop常用注解

@Before  前置通知: 目標方法之前執(zhí)行
@After   后置通知: 目標方法之后執(zhí)行(始終執(zhí)行)
@AfterReturning 返回后通知: 執(zhí)行方法結(jié)束前執(zhí)行(異常不執(zhí)行)
@AfterThrowing  異常通知: 出現(xiàn)異常時候執(zhí)行
@Around  環(huán)繞通知: 環(huán)繞目標方法執(zhí)行

1.1.2 業(yè)務(wù)類

//接口CalcService
public interface CalcService {
    public int div(int x,int y);
}

//接口實現(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)用了,我們的計算結(jié)果:"+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("我是環(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

正常

image-20210122143701670.png

異常

image-20210122143719917.png

spring4默認用的是JDK的動態(tài)代理

1.1.4 Spring5+springboot2.3.x

aop正常順序+異常順序

spring5--aop正常流程

image-20210122143840875.png

spring5--aop異常流程

image-20210122143915081.png

spring5默認動態(tài)代理用的是cglib,不再是JDK的動態(tài)代理枯途,因為JDK必須要實現(xiàn)接口籍滴,但有些類它并沒有實現(xiàn)接口,所以更加通用的話就是cglib

1.1.5 總結(jié)

image-20210122144014724.png

1.2 spring的循環(huán)依賴

  • 你解釋下spring中的三級緩存?
  • 三級緩存分別是什么?三個Map有什么異同?
  • 什么是循環(huán)依賴?請你談?wù)?看過spring源碼嗎?一般我們說的spring容器是什么
  • 如何檢測是否存在循環(huán)依賴?實際開發(fā)中見過循環(huán)依賴的異常嗎?
  • 多例的情況下晚岭,循環(huán)依賴問題為什么無法解決?

1.2.1 什么是循環(huán)依賴

多個bean之間相互依賴勋功,形成了一個閉環(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)依賴借跪, 一定是指默認的單例Bean中酌壕,屬性互相引用的場景

image-20210122144611965.png

也就是說歇由,Spring的循環(huán)依賴沦泌,是Spring容器注入時候出現(xiàn)的問題

1.2.2 兩種注入方式對循環(huán)依賴的影響

循環(huán)依賴官網(wǎng)說明

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans

image-20210122144707814.png

我們AB循環(huán)依賴問題只要A的注入方式是setter且singleton, 就不會有循環(huán)依賴問題

1.2.3 spring容器循環(huán)依賴報錯BeanCurrentlylnCreationException

image-20210122145608897.png

循環(huán)依賴現(xiàn)象在Spring容器中 注入依賴的對象谢谦,有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,下面兩個bean循環(huán)依賴
 *
 * 測試后發(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);
    }
}

默認的單例(singleton)的場景是支持循環(huán)依賴的,不報錯

原型(Prototype)的場景是不支持循環(huán)依賴的淋袖,報錯

spring內(nèi)部通過3級緩存來解決循環(huán)依賴

DefaultSingletonBeanRegistry

只有單例的Bean會通過三級緩存提前暴露來解決循環(huán)依賴的問題,而非單例的bean即碗,每次從容器中獲取都是一個新的對象,都會重新創(chuàng)建内舟,所有非單例的bean是沒有緩存的,不會將其放到三級緩存中验游。

第一級緩存〈也叫單例池)singletonObjects:存放已經(jīng)經(jīng)歷了完整生命周期的Bean對象
第二級緩存: earlySingletonObjects耕蝉,存放早期暴露出來的Bean對象夜只,Bean的生命周期未結(jié)束(屬性還未填充完整)
第三級緩存: Map<String, ObiectFactory<?>> singletonFactories垒在,存放可以生成Bean的工廠
image-20210122145721952.png

所謂的三級緩存其實就是spring容器內(nèi)部用來解決循環(huán)依賴問題的三個map

1.2.4 循環(huán)依賴Debug

  • 實例化 堆內(nèi)存中申請一塊內(nèi)存空間
  • 初始化 屬性填充 完成屬性的各種賦值

3大Map和四大方法场躯,總體相關(guān)對象

image-20210122145958153.png
三級緩存+四大方法
                                                             1.getSingleton:希望從容器里面獲得單例的bean踢关,沒有的話
                                                             2.doCreateBean: 沒有就創(chuàng)建bean
                                                             3.populateBean: 創(chuàng)建完了以后,要填充屬性
                                                             4.addSingleton: 填充完了以后榔昔,再添加到容器進行使用
                                                             
第一層singletonObjects存放的是已經(jīng)初始化好了的Bean,
第二層earlySingletonObjects存放的是實例化了瘪菌,但是未初始化的Bean,
第三層singletonFactories存放的是FactoryBean嘹朗。假如A類實現(xiàn)了FactoryBean,那么依賴注入的時候不是A類,而是A類產(chǎn)生的Bean
image-20210122150056564.png

A/B兩對象在三級緩存中的遷移說明

1 A創(chuàng)建過程中需要B默穴,于是A將自己放到三級緩存里面蓄诽,去實例化B
 
2 B實例化的時候發(fā)現(xiàn)需要A媒吗,于是B先查一級緩存,沒有闸英,再查二級緩存,還是沒有出吹,再查三級緩存辙喂,找到了A
然后把三級緩存里面的這個A放到二級緩存里面,并刪除三級緩存里面的A
 
3 B順利初始化完畢巍耗,將自己放到一級緩存里面(此時B里面的A依然是創(chuàng)建中狀態(tài))
然后回來接著創(chuàng)建A芍锦,此時B已經(jīng)創(chuàng)建結(jié)束娄琉,直接從一級緩存里面拿到B吓歇,然后完成創(chuàng)建票腰,并將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 總結(jié)

Spring創(chuàng)建bean主要分為兩個步驟测柠,創(chuàng)建原始bean對象轰胁,接著去填充對象屬性和初始化

每次創(chuàng)建bean之前芒涡,我們都會從緩存中查下有沒有該bean趾痘,因為是單例擎颖,只能有一個

當我們創(chuàng)建 beanA的原始對象后榛斯,并把它放到三級緩存中,接下來就該填充對象屬性了搂捧,這時候發(fā)現(xiàn)依賴了beanB驮俗,接著就又去創(chuàng)建beanB,同樣的流程允跑,創(chuàng)建完 beanB填充屬性時又發(fā)現(xiàn)它依賴了beanA又是同樣的流程王凑,
不同的是:
這時候可以在三級緩存中查到剛放進去的原始對象beanA,所以不需要繼續(xù)創(chuàng)建吮蛹,用它注入beanB荤崇,完成beanB的創(chuàng)建

既然 beanB創(chuàng)建好了潮针,所以beanA就可以完成填充屬性的步驟了,接著執(zhí)行剩下的邏輯矗晃,閉環(huán)完成
image-20210122150710112.png
Spring解決循環(huán)依賴依靠的是Bean的“中間態(tài)"這個概念阔逼,而這個中間態(tài)指的是已經(jīng)實例化但還沒初始化的狀態(tài)……>半成品摩疑。
實例化的過程又是通過構(gòu)造器創(chuàng)建的片排,如果A還沒創(chuàng)建好出來怎么可能提前曝光冶共,所以構(gòu)造器的循環(huán)依賴無法解決庙楚。
 
Spring為了解決單例的循環(huán)依賴問題纳账,使用了三級緩存
其中一級緩存為單例池〈 singletonObjects)
二級緩存為提前曝光對象( earlySingletonObjects)
三級緩存為提前曝光對象工廠( singletonFactories)呢袱。
 
假設(shè)A哼御、B循環(huán)引用挟炬,實例化A的時候就將其放入三級緩存中粥喜,接著填充屬性的時候,發(fā)現(xiàn)依賴了B磺樱,同樣的流程也是實例化后放入三級緩存活孩,接著去填充屬性時又發(fā)現(xiàn)自己依賴A警儒,這時候從緩存中查找到早期暴露的A,沒有AOP代理的話,直接將A的原始對象注入B扩氢,完成B的初始化后,進行屬性填充和初始化,這時候B完成后,就去完成剩下的A的步驟舍咖,如果有AOP代理排霉,就進行AOP處理獲取代理后的對象A冒滩,注入B士八,走剩下的流程颈嚼。

spring解決循環(huán)依賴的整個流程圖

Debug的步驟---->Spring解決循環(huán)依賴過程

1 調(diào)用doGetBean()方法,想要獲取beanA健霹,于是調(diào)用getSingleton()方法從緩存中查找beanA
2 在getSingleton()方法中旺上,從一級緩存中查找,沒有糖埋,返回null
3 doGetBean()方法中獲取到的beanA為null宣吱,于是走對應(yīng)的處理邏輯,調(diào)用getSingleton()的重載方法(參數(shù)為ObjectFactory的)
4 在getSingleton()方法中瞳别,先將beanA_name添加到一個集合中凌节,用于標記該bean正在創(chuàng)建中钦听。然后回調(diào)匿名內(nèi)部類的creatBean方法
5 進入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調(diào)用構(gòu)造器創(chuàng)建出beanA的實例倍奢,然后判斷朴上。是否為單例、是否允許提前暴露引用(對于單例一般為true)卒煞、是否正在創(chuàng)建中〈即是否在第四步的集合中)痪宰。判斷為true則將beanA添加到【三級緩存】中
6 對beanA進行屬性填充,此時檢測到beanA依賴于beanB畔裕,于是開始查找beanB
7 調(diào)用doGetBean()方法衣撬,和上面beanA的過程一樣,到緩存中查找beanB扮饶,沒有則創(chuàng)建具练,然后給beanB填充屬性
8 此時beanB依賴于beanA,調(diào)用getsingleton()獲取beanA甜无,依次從一級扛点、二級、三級緩存中找岂丘,此時從三級緩存中獲取到beanA的創(chuàng)建工廠陵究,通過創(chuàng)建工廠獲取到singletonObject,此時這個singletonObject指向的就是上面在doCreateBean()方法中實例化的beanA
9 這樣beanB就獲取到了beanA的依賴奥帘,于是beanB順利完成實例化铜邮,并將beanA從三級緩存移動到二級緩存中
10 隨后beanA繼續(xù)他的屬性填充工作,此時也獲取到了beanB寨蹋,beanA也隨之完成了創(chuàng)建松蒜,回到getsingleton()方法中繼續(xù)向下執(zhí)行,將beanA從二級緩存移動到一級緩存中
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末已旧,一起剝皮案震驚了整個濱河市秸苗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌评姨,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萤晴,死亡現(xiàn)場離奇詭異吐句,居然都是意外死亡,警方通過查閱死者的電腦和手機店读,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門嗦枢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屯断,你說我怎么就攤上這事文虏÷屡担” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵氧秘,是天一觀的道長年鸳。 經(jīng)常有香客問我,道長丸相,這世上最難降的妖魔是什么搔确? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮灭忠,結(jié)果婚禮上膳算,老公的妹妹穿的比我還像新娘。我一直安慰自己弛作,他們只是感情好涕蜂,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著映琳,像睡著了一般机隙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刊头,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天黍瞧,我揣著相機與錄音,去河邊找鬼原杂。 笑死印颤,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的穿肄。 我是一名探鬼主播年局,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咸产!你這毒婦竟也來了矢否?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤脑溢,失蹤者是張志新(化名)和其女友劉穎僵朗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屑彻,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡验庙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了社牲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粪薛。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搏恤,靈堂內(nèi)的尸體忽然破棺而出违寿,到底是詐尸還是另有隱情湃交,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布藤巢,位于F島的核電站搞莺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏菌瘪。R本人自食惡果不足惜腮敌,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俏扩。 院中可真熱鬧糜工,春花似錦、人聲如沸录淡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫉戚。三九已至刨裆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間彬檀,已是汗流浹背帆啃。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窍帝,地道東北人努潘。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像坤学,于是被迫代替她去往敵國和親疯坤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容