亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java實(shí)現(xiàn)JDK動(dòng)態(tài)代理的原理詳解

 更新時(shí)間:2022年07月01日 09:57:09   作者:??下崗碼農(nóng)大飛????  
這篇文章主要介紹了Java實(shí)現(xiàn)JDK動(dòng)態(tài)代理的原理詳解,Java常用的動(dòng)態(tài)代理模式有JDK動(dòng)態(tài)代理,也有cglib動(dòng)態(tài)代理,本文重點(diǎn)講解JDK的動(dòng)態(tài)代理,需要的小伙伴可以參考一下的相關(guān)資料

概念

代理:為控制A對(duì)象,而創(chuàng)建出新B對(duì)象,由B對(duì)象代替執(zhí)行A對(duì)象所有操作,稱之為代理。一個(gè)代理體系建立涉及到3個(gè)參與角色:真實(shí)對(duì)象(A),代理對(duì)象(B),客戶端。

其中的代理對(duì)象(B)起到中介作用,連通真實(shí)對(duì)象(A)與客戶端,如果進(jìn)一步拓展,代理對(duì)象可以實(shí)現(xiàn)更加復(fù)雜邏輯,比如對(duì)真實(shí)對(duì)象進(jìn)行訪問(wèn)控制。

案例

需求:?jiǎn)T工業(yè)務(wù)層接口調(diào)用save需要admin權(quán)限,調(diào)用list不需要權(quán)限,沒(méi)權(quán)限調(diào)用時(shí)拋出異常提示。

靜態(tài)代理

/**
 * 代理接口
 */
public interface IEmployeeService {
    void save();
 
    void list();
}
/**
 * 真實(shí)對(duì)象
 */
public class EmployeeServiceImpl implements IEmployeeService {
    @Override
    public void save() {
        System.out.println("EmployeeServiceImpl-正常的save....");
    }
    @Override
    public void list() {
        System.out.println("EmployeeServiceImpl-正常的list....");
    }
}
/**
 * 模擬當(dāng)前登錄用戶對(duì)象
 */
public class SessionHolder {
    private static String currentUser;
    public static String  getCurrentUser(){
        return currentUser;
    }
    public static void   setCurrentUser(String currentUser){
        SessionHolder.currentUser = currentUser;
    }
}
/**
 * 代理對(duì)象
 */
public class EmployeeProxy implements IEmployeeService {
    //真實(shí)對(duì)象
    private EmployeeServiceImpl employeeService;
    public EmployeeProxy(EmployeeServiceImpl employeeService){
        this.employeeService = employeeService;
    }
    @Override
    public void save() {
        //權(quán)限判斷
        if("admin".equals(SessionHolder.getCurrentUser())){
            employeeService.save();
        }else{
            throw new RuntimeException("當(dāng)前非admin用戶,不能執(zhí)行save操作");
        }
    }
    @Override
    public void list() {
        employeeService.list();
    }
}
public class App {
    public static void main(String[] args) {
        System.out.println("----------------真實(shí)對(duì)象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理對(duì)象--------------------");
        SessionHolder.setCurrentUser("dafei");  //設(shè)置權(quán)限(當(dāng)前登錄用戶)
        EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
        employeeProxy.list();
        employeeProxy.save();
    }
}
----------------真實(shí)對(duì)象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理對(duì)象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 當(dāng)前非admin用戶,不能執(zhí)行save操作
	at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
	at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

使用真實(shí)對(duì)象EmployeeServiceImpl 直接調(diào)用時(shí),不管是list 還是save都能直接訪問(wèn),但不符合需求上的admin權(quán)限限制。如果使用代理對(duì)象EmployeeProxy,可以完成需求實(shí)現(xiàn)。

通過(guò)直接創(chuàng)建新類新類代理對(duì)象方式完成代理邏輯,這種方式稱之為靜態(tài)代理模式。

JDK動(dòng)態(tài)代理模式

Java常用的動(dòng)態(tài)代理模式有JDK動(dòng)態(tài)代理,也有cglib動(dòng)態(tài)代理,此處重點(diǎn)講解JDK的動(dòng)態(tài)代理

還是原來(lái)的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒(méi)變,新加一個(gè)JDK代理控制器-EmployeeInvocationHandler

/**
 * jdk動(dòng)態(tài)代理控制類,由它牽頭代理類獲取,代理方法的執(zhí)行
 */
