目錄
概念
案例
靜態代理
JDK動態代理模式
原理分析
真相大白
首頁 Java java教程 Java如何實作JDK動態代理

Java如何實作JDK動態代理

Apr 30, 2023 am 08:49 AM
java jdk

概念

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

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

案例

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

靜態代理

1

2

3

4

5

6

7

8

/**

 * 代理接口

 */

public interface IEmployeeService {

    void save();

  

    void list();

}

登入後複製

1

2

3

4

5

6

7

8

9

10

11

12

13

/**

 * 真实对象

 */

public class EmployeeServiceImpl implements IEmployeeService {

    @Override

    public void save() {

        System.out.println("EmployeeServiceImpl-正常的save....");

    }

    @Override

    public void list() {

        System.out.println("EmployeeServiceImpl-正常的list....");

    }

}

登入後複製

1

2

3

4

5

6

7

8

9

10

11

12

/**

 * 模拟当前登录用户对象

 */

public class SessionHolder {

    private static String currentUser;

    public static String  getCurrentUser(){

        return currentUser;

    }

    public static void   setCurrentUser(String currentUser){

        SessionHolder.currentUser = currentUser;

    }

}

登入後複製

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

/**

 * 代理对象

 */

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();

    }

}

登入後複製

1

2

3

4

5

6

7

8

9

10

11

12

13

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();

    }

}

登入後複製

1

2

3

4

5

6

7

8

----------------真实对象--------------------

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

/**

 * 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類別稍微改動下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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>設定真實物件

1

2

3

4

5

//真实对象-EmployeeServiceImpl

private Object target;

public EmployeeInvocationHandler(Object target){

    this.target = target;

}

登入後複製

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

//代理对象控制执行方法

//参数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動態建構出來。

1

2

3

4

5

6

7

//获取jvm在内存中生成代理对象

public Object getProxy(){

    return  Proxy.newProxyInstance(

            target.getClass().getClassLoader(),

            target.getClass().getInterfaces(),

            this);

}

登入後複製

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

1

2

3

4

5

6

7

8

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類別的實例

1

2

3

4

5

public static Object newProxyInstance(ClassLoader loader,

                                       Class<?>[] interfaces,

                                       InvocationHandler h) {

 //...

}

登入後複製

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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方法中配置代理參數讓字節碼保留

1

2

3

4

//JDK8之前

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

//JDK8之后

System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

登入後複製

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

Java如何實作JDK動態代理

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

#$Proxy0類別

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 週前 By 尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 週前 By 尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱門文章標籤

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Java 中的平方根 Java 中的平方根 Aug 30, 2024 pm 04:26 PM

Java 中的平方根

Java 中的完美數 Java 中的完美數 Aug 30, 2024 pm 04:28 PM

Java 中的完美數

Java 中的隨機數產生器 Java 中的隨機數產生器 Aug 30, 2024 pm 04:27 PM

Java 中的隨機數產生器

Java 中的阿姆斯壯數 Java 中的阿姆斯壯數 Aug 30, 2024 pm 04:26 PM

Java 中的阿姆斯壯數

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java中的Weka

Java 中的史密斯數 Java 中的史密斯數 Aug 30, 2024 pm 04:28 PM

Java 中的史密斯數

Java Spring 面試題 Java Spring 面試題 Aug 30, 2024 pm 04:29 PM

Java Spring 面試題

突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

突破或從Java 8流返回?

See all articles