首頁>技術>

接上篇Spring/Boot/Cloud系列知識——代理模式(上)

2.2、代理模式在Spring中的應用

那麼java中基於java.lang.reflect.Proxy的動態代理模式和Spring生態有什麼關係呢?Spring中的所有Bean例項儲存在一個名叫IOC容器(Inversion of Control 控制反轉容器)中。這個容器中存在著介面和實現類的對應關係(也可以直接儲存類的例項,無需這個類有任何介面的實現),而其中Bean例項的儲存方式都預設採用單例儲存。

一般情況下我們可以透過BeanFactory(Spring IOC容器工廠)介面中getBean()方法,直接獲取到這個介面在IOC容器中對應的例項。但當我們需要為這個Bean例項附加AOP切面操作時,這個例項就會被代理——視這個例項實現介面的情況和Spring的配置情況,又可以區別為使用Proxy動態代理還是使用Cglib代理。如下圖所示:

通常情況下當我們使用IOC容器中的Bean例項時,IOC容器將向使用者返回這個介面的具體實現,並不會代理這個Bean例項。如下圖所示:當我們為這個Bean設定了AOP切面功能後,這個IOC容器中的Bean例項就會被代理。以下設定了一個AOP切面攔截(AOP切面的詳細講解在後續文章中進行,這裡我們只看被代理的結果就好):
// 建立一個AOP切面攔截器@Aspect@Componentpublic class MyInterceptor {  /**    * 攔截yinwenjie.test.proxy.service包下面的所有類中的所有方法   * 後續文章還會詳細講解Spring EL和AOP攔截規則    */      @Pointcut("execution(* yinwenjie.test.proxy.service..*.*(..))")    public void executeService() {    System.out.println("攔截器作用!");  }  // 前置事件  @Before("executeService()")    public void doBeforeAdvice(JoinPoint joinPoint){    System.out.println("前置事件!");  }}123456789101112131415161718
接著我們重新執行單元測試(重新執行程式碼),就會發現“MyService”介面的實現——MyServiceImpl被代理了。代理者是Spring基礎設施中aop模組的JdkDynamicAopProxy類。

org.springframework.aop.framework.JdkDynamicAopProxy類非常重要,它實現了Java對動態代理模式的支援,既實現了java.lang.reflect.InvocationHandler介面(當然不止是實現了這個介面)。注意:需要設定您的Spring主配置檔案(這裡是Spring Boot)中spring.aop.proxy-target-class=false,否則就算您依賴注入的Bean存在介面,也會使用Cglib代理。

3. Cglib代理(動態)

要使用java對動態代理的原生支援,就需要被代理的類至少實現了一個介面。但如果某個需要被代理的類沒有實現任何介面,又怎麼辦呢?這時一個比較好的選擇就是使用Cglib代理,Cglib代理並不是java的原生代理模式,而是由第三方提供的一種代理方式。Cglib代理元件是Mockito Framework的一部分,其內部封裝了一個java位元組碼生成框架ASM:

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form. Provided common transformations and analysis algorithms allow to easily assemble custom complex transformations and code analysis tools.