public class EmployeeInvocationHandler  implements InvocationHandler {
    //真實(shí)對(duì)象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }
    //獲取jvm在內(nèi)存中生成代理對(duì)象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    //代理對(duì)象控制執(zhí)行方法
    //參數(shù)1:代理對(duì)象
    //參數(shù)2:真實(shí)對(duì)象的方法(使用方式得到方法對(duì)象)
    //參數(shù)3:真實(shí)對(duì)象方法參數(shù)列表
    //此處是代理對(duì)象對(duì)外暴露的可編輯的方法處理場(chǎng)所,代理對(duì)象每調(diào)用一個(gè)次方法,就會(huì)執(zhí)行一次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("當(dāng)前非admin用戶,不能執(zhí)行save操作");
        }
        return method.invoke(target, args);
    }
}

測(cè)試App類稍微改動(dòng)下:

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

上面代碼一樣可以實(shí)現(xiàn)需求,跟靜態(tài)代理區(qū)別就在于少創(chuàng)建了代理對(duì)象。此時(shí)存在疑問(wèn)點(diǎn),沒(méi)有創(chuàng)建代理對(duì)象,為啥可以實(shí)現(xiàn)代理類調(diào)用呢??

原理分析

先拋出結(jié)論JDK動(dòng)態(tài)代理底層實(shí)現(xiàn)原理:使用接口實(shí)現(xiàn)方式,運(yùn)行時(shí),在內(nèi)存中動(dòng)態(tài)構(gòu)建出一個(gè)類,然后編譯,執(zhí)行。這個(gè)類是一次性的,JVM停止,代理類就消失。

參與角色 要理解JDK動(dòng)態(tài)代理原理,首先得了解JDK動(dòng)態(tài)代理涉及到的類

InvocationHandler:真實(shí)對(duì)象方法調(diào)用處理器,內(nèi)置invoke方法,其功能:為真實(shí)對(duì)象定制代理邏輯

EmployeeInvocationHandler:?jiǎn)T工服務(wù)真實(shí)對(duì)象方法調(diào)用處理器,此類有3個(gè)用途: 1>設(shè)置真實(shí)對(duì)象

     //真實(shí)對(duì)象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }

2>定制代理方法實(shí)現(xiàn)邏輯

為真實(shí)對(duì)象save方法添加了權(quán)限校驗(yàn)邏輯

    //代理對(duì)象控制執(zhí)行方法
    //參數(shù)1:代理對(duì)象
    //參數(shù)2:真實(shí)對(duì)象的方法(使用方式得到方法對(duì)象)
    //參數(shù)3:真實(shí)對(duì)象方法參數(shù)列表
    //此處是代理對(duì)象對(duì)外暴露的可編輯的方法處理場(chǎng)所,代理對(duì)象每調(diào)用一個(gè)次方法,就會(huì)執(zhí)行一次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("當(dāng)前非admin用戶,不能執(zhí)行save操作");
        }
        return method.invoke(target, args);
    }

3>返回代理對(duì)象

方法執(zhí)行完之后,返回一個(gè)名為:$ProxyX的代理類(其中的X是序號(hào),一般默認(rèn)為0),這代理類由JDK動(dòng)態(tài)構(gòu)建出來(lái)。

    //獲取jvm在內(nèi)存中生成代理對(duì)象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

Proxy:動(dòng)態(tài)代理控制類,是JDK動(dòng)態(tài)生成的$ProxyX類的父類,它作用如下:
1>通過(guò)調(diào)用ProxyBuilder 類builder方法構(gòu)建代理對(duì)象類

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>通過(guò)newProxyInstance方法返回$ProxyX類的實(shí)例

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

