自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

面試官提問:什么是動態(tài)代理?

網(wǎng)絡(luò) 通信技術(shù)
據(jù)史料記載,代理這個(gè)詞最早出現(xiàn)在代理商這個(gè)行業(yè),所謂代理商,簡而言之,其實(shí)就是幫助企業(yè)或者老板打理生意,自己本身不做生產(chǎn)任何商品。

[[439196]]

本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲Tang。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。

一、介紹

何謂代理?

據(jù)史料記載,代理這個(gè)詞最早出現(xiàn)在代理商這個(gè)行業(yè),所謂代理商,簡而言之,其實(shí)就是幫助企業(yè)或者老板打理生意,自己本身不做生產(chǎn)任何商品。

舉個(gè)例子,我們?nèi)セ疖囌举I票的時(shí)候,人少老板一個(gè)人還忙的過來,但是人一多的話,就會非常擁擠,于是就有了各種代售點(diǎn),我們可以從代售點(diǎn)買車票,從而加快老板的賣票速度。

代售點(diǎn)的出現(xiàn),可以說,很直觀的幫助老板提升了用戶購票體驗(yàn)。

站在軟件設(shè)計(jì)的角度,其實(shí)效果也是一樣的,采用代理模式的編程,能顯著的增強(qiáng)原有的功能和簡化方法調(diào)用方式。

在介紹動態(tài)代理之前,我們先來聊解靜態(tài)代理。

二、靜態(tài)代理

下面,我們以兩數(shù)相加為例,實(shí)現(xiàn)過程如下!

