1. 程式人生 > >java的三種代理模式:靜態代理,動態代理,cglib代理

java的三種代理模式:靜態代理,動態代理,cglib代理

原文:https://segmentfault.com/a/1190000011291179

一、代理模式介紹

代理模式是一種設計模式,提供了對目標物件額外的訪問方式,即通過代理物件訪問目標物件,這樣可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。

簡言之,代理模式就是設定一箇中間代理來控制訪問原目標物件,以達到增強原物件的功能和簡化訪問方式。

 

舉個例子,我們生活中經常到火車站去買車票,但是人一多的話,就會非常擁擠,於是就有了代售點,我們能從代售點買車票了。這其中就是代理模式的體現,代售點代理了火車站物件,提供購買車票的方法。

二、靜態代理

這種代理方式需要代理物件和目標物件實現一樣的介面。

優點:可以在不修改目標物件的前提下擴充套件目標物件的功能。

缺點:

  1. 冗餘。由於代理物件要實現與目標物件一致的介面,會產生過多的代理類。
  2. 不易維護。一旦介面增加方法,目標物件與代理物件都要進行修改。

舉例:儲存使用者功能的靜態代理實現

  • 介面類: IUserDao
package com.proxy;

public interface IUserDao {
    public void save();
}
  • 目標物件:UserDao
package com.proxy;

public class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("儲存資料");
    }
}
  • 靜態代理物件:UserDapProxy 需要實現IUserDao介面!
package com.proxy;

public class UserDaoProxy implements IUserDao{

    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    
    @Override
    public void save() {
        System.out.println("開啟事務");//擴充套件了額外功能
        target.save();
        System.out.println("提交事務");
    }
}
  • 測試類:TestProxy
package com.proxy;

import org.junit.Test;

public class StaticUserProxy {
    @Test
    public void testStaticProxy(){
        //目標物件
        IUserDao target = new UserDao();
        //代理物件
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();
    }
}
  • 輸出結果
開啟事務
儲存資料
提交事務

三、動態代理

動態代理利用了JDK API,動態地在記憶體中構建代理物件,從而實現對目標物件的代理功能。動態代理又被稱為JDK代理或介面代理。

靜態代理與動態代理的區別主要在:

  • 靜態代理在編譯時就已經實現,編譯完成後代理類是一個實際的class檔案
  • 動態代理是在執行時動態生成的,即編譯完成後沒有實際的class檔案,而是在執行時動態生成類位元組碼,並載入到JVM中

特點:
動態代理物件不需要實現介面,但是要求目標物件必須實現介面,否則不能使用動態代理。

JDK中生成代理物件主要涉及的類有

static Object    newProxyInstance(ClassLoader loader,  //指定當前目標物件使用類載入器

 Class<?>[] interfaces,    //目標物件實現的介面的型別
 InvocationHandler h      //事件處理器
) 
//返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的呼叫處理程式。
 Object    invoke(Object proxy, Method method, Object[] args) 
// 在代理例項上處理方法呼叫並返回結果。

舉例:儲存使用者功能的動態代理實現

  • 介面類: IUserDao
package com.proxy;

public interface IUserDao {
    public void save();
}
  • 目標物件:UserDao
package com.proxy;

public class UserDao implements IUserDao{

    @Override
    public void save() {
        System.out.println("儲存資料");
    }
}
  • 動態代理物件:UserProxyFactory
package com.proxy;

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

public class ProxyFactory {

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

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

    // 為目標物件生成代理物件
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("開啟事務");

                        // 執行目標物件方法
                        Object returnValue = method.invoke(target, args);

                        System.out.println("提交事務");
                        return null;
                    }
                });
    }
}
  • 測試類:TestProxy
package com.proxy;

import org.junit.Test;

public class TestProxy {

    @Test
    public void testDynamicProxy (){
        IUserDao target = new UserDao();
        System.out.println(target.getClass());  //輸出目標物件資訊
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());  //輸出代理物件資訊
        proxy.save();  //執行代理方法
    }
}
  • 輸出結果
class com.proxy.UserDao
class com.sun.proxy.$Proxy4
開啟事務
儲存資料
提交事務

四、cglib代理

cglib is a powerful, high performance and quality Code Generation Library. It can extend JAVA classes and implement interfaces at runtime.

cglib (Code Generation Library )是一個第三方程式碼生成類庫,執行時在記憶體中動態生成一個子類物件從而實現對目標物件功能的擴充套件。

cglib特點

  • JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面。
    如果想代理沒有實現介面的類,就可以使用CGLIB實現。
  • CGLIB是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件Java類與實現Java介面。
    它廣泛的被許多AOP的框架使用,例如Spring AOP和dynaop,為他們提供方法的interception(攔截)。
  • CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM,來轉換位元組碼並生成新的類。
    不鼓勵直接使用ASM,因為它需要你對JVM內部結構包括class檔案的格式和指令集都很熟悉。

cglib與動態代理最大的區別就是

  • 使用動態代理的物件必須實現一個或多個介面
  • 使用cglib代理的物件則無需實現介面,達到代理類無侵入。

使用cglib需要引入cglib的jar包,如果你已經有spring-core的jar包,則無需引入,因為spring中包含了cglib。

  • cglib的Maven座標
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

舉例:儲存使用者功能的動態代理實現

  • 目標物件:UserDao
package com.cglib;

public class UserDao{

    public void save() {
        System.out.println("儲存資料");
    }
}
  • 代理物件:ProxyFactory
package com.cglib;

import java.lang.reflect.Method;

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

public class ProxyFactory implements MethodInterceptor{

    private Object target;//維護一個目標物件
    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    //為目標物件生成代理物件
    public Object getProxyInstance() {
        //工具類
        Enhancer en = new Enhancer();
        //設定父類
        en.setSuperclass(target.getClass());
        //設定回撥函式
        en.setCallback(this);
        //建立子類物件代理
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開啟事務");
        // 執行目標物件的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("關閉事務");
        return null;
    }
}
  • 測試類:TestProxy
package com.cglib;

import org.junit.Test;

public class TestProxy {

    @Test
    public void testCglibProxy(){
        //目標物件
        UserDao target = new UserDao();
        System.out.println(target.getClass());
        //代理物件
        UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
        System.out.println(proxy.getClass());
        //執行代理物件方法
        proxy.save();
    }
}
  • 輸出結果
class com.cglib.UserDao
class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6
開啟事務
儲存資料
關閉事務

五、總結

  1. 靜態代理實現較簡單,只要代理物件對目標物件進行包裝,即可實現增強功能,但靜態代理只能為一個目標物件服務,如果目標物件過多,則會產生很多代理類。
  2. JDK動態代理需要目標物件實現業務介面,代理類只需實現InvocationHandler介面。
  3. 動態代理生成的類為 lass com.sun.proxy.$Proxy4,cglib代理生成的類為class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
  4. 靜態代理在編譯時產生class位元組碼檔案,可以直接使用,效率高。
  5. 動態代理必須實現InvocationHandler介面,通過反射代理方法,比較消耗系統性能,但可以減少代理類的數量,使用更靈活。
  6. cglib代理無需實現介面,通過生成類位元組碼實現代理,比反射稍快,不存在效能問題,但cglib會繼承目標物件,需要重寫方法,所以目標物件不能為final類。

六、相關資料

代理模式相關知識