<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java設(shè)計模式之動態(tài)代理

          共 10440字,需瀏覽 21分鐘

           ·

          2020-12-08 07:38

          本文主要介紹Java中兩種常見的動態(tài)代理方式:JDK原生動態(tài)代理CGLIB動態(tài)代理

          什么是代理模式

          就是為其他對象提供一種代理以控制對這個對象的訪問。代理可以在不改動目標(biāo)對象的基礎(chǔ)上,增加其他額外的功能(擴(kuò)展功能)。

          代理模式角色分為 3 種:

          • Subject(抽象主題角色):定義代理類和真實(shí)主題的公共對外方法,也是代理類代理真實(shí)主題的方法;

          • RealSubject(真實(shí)主題角色):真正實(shí)現(xiàn)業(yè)務(wù)邏輯的類;

          • Proxy(代理主題角色):用來代理和封裝真實(shí)主題;

          如果根據(jù)字節(jié)碼的創(chuàng)建時機(jī)來分類,可以分為靜態(tài)代理和動態(tài)代理:

          • 所謂靜態(tài)也就是在程序運(yùn)行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和真實(shí)主題角色的關(guān)系在運(yùn)行前就確定了。

          • 而動態(tài)代理的源碼是在程序運(yùn)行期間由JVM根據(jù)反射等機(jī)制動態(tài)的生成,所以在運(yùn)行前并不存在代理類的字節(jié)碼文件

          靜態(tài)代理

          學(xué)習(xí)動態(tài)代理前,有必要來學(xué)習(xí)一下靜態(tài)代理。

          靜態(tài)代理在使用時,需要定義接口或者父類,被代理對象(目標(biāo)對象)與代理對象(Proxy)一起實(shí)現(xiàn)相同的接口或者是繼承相同父類。

          來看一個例子,模擬小貓走路的時間。

          //?接口
          public?interface?Walkable?{
          ????void?walk();
          }

          //?實(shí)現(xiàn)類
          public?class?Cat?implements?Walkable?{

          ????@Override
          ????public?void?walk()?{
          ????????System.out.println("cat?is?walking...");
          ????????try?{
          ????????????Thread.sleep(new?Random().nextInt(1000));
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          }

          如果我想知道走路的時間怎么辦?可以將實(shí)現(xiàn)類Cat修改為:

          public?class?Cat?implements?Walkable?{

          ????@Override
          ????public?void?walk()?{
          ????????long?start?=?System.currentTimeMillis();
          ????????System.out.println("cat?is?walking...");
          ????????try?{
          ????????????Thread.sleep(new?Random().nextInt(1000));
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("walk?time?=?"?+?(end?-?start));
          ????}
          }

          這里已經(jīng)侵入了源代碼,如果源代碼是不能改動的,這樣寫顯然是不行的,這里可以引入時間代理類CatTimeProxy

          public?class?CatTimeProxy?implements?Walkable?{
          ????private?Walkable?walkable;

          ????public?CatTimeProxy(Walkable?walkable)?{
          ????????this.walkable?=?walkable;
          ????}

          ????@Override
          ????public?void?walk()?{
          ????????long?start?=?System.currentTimeMillis();

          ????????walkable.walk();

          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("Walk?time?=?"?+?(end?-?start));
          ????}
          }

          如果這時候還要加上常見的日志功能,我們還需要創(chuàng)建一個日志代理類CatLogProxy

          public?class?CatLogProxy?implements?Walkable?{
          ????private?Walkable?walkable;

          ????public?CatLogProxy(Walkable?walkable)?{
          ????????this.walkable?=?walkable;
          ????}

          ????@Override
          ????public?void?walk()?{
          ????????System.out.println("Cat?walk?start...");

          ????????walkable.walk();

          ????????System.out.println("Cat?walk?end...");

          ????}
          }

          如果我們需要先記錄日志,再獲取行走時間,可以在調(diào)用的地方這么做:

          public?static?void?main(String[]?args)?{
          ????Cat?cat?=?new?Cat();
          ????CatLogProxy?p1?=?new?CatLogProxy(cat);
          ????CatTimeProxy?p2?=?new?CatTimeProxy(p1);

          ????p2.walk();
          }

          這樣的話,計時是包括打日志的時間的。

          靜態(tài)代理的問題

          如果我們需要計算SDK中100個方法的運(yùn)行時間,同樣的代碼至少需要重復(fù)100次,并且創(chuàng)建至少100個代理類。往小了說,如果Cat類有多個方法,我們需要知道其他方法的運(yùn)行時間,同樣的代碼也至少需要重復(fù)多次。因此,靜態(tài)代理至少有以下兩個局限性問題:

          • 如果同時代理多個類,依然會導(dǎo)致類無限制擴(kuò)展

          • 如果類中有多個方法,同樣的邏輯需要反復(fù)實(shí)現(xiàn)

          所以,我們需要一個通用的代理類來代理所有的類的所有方法,這就需要用到動態(tài)代理技術(shù)。

          動態(tài)代理

          學(xué)習(xí)任何一門技術(shù),一定要問一問自己,這到底有什么用。其實(shí),在這篇文章的講解過程中,我們已經(jīng)說出了它的主要用途。你發(fā)現(xiàn)沒,使用動態(tài)代理我們居然可以在不改變源碼的情況下,直接在方法中插入自定義邏輯。這有點(diǎn)不太符合我們的一條線走到底的編程邏輯,這種編程模型有一個專業(yè)名稱叫AOP。所謂的AOP,就像刀一樣,抓住時機(jī),趁機(jī)插入。

          Jdk動態(tài)代理

          JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數(shù):

          @CallerSensitive
          ????public?static?Object?newProxyInstance(ClassLoader?loader,
          ??????????????????????????????????????????Class[]?interfaces,
          ??????????????????????????????????????????InvocationHandler?h)

          ????????throws?IllegalArgumentException
          ????
          {
          ????????Objects.requireNonNull(h);

          ????????final?Class[]?intfs?=?interfaces.clone();
          ????????final?SecurityManager?sm?=?System.getSecurityManager();
          ????????if?(sm?!=?null)?{
          ????????????checkProxyAccess(Reflection.getCallerClass(),?loader,?intfs);
          ????????}

          ????????/*
          ?????????*?Look?up?or?generate?the?designated?proxy?class.
          ?????????*/

          ????????Class?cl?=?getProxyClass0(loader,?intfs);

          ????????/*
          ?????????*?Invoke?its?constructor?with?the?designated?invocation?handler.
          ?????????*/

          ????????try?{
          ????????????if?(sm?!=?null)?{
          ????????????????checkNewProxyPermission(Reflection.getCallerClass(),?cl);
          ????????????}

          ????????????final?Constructor?cons?=?cl.getConstructor(constructorParams);
          ????????????final?InvocationHandler?ih?=?h;
          ????????????if?(!Modifier.isPublic(cl.getModifiers()))?{
          ????????????????AccessController.doPrivileged(new?PrivilegedAction()?{
          ????????????????????public?Void?run()?{
          ????????????????????????cons.setAccessible(true);
          ????????????????????????return?null;
          ????????????????????}
          ????????????????});
          ????????????}
          ????????????return?cons.newInstance(new?Object[]{h});
          ????????}?catch?(IllegalAccessException|InstantiationException?e)?{
          ????????????throw?new?InternalError(e.toString(),?e);
          ????????}?catch?(InvocationTargetException?e)?{
          ????????????Throwable?t?=?e.getCause();
          ????????????if?(t?instanceof?RuntimeException)?{
          ????????????????throw?(RuntimeException)?t;
          ????????????}?else?{
          ????????????????throw?new?InternalError(t.toString(),?t);
          ????????????}
          ????????}?catch?(NoSuchMethodException?e)?{
          ????????????throw?new?InternalError(e.toString(),?e);
          ????????}
          ????}

          方法是在Proxy類中是靜態(tài)方法,且接收的三個參數(shù)依次為:

          • ClassLoader loader ?//指定當(dāng)前目標(biāo)對象使用類加載器

          • Class[] interfaces ?//目標(biāo)對象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型

          • InvocationHandler h ?//事件處理器

          主要是完成InvocationHandler h的編寫工作。

          接口類UserService

          public?interface?UserService?{

          ????public?void?select();

          ????public?void?update();
          }

          接口實(shí)現(xiàn)類,即要代理的類UserServiceImpl

          public?class?UserServiceImpl?implements?UserService?{
          ????@Override
          ????public?void?select()?{
          ????????System.out.println("查詢?selectById");
          ????}

          ????@Override
          ????public?void?update()?{
          ????????System.out.println("更新?update");
          ????}
          }

          代理類UserServiceProxy

          public?class?UserServiceProxy?implements?UserService?{
          ????private?UserService?target;

          ????public?UserServiceProxy(UserService?target){
          ????????this.target?=?target;
          ????}

          ????@Override
          ????public?void?select()?{
          ????????before();
          ????????target.select();
          ????????after();
          ????}

          ????@Override
          ????public?void?update()?{
          ????????before();
          ????????target.update();
          ????????after();
          ????}

          ????private?void?before()?{?????//?在執(zhí)行方法之前執(zhí)行
          ????????System.out.println(String.format("log?start?time?[%s]?",?new?Date()));
          ????}
          ????private?void?after()?{??????//?在執(zhí)行方法之后執(zhí)行
          ????????System.out.println(String.format("log?end?time?[%s]?",?new?Date()));
          ????}
          }

          主程序類:

          public?class?UserServiceProxyJDKMain?{
          ????public?static?void?main(String[]?args)?{

          ????????//?1.?創(chuàng)建被代理的對象,即UserService的實(shí)現(xiàn)類
          ????????UserServiceImpl?userServiceImpl?=?new?UserServiceImpl();

          ????????//?2.?獲取對應(yīng)的classLoader
          ????????ClassLoader?classLoader?=?userServiceImpl.getClass().getClassLoader();

          ????????//?3.?獲取所有接口的Class,?這里的userServiceImpl只實(shí)現(xiàn)了一個接口UserService,
          ????????Class[]?interfaces?=?userServiceImpl.getClass().getInterfaces();

          ????????//?4.?創(chuàng)建一個將傳給代理類的調(diào)用請求處理器,處理所有的代理對象上的方法調(diào)用
          ????????//?????這里創(chuàng)建的是一個自定義的日志處理器,須傳入實(shí)際的執(zhí)行對象?userServiceImpl
          ????????InvocationHandler?logHandler?=?new?LogHandler(userServiceImpl);

          ????????/*
          ???????????5.根據(jù)上面提供的信息,創(chuàng)建代理對象?在這個過程中,
          ???????????????a.JDK會通過根據(jù)傳入的參數(shù)信息動態(tài)地在內(nèi)存中創(chuàng)建和.class?文件等同的字節(jié)碼
          ???????????????b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對應(yīng)的class,
          ???????????????c.然后調(diào)用newInstance()創(chuàng)建代理實(shí)例
          ?????????*/


          ????????//?會動態(tài)生成UserServiceProxy代理類,并且用代理對象實(shí)例化LogHandler,調(diào)用代理對象的.invoke()方法即可
          ????????UserService?proxy?=?(UserService)?Proxy.newProxyInstance(classLoader,?interfaces,?logHandler);

          ????????//?調(diào)用代理的方法
          ????????proxy.select();
          ????????proxy.update();

          ????????//?生成class文件的名稱
          ????????ProxyUtils.generateClassFile(userServiceImpl.getClass(),?"UserServiceJDKProxy");
          ????}
          }

          這里可以保存下來代理生成的實(shí)現(xiàn)了接口的代理對象:

          public?class?ProxyUtils?{
          ????/*
          ?????*?將根據(jù)類信息?動態(tài)生成的二進(jìn)制字節(jié)碼保存到硬盤中,
          ?????*?默認(rèn)的是clazz目錄下
          ?????*?params?:clazz?需要生成動態(tài)代理類的類
          ?????*?proxyName?:?為動態(tài)生成的代理類的名稱
          ?????*/

          ????public?static?void?generateClassFile(Class?clazz,?String?proxyName)?{

          ????????//根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼
          ????????byte[]?classFile?=?ProxyGenerator.generateProxyClass(proxyName,?clazz.getInterfaces());
          ????????String?paths?=?clazz.getResource(".").getPath();
          ????????System.out.println(paths);
          ????????FileOutputStream?out?=?null;

          ????????try?{
          ????????????//保留到硬盤中
          ????????????out?=?new?FileOutputStream(paths?+?proxyName?+?".class");
          ????????????out.write(classFile);
          ????????????out.flush();
          ????????}?catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????try?{
          ????????????????out.close();
          ????????????}?catch?(IOException?e)?{
          ????????????????e.printStackTrace();
          ????????????}
          ????????}
          ????}

          }

          動態(tài)代理實(shí)現(xiàn)過程

          1. 通過getProxyClass0()生成代理類。JDK生成的最終真正的代理類,它繼承自Proxy并實(shí)現(xiàn)了我們定義的接口.

          2. 通過Proxy.newProxyInstance()生成代理類的實(shí)例對象,創(chuàng)建對象時傳入InvocationHandler類型的實(shí)例。

          3. 調(diào)用新實(shí)例的方法,即原InvocationHandler類中的invoke()方法。

          代理對象不需要實(shí)現(xiàn)接口,但是目標(biāo)對象一定要實(shí)現(xiàn)接口,否則不能用動態(tài)代理

          Cglib動態(tài)代理

          JDK的動態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,而不能實(shí)現(xiàn)接口的類就不能實(shí)現(xiàn)JDK的動態(tài)代理,cglib是針對類來實(shí)現(xiàn)代理的,他的原理是對指定的目標(biāo)類生成一個子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對final修飾的類進(jìn)行代理。

          Cglib代理,也叫作子類代理,它是在內(nèi)存中構(gòu)建一個子類對象從而實(shí)現(xiàn)對目標(biāo)對象功能的擴(kuò)展。

          Cglib子類代理實(shí)現(xiàn)方法:

          1. 需要引入cglibjar文件,但是Spring的核心包中已經(jīng)包括了Cglib功能,所以直接引入Spring-core.jar即可.

          2. 引入功能包后,就可以在內(nèi)存中動態(tài)構(gòu)建子類

          3. 代理的類不能為final,否則報錯

          4. 目標(biāo)對象的方法如果為final/static,那么就不會被攔截,即不會執(zhí)行目標(biāo)對象額外的業(yè)務(wù)方法.

          基本使用


          <dependency>
          ????<groupId>cglibgroupId>
          ????<artifactId>cglibartifactId>
          ????<version>2.2version>
          dependency>

          方法攔截器

          public?class?LogInterceptor?implements?MethodInterceptor{

          ????/*
          ?????*?@param?o?要進(jìn)行增強(qiáng)的對象
          ?????*?@param?method?要攔截的方法
          ?????*?@param?objects?參數(shù)列表,基本數(shù)據(jù)類型需要傳入其包裝類
          ?????*?@param?methodProxy?對方法的代理,
          ?????*?@return?執(zhí)行結(jié)果
          ?????*?@throws?Throwable
          ?????*/

          ????@Override
          ????public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
          ????????before();
          ????????Object?result?=?methodProxy.invokeSuper(o,?objects);
          ????????after();
          ????????return?result;

          ????}

          ????private?void?before()?{
          ????????System.out.println(String.format("log?start?time?[%s]?",?new?Date()));
          ????}
          ????private?void?after()?{
          ????????System.out.println(String.format("log?end?time?[%s]?",?new?Date()));
          ????}
          }

          測試用例

          這里保存了代理類的.class文件

          public?class?CglibMain?{

          ????public?static?void?main(String[]?args)?{
          ????????//?創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類
          ????????Enhancer?enhancer?=?new?Enhancer();
          ????????//?設(shè)置目標(biāo)類的字節(jié)碼文件
          ????????enhancer.setSuperclass(UserDao.class);
          ????????//?設(shè)置回調(diào)函數(shù)
          ????????enhancer.setCallback(new?LogInterceptor());
          ????????//?create會創(chuàng)建代理類
          ????????UserDao?userDao?=?(UserDao)enhancer.create();
          ????????userDao.update();
          ????????userDao.select();
          ????}
          }

          結(jié)果

          log?start?time?[Mon?Nov?30?17:26:39?CST?2020]?
          UserDao?更新?update
          log?end?time?[Mon?Nov?30?17:26:39?CST?2020]?
          log?start?time?[Mon?Nov?30?17:26:39?CST?2020]?
          UserDao?查詢?selectById
          log?end?time?[Mon?Nov?30?17:26:39?CST?2020]?

          兩種動態(tài)代理對比

          JDK 動態(tài)代理

          • 為了解決靜態(tài)代理中,生成大量的代理類造成的冗余;

          • JDK 動態(tài)代理只需要實(shí)現(xiàn) InvocationHandler 接口,重寫 invoke 方法便可以完成代理的實(shí)現(xiàn),

          • jdk的代理是利用反射生成代理類 Proxyxx.class 代理類字節(jié)碼,并生成對象

          • jdk動態(tài)代理之所以只能代理接口是因?yàn)榇眍惐旧硪呀?jīng)extendsProxy,而java是不允許多重繼承的,但是允許實(shí)現(xiàn)多個接口

          優(yōu)點(diǎn):解決了靜態(tài)代理中冗余的代理實(shí)現(xiàn)類問題。

          缺點(diǎn)JDK 動態(tài)代理是基于接口設(shè)計實(shí)現(xiàn)的,如果沒有接口,會拋異常。

          CGLIB 代理

          • 由于JDK 動態(tài)代理限制了只能基于接口設(shè)計,而對于沒有接口的情況,JDK方式解決不了;

          • CGLib 采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢織入橫切邏輯,來完成動態(tài)代理的實(shí)現(xiàn)。

          • 實(shí)現(xiàn)方式實(shí)現(xiàn) MethodInterceptor 接口,重寫 intercept 方法,通過 Enhancer 類的回調(diào)方法來實(shí)現(xiàn)。

          • 但是CGLib在創(chuàng)建代理對象時所花費(fèi)的時間卻比JDK多得多,所以對于單例的對象,因?yàn)闊o需頻繁創(chuàng)建對象,用CGLib合適,反之,使用JDK方式要更為合適一些。

          • 同時,由于CGLib由于是采用動態(tài)創(chuàng)建子類的方法,對于final方法,無法進(jìn)行代理。

          優(yōu)點(diǎn):沒有接口也能實(shí)現(xiàn)動態(tài)代理,而且采用字節(jié)碼增強(qiáng)技術(shù),性能也不錯。

          缺點(diǎn):技術(shù)實(shí)現(xiàn)相對難理解些。

          瀏覽 49
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲操区| 日韩黄色视屏 | 天天日天天摸天天爽 | 51久久国产露脸精品国产 | 精品无码一二三 |