接口類

  1. public interface Calculator { 
  2.  
  3.     /** 
  4.      * 計(jì)算兩個(gè)數(shù)之和 
  5.      * @param num1 
  6.      * @param num2 
  7.      * @return 
  8.      */ 
  9.     Integer add(Integer num1, Integer num2); 

目標(biāo)對象

  1. public class CalculatorImpl implements Calculator { 
  2.  
  3.  
  4.     @Override 
  5.     public Integer add(Integer num1, Integer num2) { 
  6.         Integer result = num1 + num2; 
  7.         return result; 
  8.     } 

代理對象

  1. public class CalculatorProxyImpl implements Calculator { 
  2.  
  3.  
  4.     private Calculator calculator; 
  5.  
  6.  
  7.     @Override 
  8.     public Integer add(Integer num1, Integer num2) { 
  9.         //方法調(diào)用前,可以添加其他功能.... 
  10.         Integer result = calculator.add(num1, num2); 
  11.         //方法調(diào)用后,可以添加其他功能.... 
  12.         return result; 
  13.     } 
  14.  
  15.  
  16.     public CalculatorProxyImpl(Calculator calculator) { 
  17.         this.calculator = calculator; 
  18.     } 

測試類

  1. public class CalculatorProxyClient { 
  2.  
  3.     public static void main(String[] args) { 
  4.         //目標(biāo)對象 
  5.         Calculator target = new CalculatorImpl(); 
  6.         //代理對象 
  7.         Calculator proxy = new CalculatorProxyImpl(target); 
  8.         Integer result = proxy.add(1,2); 
  9.         System.out.println("相加結(jié)果:" + result); 
  10.     } 

輸出結(jié)果

  1. 相加結(jié)果:3 

通過這種代理方式,最大的優(yōu)點(diǎn)就是:可以在不修改目標(biāo)對象的前提下,擴(kuò)展目標(biāo)對象的功能。

但也有缺點(diǎn):需要代理對象和目標(biāo)對象實(shí)現(xiàn)一樣的接口,因此,當(dāng)目標(biāo)對象擴(kuò)展新的功能時(shí),代理對象也要跟著一起擴(kuò)展,不易維護(hù)!

三、動態(tài)代理

動態(tài)代理,其實(shí)本質(zhì)也是為了解決上面當(dāng)目標(biāo)對象擴(kuò)展新功能時(shí),代理對象也需要跟著一起擴(kuò)展的痛點(diǎn)問題而生。

那它是怎么解決的呢?

以 JDK 為例,當(dāng)需要給某個(gè)目標(biāo)對象添加代理處理的時(shí)候,JDK 會在內(nèi)存中動態(tài)的構(gòu)建代理對象,從而實(shí)現(xiàn)對目標(biāo)對象的代理功能。

下面,我們還是以兩數(shù)相加為例,介紹具體的玩法!

3.1、JDK 中生成代理對象的玩法

創(chuàng)建接口

  1. public interface JdkCalculator { 
  2.  
  3.     /** 
  4.      * 計(jì)算兩個(gè)數(shù)之和 
  5.      * @param num1 
  6.      * @param num2 
  7.      * @return 
  8.      */ 
  9.     Integer add(Integer num1, Integer num2); 

目標(biāo)對象

  1. public class JdkCalculatorImpl implements JdkCalculator { 
  2.  
  3.     @Override 
  4.     public Integer add(Integer num1, Integer num2) { 
  5.         Integer result = num1 + num2; 
  6.         return result; 
  7.     } 

動態(tài)代理對象

  1. public class JdkProxyFactory { 
  2.  
  3.     /** 
  4.      * 維護(hù)一個(gè)目標(biāo)對象 
  5.      */ 
  6.     private Object target; 
  7.  
  8.     public JdkProxyFactory(Object target) { 
  9.         this.target = target; 
  10.     } 
  11.  
  12.     public Object getProxyInstance(){ 
  13.         Object proxyClassObj = Proxy.newProxyInstance(target.getClass().getClassLoader(), 
  14.                 target.getClass().getInterfaces(), 
  15.                 new InvocationHandler(){ 
  16.  
  17.                     @Override 
  18.                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  19.                         System.out.println("方法調(diào)用前,可以添加其他功能...."); 
  20.  
  21.                         // 執(zhí)行目標(biāo)對象方法 
  22.                         Object returnValue = method.invoke(target, args); 
  23.                         System.out.println("方法調(diào)用后,可以添加其他功能...."); 
  24.                         return returnValue; 
  25.                     } 
  26.                 }); 
  27.         return proxyClassObj; 
  28.     } 

測試類

  1. public class TestJdkProxy { 
  2.  
  3.     public static void main(String[] args) { 
  4.         //目標(biāo)對象 
  5.         JdkCalculator target = new JdkCalculatorImpl(); 
  6.         System.out.println(target.getClass()); 
  7.         //代理對象 
  8.         JdkCalculator proxyClassObj = (JdkCalculator) new JdkProxyFactory(target).getProxyInstance(); 
  9.         System.out.println(proxyClassObj.getClass()); 
  10.         //執(zhí)行代理方法 
  11.         Integer result = proxyClassObj.add(1,2); 
  12.         System.out.println("相加結(jié)果:" + result); 
  13.     } 

輸出結(jié)果

  1. class com.example.java.proxy.jdk1.JdkCalculatorImpl 
  2. class com.sun.proxy.$Proxy0 
  3. 方法調(diào)用前,可以添加其他功能.... 
  4. 方法調(diào)用后,可以添加其他功能.... 
  5. 相加結(jié)果:3 

采用 JDK 技術(shù)動態(tài)創(chuàng)建interface實(shí)例的步驟如下:

  1. 1. 首先定義一個(gè) InvocationHandler 實(shí)例,它負(fù)責(zé)實(shí)現(xiàn)接口的方法調(diào)用 
  2. 2. 通過 Proxy.newProxyInstance() 創(chuàng)建 interface 實(shí)例,它需要 3 個(gè)參數(shù): 
  3.    (1)使用的 ClassLoader,通常就是接口類的 ClassLoader 
  4.    (2)需要實(shí)現(xiàn)的接口數(shù)組,至少需要傳入一個(gè)接口進(jìn)去; 
  5.    (3)用來處理接口方法調(diào)用的 InvocationHandler 實(shí)例。 
  6. 3. 將返回的 Object 強(qiáng)制轉(zhuǎn)型為接口 

動態(tài)代理實(shí)際上是 JVM 在運(yùn)行期動態(tài)創(chuàng)建class字節(jié)碼并加載的過程,它并沒有什么黑魔法技術(shù),把上面的動態(tài)代理改寫為靜態(tài)實(shí)現(xiàn)類大概長這樣:

  1. public class JdkCalculatorDynamicProxy implements JdkCalculator { 
  2.  
  3.     private InvocationHandler handler; 
  4.  
  5.     public JdkCalculatorDynamicProxy(InvocationHandler handler) { 
  6.         this.handler = handler; 
  7.     } 
  8.  
  9.     public void add(Integer num1, Integer num2) { 
  10.         handler.invoke( 
  11.            this, 
  12.            JdkCalculator.class.getMethod("add"Integer.class, Integer.class), 
  13.            new Object[] { num1, num2 }); 
  14.     } 

本質(zhì)就是 JVM 幫我們自動編寫了一個(gè)上述類(不需要源碼,可以直接生成字節(jié)碼)。

3.2、cglib 生成代理對象的玩法

除了 jdk 能實(shí)現(xiàn)動態(tài)的創(chuàng)建代理對象以外,還有一個(gè)非常有名的第三方框架:cglib,它也可以做到運(yùn)行時(shí)在內(nèi)存中動態(tài)生成一個(gè)子類對象從而實(shí)現(xiàn)對目標(biāo)對象功能的擴(kuò)展。

cglib 特點(diǎn)如下:

cglib 不僅可以代理接口還可以代理類,而 JDK 的動態(tài)代理只能代理接口

cglib 是一個(gè)強(qiáng)大的高性能的代碼生成包,它廣泛的被許多 AOP 的框架使用,例如我們所熟知的 Spring AOP,cglib 為他們提供方法的 interception(攔截)。

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

在使用 cglib 之前,我們需要添加依賴包,如果你已經(jīng)有spring-core的jar包,則無需引入,因?yàn)閟pring中包含了cglib。

  1. <dependency> 
  2.     <groupId>cglib</groupId> 
  3.     <artifactId>cglib</artifactId> 
  4.     <version>3.2.5</version> 
  5. </dependency> 

 

下面,我們還是以兩數(shù)相加為例,介紹具體的玩法!

  1. public interface CglibCalculator { 
  2.  
  3.     /** 
  4.      * 計(jì)算兩個(gè)數(shù)之和 
  5.      * @param num1 
  6.      * @param num2 
  7.      * @return 
  8.      */ 
  9.     Integer add(Integer num1, Integer num2); 

目標(biāo)對象

  1. public class CglibCalculatorImpl implements CglibCalculator { 
  2.  
  3.  
  4.     @Override 
  5.     public Integer add(Integer num1, Integer num2) { 
  6.         Integer result = num1 + num2; 
  7.         return result; 
  8.     } 

動態(tài)代理對象

  1. public class CglibProxyFactory implements MethodInterceptor { 
  2.  
  3.     /** 
  4.      * 維護(hù)一個(gè)目標(biāo)對象 
  5.      */ 
  6.     private Object target; 
  7.  
  8.     public CglibProxyFactory(Object target) { 
  9.         this.target = target; 
  10.     } 
  11.  
  12.     /** 
  13.      * 為目標(biāo)對象生成代理對象 
  14.      * @return 
  15.      */ 
  16.     public Object getProxyInstance() { 
  17.         //工具類 
  18.         Enhancer en = new Enhancer(); 
  19.         //設(shè)置父類 
  20.         en.setSuperclass(target.getClass()); 
  21.         //設(shè)置回調(diào)函數(shù) 
  22.         en.setCallback(this); 
  23.         //創(chuàng)建子類對象代理 
  24.         return en.create(); 
  25.     } 
  26.  
  27.     @Override 
  28.     public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 
  29.         System.out.println("方法調(diào)用前,可以添加其他功能...."); 
  30.  
  31.         // 執(zhí)行目標(biāo)對象方法 
  32.         Object returnValue = method.invoke(target, args); 
  33.         System.out.println("方法調(diào)用后,可以添加其他功能...."); 
  34.         return returnValue; 
  35.     } 

測試類

  1. public class TestCglibProxy { 
  2.  
  3.     public static void main(String[] args) { 
  4.         //目標(biāo)對象 
  5.         CglibCalculator target = new CglibCalculatorImpl(); 
  6.         System.out.println(target.getClass()); 
  7.         //代理對象 
  8.         CglibCalculator proxyClassObj = (CglibCalculator) new CglibProxyFactory(target).getProxyInstance(); 
  9.         System.out.println(proxyClassObj.getClass()); 
  10.         //執(zhí)行代理方法 
  11.         Integer result = proxyClassObj.add(1,2); 
  12.         System.out.println("相加結(jié)果:" + result); 
  13.     } 

輸出結(jié)果

  1. class com.example.java.proxy.cglib1.CglibCalculatorImpl 
  2. class com.example.java.proxy.cglib1.CglibCalculatorImpl$$EnhancerByCGLIB$$3ceadfe4 
  3. 方法調(diào)用前,可以添加其他功能.... 
  4. 方法調(diào)用后,可以添加其他功能.... 
  5. 相加結(jié)果:3 

將 cglib 生成的代理類改寫為靜態(tài)實(shí)現(xiàn)類大概長這樣:

  1. public class CglibCalculatorImplByCGLIB extends CglibCalculatorImpl implements Factory { 
  2.      
  3.  
  4.     private static final MethodInterceptor methodInterceptor; 
  5.  
  6.     private static final Method method; 
  7.          
  8.  
  9.     public final Integer add(Integer var1, Integer var2) { 
  10.         return methodInterceptor.intercept(this, method, new Object[]{var1, var2}, methodProxy); 
  11.     } 
  12.  
  13.     //.... 

其中,攔截思路與 JDK 類似,都是通過一個(gè)接口方法進(jìn)行攔截處理!

在上文中咱們還介紹到了,cglib 不僅可以代理接口還可以代理類,下面我們試試代理類。

  1. public class CglibCalculatorClass { 
  2.  
  3.     /** 
  4.      * 計(jì)算兩個(gè)數(shù)之和 
  5.      * @param num1 
  6.      * @param num2 
  7.      * @return 
  8.      */ 
  9.     public Integer add(Integer num1, Integer num2) { 
  10.         Integer result = num1 + num2; 
  11.         return result; 
  12.     } 

測試類

  1. public class TestCglibProxyClass { 
  2.  
  3.     public static void main(String[] args) { 
  4.         //目標(biāo)對象 
  5.         CglibCalculatorClass target = new CglibCalculatorClass(); 
  6.         System.out.println(target.getClass()); 
  7.         //代理對象 
  8.         CglibCalculatorClass proxyClassObj = (CglibCalculatorClass) new CglibProxyFactory(target).getProxyInstance(); 
  9.         System.out.println(proxyClassObj.getClass()); 
  10.         //執(zhí)行代理方法 
  11.         Integer result = proxyClassObj.add(1,2); 
  12.         System.out.println("相加結(jié)果:" + result); 
  13.     } 

輸出結(jié)果

  1. class com.example.java.proxy.cglib1.CglibCalculatorClass 
  2. class com.example.java.proxy.cglib1.CglibCalculatorClass$$EnhancerByCGLIB$$e68ff36c 
  3. 方法調(diào)用前,可以添加其他功能.... 
  4. 方法調(diào)用后,可以添加其他功能.... 
  5. 相加結(jié)果:3 

四、靜態(tài)織入

在上文中,我們介紹的代理方案都是在代碼運(yùn)行時(shí)動態(tài)的生成class文件達(dá)到動態(tài)代理的目的。

回到問題的本質(zhì),其實(shí)動態(tài)代理的技術(shù)目的,主要為了解決靜態(tài)代理模式中當(dāng)目標(biāo)接口發(fā)生了擴(kuò)展,代理類也要跟著一遍變動的問題,避免造成了工作傷的繁瑣和復(fù)雜。

在 Java 生態(tài)里面,還有一個(gè)非常有名的第三方代理框架,那就是AspectJ,AspectJ通過特定的編譯器可以將目標(biāo)類編譯成class字節(jié)碼的時(shí)候,在方法周圍加上業(yè)務(wù)邏輯,從而達(dá)到靜態(tài)代理的效果。

采用AspectJ進(jìn)行方法植入,主要有四種:

  • 方法調(diào)用前攔截
  • 方法調(diào)用后攔截
  • 調(diào)用方法結(jié)束攔截
  • 拋出異常攔截

使用起來也非常簡單,首先是在項(xiàng)目中添加AspectJ編譯器插件。

  1. <plugin> 
  2.     <groupId>org.codehaus.mojo</groupId> 
  3.     <artifactId>aspectj-maven-plugin</artifactId> 
  4.     <version>1.5</version> 
  5.     <executions> 
  6.         <execution> 
  7.             <goals> 
  8.                 <goal>compile</goal> 
  9.                 <goal>test-compile</goal> 
  10.             </goals> 
  11.         </execution> 
  12.     </executions> 
  13.     <configuration> 
  14.         <source>1.6</source> 
  15.         <target>1.6</target> 
  16.         <encoding>UTF-8</encoding> 
  17.         <complianceLevel>1.6</complianceLevel> 
  18.         <verbose>true</verbose> 
  19.         <showWeaveInfo>true</showWeaveInfo> 
  20.     </configuration> 
  21. </plugin> 

 

 

然后,編寫一個(gè)方法,準(zhǔn)備進(jìn)行代理。

  1. @RequestMapping({"/hello"}) 
  2. public String hello(String name) { 
  3.     String result = "Hello World"
  4.     System.out.println(result); 
  5.     return result; 

編寫代理配置類

  1. @Aspect 
  2. public class ControllerAspect { 
  3.  
  4.     /*** 
  5.      * 定義切入點(diǎn) 
  6.      */ 
  7.     @Pointcut("execution(* com.example.demo.web..*.*(..))"
  8.     public void methodAspect(){} 
  9.  
  10.     /** 
  11.      * 方法調(diào)用前攔截 
  12.      */ 
  13.     @Before("methodAspect()"
  14.     public void before(){ 
  15.         System.out.println("代理 -> 調(diào)用方法執(zhí)行之前......"); 
  16.     } 
  17.  
  18.     /** 
  19.      * 方法調(diào)用后攔截 
  20.      */ 
  21.     @After("methodAspect()"
  22.     public void after(){ 
  23.         System.out.println("代理 -> 調(diào)用方法執(zhí)行之后......"); 
  24.     } 
  25.  
  26.     /** 
  27.      * 調(diào)用方法結(jié)束攔截 
  28.      */ 
  29.     @AfterReturning("methodAspect()"
  30.     public void afterReturning(){ 
  31.         System.out.println("代理 -> 調(diào)用方法結(jié)束之后......"); 
  32.     } 
  33.  
  34.     /** 
  35.      * 拋出異常攔截 
  36.      */ 
  37.     @AfterThrowing("methodAspect()"
  38.     public void afterThrowing() { 
  39.         System.out.println("代理 -> 調(diào)用方法異常......"); 
  40.     } 

編譯后,hello方法會變成這樣。

  1. @RequestMapping({"/hello"}) 
  2. public String hello(Integer name) throws SQLException { 
  3.     JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, name); 
  4.  
  5.     Object var7; 
  6.     try { 
  7.         Object var5; 
  8.         try { 
  9.             //調(diào)用before 
  10.             Aspectj.aspectOf().doBeforeTask2(var2); 
  11.             String result = "Hello World"
  12.             System.out.println(result); 
  13.             var5 = result; 
  14.         } catch (Throwable var8) { 
  15.             Aspectj.aspectOf().after(var2); 
  16.             throw var8; 
  17.         } 
  18.         //調(diào)用after 
  19.         Aspectj.aspectOf().after(var2); 
  20.         var7 = var5; 
  21.     } catch (Throwable var9) { 
  22.         //調(diào)用拋出異常 
  23.         Aspectj.aspectOf().afterthrowing(var2); 
  24.         throw var9; 
  25.     } 
  26.     //調(diào)用return 
  27.     Aspectj.aspectOf().afterRutuen(var2); 
  28.     return (String)var7; 

很顯然,代碼被AspectJ編譯器修改了,AspectJ并不是動態(tài)的在運(yùn)行時(shí)生成代理類,而是在編譯的時(shí)候就植入代碼到class文件。

由于是靜態(tài)織入的,所以性能相對來說比較好!

五、小結(jié)

看到上面的介紹靜態(tài)織入方案,跟我們現(xiàn)在使用Spring AOP的方法極其相似,可能有的同學(xué)會發(fā)出疑問,我們現(xiàn)在使用的Spring AOP動態(tài)代理,到底是動態(tài)生成的還是靜態(tài)織入的呢?

實(shí)際上,Spring AOP代理是對JDK代理和CGLIB代理做了一層封裝,同時(shí)引入了AspectJ中的一些注解@pointCut、@after,@before等等,本質(zhì)是使用的動態(tài)代理技術(shù)。

總結(jié)起來就三點(diǎn):

如果目標(biāo)是接口的話,默認(rèn)使用 JDK 的動態(tài)代理技術(shù);

如果目標(biāo)是類的話,使用 cglib 的動態(tài)代理技術(shù);

引入了AspectJ中的一些注解@pointCut、@after,@before,主要是為了簡化使用,跟AspectJ的關(guān)系并不大;

那為什么Spring AOP不使用AspectJ這種靜態(tài)織入方案呢?

雖然AspectJ編譯器非常強(qiáng),性能非常高,但是只要目標(biāo)類發(fā)生了修改就需要重新編譯,主要原因可能還是AspectJ的編譯器太過于復(fù)雜,還不如動態(tài)代理來的省心!

六、參考

1、Java三種代理模式:靜態(tài)代理、動態(tài)代理和cglib代理

 

2、Java 動態(tài)代理作用是什么?

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2022-09-29 07:30:57

數(shù)據(jù)庫索引字段

2023-07-05 08:17:38

JDK動態(tài)代理接口

2021-09-07 10:44:33

Java 注解開發(fā)

2024-02-22 15:36:23

Java內(nèi)存模型線程

2023-12-06 09:10:28

JWT微服務(wù)

2021-02-19 10:02:57

HTTPSJava安全

2020-08-17 07:40:19

消息隊(duì)列

2021-04-19 18:56:58

大數(shù)字符串運(yùn)算

2022-04-29 08:17:38

RPC遠(yuǎn)程代理代理模式

2020-08-06 07:49:57

List元素集合

2023-12-20 14:35:37

Java虛擬線程

2025-03-10 07:05:07

2021-05-12 08:20:53

開發(fā)

2024-04-15 00:01:00

STWJava垃圾

2022-01-05 09:55:26

asynawait前端

2024-01-11 08:12:20

重量級監(jiān)視器

2021-08-24 08:05:41

泛型類型擦除Class

2020-07-22 08:05:44

中間人攻擊

2019-04-15 14:40:46

消息隊(duì)列Java編程

2025-03-05 00:01:00

ReduxReact
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號