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

          動(dòng)態(tài)代理總結(jié),面試你要知道的都在這里,無(wú)廢話(huà)!

          共 15460字,需瀏覽 31分鐘

           ·

          2021-05-19 09:03

          前言

          面試題:講講jdk動(dòng)態(tài)代理,cglib區(qū)別,實(shí)現(xiàn)原理,優(yōu)缺點(diǎn),怎么實(shí)現(xiàn)方法的調(diào)用的

          來(lái)自:社招一年半面經(jīng)分享(含阿里美團(tuán)頭條京東滴滴)

          這篇文章總結(jié)你需要回答的知識(shí)點(diǎn),全程少?gòu)U話(huà),懟干貨,文章較長(zhǎng),可以點(diǎn)贊在看,喜歡這種文章的話(huà),我之后也會(huì)一直分享的,硬核文章也會(huì)定期分享!

          同時(shí)之前的個(gè)人網(wǎng)站:https://upheart.cn/,最近兩天想了想,決定繼續(xù)維護(hù)著,公眾號(hào)文章會(huì)定期(一般2天左右)同步更新到上去

          至于之所以決定繼續(xù)維護(hù),主要是為了大家工作的時(shí)候也方便學(xué)習(xí),畢竟大家工作的時(shí)候總不能玩手機(jī)看公眾號(hào)文章吧,哈哈!

          代理模式

          代理模式是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象額外的訪問(wèn)方式,即通過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象,這樣可以在不修改原目標(biāo)對(duì)象的前提下,提供額外的功能操作,擴(kuò)展目標(biāo)對(duì)象的功能

          一個(gè)比方:在租房的時(shí)候,有的人會(huì)通過(guò)房東直租,有的人會(huì)通過(guò)中介租房。

          這兩種情況哪種比較方便呢?當(dāng)然是通過(guò)中介更加方便。

          這里的中介就相當(dāng)于代理,用戶(hù)通過(guò)中介完成租房的一系列操作(看房、交押金、租房、清掃衛(wèi)生)代理模式可以有效的將具體的實(shí)現(xiàn)與調(diào)用方進(jìn)行解耦,通過(guò)面向接口進(jìn)行編碼完全將具體的實(shí)現(xiàn)隱藏在內(nèi)部。

          分類(lèi):

          靜態(tài)代理: 在編譯時(shí)就已經(jīng)實(shí)現(xiàn),編譯完成后代理類(lèi)是一個(gè)實(shí)際的class文件

          動(dòng)態(tài)代理: 在運(yùn)行時(shí)動(dòng)態(tài)生成的,即編譯完成后沒(méi)有實(shí)際的class文件,而是在運(yùn)行時(shí)動(dòng)態(tài)生成類(lèi)字節(jié)碼,并加載到JVM中

          靜態(tài)代理

          使用方式

          創(chuàng)建一個(gè)接口,然后創(chuàng)建被代理的類(lèi)實(shí)現(xiàn)該接口并且實(shí)現(xiàn)該接口中的抽象方法。之后再創(chuàng)建一個(gè)代理類(lèi),同時(shí)使其也實(shí)現(xiàn)這個(gè)接口。在代理類(lèi)中持有一個(gè)被代理對(duì)象的引用,而后在代理類(lèi)方法中調(diào)用該對(duì)象的方法。

          public interface UserDao {    
            void save();     
          }
          public class UserDaoImpl implements UserDao {
              @Override
              public void save() {
                  System.out.println("正在保存用戶(hù)...");
              }
          }
          public class TransactionHandler implements UserDao {
              //目標(biāo)代理對(duì)象
              private UserDao target;
              //構(gòu)造代理對(duì)象時(shí)傳入目標(biāo)對(duì)象
              public TransactionHandler(UserDao target) {
                  this.target = target;
              }
              @Override
              public void save() {
                  //調(diào)用目標(biāo)方法前的處理
                  System.out.println("開(kāi)啟事務(wù)控制...");
                  //調(diào)用目標(biāo)對(duì)象的方法
                  target.save();
                  //調(diào)用目標(biāo)方法后的處理
                  System.out.println("關(guān)閉事務(wù)控制...");
              }
          }
          public class Main {
              public static void main(String[] args) {
                  //新建目標(biāo)對(duì)象
                  UserDaoImpl target = new UserDaoImpl();
                  //創(chuàng)建代理對(duì)象, 并使用接口對(duì)其進(jìn)行引用
                  UserDao userDao = new TransactionHandler(target);
                  //針對(duì)接口進(jìn)行調(diào)用
                  userDao.save();
              }
          }

          使用JDK靜態(tài)代理很容易就完成了對(duì)一個(gè)類(lèi)的代理操作。但是JDK靜態(tài)代理的缺點(diǎn)也暴露了出來(lái):由于代理只能為一個(gè)類(lèi)服務(wù),如果需要代理的類(lèi)很多,那么就需要編寫(xiě)大量的代理類(lèi),比較繁瑣

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

          使用JDK動(dòng)態(tài)代理的五大步驟:

          1. 通過(guò)實(shí)現(xiàn)InvocationHandler接口來(lái)自定義自己的InvocationHandler;

          2. 通過(guò)Proxy.getProxyClass獲得動(dòng)態(tài)代理類(lèi);

          3. 通過(guò)反射機(jī)制獲得代理類(lèi)的構(gòu)造方法,方法簽名為getConstructor(InvocationHandler.class)

          4. 通過(guò)構(gòu)造函數(shù)獲得代理對(duì)象并將自定義的InvocationHandler實(shí)例對(duì)象傳為參數(shù)傳入;

          5. 通過(guò)代理對(duì)象調(diào)用目標(biāo)方法;

          public interface IHello {
              void sayHello();
          }
           public class HelloImpl implements IHello {
              @Override
              public void sayHello() {
                  System.out.println("Hello world!");
              }
          }
          import java.lang.reflect.InvocationHandler;
          import java.lang.reflect.Method;
           
          public class MyInvocationHandler implements InvocationHandler {
           
              /** 目標(biāo)對(duì)象 */
              private Object target;
           
              public MyInvocationHandler(Object target){
                  this.target = target;
              }
           
              @Override
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                  System.out.println("------插入前置通知代碼-------------");
                  // 執(zhí)行相應(yīng)的目標(biāo)方法
                  Object rs = method.invoke(target,args);
                  System.out.println("------插入后置處理代碼-------------");
                  return rs;
              }
          }
          import java.lang.reflect.Constructor;
          import java.lang.reflect.InvocationHandler;
          import java.lang.reflect.InvocationTargetException;
          import java.lang.reflect.Proxy;

          public class MyProxyTest {
              public static void main(String[] args)
                      throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException 
          {
                  // =========================第一種==========================
                  // 1、生成$Proxy0的class文件
                  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles""true");
                  // 2、獲取動(dòng)態(tài)代理類(lèi)
                  Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
                  // 3、獲得代理類(lèi)的構(gòu)造函數(shù),并傳入?yún)?shù)類(lèi)型InvocationHandler.class
                  Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
                  // 4、通過(guò)構(gòu)造函數(shù)來(lái)創(chuàng)建動(dòng)態(tài)代理對(duì)象,將自定義的InvocationHandler實(shí)例傳入
                  IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
                  // 5、通過(guò)代理對(duì)象調(diào)用目標(biāo)方法
                  iHello1.sayHello();
           
                  // ==========================第二種=============================
                  /**
                   * Proxy類(lèi)中還有個(gè)將2~4步驟封裝好的簡(jiǎn)便方法來(lái)創(chuàng)建動(dòng)態(tài)代理對(duì)象,
                   *其方法簽名為:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
                   */

                  IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加載接口的類(lèi)加載器
                          new Class[]
          {IHello.class}, // 一組接口
                          new MyInvocationHandler(new HelloImpl()))
          // 自定義的InvocationHandler
                  iHello2.sayHello();
              }
          }

          JDK靜態(tài)代理與JDK動(dòng)態(tài)代理之間有些許相似,比如說(shuō)都要?jiǎng)?chuàng)建代理類(lèi),以及代理類(lèi)都要實(shí)現(xiàn)接口等。

          不同之處: 在靜態(tài)代理中我們需要對(duì)哪個(gè)接口和哪個(gè)被代理類(lèi)創(chuàng)建代理類(lèi),所以我們?cè)诰幾g前就需要代理類(lèi)實(shí)現(xiàn)與被代理類(lèi)相同的接口,并且直接在實(shí)現(xiàn)的方法中調(diào)用被代理類(lèi)相應(yīng)的方法;但是動(dòng)態(tài)代理則不同,我們不知道要針對(duì)哪個(gè)接口、哪個(gè)被代理類(lèi)創(chuàng)建代理類(lèi),因?yàn)樗窃谶\(yùn)行時(shí)被創(chuàng)建的。

          一句話(huà)來(lái)總結(jié)一下JDK靜態(tài)代理和JDK動(dòng)態(tài)代理的區(qū)別:

          JDK靜態(tài)代理是通過(guò)直接編碼創(chuàng)建的,而JDK動(dòng)態(tài)代理是利用反射機(jī)制在運(yùn)行時(shí)創(chuàng)建代理類(lèi)的。

          其實(shí)在動(dòng)態(tài)代理中,核心是InvocationHandler。每一個(gè)代理的實(shí)例都會(huì)有一個(gè)關(guān)聯(lián)的調(diào)用處理程序(InvocationHandler)。對(duì)待代理實(shí)例進(jìn)行調(diào)用時(shí),將對(duì)方法的調(diào)用進(jìn)行編碼并指派到它的調(diào)用處理器(InvocationHandler)的invoke方法

          對(duì)代理對(duì)象實(shí)例方法的調(diào)用都是通過(guò)InvocationHandler中的invoke方法來(lái)完成的,而invoke方法會(huì)根據(jù)傳入的代理對(duì)象、方法名稱(chēng)以及參數(shù)決定調(diào)用代理的哪個(gè)方法。

          CGLIB

          CGLIB包的底層是通過(guò)使用一個(gè)小而快的字節(jié)碼處理框架ASM,來(lái)轉(zhuǎn)換字節(jié)碼并生成新的類(lèi)

          CGLIB代理實(shí)現(xiàn)如下:

          1. 首先實(shí)現(xiàn)一個(gè)MethodInterceptor,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類(lèi)的intercept()方法。
          2. 然后在需要使用的時(shí)候,通過(guò)CGLIB動(dòng)態(tài)代理獲取代理對(duì)象。

          使用案例

           public class HelloService {
           
              public HelloService() {
                  System.out.println("HelloService構(gòu)造");
              }
           
              /**
               * 該方法不能被子類(lèi)覆蓋,Cglib是無(wú)法代理final修飾的方法的
               */

              final public String sayOthers(String name) {
                  System.out.println("HelloService:sayOthers>>"+name);
                  return null;
              }
           
              public void sayHello() {
                  System.out.println("HelloService:sayHello");
              }
          }
          import net.sf.cglib.proxy.MethodInterceptor;
          import net.sf.cglib.proxy.MethodProxy;
           
          import java.lang.reflect.Method;
           
          /**
           * 自定義MethodInterceptor
           */

          public class MyMethodInterceptor implements MethodInterceptor{
           
              /**
               * sub:cglib生成的代理對(duì)象
               * method:被代理對(duì)象方法
               * objects:方法入?yún)?br>     * methodProxy: 代理方法
               */

              @Override
              public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                  System.out.println("======插入前置通知======");
                  Object object = methodProxy.invokeSuper(sub, objects);
                  System.out.println("======插入后者通知======");
                  return object;
              }
          }
          import net.sf.cglib.core.DebuggingClassWriter;
          import net.sf.cglib.proxy.Enhancer;
           
          public class Client {
              public static void main(String[] args) {
                  // 代理類(lèi)class文件存入本地磁盤(pán)方便我們反編譯查看源碼
                  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
                  // 通過(guò)CGLIB動(dòng)態(tài)代理獲取代理對(duì)象的過(guò)程
                  Enhancer enhancer = new Enhancer();
                  // 設(shè)置enhancer對(duì)象的父類(lèi)
                  enhancer.setSuperclass(HelloService.class);
                  // 設(shè)置enhancer的回調(diào)對(duì)象
                  enhancer.setCallback(new MyMethodInterceptor());
                  // 創(chuàng)建代理對(duì)象
                  HelloService proxy= (HelloService)enhancer.create();
                  // 通過(guò)代理對(duì)象調(diào)用目標(biāo)方法
                  proxy.sayHello();
              }
          }

          JDK代理要求被代理的類(lèi)必須實(shí)現(xiàn)接口,有很強(qiáng)的局限性。

          而CGLIB動(dòng)態(tài)代理則沒(méi)有此類(lèi)強(qiáng)制性要求。簡(jiǎn)單的說(shuō),CGLIB會(huì)讓生成的代理類(lèi)繼承被代理類(lèi),并在代理類(lèi)中對(duì)代理方法進(jìn)行強(qiáng)化處理(前置處理、后置處理等)。

          總結(jié)一下CGLIB在進(jìn)行代理的時(shí)候都進(jìn)行了哪些工作

          • 生成的代理類(lèi)繼承被代理類(lèi)。在這里我們需要注意一點(diǎn):如果委托類(lèi)被final修飾,那么它不可被繼承,即不可被代理;同樣,如果委托類(lèi)中存在final修飾的方法,那么該方法也不可被代理
          • 代理類(lèi)會(huì)為委托方法生成兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法,它在方法中會(huì)通過(guò)super調(diào)用委托方法;另一個(gè)是代理類(lèi)獨(dú)有的方法
          • 當(dāng)執(zhí)行代理對(duì)象的方法時(shí),會(huì)首先判斷一下是否存在實(shí)現(xiàn)了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,則將調(diào)用MethodInterceptor中的intercept方法

          intercept方法中,我們除了會(huì)調(diào)用委托方法,還會(huì)進(jìn)行一些增強(qiáng)操作。在Spring AOP中,典型的應(yīng)用場(chǎng)景就是在某些敏感方法執(zhí)行前后進(jìn)行操作日志記錄

          在CGLIB中,方法的調(diào)用并不是通過(guò)反射來(lái)完成的,而是直接對(duì)方法進(jìn)行調(diào)用:通過(guò)FastClass機(jī)制對(duì)Class對(duì)象進(jìn)行特別的處理,比如將會(huì)用數(shù)組保存method的引用,每次調(diào)用方法的時(shí)候都是通過(guò)一個(gè)index下標(biāo)來(lái)保持對(duì)方法的引用

          Fastclass機(jī)制

          CGLIB采用了FastClass的機(jī)制來(lái)實(shí)現(xiàn)對(duì)被攔截方法的調(diào)用。

          FastClass機(jī)制就是對(duì)一個(gè)類(lèi)的方法建立索引,通過(guò)索引來(lái)直接調(diào)用相應(yīng)的方法

          public class test10 {
            //這里,tt可以看作目標(biāo)對(duì)象,fc可以看作是代理對(duì)象;首先根據(jù)代理對(duì)象的getIndex方法獲取目標(biāo)方法的索引,
            //然后再調(diào)用代理對(duì)象的invoke方法就可以直接調(diào)用目標(biāo)類(lèi)的方法,避免了反射
              public static void main(String[] args){
                  Test tt = new Test();
                  Test2 fc = new Test2();
                  int index = fc.getIndex("f()V");
                  fc.invoke(index, tt, null);
              }
          }

          class Test{
              public void f(){
                  System.out.println("f method");
              }
              
              public void g(){
                  System.out.println("g method");
              }
          }
          class Test2{
              public Object invoke(int index, Object o, Object[] ol){
                  Test t = (Test) o;
                  switch(index){
                  case 1:
                      t.f();
                      return null;
                  case 2:
                      t.g();
                      return null;
                  }
                  return null;
              }
              //這個(gè)方法對(duì)Test類(lèi)中的方法建立索引
              public int getIndex(String signature){
                  switch(signature.hashCode()){
                  case 3078479:
                      return 1;
                  case 3108270:
                      return 2;
                  }
                  return -1;
              }
          }

          上例中,Test2是Test的Fastclass,在Test2中有兩個(gè)方法getIndex和invoke。

          在getIndex方法中對(duì)Test的每個(gè)方法建立索引,并根據(jù)入?yún)ⅲǚ椒?方法的描述符)來(lái)返回相應(yīng)的索引。

          Invoke根據(jù)指定的索引,以ol為入?yún)⒄{(diào)用對(duì)象O的方法。這樣就避免了反射調(diào)用,提高了效率

          三種代理方式之間對(duì)比

          代理方式實(shí)現(xiàn)優(yōu)點(diǎn)缺點(diǎn)特點(diǎn)
          JDK靜態(tài)代理代理類(lèi)與委托類(lèi)實(shí)現(xiàn)同一接口,并且在代理類(lèi)中需要硬編碼接口實(shí)現(xiàn)簡(jiǎn)單,容易理解代理類(lèi)需要硬編碼接口,在實(shí)際應(yīng)用中可能會(huì)導(dǎo)致重復(fù)編碼,浪費(fèi)存儲(chǔ)空間并且效率很低好像沒(méi)啥特點(diǎn)
          JDK動(dòng)態(tài)代理代理類(lèi)與委托類(lèi)實(shí)現(xiàn)同一接口,主要是通過(guò)代理類(lèi)實(shí)現(xiàn)InvocationHandler并重寫(xiě)invoke方法來(lái)進(jìn)行動(dòng)態(tài)代理的,在invoke方法中將對(duì)方法進(jìn)行增強(qiáng)處理不需要硬編碼接口,代碼復(fù)用率高只能夠代理實(shí)現(xiàn)了接口的委托類(lèi)底層使用反射機(jī)制進(jìn)行方法的調(diào)用
          CGLIB動(dòng)態(tài)代理代理類(lèi)將委托類(lèi)作為自己的父類(lèi)并為其中的非final委托方法創(chuàng)建兩個(gè)方法,一個(gè)是與委托方法簽名相同的方法,它在方法中會(huì)通過(guò)super調(diào)用委托方法;另一個(gè)是代理類(lèi)獨(dú)有的方法。在代理方法中,它會(huì)判斷是否存在實(shí)現(xiàn)了MethodInterceptor接口的對(duì)象,若存在則將調(diào)用intercept方法對(duì)委托方法進(jìn)行代理可以在運(yùn)行時(shí)對(duì)類(lèi)或者是接口進(jìn)行增強(qiáng)操作,且委托類(lèi)無(wú)需實(shí)現(xiàn)接口不能對(duì)final類(lèi)以及final方法進(jìn)行代理底層將方法全部存入一個(gè)數(shù)組中,通過(guò)數(shù)組索引直接進(jìn)行方法調(diào)用

          問(wèn)題

          CGlib比JDK快?

          • 使用CGLiB實(shí)現(xiàn)動(dòng)態(tài)代理,CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類(lèi), 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對(duì)聲明為final的方法進(jìn)行代理, 因?yàn)镃GLib原理是動(dòng)態(tài)生成被代理類(lèi)的子類(lèi)。

          • 在jdk6、jdk7、jdk8逐步對(duì)JDK動(dòng)態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下,JDK代理效率高于CGLIB代理效率。只有當(dāng)進(jìn)行大量調(diào)用的時(shí)候,jdk6和jdk7比CGLIB代理效率低一點(diǎn),但是到j(luò)dk8的時(shí)候,jdk代理效率高于CGLIB代理,總之,每一次jdk版本升級(jí),jdk代理效率都得到提升,而CGLIB代理消息確有點(diǎn)跟不上步伐。

          Spring如何選擇用JDK還是CGLIB?

          • 當(dāng)Bean實(shí)現(xiàn)接口時(shí),Spring就會(huì)用JDK的動(dòng)態(tài)代理。
          • 當(dāng)Bean沒(méi)有實(shí)現(xiàn)接口時(shí),Spring使用CGlib實(shí)現(xiàn)。
          • 可以強(qiáng)制使用CGlib

          這些線(xiàn)程安全的坑,你在工作中踩了么?


          社招一年半面經(jīng)分享(含阿里美團(tuán)頭條京東滴滴)


          條件語(yǔ)句的多層嵌套問(wèn)題優(yōu)化,助你寫(xiě)出不讓同事吐槽的代碼

          瀏覽 38
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲男人色天堂 | 午夜性爱网站 | 大鸡八网站 | 国产福利一区二区在线观看 | 毛片在线看不卡 |