淺談Spring的AOP實現-代理機制

說起Spring的AOP(Aspect-Oriented Programming)面向切面編程大家都很熟悉(Spring不是這次博文的重點),但是我先提出幾個問題,看看同學們是否瞭解,如果瞭解的話可以不用繼續往下讀:

1. Spring的AOP的實現方式有哪些?

2. 爲什麼使用代理機制?

3. 它們是怎麼實現的?

4. 它們的區別是什麼?

下面進入正題,Spring採用代理的方式實現AOP,具體採用了JDK的動態代理和CGLib實現。使用動態代理和CGLib的目的是在現有類的基礎上增加一些功能。簡單地將就是有一個Proxy類,實現了原始類的方法,並且在原始類的基礎上增加了新的功能。那麼這麼做可以實現很多功能:

1. 在方法前後進行日誌處理。

2. 進行額外的校驗,比如參數的驗證功能等。

3. 實現一些懶加載,也就是實例化的時候如果不去調用真正的方法的時候,這個類的屬性就不會存在(Hibernate有這樣類似的功能)。

下面咱們用簡單的代碼實現它是如何進行代理的,首先採用的是JDK的動態代理實現:

定義一個接口:

package com.hqs.proxy;
 /** * 操作系統光接口
 * @author hqs
 * */
public interface OpSystem { public void work();
}

定義一個實現類:

package com.hqs.proxy;
 /** * Mac 的實現
 * @author hqs
 * */
public class Mac implements OpSystem { public void work() {
 System.out.println("Mac is running"); 
 }
}

位置來了,我們通過實現JDK自帶的反射機制的包的InvocationHandler來進行反射處理,實現它之後需要實現裏邊的invoke方法,這個invoke方法裏邊的參數分別爲:代理類實例,用於調用method的;method參數是實際執行的方法;args所傳輸的參數數組。

 

package com.hqs.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class OpHandler implements InvocationHandler {
 
 private final OpSystem ops; 
 
 public OpHandler(OpSystem ops) {
 this.ops = ops;
 }
 
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 System.out.println("Before system running");
 method.invoke(ops, args);
 System.out.println("After system running");
 return null;
 }
 
 public static void main(String[] args) {
 Mac mac = new Mac();
 OpHandler oph = new OpHandler(mac);
 OpSystem os = (OpSystem)Proxy.newProxyInstance(oph.getClass().getClassLoader(),
 mac.getClass().getInterfaces(), oph);
 
 os.work();
 System.out.println(os.getClass());
 }
 
}
輸出:
Before system running
Mac is running
After system running
class com.sun.proxy.$Proxy0

然後看到裏邊的main方法中,代理類實例化對象的方法Proxy.newProxyInstance,這個是JDK的反射方法去實例化代理類,其中有三個參數分別是,去實例化代理類的class loader;所代理的類的所有接口Class數組;hander處理類,用於做攔截使用的類。最後我輸出了一下os.getClass(),大家可以看到的是代理類的實例,而不是真正代理類的實例,這麼做的好處就是很方便的複用這個代理類,比如你可以重複調用它而不用去重新實例化新類,再一點就是你可以針對不同的方法進行攔截,比如你可以method.getName()去判斷調用的方法名字是什麼從而更細粒度的攔截方法。咱們繼續看用CGLib的實現:

package com.hqs.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
 * CGLib Interceptor用於方法攔截
 * @author hqs
 *
 */
public class CGLibInterceptor implements MethodInterceptor {
 
 private final Mac mac;
 
 public CGLibInterceptor(Mac mac) {
 this.mac = mac;
 }
 @Override
 public Object intercept(Object obj, Method method, Object[] args, 
 MethodProxy methodProxy) throws Throwable {
 System.out.println("Before system running");
 method.invoke(mac, args);
 System.out.println("After system running");
 return null;
 }
 
 public static void main(String[] args) {
 Mac mac = new Mac(); //實例而非接口
 MethodInterceptor handler = new CGLibInterceptor(mac);
 Mac m = (Mac)Enhancer.create(mac.getClass(), handler);
 m.work();
 System.out.println(m.getClass());
 
 }
}
輸出:
Before system running
Mac is running
After system running
class com.hqs.proxy.Mac$$EnhancerByCGLIB$$1f2c9d4a

首先需要引入cglib包,然後才能使用他的MethodInterptor,它也採用method.invoke實現對代理類的調用。它的代理類創建採用Enhancer的create方法,其中傳入了需要創建的類的class,以及Callback對象,因爲MethodInterceptor繼承了Callback對象。用於指向方法前後進行調用的類。

public interface MethodInterceptor
extends Callback

這是這兩個類的基本實現,那麼它們的 區別 是什麼呢?

 

  1. JDK的動態代理只能針對接口和其實現類,如果沒有實現類只有接口也是可以代理的,這裏就不在舉例了。爲什麼JDK的動態代理只針對接口代理,因爲這個是JDK的定義。
  2. 如果不針對接口實現動態代理那就用到了CGLib了,也就是可以針對具體的類進行代理,大家可以參考我的代碼。

這些是它們的根本區別,但是Spring推薦使用JDK的動態代理,面向接口去編程。使用CGLib去做代理的時候需要注意,它生成的代理類存放在JVM的Perm space裏邊,那麼是不是生成的代理對象就不進行回收了?其實不是的,不經常回收但是還是回收的,當類被加載,加載類的classLoader什麼時候變得對垃圾回收可用的時候才進行回收。也就是你自己創建所有類移除classLoader之後,那麼這個classLoader就會被回收,一般非常精通CGLib的話可以進行這塊內容深入開發,因爲它可以做出amzing的事情如果你熟悉的話。

end:如果覺得本文對你有幫助的話,記得點贊關注哈!想學習更多方面的Java技術的知識的朋友們,可以進我的一個Java高級架構師交流羣,裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料,進羣即可免費領取哦,羣號:680075317,也可以進羣一起交流,比如遇到技術瓶頸、面試不過的,大家一些交流學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章