靜態代理、動態代理


  代理模式最大的優勢就是能夠解耦,在spring中也是廣泛使用。spring中一個重要的特性就是aop,aop是個啥東西呢?其實很簡單,比如現在有個業務方法,那這個業務方法很重要,涉及到非常重要的業務數據,那對於廣大企業應用來說,為了以后能夠及時的定位問題,需要記錄相關入參以及出參到日志表。

但是對於企業應用來說,需要記錄日志的地方應該是蠻多的,如果每個方法中都手動的去寫這些記錄日志的東西,就會特別的冗余,那使用代理模式就可以解決。

 

一、靜態代理

  1、User接口

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下沒有難敲的代碼!
 */
public interface User {

    /*
     * 業務邏輯接口
     */
    public void work(String workName);
}

  

  2、UserImpl實現類

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下沒有難敲的代碼!
 */
public class UserImpl implements User {

    /*
     * 實際業務邏輯實現方法
     */
    @Override
    public void work(String workName) {
        System.out.println("我是做" + workName + "的");
    }

}

 

  3、代理類

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下沒有難敲的代碼!
 * 此類為代理類,並且實現業務邏輯類接口,接收一個實際業務處理對象
 */
public class ProxyUser implements User {

    private User user;
    
    public ProxyUser(User user) {
        this.user = user;
    }
    
    @Override
    public void work(String workName) {
        /*
         * 調用實際業務邏輯處理前可以定制化一些功能
         */
        System.out.println("工作前先放松放松=============");
        /*
         * 調用實際業務邏輯處理方法
         */
        user.work(workName);
        /*
         * 調用實際業務邏輯處理后也可以定制一些功能
         */
        System.out.println("工作后還是要放松放松=============");
    }

}

 

  4、StaticProxyDemo

package com.ty.staticProxy;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下沒有難敲的代碼!
 */
public class StaticProxyDemo {

    public static void main(String[] args) {
        User proxyUser = new ProxyUser(new UserImpl());
        proxyUser.work("java開發");
    }
}

運行結果:

工作前先放松放松=============
我是做java開發的
工作后還是要放松放松=============

優點:

  • 1、 代理模式能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。
  • 2、 代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了的作用和保護了目標對象的

缺點:

 

  • 1、由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
  • 2、 實現代理模式需要額外的工作,有些代理模式的實現非常復雜。使用靜態代理模式需要程序員手寫很多代碼。

 

二、動態代理

在java中,實現動態代理主要有兩種方式,一種是jdk動態代理,一種是cglib

1、jdk動態代理

User以及UserImpl跟上面一致

a、UserDynamicProxy

package com.ty.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下沒有難敲的代碼!
 */
public class UserDynamicProxy implements InvocationHandler {

    private User user;
    
    public UserDynamicProxy(User user) {
        this.user = user;
    }

    /*
     * jdk動態代理基於接口,其中proxy好像沒啥卵用、method代表當前被代理對象的實際調用方法、args則代表方法參數
     * 由invoke方法對被代理對象進行相關的增強
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("工作前放松放松========");
        method.invoke(user, args);
        System.out.println("工作后也要放松放松===========");
        return null;
    }

}

 

b、DynamicProxyDemo

package com.ty.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下沒有難敲的代碼!
 */
public class DynamicProxyDemo {

    public static void main(String[] args) {
        User user = new UserImpl();
        InvocationHandler h = new UserDynamicProxy(user);
        /*
         * 使用Proxy的靜態方法是生成代理類的核心。
         * 一共有三個參數:
         * 1、第一個參數是被代理類的類加載器,通過此類加載器將代理類加載入jvm中;
         * 2、第二個參數則是被代理類所實現的所有接口,需要所有的接口的目的是創建新的代理類實現被代理類的所有接口,保證被代理類所有方法都能夠
         * 被代理。其實代理的核心就是新創建一個類並實例化對象,去集成被代理對象所有功能的同時,再加入某些特性化的功能;
         * 3、第三個參數則是真正的擴展,使用動態代理的主要目的就是能夠對原方法進行擴展,尤其是對於大部分方法都具有的重復方法(例如記錄日志),
         * 可以理解為面向切面編程中的增強.
         */
        User proxy = (User) Proxy.newProxyInstance(User.class.getClassLoader(), user.getClass().getInterfaces(), h);
        /*
         * 在調用生成的代理類對象后,調用原方法后,該method對象以及參數等會被傳入到InvocationHandler的invoke方法中,由InvocationHandler的
         * invoke方法對被代理類對象進行增強。
         */
        proxy.work("敲代碼");
        proxy.eat("吃大餐");
        
    }
}

 

 2、cglib代理

jdk動態代理的缺點就是必須基於接口,沒有接口就無法實現代理,而cglib則是使用繼承的方式去生成代理類,使用范圍更廣

a、添加cglib.jar、asm.jar(注意jar包版本)

使用cglib前必須進行導包,並且版本如果過低會導致報錯

 

 

b、UserImpl(使不使用接口都可以)

package com.ty.dynamic.cglib;

/**
 * @author Taoyong
 * @date 2018年7月1日
 * 天下沒有難敲的代碼!
 */
public class UserImpl {
    
