03.25 面試被問到Spring IOC、AOP和動態代理,用這篇文章懟過去

面試被問到Spring IOC、AOP和動態代理,用這篇文章懟過去

前言

我學弟的一篇文章,非常優秀的2020屆學弟。

因為不能貼相關鏈接,所以想要他聯繫方式的小夥伴,可以後臺私聊我

正文

01、談理解

首先你要知道,Spring框架原理基本是Java崗面試必問的問題。偶爾會擴展到Spring mvc框架,不過一般很少。每當面試官向你提問,讓你說一下Spring的框架原理,你必須要清楚明白的向面試官闡述你的理解,最好不要照本宣科。這一塊,我貼出我面對這種問題的回答,供讀者參考。

對於Spring,核心就是IOC容器,這個容器說白了就是把你放在裡面的對象(Bean)進行統一管理,你不用考慮對象如何創建如何銷燬,從這方面來說,所謂的控制反轉就是獲取對象的方式被反轉了。既然你都把對象交給人家Spring管理了,那你需要的時候不得給人家要呀。這就是依賴注入(DI)!再想下,我們在傳入一個參數的時候除了在構造方法中就是在setter方法中,換個好聽的名字就是構造注入和設值注入。

至於AOP(面向切面),這玩意我舉個例子說下,比如你寫了個方法用來做一些事情,但這個事情要求登錄用戶才能做,你就可以在這個方法執行前驗證一下,執行後記錄下操作日誌,把前後的這些與業務邏輯無關的代碼抽取出來放一個類裡,這個類就是切面(Aspect),這個被環繞的方法就是切點(Pointcut),你所做的執行前執行後的這些方法統一叫做增強處理(Advice)。

02、動態代理方式

當你回答了AOP完之後,面試官緊接著會問你AOP原理,很簡單,動態代理。那你回答完動態代理,面試官肯定會讓你來講一下實現動態代理的方式。

我覺得你完全可以從容不迫的先說一下靜態代理,一句話,自己手寫代理類就是靜態代理。手寫代理類也有兩種思路,一是通過繼承被代理類的方式實現其子類,重寫父類方法;二是與被代理類實現共同的一個接口,尷尬的一點是被代理類未必會有接口。

到了這裡,你就可以引出動態代理的概念。簡單來說,動態代理就是交給程序去自動生成代理類(在程序運行期間由JVM根據反射等機制動態的生成源碼 )。

從靜態代理的實現也可以類比出,動態代理的實現也有兩種方式,即基於接口的JDK動態代理和基於繼承的cglib動態代理。

03、JDK的動態代理

使用JDK的動態代理去生成代理只需要一行代碼:

Moveable move = (Moveable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new LogHandler(new Car()));

傳入的參數中其實就倆,一是被代理類的類對象,二是自定義的增強處理代碼。下面看下LogHandler源碼:

public class LogHandler implements InvocationHandler{ private Object target; 
public LogHandler(Object object){
super();
this.target = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //增強處理
Object o = method.invoke(target,args);
return o;
}
}

觀察InvocationHandler實現類的源碼可以發現,首先是定義了一個含參構造方法,該參數即為要代理的實例對象,觀察target對象的使用位置在method.invoke()方法的參數中,不難看出,目的也就是為了執行目標方法。那重寫的invoke()方法的三個參數又是什麼呢?顧名思義,

  • proxy:動態生成的代理對象
  • method:目標方法的實例
  • args:目標方法的參數

從Proxy.newProxyInstance()的方法參數中包含了被代理類的接口的類對象也不難得知,JDK的動態代理只能代理實現了接口的類, 沒有實現接口的類不能實現動態代理。

關於Jdk動態代理的原理,我歸納得出四步

  1. 聲明一段源碼,源碼動態產生動態代理
  2. 源碼產生java文件,對java文件進行編譯
  3. 得到編譯生成後的class文件
  4. 把class文件load到內存之中,產生代理類對象返回即可。

依照這四步流程,實現一個Demo版的Jdk動態代理框架,問題不大。

04、cglib的動態代理

cglib使用動態代理的流程:

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//設置被代理類

enhancer.setSuperclass(Car.class);
//設置回調函數
enhancer.setCallback(new MethodInterceptor() { @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //增強處理... //增強處理...
Object o= proxy.invokeSuper(obj, args);//代理類調用父類的方法
//增強處理...
return o;
}
});
//創建代理類並使用回調
Car car = (Car) enhancer.create();
//執行目標方法
System.out.println(car.move());
}

從上面的代碼看到,cglib的使用流程還是很清晰明瞭,各種參數顧名思義,和jdk的區別不大。生成的代理對象直接被該類引用,與我們認知的基於繼承的動態代理沒衝突。不過這種基於繼承的方式就沒有什麼缺點嗎?最明顯的一點就是final修飾的類無法使用。

05、Spring用的啥

Jdk的動態代理和cglib的動態代理你都已經知曉,spring底層究竟是如何選擇哪種動態代理方式,這也是面試經常考到的一個知識點。

標準回答如下:Spring會根據具體的Bean是否具有接口去選擇動態代理方式,如果有接口,使用的是Jdk的動態代理方式,如果沒有接口,使用的是cglib的動態代理方式。


分享到:


相關文章: