Java面試題沖刺第八天--Spring框架2
面試題1:聊一下你對AOP的理解吧?
AOP
(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(兩外兩個(gè):IOC-控制反轉(zhuǎn)、DI-依賴注入)。AOP主要應(yīng)用于處理一些具有橫切性質(zhì)的系統(tǒng)級服務(wù)
,如日志收集
、事務(wù)管理
、安全檢查(權(quán)限校驗(yàn))
、緩存
、對象池管理等
。
那么AOP是干啥的?在我們的程序中,經(jīng)常存在一些系統(tǒng)性的需求,比如權(quán)限校驗(yàn)、記錄日志等,這些代碼會散落穿插在各個(gè)業(yè)務(wù)邏輯中,非常冗余且不利于維護(hù)。例如下面這個(gè)示意圖:
有多少業(yè)務(wù)操作,就要寫多少重復(fù)的校驗(yàn)和日志記錄代碼,這顯然是無法接受的。當(dāng)然,用面向?qū)ο蟮乃枷耄覀兛梢园堰@些重復(fù)的代碼抽離出來,寫成公共方法
,就是下面這樣:
這樣,代碼冗余和可維護(hù)性的問題得到了解決,但每個(gè)業(yè)務(wù)方法中依然要依次手動調(diào)用這些公共方法,也是略顯繁瑣。有沒有更好的方式呢?有的,那就是AOP
,AOP將權(quán)限校驗(yàn)、日志記錄等非業(yè)務(wù)代碼完全提取出來,與業(yè)務(wù)代碼分離,并尋找節(jié)點(diǎn)切入業(yè)務(wù)代碼中:
使用AOP的好處主要是降低模塊的耦合度、使系統(tǒng)容易擴(kuò)展、提高代碼復(fù)用性
。
簡單地去理解,其實(shí)AOP要做三類事:
- 在哪里切入,也就是權(quán)限校驗(yàn)等非業(yè)務(wù)操作在哪些業(yè)務(wù)代碼中執(zhí)行。
- 在什么時(shí)候切入,是業(yè)務(wù)代碼執(zhí)行前還是執(zhí)行后。
- 切入后做什么事,比如做權(quán)限校驗(yàn)、日志記錄等。
因此,AOP的體系可以梳理為下圖:
AOP的一些概念:
切入點(diǎn)(Pointcut)
:決定處理如權(quán)限校驗(yàn)、日志記錄等在何處切入業(yè)務(wù)代碼中(即織入切面)。切點(diǎn)分為execution方式和annotation方式。前者可以用路徑表達(dá)式指定哪些類織入切面,后者可以指定被哪些注解修飾的代碼織入切面。- 通知(Advice):我們也叫它處理(即“切面”對于某個(gè)“連接點(diǎn)”所產(chǎn)生的動作),包括處理時(shí)機(jī)和處理內(nèi)容。處理內(nèi)容就是要做什么事,比如校驗(yàn)權(quán)限和記錄日志。處理時(shí)機(jī)就是在什么時(shí)機(jī)執(zhí)行處理內(nèi)容,分為前置處理(即業(yè)務(wù)代碼執(zhí)行前)、后置處理(業(yè)務(wù)代碼執(zhí)行后)等。
- 切面(Aspect):即Pointcut和Advice。
- 連接點(diǎn)(Joint point):是程序執(zhí)行的一個(gè)點(diǎn)。例如,一個(gè)方法的執(zhí)行或者一個(gè)異常的處理。在 Spring AOP 中,一個(gè)連接點(diǎn)總是代表一個(gè)方法執(zhí)行。
- 織入(Weaving):就是通過動態(tài)代理,在目標(biāo)對象方法中執(zhí)行處理內(nèi)容的過程。
- 目標(biāo)對象(Target Object) :被一個(gè)或者多個(gè)切面所通知的對象。
- AOP代理(AOP Proxy) 在Spring AOP中有兩種代理方式,JDK動態(tài)代理和CGLIB代理。
追問1:Advice通知的類型有哪幾種?
在AOP術(shù)語中,切面的工作被稱為通知
,實(shí)際上是程序執(zhí)行時(shí)要通過SpringAOP框架觸發(fā)的代碼段
。
Spring切面可以應(yīng)用5種類型的通知:
- 前置通知(Before):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能;
- 后置通知(After):在目標(biāo)方法完成之后調(diào)用通知,此時(shí)不會關(guān)心方法的輸出是什么;
- 返回通知(After-returning ):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知;
- 異常通知(After-throwing):在目標(biāo)方法拋出異常后調(diào)用通知;
- 環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的行為。
追問2:在同一個(gè)切面(Aspect)中,不同Advice的執(zhí)行順序
無異常情況下:
/*************不同Advice的執(zhí)行順序*****************/ 1. around before advice 2. before advice 3. target method (執(zhí)行代碼段) 4. around after advice 5. after advice /*******************前五個(gè)都一樣*******************/ 6. afterReturning /*************************************************/
有異常情況下:
/*************不同Advice的執(zhí)行順序*****************/ 1. around before advice 2. before advice 3. target method (執(zhí)行代碼段) 4. around after advice 5. after advice /*******************前五個(gè)都一樣*******************/ 6. afterThrowing:異常發(fā)生 7. java.lang.RuntimeException: 異常發(fā)生 /*************************************************/
面試題2:AspectJ AOP 和 Spring AOP 有什么區(qū)別?
AOP實(shí)現(xiàn)的關(guān)鍵在于代理模式
,AOP代理主要分為靜態(tài)代理
和動態(tài)代理
。
- 靜態(tài)代理的代表為AspectJ;
- 動態(tài)代理則以Spring AOP為代表;
Spring AOP和AspectJ有不同的目標(biāo)。
- Spring AOP旨在通過Spring IoC提供一個(gè)簡單的AOP實(shí)現(xiàn),以解決編碼人員面臨的最常出現(xiàn)的問題。這并不是完整的AOP解決方案,
它只能用于Spring容器管理的beans
。 - 另一方面,AspectJ是最原始的AOP實(shí)現(xiàn)技術(shù),提供了玩這個(gè)的AOP解決方案。AspectJ更為健壯,相對于Spring AOP也顯得更為復(fù)雜。值得注意的是,AspectJ能夠被應(yīng)用于所有的領(lǐng)域?qū)ο蟆?/li>
從原理上看:
Spring AOP
- 基于動態(tài)代理來實(shí)現(xiàn),默認(rèn)如果使用接口的,用JDK提供的動態(tài)代理實(shí)現(xiàn),如果是方法則使用CGLIB實(shí)現(xiàn)
- Spring AOP需要依賴IOC容器來管理,并且只能作用于Spring容器,使用純Java代碼實(shí)現(xiàn)
- 在性能上,由于Spring AOP是基于動態(tài)代理來實(shí)現(xiàn)的,在容器啟動時(shí)需要生成代理實(shí)例,在方法調(diào)用上也會增加棧的深度,使得Spring AOP的性能不如AspectJ的那么好
AspectJ
AspectJ屬于靜態(tài)代理(織入),通過修改代碼來實(shí)現(xiàn),有如下幾個(gè)織入的時(shí)機(jī):
編譯期織入
(Compile-time weaving): 如類 A 使用 AspectJ 添加了一個(gè)屬性,類 B 引用了它,這個(gè)場景就需要編譯期的時(shí)候就進(jìn)行織入,否則沒法編譯類 B。編譯后織入
(Post-compile weaving): 也就是已經(jīng)生成了 .class 文件,或已經(jīng)打成 jar 包了,這種情況我們需要增強(qiáng)處理的話,就要用到編譯后織入。類加載后織入
(Load-time weaving): 指的是在加載類的時(shí)候進(jìn)行織入,要實(shí)現(xiàn)這個(gè)時(shí)期的織入,有幾種常見的方法。1、自定義類加載器來干這個(gè),這個(gè)應(yīng)該是最容易想到的辦法,在被織入類加載到 JVM 前去對它進(jìn)行加載,這樣就可以在加載的時(shí)候定義行為了。2、在 JVM 啟動的時(shí)候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
AspectJ可以做Spring AOP干不了的事情,它是AOP編程的完全解決方案,Spring AOP則致力于解決企業(yè)級開發(fā)中最普遍的AOP(方法織入)。而不是成為像AspectJ一樣的AOP方案。
因?yàn)锳spectJ在實(shí)際運(yùn)行之前就完成了織入,所以說它生成的類是沒有額外運(yùn)行時(shí)開銷的
指標(biāo)項(xiàng) | Spring AOP | AspectJ |
---|---|---|
使用語言 | 在純 Java 中實(shí)現(xiàn) | 使用 Java 編程語言的擴(kuò)展實(shí)現(xiàn) |
是否需要編譯 | 不需要單獨(dú)的編譯過程 | 除非設(shè)置 LTW,否則需要 AspectJ 編譯器 (ajc) |
織入方式 | 只能使用運(yùn)行時(shí)織入 | 運(yùn)行時(shí)織入不可用。支持編譯時(shí)、編譯后和加載時(shí)織入 |
織入能力 | 功能不強(qiáng)-僅支持方法級編織 | 更強(qiáng)大 – 可以編織字段、方法、構(gòu)造函數(shù)、靜態(tài)初始值設(shè)定項(xiàng)、最終類/方法等……。 |
適用范圍 | 只能在由 Spring 容器管理的 bean 上實(shí)現(xiàn) | 可以在所有域?qū)ο笊蠈?shí)現(xiàn) |
切入點(diǎn)要求 | 僅支持方法執(zhí)行切入點(diǎn) | 支持所有切入點(diǎn) |
代理局限 | 代理是由目標(biāo)對象創(chuàng)建的, 并且切面應(yīng)用在這些代理上 | 在執(zhí)行應(yīng)用程序之前 (在運(yùn)行時(shí)) 前, 各方面直接在代碼中進(jìn)行織入 |
性能 | 比 AspectJ 慢很多 | 更好的性能 |
復(fù)雜度 | 易于學(xué)習(xí)和應(yīng)用 | 相對于 Spring AOP 來說更復(fù)雜 |
追問1:了解JDK動態(tài)代理和CGLIB動態(tài)代理的原理么?他倆有哪些區(qū)別?
Spring AOP中的動態(tài)代理主要有兩種方式,JDK動態(tài)代理
和CGLIB動態(tài)代理
:
JDK動態(tài)代理
:是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用 InvokeHandler 來處理,他有一個(gè)限制,就是它只能為接口創(chuàng)建代理實(shí)例,那么對于沒有通過接口定義業(yè)務(wù)方法的類
,就要用CGLIB動態(tài)代理了
。 CGLIB(Code Generation Library)動態(tài)代理:是一個(gè)基于ASM的字節(jié)碼生成庫,它允許我們在運(yùn)行時(shí)對字節(jié)碼進(jìn)行修改和動態(tài)生成。CGLIB通過繼承方式實(shí)現(xiàn)代理,在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用并順勢織入橫切邏輯。
JDK動態(tài)代理具體實(shí)現(xiàn)原理:
- 通過實(shí)現(xiàn)InvocationHandler接口創(chuàng)建自己的調(diào)用處理器;
- 通過為Proxy類指定ClassLoader對象和一組interface來創(chuàng)建動態(tài)代理;
- 通過反射機(jī)制獲取動態(tài)代理類的構(gòu)造函數(shù),其唯一參數(shù)類型就是調(diào)用處理器接口類型;
- 通過構(gòu)造函數(shù)創(chuàng)建動態(tài)代理類實(shí)例,構(gòu)造時(shí)調(diào)用處理器對象作為參數(shù)參入;
JDK動態(tài)代理是面向接口的代理模式,如果被代理目標(biāo)沒有接口那么Spring也無能為力,Spring通過Java的反射機(jī)制生產(chǎn)被代理接口的新的匿名實(shí)現(xiàn)類,重寫了其中AOP的增強(qiáng)方法。
CGLib動態(tài)代理:
利用ASM開源包,對代理對象類的class文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理。
如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下會采用JDK的動態(tài)代理實(shí)現(xiàn)AOP;
如果目標(biāo)對象實(shí)現(xiàn)了接口,可以強(qiáng)制使用CGLIB實(shí)現(xiàn)AOP;
如果目標(biāo)對象
沒有實(shí)現(xiàn)了接口,必須采用CGLIB庫,spring會自動在JDK動態(tài)代理和CGLIB之間轉(zhuǎn)換;
3、兩者對比:
- JDK動態(tài)代理是
面向接口的。
- CGLib動態(tài)代理是通過字節(jié)碼底層繼承要代理類來實(shí)現(xiàn)(
對指定的類生成一個(gè)子類,覆蓋其中的方法
),因此如果被代理類被final關(guān)鍵字所修飾,會失敗。
面試題3:什么是基于Java的Spring注解配置? 給一些注解的例子
基于Java的配置,允許你在少量的Java注解的幫助下進(jìn)行大部分Spring配置,而非通過XML文件。當(dāng)然,也不不建議啥都用注解配置,畢竟如果修改就要動class文件很麻煩。因此建議:不會修改、極少修改的用注解,會修改的用xml配置,如AOP的配置我就用XML,因?yàn)檫@個(gè)需要改的場景比較多。
以@Configuration 注解
為例,它用來標(biāo)記類可以當(dāng)做一個(gè)bean的定義,被Spring IOC容器使用。另一個(gè)是通過@Bean注解
,它表示此方法將要返回一個(gè)對象,作為一個(gè)bean注冊進(jìn)Spring應(yīng)用上下文。
@Configuration public class StudentConfig { @Bean public StudentBean myStudent() { return new StudentBean(); }
怎樣開啟注解裝配呢?
注解裝配在默認(rèn)情況下是不開啟的,為了使用注解裝配,我們必須在Spring配置文件中配置 <context:annotation-config/>元素。
一些常見的注解:
1、@Component:
這將 java 類標(biāo)記為 bean。它是任何 Spring 管理組件的通用構(gòu)造型。spring 的組件掃描機(jī)制現(xiàn)在可以將其拾取并將其拉入應(yīng)用程序環(huán)境中。
2、@Controller:
這將一個(gè)類標(biāo)記為 Spring Web MVC 控制器。標(biāo)有它的 Bean 會自動導(dǎo)入到 IoC 容器中。
3、@Service:
此注解是組件注解的特化。它不會對 @Component 注解提供任何其他行為。我們可以在服務(wù)層類中使用 @Service 而不是 @Component,因?yàn)樗愿玫姆绞街付艘鈭D。
4、@Repository:
這個(gè)注解是具有類似用途和功能的 @Component 注解的特化。它為 DAO 提供了額外的好處。它將 DAO 導(dǎo)入 IoC 容器,并使未經(jīng)檢查的異常有資格轉(zhuǎn)換為 Spring DataAccessException。
5、@Required :
這個(gè)注解表明bean的屬性必須在配置的時(shí)候設(shè)置,通過一個(gè)bean定義的顯式的屬性值或通過自動裝配,若@Required注解的bean屬性未被設(shè)置,容器將拋出BeanInitializationException
。
示例:
public class Employee { private String name; @Required public void setName(String name){ this.name=name; } public string getName(){ return name; } }
6、@Autowired :
@Autowired默認(rèn)是按照類型裝配注入的,默認(rèn)情況下它要求依賴對象必須存在(可以設(shè)置它required屬性為false)。@Autowired 注解提供了更細(xì)粒度的控制,包括在何處以及如何完成自動裝配。
它的用法和@Required一樣,修飾setter方法、構(gòu)造器、屬性或者具有任意名稱和/或多個(gè)參數(shù)的PN方法。
public class Employee { private String name; @Autowired public void setName(String name) { this.name=name; } public string getName(){ return name; }
@Autowired和@Resource
的區(qū)別
@Autowired可用于:構(gòu)造函數(shù)、成員變量、Setter方法
@Autowired和@Resource之間的區(qū)別
@Autowired
:默認(rèn)是按照類型
裝配注入的,默認(rèn)情況下它要求依賴對象必須存在(可以設(shè)置它required屬性為false)。
@Resource
:默認(rèn)是按照名稱
來裝配注入的,只有當(dāng)找不到與名稱匹配的bean才會按照類型來裝配注入。
7、@Qualifier :
當(dāng)創(chuàng)建多個(gè)相同類型的 bean 并希望僅使用屬性裝配其中一個(gè) bean 時(shí),可以使用@Qualifier 注解和 @Autowired 通過指定應(yīng)該裝配哪個(gè)確切的 bean 來消除歧義。
8、@RequestMapping :
@RequestMapping 注解用于將特定 HTTP 請求方法映射到將處理相應(yīng)請求的控制器中的特定類/方法。此注釋可應(yīng)用于兩個(gè)級別:
- 類級別:映射請求的URL
- 方法級別:映射 URL 以及 HTTP 請求方法
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更對內(nèi)容!
相關(guān)文章
Java日期工具類時(shí)間校驗(yàn)實(shí)現(xiàn)
一般項(xiàng)目中需要對入?yún)⑦M(jìn)行校驗(yàn),比如必須是一個(gè)合法的日期,本文就來介紹一下Java日期工具類時(shí)間校驗(yàn)實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12淺談Java中BIO、NIO和AIO的區(qū)別和應(yīng)用場景
這篇文章主要介紹了Java中BIO、NIO和AIO的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Java基于分治算法實(shí)現(xiàn)的線性時(shí)間選擇操作示例
這篇文章主要介紹了Java基于分治算法實(shí)現(xiàn)的線性時(shí)間選擇操作,涉及java排序、比較、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11SpringCloud配置服務(wù)端的ConfigServer設(shè)置安全認(rèn)證
這篇文章主要為大家介紹了SpringCloud配置服務(wù)端的ConfigServer設(shè)置安全認(rèn)證,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08