以上引用自OW2組織對ASM的描述(http://asm.ow2.org)。簡單來說就是ASM位元組碼框架可在執行時動態生成Class位元組碼內容,或者擴充套件已有的某個Class。實際上Spring生態中預設使用Cglib動態代理作為主要的動態代理方式——不是java原生的java.lang.reflect.Proxy動態代理。無論被代理的Bean例項是否實現了任何介面,Cglib都能更好勝任代理工作任務。

3.1 Cglib元件基本使用
<dependency>    <groupId>org.mockito</groupId>    <artifactId>mockito-all</artifactId>    <version>1.10.19</version></dependency>12345

在本小節的示例講解中,我們需要在工程的maven檔案中匯入Cglib元件的引用(1.10.19版本)。接下來我們可以使用一個示例,來看看Cglib元件的基本使用。Cglib元件中的基本角色包括:

代理器:代理器實現了org.mockito.cglib.proxy.MethodInterceptor介面,當代理者被外部使用者呼叫時,就會觸發其中的intercept方法。在本示例中,我們建立了兩個代理器DefaultProxy和CglibProxy:DefaultProxy代理器// 預設的代理器,什麼都不執行 public class DefaultProxy implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 這裡不做任何處理(也可以直接使用proxy執行父級程式碼) return null; } }12345678CglibProxy代理器/** * 這是一個基於Cglib的代理器 * 它實現了org.mockito.cglib.proxy.MethodInterceptor介面 * @author yinwenjie */ public class CglibProxy implements MethodInterceptor { // 日誌 private static final Logger LOG = LoggerFactory.getLogger(CglibProxy.class); /** * 該方法在代理者被呼叫時觸發 * @param obj 這是代理者,按照本示例就是使用Enhancer生成的子級類 * @param method 被呼叫的方法 * @param args 呼叫方法時可能傳入的引數 * @param proxy cglib內部的方法代理,可以用它來執行真正的業務過程 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { LOG.info("透過debug觀察Enhancer生成的子級類,是否有似曾相識的感覺?"); LOG.info("被呼叫的方法:" + method.getName()); LOG.info("可以在正式呼叫前執行一些額外的過程"); // 這裡返回真正的業務呼叫過程 Object result = null; try { result = proxy.invokeSuper(obj, args); } catch (Exception e) { LOG.error(e.getMessage() , e); LOG.info("在呼叫執行異常後,執行一些額外的過程"); return null; } LOG.info("在呼叫執行成功後,執行一些額外的過程"); return result; } }123456789101112131415161718192021222324252627282930313233被代理者:被代理者就是負責真正業務過程處理的類。本示例中我們建立了一個TargetClass類,作為真正業務的處理者
/** * 這是要被代理的業務處理類<br> * 這個類既沒有任何父類,也沒有實現任何介面 * @author yinwenjie */public class TargetClass {  // 日誌  private static final Logger LOG = LoggerFactory.getLogger(TargetClass.class);  public void handleSomething(String param) {    LOG.info("傳入了一個引數:" + param + ",做了一些業務處理");  }}123456789101112
位元組碼增強操作器EnhancerEnhancer是Cglib中用於動態生成java位元組碼的最重要工具,其內部是對ASM框架的封裝。以下示例是Enhancer的基本使用:
public class Run {  static {    BasicConfigurator.configure();  }  public static void main(String[] args) {    /*     * Cglib的核心思路,是依靠一個java位元組碼操作框架ASM     * 在程式碼執行時,為被代理的Class生成擴充套件程式碼(子級程式碼)     * 然後透過執行子級程式碼,實現代理     *      * Enhancer是其中用來進行程式碼生成的主要類,請看如下執行示例     * */    Enhancer enhancer = new Enhancer();    // 基於被代理的TargetClass,為它建立一個子類進行代理執行    enhancer.setSuperclass(TargetClass.class);    // 設定代理器,這裡設定了兩個代理器DefaultProxy和CglibProxy(這裡也可以傳null)    enhancer.setCallbacks(new Callback[]{new DefaultProxy() , new CglibProxy()});    // 設定過濾器(很多資料都寫錯了用法,請參見本人MethodFilter中的註釋)    enhancer.setCallbackFilter(new MethodFilter());    // 在執行時動態建立代理類(TargetClass類的子類)    TargetClass proxy = (TargetClass)enhancer.create();    // 以下方法將被DefaultProxy代理    proxy.toString();    // 以下方法將被CglibProxy代理    proxy.handleSomething("做一些對社會有益的事情!");  }  ……}12345678910111213141516171819202122232425262728293031
其它的角色——代理過濾器代理過濾器CallbackFilter的主要工作,是根據當前外部使用者所呼叫的代理者的方法,來決定使用哪一個代理器:
public class Run {  ……  static class MethodFilter implements CallbackFilter {    @Override    public int accept(Method method) {      /*       * 在main方法中,我們一共設定了兩個代理器,DefaultProxy和CglibProxy。       * 當被執行的方法是Object類中的,類似clone、toString、hashCode這樣的方法,則使用DefaultProxy進行代理       * 其他的方法,就是我們真正需要被代理的業務方法了,使用CglibProxy這個代理器進行代理       *        * 這裡返回的結果,就是我們在main方法中使用enhancer.setCallbacks設定的代理器順序       * 返回0,表示使用DefaultProxy代理器;返回1,表示使用CglibProxy代理器       *        * 程式碼就不解釋了,很容易看懂。       * 大家還可以參見org.mockito.cglib.proxy.Proxy.getProxyClass的原始碼,後文中我們還會提到這個Proxy類       */      if (method.getDeclaringClass().getName().equals("java.lang.Object")) {        return 0;      }      return 1;    }  }}1234567891011121314151617181920212223
下圖展示了debug過程中,觀察到的obj物件的結構效果。是否有似曾相似的感覺?

以下是示例程式碼的執行結果:

09:27:36.939 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 透過debug觀察Enhancer生成的子級類,是否有似曾相識的感覺?09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 被呼叫的方法:handleSomething09:27:36.964 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在正式呼叫前執行一些額外的過程09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.target.TargetClass - 傳入了一個引數:做一些對社會有益的事情!,做了一些業務處理09:27:36.998 [main] INFO yinwenjie.test.proxy.cglib.CglibProxy - 在呼叫執行成功後,執行一些額外的過程12345
3.2 Cglib替代Java原生動態代理

Cglib元件中有類似java原生動態代理的實現方式,可以完整替代java中原生動態代理的支援。這使得Spring預設使用Cglib動態代理作為主要的動態代理方式有了基礎支撐。本小節我們來看看Cglib如何提供類似java原生動態代理的支援(我們使用的Cglib版本還是1.10.19)。

以下是介面和介面的實現類,也是我們需要進行進行代理的業務處理類。這裡的程式碼示例已經在之前的文章中出現過,這裡就不再贅述了:
// 被代理的介面public interface TargetOneInterface {  // 這是一個方法  public void doSomething();  // 這是第二個方法  public void handleSomething();}......// 介面實現,也是真正的業務處理類public class TargetOneImpl implements TargetOneInterface {  // 日誌  private static final Logger LOG = LoggerFactory.getLogger(TargetOneImpl.class);  @Override  public void doSomething() {    LOG.info("doSomething 方法被執行");  }  @Override  public void handleSomething() {    LOG.info("handleSomething 方法被執行");  }}1234567891011121314151617181920212223
以下我們基於Cglib提供的InvocationHandler介面,實現的代理器。注意,在Java的原生動態代理支援中,也有這個名稱相同的介面——包名不一樣:
......import org.mockito.cglib.proxy.InvocationHandler;......// 代理者處理器public class ProxyInvocationHandler implements InvocationHandler {  /**   * 模擬一個簡單的IOC容器<br>   * 當然實際情況沒有這麼簡單,IOC內部有一個組織結構的   */  private static Map<Class<?>, Object> simulatorContainer = new HashMap<>();  static {    // 這是TargetOneInterface的實現    simulatorContainer.put(TargetOneInterface.class, new TargetOneImpl());  }  /**   * @param proxy 代理物件,注意是代理者,不是被代理者   * @param method 被代理的方法   * @param args 被執行的代理方法中傳入的引數   */  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    // 被代理的介面    Class<?> targetInterface = method.getDeclaringClass();    // 從容器中取出執行類    Object target = simulatorContainer.get(targetInterface);    if(target == null) {      return null;    }    // 開始呼叫    return method.invoke(target, args);  }}12345678910111213141516171819202122232425262728293031323334

在以上這個代理器示例中,我們模擬了一個簡易的IOC容器,並根據被代理的介面從這個簡易的容器中取出介面對應的真實業務處理類,最後執行。

以下是呼叫程式碼和執行結果
public class Run {  static {    BasicConfigurator.configure();  }  public static void main(String[] args) {    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();    //這是Cglib代理處理器    ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();    Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class}, invocationHandler);    // 開始呼叫(測試第一個介面)    TargetOneInterface targetOne = (TargetOneInterface)proxy;    targetOne.doSomething();    targetOne.handleSomething();  }}123456789101112131415161718

對以上程式碼進行debug操作,會發現類似如下的除錯資訊:

從除錯資訊可以看出,當我們呼叫org.mockito.cglib.proxy.Proxy.newProxyInstance這個靜態方法時,一旦執行成功,該方法就將返回一個由Cglib元件動態生成的類。上圖中表示為:

。後文內容中我們會閱讀org.mockito.cglib.proxy.Proxy.newProxyInstance中的原始碼,來講解其中的工作內容。以下是示例的執行結果:

[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - doSomething 方法被執行[main] INFO yinwenjie.test.proxy.cglib.dproxy.target.TargetOneImpl - handleSomething 方法被執行

接後文連續三集 ,各位老鐵多關注關注我 。 每天更新java文章

11
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 用分散式資料庫後效能提高50%,為何還是放棄了?