$Proxy0:App類運(yùn)行時(shí),JDK動(dòng)態(tài)構(gòu)建出來(lái)的代理類,繼承至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("----------------真實(shí)對(duì)象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理對(duì)象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
                     new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

默認(rèn)情況下JVM是不保存動(dòng)態(tài)創(chuàng)建代理類字節(jié)碼對(duì)象的,可以在main方法中配置代理參數(shù)讓字節(jié)碼保留

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

執(zhí)行完之后,會(huì)在項(xiàng)目根目錄生成代理類字節(jié)碼對(duì)象。 

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

$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的特點(diǎn):

  • 1>繼承了Proxy類,實(shí)現(xiàn)了IEmployeeService 接口
  • 2>通過(guò)靜態(tài)塊的方式反射IEmployeeService接口save與list方法,得到他們的方法對(duì)象Method
  • 3>調(diào)用父類構(gòu)造器,需要傳入InvocationHandler 參數(shù)
  • 4>重寫IEmployeeService接口的save list方法靠的是父類Proxy的h屬性.invoke方法

真相大白

下圖所有參與動(dòng)態(tài)代理的類:

 下圖是上圖的操作時(shí)序圖,跟著走就對(duì)了

到這,JDK動(dòng)態(tài)代理就ok了。

相關(guān)文章

  • JAVA判斷兩個(gè)時(shí)間之間的差

    JAVA判斷兩個(gè)時(shí)間之間的差

    經(jīng)常會(huì)遇到需要判斷兩個(gè)時(shí)間之間的差異的情況,本文主要介紹了JAVA計(jì)算兩個(gè)時(shí)間之間的差,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • java學(xué)習(xí)指南之字符串與正則表達(dá)式

    java學(xué)習(xí)指南之字符串與正則表達(dá)式

    在日常Java后端開(kāi)發(fā)過(guò)程中,免不了對(duì)數(shù)據(jù)字段的解析,自然就少不了對(duì)字符串的操作,這其中就包含了正則表達(dá)式這一塊的內(nèi)容,下面這篇文章主要給大家介紹了關(guān)于java學(xué)習(xí)指南之字符串與正則表達(dá)式的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • Mybatis用注解寫in查詢的實(shí)現(xiàn)

    Mybatis用注解寫in查詢的實(shí)現(xiàn)

    這篇文章主要介紹了Mybatis用注解寫in查詢的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • java 對(duì)稱加密算法實(shí)現(xiàn)詳解

    java 對(duì)稱加密算法實(shí)現(xiàn)詳解

    這篇文章主要介紹了java 對(duì)稱加密算法實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • Ubuntu安裝JDK與IntelliJ?IDEA的詳細(xì)過(guò)程

    Ubuntu安裝JDK與IntelliJ?IDEA的詳細(xì)過(guò)程

    APT是Linux系統(tǒng)上的包管理工具,能自動(dòng)解決軟件包依賴關(guān)系并從遠(yuǎn)程存儲(chǔ)庫(kù)中獲取安裝軟件包,這篇文章主要介紹了Ubuntu安裝JDK與IntelliJ?IDEA的過(guò)程,需要的朋友可以參考下
    2023-08-08
  • Java?基于Hutool實(shí)現(xiàn)DES加解密示例詳解

    Java?基于Hutool實(shí)現(xiàn)DES加解密示例詳解

    這篇文章主要介紹了Java基于Hutool實(shí)現(xiàn)DES加解密,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的方法

    Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的方法

    這篇文章主要給大家介紹了關(guān)于Spring cloud踩坑記錄之使用feignclient遠(yuǎn)程調(diào)用服務(wù)404的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • Java開(kāi)發(fā)或調(diào)用WebService的幾種方式總結(jié)

    Java開(kāi)發(fā)或調(diào)用WebService的幾種方式總結(jié)

    java開(kāi)發(fā)過(guò)程中,很多地方都會(huì)遇到數(shù)據(jù)傳遞,遠(yuǎn)程獲取數(shù)據(jù)問(wèn)題,這篇文章主要介紹了Java開(kāi)發(fā)或調(diào)用WebService的幾種方式的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • Java中4種校驗(yàn)注解詳解(值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn))

    Java中4種校驗(yàn)注解詳解(值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn))

    這篇文章主要給大家介紹了關(guān)于Java中4種校驗(yàn)注解詳解的相關(guān)資料,分別包括值校驗(yàn)、范圍校驗(yàn)、長(zhǎng)度校驗(yàn)、格式校驗(yàn)等,Java注解(Annotation)是一種元數(shù)據(jù),它可以被添加到Java代碼中,并可以提供額外的信息和指令,需要的朋友可以參考下
    2023-08-08
  • 一文詳解Spring任務(wù)執(zhí)行和調(diào)度(小結(jié))

    一文詳解Spring任務(wù)執(zhí)行和調(diào)度(小結(jié))

    這篇文章主要介紹了一文詳解Spring任務(wù)執(zhí)行和調(diào)度(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08

最新評(píng)論