    /*
     * 實際業務邏輯實現方法
     */
    public void work(String workName) {
        System.out.println("我是做" + workName + "的");
    }

    
}

 

c、CglibDynamicProxy

package com.ty.dynamic.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下沒有難敲的代碼!
 */
public class CglibDynamicProxy implements MethodInterceptor {

    /*
     * 實際的增強
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("這是前處理==============");
        methodProxy.invokeSuper(obj, objects);        
        System.out.println("這是前處理==============");
        return null;
    }

}

 

d、CglibProxyDemo

package com.ty.dynamic.cglib;

import net.sf.cglib.proxy.Enhancer;

/**
 * @author Taoyong
 * @date 2018年7月2日
 * 天下沒有難敲的代碼!
 */
public class CglibProxyDemo {

    public static void main(String[] args) {
        /*
         * 創建字節碼增強器
         */
        Enhancer enhancer = new Enhancer();
        /*
         * 被代理類設置為字節碼增強器父類,cglib使用的是繼承方式去創建代理類
         */
        enhancer.setSuperclass(UserImpl.class);
        /*
         * 設置字節碼增強器回調方法。對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔截
         */ 
enhancer.setCallback(
new CglibDynamicProxy());

/*
* 創建代理實例

*/
UserImpl userImpl
= (UserImpl) enhancer.create(); userImpl.work("敲代碼"); } }

3、原理-----基於jdk動態代理

上面說到,動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因為所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。上述代碼里,唯一的“黑厘子”就是Proxy.newProxyInstance()方法,除此之外再沒有任何特殊之處。

在JDK動態代理中涉及如下角色:

業務接口Interface、業務實現類target、業務處理類Handler、JVM在內存中生成的動態代理類$Proxy0

動態代理原理圖:

 

說白了,動態代理的過程是這樣的:

  1. Proxy通過傳遞給它的參數(interfaces/invocationHandler)生成代理類$Proxy0;

  2. Proxy通過傳遞給它的參數(ClassLoader)來加載生成的代理類$Proxy0的字節碼文件;

動態代理的關鍵代碼就是Proxy.newProxyInstance(classLoader, interfaces, handler),我們跟進源代碼看看

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
  // handler不能為空
    if (h == null) {
        throw new NullPointerException();
    }

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
  // 通過loader和接口,得到代理的Class對象
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
       // 創建代理對象的實例
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

我們看一下newInstance方法的源代碼:

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
    try {
        return cons.newInstance(new Object[] {h} );
    } catch (IllegalAccessException | InstantiationException e) {
        throw new InternalError(e.toString());
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString());
        }
    }
}

講解完了代理類的生成源碼,我們一定想要看看代理類的代碼是什么樣的,下面提供一個生成代理類的方法供大家使用: 

/**
 * 代理類的生成工具 
 * @author ChenHao
 * @since 2019-4-2 
 */
public class ProxyGeneratorUtils {

    /**
     * 把代理類的字節碼寫到硬盤上 
     * @param path 保存路徑 
     */
    public static void writeProxyClassToHardDisk(String path) {
        // 第一種方法
        // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);  

        // 第二種方法  

        // 獲取代理類的字節碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());

        FileOutputStream out = null;

        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("C:/x/$Proxy11.class");
    }

}

此時就會在指定的C盤x文件夾下生成代理類的.class文件,我們看下反編譯后的結果:

package org.fenixsoft.bytecode;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements DynamicProxyTest.IHello
{
  private static Method m3;
  private static Method m1;
  private static Method m0;
  private static Method m2;
  /**
  *注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白
  *super(paramInvocationHandler),是調用父類Proxy的構造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  /**
  * 
  *這里調用代理對象的sayHello方法,直接就調用了InvocationHandler中的invoke方法,並把m3傳了進去。
  *this.h.invoke(this, m3, null);  this.h就是父類Proxy中保存的InvocationHandler實例變量
  *來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象,
  *再聯系到InvacationHandler中的invoke方法。嗯,就是這樣。
  */
  public final void sayHello()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  // 此處由於版面原因,省略equals()、hashCode()、toString()三個方法的代碼
  // 這3個方法的內容與sayHello()非常相似。

  static
  {
    try
    {
      m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

java.lang.reflect.Proxy

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h;

    private Proxy() {
    }

    protected Proxy(InvocationHandler h) {
        doNewInstanceCheck();
        this.h = h;
    }
    //
}

這個代理類的實現代碼也很簡單,它為傳入接口中的每一個方法,以及從 java.lang.Object中繼承來的equals()、hashCode()、toString()方法都生成了對應的實現 ,並且統一調用了InvocationHandler對象的invoke()方法(代碼中的“this.h”就是父類Proxy中保存的InvocationHandler實例變量)來實現這些方法的內容,各個方法的區別不過是傳入的參數和Method對象有所不同而已,所以無論調用動態代理的哪一個方法,實際上都是在執行InvocationHandler.invoke()中的代理邏輯。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM