1. 程式人生 > >java三種代理方式總結

java三種代理方式總結

最近在學習java代理,總結輸出如下。

java代理分為三種實現方式JDK靜態代理,JDK動態代理和CGLIB代理,三種代理的特點及比較如下表

代理方式 實現 優點 缺點 其他
JDK靜態代理 代理類與委託類實現同一介面,並且在代理類中需要硬編碼介面 實現簡單,容易理解 代理類需要硬編碼介面,在實際應用中可能會導致重複編碼,浪費儲存空間並且效率很低 實現簡單
JDK動態代理 代理類與委託類實現同一介面,主要是通過代理類實現InvocationHandler並重寫invoke方法來進行動態代理的,在invoke方法中將對方法進行增強處理 不需要硬編碼介面,程式碼複用率高 只能夠代理實現了介面的委託類 底層使用反射機制進行方法的呼叫
CGLIB動態代理 代理類將委託類作為自己的父類併為其中的非final委託方法建立兩個方法,一個是與委託方法簽名相同的方法,它在方法中會通過super呼叫委託方法;另一個是代理類獨有的方法。在代理方法中,它會判斷是否存在實現了MethodInterceptor介面的物件,若存在則將呼叫intercept方法對委託方法進行代理 可以在執行時對類或者是介面進行增強操作,且委託類無需實現介面 不能對final類以及final方法進行代理 底層將方法全部存入一個數組中,通過陣列索引直接進行方法呼叫
  • JDK靜態代理實現

如上表,靜態代理需要代理類與目標類實現同一介面。
統一介面類Person,Man類實現Person介面;靜態代理類StaticManProxy對Man類進行增強;即代理。

Person.java

public interface Person{
    String eat(String food);    
    String sleep(String where); 
}

Man.java

public class Man implements Person{
    public String sleep(String name){
        System.out.println(name+"睡覺了"
); return ""; } public String eat(String name){ System.out.println(name+"吃飯了"); return ""; } }

靜態代理類需要與目標類的介面,即StaticManProxy同樣需要實現Person

StaticManProxy.java

public class StaticManProxy implements Person{

    private Person target;

    public StaticManProxy(Person target){
        this.target=target;
    }

    public String eat(String name) {
        System.out.println("靜態代理 do something~");
        target.eat(name);
        return null;
    }

    public String sleep(String name) {
        return null;
    }
}

測試程式碼

public class ProxyTest {

    public static void main(String[] args){     
        //1、靜態代理
        Person person=new Man();
        StaticManProxy manProxy=new StaticManProxy(person);
        manProxy.eat("靜態代理");   
    }
}

結果

靜態代理 do something~
靜態代理吃飯了

如上StaticManProxy 即為Man的代理類,同時為其他實現Person介面的代理類;

  • JDK動態代理實現

JDK動態代理通過反射實現,直接使用JDK的java.lang.reflect.Proxy.newProxyInstance方法生成代理;

實現程式碼如下

public class ProxyFactory {

    // 代理目標
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy() {

        return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
                target.getClass().getInterfaces(),
                /**
                 * InvocationHandler介面只定義了一個invoke方法,因此對於這樣的介面,
                 * 我們不用單獨去定義一個類來實現該介面, 而是直接使用一個匿名內部類來實現該介面,new
                 * InvocationHandler() {}就是針對InvocationHandler介面的匿名實現類
                 */
                /**
                 * 在invoke方法編碼指定返回的代理物件乾的工作 proxy : 把代理物件自己傳遞進來 method:
                 * 把代理物件當前呼叫的方法傳遞進來 args: 把方法引數傳遞進來
                 * 
                 * 當呼叫代理物件的方法時,
                 * 實際上執行的都是invoke方法裡面的程式碼,
                 * 因此我們可以在invoke方法中使用method.getName()就可以知道當前呼叫的是代理物件的哪個方法
                 */
                new InvocationHandler() {

                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        System.out.println("動態代理do something");

                        return method.invoke(target, args);

                    }
                });
    }

    public Object getProxyByLambda() {

        return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(),
                target.getClass().getInterfaces(), (proxy, method, args) -> {

                    System.out.println("Lambda動態代理do something");
                    return method.invoke(target, args);

                });
    }

}

測試程式碼段:

ProxyFactory proxy=new ProxyFactory(man);

        //2、匿名函式 模式
        Person p1=(Person)proxy.getProxy();
        System.out.println(p1.eat("匿名函式"));

        //3、Lambda 模式
        Person p=(Person)proxy.getProxyByLambda();

        System.out.println(p.eat("Lambda模式"));

結果

動態代理do something
匿名函式吃飯了

Lambda動態代理do something
Lambda模式吃飯了

如上,JDK動態代理通過Proxy.newProxyInstance直接生成代理物件,如需通過代理實現事物處理或者日誌新增,則可通過以上代理類直接在代理Factory中增加對應需要處理的邏輯即可,程式碼呼叫時只需要將目標類傳入則實現了對應的增強功能。針對所有介面實現類通用。

  • CGLIB動態代理

Spring中繼承了CGLIB代理相關的程式碼,所以實現CGLIB代理只需要匯入spring-core的jar包即可。

CGLIB代理的目標類不需要實現任何介面;

public class CgLibMan{

    public String eat(String name){
        System.out.println(name+"吃飯了");
        return "";
    }

    public String sleep(String name){
        System.out.println(name+"睡覺了");
        return "";
    }   
}

CGLIB代理實現類需要實現MethodInterceptor介面。如下:

public class CgLibProxyFactory implements MethodInterceptor{

    //維護目標物件
    private Object target;

    public CgLibProxyFactory(Object target){
        this.target=target;
    }

    //給目標物件建立一個代理物件
    public Object getCgLibProxy(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設定父類
        en.setSuperclass(target.getClass());
        //3.設定回撥函式
        en.setCallback(this);
        //4.建立子類(代理物件)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {

        System.out.println("開始事務...");

        //通過反射 執行目標物件的方法
        Object returnValue = method.invoke(target, args);
        proxy.invokeSuper(o, args);

        System.out.println("提交事務...");

        return returnValue;

    }

}

測試程式碼段:

        //4、Cglib實現代理
        //目標物件
        CgLibMan ldhCg=new CgLibMan();
        //代理物件
        CgLibMan proxyCg=(CgLibMan) new CgLibProxyFactory(ldhCg).getCgLibProxy();
        proxyCg.eat("Cglib");