<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>

          深入理解JDK動態(tài)代理

          共 14869字,需瀏覽 30分鐘

           ·

          2021-04-25 12:02


          代理模式的目的是在不修改原有類方法設(shè)計的基礎(chǔ)上,對方法行為進(jìn)行增強(qiáng)。

          為了好理解,舉個實(shí)際場景,我們業(yè)務(wù)場景中經(jīng)常有限流的需求,常規(guī)操作是在需要限流的接口代碼前加入調(diào)用次數(shù)判斷的代碼,但是這樣每個需要限流的方法都需要加,工作量大不說,一方面不好維護(hù),不能很清晰的知道每個接口限流值,另一方面,限流代碼和業(yè)務(wù)代碼堆疊在一起,也影響代碼閱讀。解法是做一套統(tǒng)一限流,一般好點(diǎn)的會有專門的接口限流平臺,配置對應(yīng)的接口名,設(shè)置限流值,直接就可以限流,實(shí)現(xiàn)方式就可以用動態(tài)代理。不修改原接口的實(shí)現(xiàn),對接口進(jìn)行增強(qiáng)。

          動態(tài)代理的優(yōu)勢是實(shí)現(xiàn)無侵入式的代碼擴(kuò)展,做方法的增強(qiáng);讓你可以在不用修改源碼的情況下,增強(qiáng)一些方法;在方法的前后你可以做你任何想做的事情(甚至不去執(zhí)行這個方法就可以)。

          靜態(tài)代理

          既然有動態(tài),那一定有靜態(tài),說下區(qū)別吧,

          靜態(tài):最大的區(qū)別是靜態(tài)是編譯期就決定了,在程序運(yùn)行之前,代理類的.class文件已經(jīng)存在了。被代理類是什么,代理類實(shí)現(xiàn)方式。

          舉個栗子:

          我現(xiàn)在有個接口,是把Json字符串解析成Object 對象,接口如下:

          public interface IProvider {
             
            Object getData(String json);
            
          }

          接口的實(shí)現(xiàn)類如下:

          public class SimpleProvider implements IProvider {
              @Override
              public Object getData(String json) {
                  //解析json 拿到數(shù)據(jù)
                  return parseJson(json);
              }

          那現(xiàn)在有個需求,需要對 getData 方法做限流,指定用靜態(tài)代理的方式。

          需要很簡單,我就直接貼了:

          public class ProviderProxy implements IProvider{

              //持有一個被代理對象的引用(在這里是SimpleProvider)
              IProvider iProvider;

              public StaticProviderProxy(IProvider iProvider){
                  this.iProvider = iProvider;
              }

              @Override
              public Object getData(String json) {
                  //做限流檢查
                  if(callSpeed > flowLimt) {
                    //流量超限
                     throw FlowLimitException();
                  }
                  Object object = iProvider.getData(json);
                  return object;
              }
          }
          //main 
          public static void main(String[] args) {
            IProvider provider = new ProviderProxy(new SimpleProvider());
              provider.getData("{\"data\":{}}");
          }

          這就是靜態(tài)代理,代理類(ProviderProxy)實(shí)現(xiàn)和需要做方法增強(qiáng)的被代理類(SimpleProvider)實(shí)現(xiàn)同一個接口(IProvider),方法具體實(shí)現(xiàn)上做增強(qiáng),這里是限流檢查。

          動態(tài)代理

          Java 動態(tài)代理

          • 動態(tài)代理類:在程序運(yùn)行時,通過反射機(jī)制動態(tài)生成。
          • 動態(tài)代理類通常代理接口下的所有類。靜態(tài)一般指定某個類代理。
          • 動態(tài)代理事先不知道要代理的是什么,只有在運(yùn)行的時候才能確定。靜態(tài)是編譯期確定的。

          還是以IProvider 接口為例,同樣是要對 SimpleProvider 做增強(qiáng),如下:

          public class ProviderHandler implements InvocationHandler {
              Object target;

              public Object bind(Object target){
                  this.target = target;
                  //這里生成了代理對象
                  return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                          target.getClass().getInterfaces(), this);
              }

              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  //限流
                  flowLimit(args);
                  Object obj = method.invoke(target, args);
                  //打印日志
                  logger.info("print log...");
                  return obj;
              }
          }
          //main
          public static void main(String[] args) {
             ProviderHandler providerHandler = new ProviderHandler();
             IProvider iProvider = (IProvider) providerHandler.bind(new SimpleProvider());
             iProvider.getData("weibo.data");
          }

          這里有三個對象:

          1. SimpleProvider 對象 , 我們稱之為被代理對象

          2. ProviderHandler 對象,我們稱之為執(zhí)行者對象

          3. Proxy對象 (通過在ProviderHandler bind方法中使用Proxy.newProxyInstance生成的對象) 我們稱之為代理對象

          這三個對象是什么關(guān)系呢?

          Proxy是真正的代理類,SimpleProvider是被代理類,ProviderHandler是執(zhí)行方法增強(qiáng)的執(zhí)行者。

          我們是為了增強(qiáng)SimpleProvider (被代理對象)的getData方法,就Proxy對象來代理被代理對象的執(zhí)行,Proxy不親自來做這件事,而是交給執(zhí)行者對象ProviderHandler 來實(shí)現(xiàn)增加的目錄,執(zhí)行調(diào)用前的限流校驗(yàn)。

          實(shí)際怎么實(shí)現(xiàn)的呢?

          newProxyInstance源碼

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

                  throws IllegalArgumentException
              
          {
                  //對 Invocationhandler做判空處理
                  Objects.requireNonNull(h);
                  //復(fù)制[IProvider接口]
                  final Class<?>[] intfs = interfaces.clone();

                 //根據(jù)IProvider的類加載器IProvider接口生成了Proxy類,關(guān)鍵:根據(jù)類加載器和接口對象在JVM緩存中生成一個類對象
                  Class<?> cl = getProxyClass0(loader, intfs);
                  //獲取構(gòu)造器
                  final Constructor<?> cons = cl.getConstructor(constructorParams);
                  //保存InvocationHandler的引用
                  final InvocationHandler ih = h;
                  //通過構(gòu)造器實(shí)例化Proxy代理對象
                  return cons.newInstance(new Object[]{h});
              }

          代碼注釋寫的很清晰。

          可能這個地方大家都會疑惑,生成的Proxy對象是怎樣調(diào)用執(zhí)行者的invoke函數(shù)的。

          這個地方通過這段代碼將Proxy0的class字節(jié)碼輸出到文件。

          byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", WeiboProvider.class.getInterfaces());
          String path = "C:**/IdeaProjects/study/out/production/study/SimpleProxy.class";
          try(FileOutputStream fos = new FileOutputStream(path)) {
              fos.write(classFile);
              fos.flush();
              System.out.println("代理類class文件寫入成功");
             } catch (Exception e) {
               System.out.println("寫文件錯誤");
           }

          反編譯Proxy0如下:

          //Proxy0 是動態(tài)生成的類,繼承自Proxy,實(shí)現(xiàn)了IProvider接口
          public final class $Proxy0 extends Proxy implements IProvider {
              private static Method m1;
              private static Method m2;
              private static Method m3;
              private static Method m0;

              public $Proxy0(InvocationHandler var1) throws  {
                  super(var1);
              }

              public final boolean equals(Object var1) throws  {
                  try {
                      return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
                  } catch (RuntimeException | Error var3) {
                      throw var3;
                  } catch (Throwable var4) {
                      throw new UndeclaredThrowableException(var4);
                  }
              }

              public final String toString() throws  {
                  try {
                      return (String)super.h.invoke(this, m2, (Object[])null);
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

              public final String getData(String var1) throws  {
                  try {
                      //m3就是IProvider 接口的getData方法 
                      //super.h 是父類java.lang.reflect.Proxy的屬性 InvocationHandler
                      return (String)super.h.invoke(this, m3, new Object[]{var1});
                  } catch (RuntimeException | Error var3) {
                      throw var3;
                  } catch (Throwable var4) {
                      throw new UndeclaredThrowableException(var4);
                  }
              }

              public final int hashCode() throws  {
                  try {
                      return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
                  } catch (RuntimeException | Error var2) {
                      throw var2;
                  } catch (Throwable var3) {
                      throw new UndeclaredThrowableException(var3);
                  }
              }

              static {
                  try {
                      m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[]{Class.forName("java.lang.Object")});
                      m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);
                      //m3就是IProvider 接口的getData方法
                      m3 = Class.forName("aop.IProvider").getMethod("getData"new Class[]{Class.forName("java.lang.String")});
                      m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);
                  } catch (NoSuchMethodException var2) {
                      throw new NoSuchMethodError(var2.getMessage());
                  } catch (ClassNotFoundException var3) {
                      throw new NoClassDefFoundError(var3.getMessage());
                  }
              }
          }

          重點(diǎn)在 return (String)super.h.invoke(this, m3, new Object[]{var1});代碼。

          $Proxy0繼承Proxy類,實(shí)現(xiàn)了IProvider接口,所以也有g(shù)etData()函數(shù),而getData函數(shù)調(diào)用的是執(zhí)行者InvocationHandler的invoke方法,m3是通過反射拿到的Method對象,所以看getData調(diào)用invoke傳遞的。三個參數(shù),第一個是Proxy對象,第二個是getData方法對象,第三個是參數(shù)。

          總結(jié)一下:

          • 動態(tài)代理的本質(zhì)就是,生成一個繼承自Proxy,實(shí)現(xiàn)被代理接口(IProvider)的類 - Proxy0。
          • Proxy0 持有InvocationHandler實(shí)例,InvocationHandler 持有SimpleProvider實(shí)例。Proxy0調(diào)用接口 getData方法時,先傳遞給InvocationHandler,InvocationHandler再傳遞給SimpleProvider實(shí)例。

          動態(tài)代理實(shí)際上就是幫我們在JVM內(nèi)存中直接重新生成了代理類class和對應(yīng)類對象,然后通過執(zhí)行者InvocationHandler調(diào)用被代理對象SimpleProvider。

          Spring AOP中的代理

          Spring代理其實(shí)是對JDK動態(tài)代理和CGLIB代理進(jìn)行了封裝,并且引入了AOP的概念,同時引入了AspectJ中的一些注解:@pointCut @After 等。

          public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
                     if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
             Class<?> targetClass = config.getTargetClass();
             if (targetClass == null) {
              throw new AopConfigException("TargetSource cannot determine target class: " +
                "Either an interface or a target is required for proxy creation.");
             }
                // 如果是接口,使用jdk代理 
             if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
              return new JdkDynamicAopProxy(config);
             }
                //否則使用cglib
             return new ObjenesisCglibAopProxy(config);
            }
            else {
             return new JdkDynamicAopProxy(config);
            }
           }


          1. 把Redis當(dāng)作隊列來用,真的合適嗎?

          2. 一次簡單的 JVM 調(diào)優(yōu),性能提升了15%

          3. Git 這樣回退代碼,才足夠優(yōu)雅

          4. SpringBoot 集成 Shiro 極簡教程(實(shí)戰(zhàn)版)

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 80
          點(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>
                  影音先锋久久久 | 五月天婷婷激情综合网 | 日韩一级高清在线 | 美日韩操逼 | 天天干91 |