首頁  >  文章  >  Java  >  Java如何實作JDK動態代理

Java如何實作JDK動態代理

WBOY
WBOY轉載
2023-04-30 08:49:06948瀏覽

概念

代理:為控制A對象,而建立出新B對象,由B對象取代執行A對象所有操作,稱為代理。一個代理體系建立涉及3個參與角色:真實物件(A),代理物件(B),客戶端。

其中的代理物件(B)起到中介作用,連通真實物件(A)與客戶端,如果進一步拓展,代理物件可以實現更加複雜邏輯,例如對真實物件進行存取控制。

案例

需求:員工業務層介面呼叫save需要admin權限,呼叫list不需要權限,沒權限呼叫時拋出例外提示。

靜態代理

/**
 * 代理接口
 */
public interface IEmployeeService {
    void save();
 
    void list();
}
/**
 * 真实对象
 */
public class EmployeeServiceImpl implements IEmployeeService {
    @Override
    public void save() {
        System.out.println("EmployeeServiceImpl-正常的save....");
    }
    @Override
    public void list() {
        System.out.println("EmployeeServiceImpl-正常的list....");
    }
}
/**
 * 模拟当前登录用户对象
 */
public class SessionHolder {
    private static String currentUser;
    public static String  getCurrentUser(){
        return currentUser;
    }
    public static void   setCurrentUser(String currentUser){
        SessionHolder.currentUser = currentUser;
    }
}
/**
 * 代理对象
 */
public class EmployeeProxy implements IEmployeeService {
    //真实对象
    private EmployeeServiceImpl employeeService;
    public EmployeeProxy(EmployeeServiceImpl employeeService){
        this.employeeService = employeeService;
    }
    @Override
    public void save() {
        //权限判断
        if("admin".equals(SessionHolder.getCurrentUser())){
            employeeService.save();
        }else{
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
    }
    @Override
    public void list() {
        employeeService.list();
    }
}
public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");  //设置权限(当前登录用户)
        EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
        employeeProxy.list();
        employeeProxy.save();
    }
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
	at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
	at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

使用真實物件EmployeeServiceImpl 直接呼叫時,不管是list 還是save都能直接訪問,但不符合需求上的admin權限限制。如果使用代理對象EmployeeProxy,可以完成需求實作。

透過直接建立新類別新類別代理物件方式完成代理邏輯,這種方式稱之為靜態代理模式。

JDK動態代理模式

Java常用的動態代理模式有JDK動態代理,也有cglib動態代理,此處重點講解JDK的動態代理

還是原來的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒變,新加一個JDK代理控制器-EmployeeInvocationHandler

/**
 * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行
 */
public class EmployeeInvocationHandler  implements InvocationHandler {
    //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }
    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }
}

測試App類別稍微改動下:

public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
 
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
            new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

上面程式碼一樣可以實現需求,跟靜態代理區別就在於少創建了代理物件。此時存在疑問點,沒有創建代理對象,為啥可以實現代理類別呼叫呢? ?

原理分析

先拋出結論JDK動態代理底層實作原理:使用介面實作方式,運行時,在記憶體中動態建構出一個類,然後編譯,執行。這個類別是一次性的,JVM停止,代理類別就消失。

參與角色 要理解JDK動態代理原理,首先得了解JDK動態代理所涉及的類別

Java如何實作JDK動態代理

InvocationHandler :真實物件方法呼叫處理器,內建invoke方法,其功能:為真實物件自訂代理邏輯

EmployeeInvocationHandler:員工服務真實物件方法呼叫處理器,此類有3個用途: 1>設定真實物件

     //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }

2>自訂代理方法實作邏輯

為真實物件save方法新增了權限校驗邏輯

    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }

#3>傳回代理物件

方法執行完之後,傳回一個名為:$ProxyX的代理類別(其中的X是序號,一般預設為0),而這個代理類別由JDK動態建構出來。

    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

Proxy:動態代理控制類,是JDK動態產生的$ProxyX類的父類,它作用如下:
1>透過呼叫ProxyBuilder 類別builder方法建構代理對象類別

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces){
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
}

2>透過newProxyInstance方法傳回$ProxyX類別的實例

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    //...
   }

$Proxy0:App類別運行時,JDK動態建構出來的代理類,繼承至Proxy類別

public class App {
    public static void main(String[] args) {
        //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
                     new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

預設情況下JVM是不保存動態建立代理類別字節碼物件的,可以在main方法中配置代理參數讓字節碼保留

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

執行完之後,會在專案根目錄產生代理類別字節碼物件。

Java如何實作JDK動態代理

為了方便解讀,將一些不需要的方法剔除之後

#$Proxy0類別

public class $Proxy0 extends Proxy implements IEmployeeService {
    private static Method m4;
    private static Method m3;
    static {
        try {
            m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("save");
            m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("list");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public $Proxy0(InvocationHandler var1) throws Throwable {
        super(var1);
    }
    public final void save() throws Throwable {
        super.h.invoke(this, m4, (Object[])null);
    }
 
    public final void list() throws  Throwable{
        super.h.invoke(this, m3, (Object[])null);
    }
}

從原始碼上看,$Proxy0的特點:

  • 1>繼承了Proxy類,實作了IEmployeeService 介面

  • 2>透過靜態區塊的方式反射IEmployeeService介面save與list方法,得到他們的方法物件Method

  • 3>呼叫父類別建構器,需要傳入InvocationHandler 參數

  • 4>重寫IEmployeeService介面的save list方法靠的是父類別Proxy的h屬性.invoke方法

真相大白

下圖所有參與動態代理的類別:

Java如何實作JDK動態代理

 下圖是上圖的操作時序圖,跟著走就對了

Java如何實作JDK動態代理

以上是Java如何實作JDK動態代理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除