求求你,下次面試別再問我什麼是 Spring AOP 和代理了!
來源:程式設計師私房菜(ID:eson_15)
我們知道,Spring 中 AOP 是一大核心技術,也是面試中經常會被問到的問題,最近我在網上也看到很多面試題,其中和 Spring AOP 相關的就有不少,這篇文章主要來總結下相關的技術點,希望對大家有用。
0. 幾個常見的問題
針對這一塊的東西,一般下面幾個問題面試官問的比較多:
* Spring AOP用的是哪種設計模式?
* 談談你對代理模式的理解?
* 靜態代理和動態代理有什麼區別?
* 如何實現動態代理?
* Spring AOP中用的是哪種代理技術?
如果這些問題都能回答的很流暢的話,說明對代理這一塊的基本知識有一定的瞭解了。因為我們在實際開發中,寫業務程式碼會更多,所以這一塊的東西,大部分人可能知道個一二,但是如果讓他們很有條理的表達出來,可能就不那麼容易了。
1. 什麼是 Spring AOP?
一般面試官問到這個問題,面試者基本上都會回答:AOP 就是面向切面程式設計。其實這真的是句廢話,這麼回答真的沒有任何意義。
或許你可以給面試官舉個例子:歌星都有好多助理,歌星最重要的一件事就是唱歌,其他事他不用關注,比如唱歌前可能需要和其他人談合作,還要佈置場地,唱歌后還要收錢等等,這些統統交給他對應的助理去做。也許哪一天,這個歌星做慈善,免費唱歌了,不收錢了,那麼就可以把收錢這個助力給辭退了。這就是 AOP,每個人各司其職,靈活組合,達到一種可配置的、可插拔的程式結構。AOP 的實現原理就是代理模式。
在程式中也是如此,通過代理,可以詳細控制訪問某個或者某類物件的方法,在呼叫這個方法前做前置處理,呼叫這個方法後做後置處理。
2. 什麼是代理模式?
代理模式的核心作用就是通過代理,控制對物件的訪問。它的設計思路是:定義一個抽象角色,讓代理角色和真實角色分別去實現它。
真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色呼叫。它只關注真正的業務邏輯,比如歌星唱歌。
代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並在前後可以附加自己的操作,比如談合同,佈置場地,收錢等等。
這就是代理模式的設計思路。代理模式分為靜態代理和動態代理。靜態代理是我們自己建立一個代理類,而動態代理是程式自動幫我們生成一個代理,我們就不用管了。下面我詳細介紹一下這兩種代理模式。
3. 靜態代理模式
就舉明星唱歌這個例子,根據上面提供的設計思路,首先我們需要建立明星這個抽象角色,
/**
* 明星介面類
* @author shengwu ni
* @date 2018-12-07
*/
public interface Star {
/**
* 唱歌方法
*/
void sing();
}
靜態代理需要建立真實角色和代理角色,分別實現唱歌這個介面,真實角色很簡單,直接實現即可,因為真實角色的主要任務就是唱歌。
/**
* 真實明星類
* @author shengwu ni
* @date 2018-12-08
*/
public class RealStar implements Star {
@Override
public void sing() {
System.out.println("明星本人開始唱歌……");
}
}
代理類就需要做點工作了,我們思考一下,代理只是在明星唱歌前後做一些準備和收尾的事,唱歌這件事還得明星親自上陣,代理做不了。所以代理類裡面是肯定要將真實的物件傳進來。有了思路,我們將代理類寫出來。
/**
* 明星的靜態代理類
*
* @author shengwu ni
* @date 2018-12-08
*/
public class ProxyStar implements Star {
/**
* 接收真實的明星物件
*/
private Star star;
/**
* 通過構造方法傳進來真實的明星物件
* @param star star
*/
public ProxyStar(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("代理先進行談判……");
// 唱歌只能明星自己唱
this.star.sing();
System.out.println("演出完代理去收錢……");
}
}
這樣的話,邏輯就非常清晰了。在代理類中,可以看到,維護了一個Star物件,通過構造方法傳進來一個真實的Star物件給其賦值,然後在唱歌這個方法裡,使用真實物件來唱歌。所以說面談、收錢都是由代理物件來實現的,唱歌是代理物件讓真實物件來做。下面寫個客戶端測試下。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {
/**
* 測試靜態代理結果
* @param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = new ProxyStar(realStar);
proxy.sing();
}
}
讀者可以自己執行下結果,靜態代理比較簡單。動態代理比靜態代理使用的更廣泛,動態代理在本質上,代理類不用我們來管,我們完全交給工具去生成代理類即可。動態代理一般有兩種方式:JDK 動態代理和 CGLIB 動態代理。
4. JDK 動態代理
既然動態代理不需要我們去建立代理類,那我們只需要編寫一個動態處理器就可以了。真正的代理物件由 JDK 在執行時為我們動態的來建立。
/**
* 動態代理處理類
*
* @author shengwu ni
* @date 2018-12-08
*/
public class JdkProxyHandler {
/**
* 用來接收真實明星物件
*/
private Object realStar;
/**
* 通過構造方法傳進來真實的明星物件
*
* @param star star
*/
public JdkProxyHandler(Star star) {
super();
this.realStar = star;
}
/**
* 給真實物件生成一個代理物件例項
*
* @return Object
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(realStar.getClass().getClassLoader(),
realStar.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("代理先進行談判……");
// 唱歌需要明星自己來唱
Object object = method.invoke(realStar, args);
System.out.println("演出完代理去收錢……");
return object;
});
}
}
這裡說一下 Proxy.newProxyInstance() 方法,該方法接收三個引數:第一個引數指定當前目標物件使用的類載入器,獲取載入器的方法是固定的;第二個引數指定目標物件實現的介面的型別;第三個引數指定動態處理器,執行目標物件的方法時,會觸發事件處理器的方法。網上針對第三個引數的寫法都是 new 一個匿名類來處理,我這直接用的 Java8 裡面的 lamda 表示式來寫的,都一樣。底層原理使用的是反射機制。接下來寫一個客戶端程式來測試下。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {
/**
* 測試JDK動態代理結果
* @param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
// 建立一個代理物件例項
Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();
proxy.sing();
}
}
可以看出,建立一個真實的物件,送給 JdkProxyHandler 就可以建立一個代理物件了。
我們對 JDK 動態代理做一個簡單的總結:相對於靜態代理,JDK 動態代理大大減少了我們的開發任務,同時減少了對業務介面的依賴,降低了耦合度。JDK 動態代理是利用反射機制生成一個實現代理介面的匿名類,在呼叫具體方法前呼叫InvokeHandler 來處理。但是 JDK 動態代理有個缺憾,或者說特點:JDK 實現動態代理需要實現類通過介面定義業務方法。也就是說它始終無法擺脫僅支援 interface 代理的桎梏,因為它的設計就註定了這個遺憾。
5. CGLIB 動態代理
由上面的分析可知,JDK 實現動態代理需要實現類通過介面定義業務方法,那對於沒有介面的類,如何實現動態代理呢,這就需要 CGLIB 了。
CGLIB 採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。我們來寫一個 CBLIB 代理類。
/**
* cglib代理處理類
* @author shengwu ni
* @date 2018-12-08
*/
public class CglibProxyHandler implements MethodInterceptor {
/**
* 維護目標物件
*/
private Object target;
public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer類是CGLIB中的一個位元組碼增強器,它可以方便的對你想要處理的類進行擴充套件
Enhancer enhancer = new Enhancer();
// 將被代理的物件設定成父類
enhancer.setSuperclass(this.target.getClass());
// 回撥方法,設定攔截器
enhancer.setCallback(this);
// 動態建立一個代理類
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("代理先進行談判……");
// 唱歌需要明星自己來唱
Object result = methodProxy.invokeSuper(object, args);
System.out.println("演出完代理去收錢……");
return result;
}
}
使用 CGLIB 需要實現 MethodInterceptor 介面,並重寫intercept 方法,在該方法中對原始要執行的方法前後做增強處理。該類的代理物件可以使用程式碼中的位元組碼增強器來獲取。接下來寫個客戶端測試程式。
/**
* 測試客戶端
* @author shengwu ni
* @date 2018-12-08
*/
public class Client {
/**
* 測試Cglib動態代理結果
* @param args args
*/
public static void main(String[] args) {
Star realStar = new RealStar();
Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);
proxy.sing();
}
}
這個客戶端測試程式和 JDK 動態代理的邏輯一模一樣,所以也可以看出,代理模式中的動態代理,其實套路都是相同的,只是使用了不同的技術而已。
我們也對 CGLIB 動態代理做一下總結:CGLIB 建立的動態代理物件比 JDK 建立的動態代理物件的效能更高,但是 CGLIB 建立代理物件時所花費的時間卻比 JDK 多得多。所以對於單例的物件,因為無需頻繁建立物件,用 CGLIB 合適,反之使用JDK方式要更為合適一些。同時由於 CGLIB 由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。
當然了,不管是哪種動態代理技術,在上面的程式碼裡,要代理的類中可能不止一種方法,有時候我們需要對特定的方法進行增強處理,所以可以對傳入的 method 引數進行方法名的判斷,再做相應的處理。
6. Spring AOP 採用哪種代理?
JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。對於這一塊內容,面試官問的比較多,他們往往更想聽聽面試者是怎麼回答的,有沒有看過這一塊的原始碼等等。
針對於這一塊內容,我們看一下 Spring 5 中對應的原始碼是怎麼說的。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判斷目標類是否是介面或者目標類是否Proxy型別,若是則使用JDK動態代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 配置了使用CGLIB進行動態代理或者目標類沒有介面,那麼使用CGLIB的方式建立代理物件
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面的三個方法沒有一個為true,那使用JDK的提供的代理方式生成代理物件
return new JdkDynamicAopProxy(config);
}
}
//其他方法略……
}
從上述原始碼片段可以看出,是否使用 CGLIB 是在程式碼中進行判斷的,判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 與 config.isProxyTargetClass()預設返回都是 false,這種情況下判斷結果就由
hasNoUserSuppliedProxyInterfaces(config)的結果決定了。
簡單來說,
hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的物件是否有實現介面,有實現介面的話直接走 JDK 分支,即使用 JDK 的動態代理。
所以基本上可以總結出 Spring AOP 中的代理使用邏輯了:如果目標物件實現了介面,預設情況下會採用 JDK 的動態代理實現 AOP;如果目標物件沒有實現了介面,則採用 CGLIB 庫,Spring 會自動在 JDK 動態代理和 CGLIB 動態代理之間轉換。
當然,原始碼我也沒讀那麼深,暫且就只能寫到這,後面深入了,有新的見解再給大家分享。還記得文章開頭的幾個問題嗎?相信你讀到這裡,心中應該已經有了答案了。
如果覺得對自己有幫助,可以轉發給更多的夥伴們。
Java團長
專注於Java乾貨分享
掃描上方二維碼獲取更多Java乾貨
相關文章
- 面試被問爛的 Spring IOC(求求你別再問了)面試Spring
- 面試問爛的 Spring AOP 原理、SpringMVC 過程(求求你別問了)面試SpringMVC
- 求求你們不要再問HashMap原理了....HashMap
- 別再問我什麼是「極樂」迪斯科
- 面試別再問我String了面試
- 拜託,面試別再問我JVM了!!!面試JVM
- 拜託,面試別再問我TopK了!!!面試TopK
- 拜託,面試別再問我堆(排序)了!面試排序
- 拜託,面試別再問我計數排序了!!!面試排序
- 面試官:什麼是 YAML?和 Spring Boot 有什麼關係?面試YAMLSpring Boot
- 面試官問我:什麼是JavaScript閉包,我該如何回答面試JavaScript
- 面試官問我:spring、springboot、springcloud的區別,我笑了面試Spring BootGCCloud
- 面試官靈魂三問:什麼是SOA?什麼是微服務?SOA和微服務有什麼區別?面試微服務
- 拜託,面試別再問我時間複雜度了!!面試時間複雜度
- 拜託!面試請不要再問我Spring Cloud底層原理面試SpringCloud
- 【Java面試】Mybatis中#{}和${}的區別是什麼?Java面試MyBatis
- 面試官問我按鈕級別許可權怎麼控制,我說v-if,面試官說再見面試
- 面試官,你再問我 Bit Operation 試試?面試
- 美團面試官問我: ZGC 的 Z 是什麼意思面試GC
- 面試問爛的 Spring AOP 原理、SpringMVC 過程面試SpringMVC
- 別再問我們用什麼畫圖的了!問就是excalidraw
- 面試問你為什麼要用Spring怎麼答?面試Spring
- Spring AOP(面向切面程式設計)是什麼?Spring程式設計
- 求你了,再問你Java記憶體模型的時候別再給我講堆疊方法區了…Java記憶體模型
- 死磕Spring之AOP篇 - Spring AOP常見面試題Spring面試題
- 面試官問我HTTP,我真的是面試HTTP
- 面試官問我:什麼是訊息佇列?什麼場景需要他?用了會出現什麼問題?面試佇列
- 拜託,面試別再讓我數1了!!!面試
- 美團一面問我i++跟++i的區別是什麼
- 求求你,別問了,Java字串是不可變的Java字串
- 我們到底為什麼要用 IoC 和 AOP
- 面試官:你還有什麼想問我的?面試
- 面試官,請別再問我 3 次握手與 4 次揮手了!面試
- 對線面試官 | 別再問我Java List八股文了面試Java
- 別再問我ConcurrentHashMap了HashMap
- #如何看待問“a = a + b和a += b有什麼區別”的Java面試官?Java面試
- 【java面試】Spring的IOC是啥?有什麼好處?Java面試Spring
- 【Java面試】IO和NIO有什麼區別?Java面試