囉裏吧嗦式講解java靜態代理動態代理模式

一.爲啥寫這個

文章寫的比較囉嗦,有些東西能夠不看,由於想看懂框架, 想了解SSH或者SSM框架的設計原理和設計思路,
又去從新看了一遍反射和註解,
而後看別人的博客說想要看懂框架得先看懂設計模式,因而遇到了動態代理這個大坑,
寫博客等因而對本身學習過程的一個回顧和總結
本文主要參考歐陽鋒的10分鐘看懂動態代理設計模式

二.理解和弄懂代理的前期準備

2.1.什麼是代理

簡單來講就是有活不本身幹,讓別人幹, 好比你不想寫做業, 讓同窗幫你寫,而後寫上本身的名字,
這個同窗就是你的代理, 幫你處理一些事情

2.2.如何利用代碼生成一個java文件

生成一個java文件不過就是字符串的拼接,最後利用流輸出到一個目錄下面,以.java結尾,不須要從頭開始寫,
有個好用的工具包javaPoet,能夠去GitHub上下載,也能夠經過maven的方式引入
<dependency> 
<groupId>com.squareup</groupId> <artifactId>javapoet</artifactId> 
<version>1.8.0</version> 
</dependency>

 

2.3.如何使用代碼將生成的java文件編譯成class文件

//編譯桌面 proxy文件夾下的Proxy.java文件 會生成一個Proxy.class文件 
JavaCompiler.compile(new File(sourcePath + "/proxy/Proxy.java"));

 

個人地址(sourcePath)輸出在桌面:"C:/Users/你的計算機登錄用戶名/Desktop"

2.4.如何利用代碼將class 使用 類加載器加載進內存,並建立對象

利用類加載器,將該目錄下的class文件加載進內存中
// 得到類加載器    使用反射load到內存
URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file:C:\\Users\\保密\\Desktop\\") });
Class clazz1 = classLoader.loadClass("proxy.Proxy");
//經過Class的對象 clazz1 的getConstructor方法, 獲得類Proxy的構造器對象, InvocationHandler.class是入參
Constructor constructor = clazz1.getConstructor(InvocationHandler.class);
//經過構造器對象的newInstance 方法, 去建立一個類的對象
Object obj = constructor.newInstance(handler);

 

2.5.方法的反射知識

java有個Method類,能夠經過method.invoke執行該方法
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method;


public class Person {

  public void buy() {
    System.out.println("買買買");
}

public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

new Person().buy();
   //拿到Person類下面 public void buy(){} 方法對象Method method = Person.class.getMethod("buy"){};
Method m = Person.class.getMethod("buy");
   //經過method方法的invoke方法能夠執行該方法, 入參是該方法屬於的類的對象,和參數 method.invoke(person,new Object[] {}) 效果等同於new Person.buy()
m.invoke(new Person(), new Object[] {});
}
}
買買買
買買買

 

寫代碼的過程當中會遇到各類各樣的錯誤,好比什麼NoSuchException,遇到這類問題不要慌,大膽的用System.out.println打印每一個對象的信息,去分析可能出現的緣由

三.靜態代理

前面的知識知道也行,不知道也無所謂,不影響了解靜態代理
假設一個jar包下面有個接口Servlet, Servlet接口裏有個方法好比上傳文件,
有個實現類HttpServlet,實現了這個功能
public interface Servlet { 
  //假設一個java jar包下面的接口 有這樣一個功能就是上傳文件
  void uploadFile();
}

public class HttpServlet implements Servlet{

  @Override public void uploadFile() {
  
     System.out.println("我要開始上傳文件啦......");
System.out.println("真的很複雜啦......");
}
}

 

 你如今不能動jar包的源碼, 你想要在上傳文件前或者後乾點本身的事情,
 就比如做業讓HttpServlet幫你幹,但最後寫上本身的名字,
 這裏咱們假設你須要計算上傳文件的時間那麼
思路1 在每一個該方法的調用前加入一段記錄時間的代碼 
public static void main(String[] args) {
  long t1 = System.currentTimeMillis();
new HttpServlet().uploadFile();
long t2 = System.currentTimeMillis;
system.out.println("花費的時間" + t2-t1);
}

我要開始上傳文件啦...... 真的很複雜啦......
1 ------------------ 這種方式顯而易見的弊端就是,只要調用uploadFile方法,就要加兩行代碼計算,
代碼重複了N份,之後若是又不須要計算時間了 改起來會很是麻煩

  

思路2 經過繼承的方式重寫uploadFile()的方法 

public class HttpServletForCalTime extends HttpServlet{
  @Override
public void uploadFile() {
    System.out.println("統計時間....");
super.uploadFile();
System.out.println("統計時間....");
}

public static void main(String[] args) {
HttpServletForCalTime a = new HttpServletForCalTime();
a.uploadFile();
}
}
統計時間....
我要開始上傳文件啦......
真的很複雜啦......
統計時間....

--------------------- 這種寫法的好處是統計文件上傳時間用HttpServletForCalTime 類就好了,
可是弊端就是很差擴展,好比我想在統計時間前 先對文件的關鍵字進行屏蔽處理,把王八蛋換成空格,那麼又得繼承
若是我想先計算時間 在屏蔽關鍵字,在上傳文件,那麼又得建立一個新的繼承類
類無限的擴展,由於繼承是一種包裹關係,不夠靈活思路3---靜態代理 使用聚合,動態注入所要代理的對象, 原文中有句很是經典的話:其實設計模式,多多少少都跟多態有關
我理解的多態就是java的具體執行哪一個方法不是在編譯期綁定,而是在運行期動態綁定 

//建立一個代理類, 有個成員變量, 執行uploadFile的內容 取決於你傳入什麼Servlet對象
public class ServletTimeProxy implements Servlet{
  private Servlet servlet;
  
  public ServletTimeProxy(Servlet servlet) {
    this.servlet = servlet;
  }

@Override
public void uploadFile() {
      System.out.println("計算時間...");
servlet.uploadFile();
System.out.println("計算時間...");
}
}

public class ServletLogProxy implements Servlet{
  private Servlet servlet;
public ServletLogProxy(Servlet servlet) {
this.servlet = servlet;
}
@Override
public void uploadFile() {
System.out.println("打印日誌...");
servlet.uploadFile();
System.out.println("打印日誌...");
}
}

執行什麼內容取決於你傳入什麼對象,比較靈活,若是想先計算時間再打印日誌
就調個個就好了 活仍是HttpServlet代替你幹,

public static void main(String[] args) {
HttpServlet impl = new HttpServlet();
ServletTimeProxy a = new ServletTimeProxy(impl);
//統計時間
ServletLogProxy b = new ServletLogProxy(a);//
打印日誌
b.uploadFile();
}
-----------------------------------------
我要打印日誌....
計算時間...
我要開始上傳文件啦......
真的很複雜啦......
計算時間...
我要打印日誌....

該方法也有弊端,就是擴展性仍是不夠好,若是jar包裏我有N個類的xx方法都須要計算執行時間,打印日誌
那麼得實現N個jar包裏的接口,注入接口,代理該接口的方法

 

接下來的神仙方法就來了, 我根據你傳過來的接口對象,根據你想自定義的執行方法,
我給你生成java類,我給你編譯Java類,我去將class加載進內存中, 我去執行 你指定的方法,
你就不用寫那麼多份xx implements xxInterface代碼了
 
java.lang.reflect.Proxy @since 1.3

 

四.動態代理的原理

沒時間的童鞋能夠不看,我是被10分鐘看懂動態代理給坑了,
它讓你本身寫了java反射類Proxy的大概實現,這時候前面的知識就用得上了 思路1,首先咱們想經過代碼建立一個java類,它長這樣 package proxy; 

import java.lang.Override;
import java.lang.System;
import staticProxy.Servlet;

class Proxy implements Servlet {
private Servlet impl;

public Proxy(Servlet impl) { this.impl = impl; }

@Override
public void uploadFile() {
        long start = System.currentTimeMillis();
this.impl.uploadFile();
long end = System.currentTimeMillis();
System.out.println("Fly Time =" + (end - start));
}
}

那麼利用javapoet能夠這麼寫,我寫Proxy1 是1.0版本
public class Proxy1 {

public static Object newProxyInstance() throws IOException {
//第一步 經過javapoet工具類 建立一個名爲Proxy的java類 , 該類實現了Servlet接口
// == public class Proxy implement Servlet //這裏以前少寫一個public 致使後面獲取構造器對象時報NoSuchException,
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy") .addModifiers(Modifier.PUBLIC)
//定義類的修飾符//構造一個類,類名
.addSuperinterface(Servlet.class);

//第二步 建立類裏面的屬性 咱們要注入該接口
//咱們但願的屬性是 private Servlet impl //因此指定 屬性的類型 和 屬性的名字 和 屬性的範圍
//
注意 Modifier包千萬不要導錯了,import javax.lang.model.element.Modifier; //以前報錯找了很久
FieldSpec fieldSpec = FieldSpec.builder(Servlet.class, "impl", Modifier.PRIVATE).build();

//將屬性 添加到類中
typeSpecBuilder.addField(fieldSpec);

//咱們但願構造方法中 將該接口的實現類 傳到構造器裏 方便靈活調用要處理的方法 //Proxy(Servlet impl) { // this.impl = impl; // }
MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC).addParameter(Servlet.class, "impl")
//構造器傳入參數的類型 就是你要建立的java類實現接口的類型, 屬性名字
.addStatement("this.impl = impl")//構造器裏面的語句
.build();

           typeSpecBuilder.addMethod(constructorMethodSpec);

//類 ,屬性 ,構造器都寫完了, 接下來開始寫方法 , 你實現的接口有N個方法
Method[] methods = Servlet.class.getDeclaredMethods();
for (Method method : methods) {

//在文件中寫入方法 和 方法內容 方法裏有返回值 入參 方法名
MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())

//給方法添加範圍
.addModifiers(Modifier.PUBLIC)
//給方法添加 註解 一般都是實現 x接口的覆蓋註解
.addAnnotation(Override.class)
//給方法添加返回值
經過method.getReturnType能拿到該方法return接收的對象
.returns(method.getReturnType())
//給方法添加語句 這裏是給每一個方法都加上了一個計算時間
.addStatement("long start = $T.currentTimeMillis()", System.class)
 //添加換行符 
.addCode("\n")
//添加執行語句 咱們但願的執行語句是 執行傳過來的接口的某個方法 //好比 impl.upload()
.addStatement("this.impl." + method.getName() + "()") .addCode("\n") //$T能夠理解爲佔位符,javapoet會去找對應的類
.addStatement("long end = $T.currentTimeMillis()", System.class)
.addStatement("$T.out.println(\"Fly Time =\" + (end - start))", System.class)
.build();


typeSpecBuilder.addMethod(methodSpec);
}

      //生成一個 TypeSpec 前面定義的java文件 第一個參數是包名 這樣生成的xx.java文件再次包下
      JavaFile javaFile = JavaFile.builder("proxy", typeSpecBuilder.build()).build();
      String sourcePath = "C:/Users/本身登錄的電腦用戶名/Desktop";

      //在桌面上生成一個Proxy.java的文件
      javaFile.writeTo(new File(sourcePath)); return null;
}

 

執行 Proxy1.newProxyInstance()方法,發現確實在桌面上生成了一個proxy文件夾,裏面有個proxy.java
1.0版本咱們寫死了Proxy要實現的接口,如今咱們把它做爲參數傳進去,要改動幾個點,升級爲2.0
 
//代碼只貼了改動點
public class Proxy2 {
  public static Object newProxyInstance(Class clazz) throws IOException {
                 TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy") .addSuperinterface(clazz); 
                     //指定屬性 是那種類型 ,代理,代理, 就是代理的你要實現的類的接口, 起碼如今看來是這樣
                   FieldSpec fieldSpec = FieldSpec.builder(clazz, "impl", Modifier.PRIVATE).build();
                    //方法的構造器
                     MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(clazz, "impl")
                      //構造器傳入參數的類型 就是你要建立的java類實現接口的類型, 屬性名字
                      .addStatement("this.impl = impl")//構造器裏面的語句
                      .build();
}

 

 執行Proxy2 .newProxyInstance(Servlet.class);
 如今雖然咱們能實現代理任意接口了,可是目前只可以再任意接口的 任意方法上面加上時間計算, 如何能夠實現自定義執行邏輯呢----代理中的代理 Proxy3.0版本
 
 咱們但願能執行用戶自定義的方法, java大神可能就想到,我定義一個接口, 而後你去實現該接口的方法 
 我利用方法的反射 來執行你 要自定義的方法邏輯
 
 因此java定義了一個InvocationHandler,全部要使用動態代理的都得先實現該接口 ,
 
 實際上生成的動態代理類 是代理了 InvocationHandler接口, 而後 你自定義實現了InvocationHandler接口的類代理了 你須要代理的類
 所謂代理中的代理 
 
 話很少說,接着貼代碼,我當初看也是一臉懵逼
 
public interface InvocationHandler {
    void invoke(Object proxy, Method method, Object[] args);
}
package animateProxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import staticProxy.Servlet;

public class MyInvocationHandler implements InvocationHandler {

    private Servlet impl; //爲了完成對被代理對象的方法攔截,咱們須要在InvocationHandler對象中傳入被代理對象實例。

    public MyInvocationHandler(Servlet impl) {
        this.impl = impl;
    }
    
    
    //參數1  想要、
    
    //參數2  這個參數表示  傳入接口  中的全部Method對象
    
    //參數3 這個參數   對應當前method方法   中的參數
    @Override
    public void invoke(Object proxy, Method method, Object[] args) {
        // TODO Auto-generated method stub
        //1.自定義想要加在 動態代理類 的方法的處理 好比 
        //統計想要 代理類的 方法的時間
        long start = System.currentTimeMillis();
        
        System.out.println("proxy:" + proxy); //proxy: proxy.Proxy@636a59bb
        
        try {
            //方法的反射操做
            //方法的反射操做是method對象來進行方法調用
            //和 執行  impl.upload() 效果徹底相同
            method.invoke(impl, new Object[] {});
            
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

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

}

而後咱們但願動態代理工具proxy3.0 能生成一個這樣的文件 java

package proxy;

import animateProxy.InvocationHandler;
import java.lang.Override;
import java.lang.reflect.Method;
import staticProxy.Servlet;

public class Proxy implements Servlet {
  private InvocationHandler handler;

  public Proxy(InvocationHandler handler) {
    this.handler = handler;
  }

  @Override
  public void uploadFile() {
    try {
        Method method = staticProxy.Servlet.class.getMethod("uploadFile");
         this.handler.invoke(this, method, null);
    } catch(Exception e) {
        e.printStackTrace();
    }
  }
}

 

 接下來修改Proxy類 
 
package animateProxy;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.lang.model.element.Modifier;




import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

/**
 * 
 * 如今在proxy2的基礎上進化下, 
 * 
 * 如今想執行  咱們本身 想執行的代碼 而不是 
 * 
 * 固定的寫死的 計算每一個方法時間的代碼
 * 
 * 這點比較難想到 , 就是代理的邏輯抽取出來, 把打印方法執行時間的邏輯抽取出來
 * 能夠自定義個接口 用於處理本身想  加在代理對象上面的方法
 * 
 * 定義個接口 ,全部想寫本身的代理的類的 處理邏輯的 都必須 實現該接口
 * 
 * 而後將實現了 該接口的對象  接口傳進來 
 * 
 * 
 * 修改後是爲了實現 TimProxy裏面的內容
 * 
 * this.handler.invoke(this, method, null);
 * 代碼理解起來比較吃力  看個圖
 *                                                                      InvocationHandler
 *                                  InterfaceT.java                     |·
 *                                  |           |                       聚合了MyInvocationHandler.java
 * Proxy.newProxyInstance----->TimeProxy      interfaceTimpl.java       |
 *                                  |                                   |
 *                                  ------------------------------------
 * 
 * 
 * TimeProxy.upload()---------->MyInvocationHandler.invoke()---->interfaceTimpl.upload()
 * 
 * <p>Title: Proxy1</p>  
 * <p>Description: </p>  
 * @author 18045153  
 * @date 2019年2月27日
 */
public class Proxy3 {
    
    //只要你在newProxyInstance方法中指定代理須要實現的接口Class clazz
    //jdk1.3 Class<?>[]:第二個參數也和咱們的實現版本不一致,這個其實很容易理解,
    //咱們應該容許咱們本身實現的代理類同時實現多個接口。前面設計只傳入一個接口,只是爲了簡化實現,讓你專一核心邏輯實現而已
    public static Object newProxyInstance(Class clazz, InvocationHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        
        //第一步 經過javapoet工具類 建立一個名爲TimeProxy的java類 , 該類實現了interfaceT接口
        //InterfaceT.class ===   public class TimeProxy implement InterfaceT
        
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy")
                .addModifiers(Modifier.PUBLIC)//定義類的修飾符//構造一個類,類名
                .addSuperinterface(clazz);
        
        
        
        //第二步 建立類裏面的屬性 咱們要注入該接口 具體參考interfaJuhe.java 的寫法
        //咱們但願的屬性是 InterfaceT impl
        //因此指定 屬性的類型 和 屬性的名字 和 屬性的範圍
        
        //在生成的代理類中增長成員變量handler 效果就是 private InvocationHandler handler
        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();
        
        //將屬性 添加到類中
        typeSpecBuilder.addField(fieldSpec);
        
        
        //咱們但願構造方法中 將該接口的實現類 傳到構造器裏 方便靈活調用要處理的方法
        //interfaJuhe(InterfaceT impl) {
        //    this.impl = impl;
        //  }
        
        
        //效果就是 public TimeProxy(InvocationHandler handler ) {
        //              this.handler = handler;
        //}
        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(InvocationHandler.class, "handler")//構造器傳入參數的類型 就是你要建立的java類實現接口的類型, 屬性名字
                .addStatement("this.handler = handler")//構造器裏面的語句
                .build();
        
        typeSpecBuilder.addMethod(constructorMethodSpec);
        
        //類 ,屬性 ,構造器都寫完了, 接下來開始寫方法 , 你實現的接口有N個方法
        //原本方法是寫死的 , 如今該爲 
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            
            //在文件中寫入方法 和 方法內容  方法裏有返回值 入參 方法名 
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                     //給方法添加範圍
                    .addModifiers(Modifier.PUBLIC)
                     //給方法添加 註解  一般都是實現 x接口的覆蓋註解
                    .addAnnotation(Override.class)
                     //給方法添加返回值    經過method.getReturnType能拿到該方法return接收的對象
                    .returns(method.getReturnType())
                     //給方法添加語句  這裏是給每一個方法都加上了一個計算時間
                    
                    .addCode("try {\n")
                    
                     //Method method = staticProxy.Servlet.class.getMethod("uploadFile");
                     //這樣寫是爲了拿到 要代理的那個接口類的 xx 方法
                    .addStatement("\t$T method = " + clazz.getName() + ".class.getMethod(\"" + method.getName() + "\")", Method.class)
                     // 爲了簡單起見,這裏參數直接寫死爲空
                    
                     //這樣寫詩爲了執行 實現了InvocationHandler接口的 java類本身的處理方法
                     // this.handler.invoke(this, method, null);
                    .addStatement("\t this.handler.invoke(this, method, null)")
                    .addCode("} catch(Exception e) {\n")
                    .addCode("\te.printStackTrace();\n")
                    .addCode("}\n")

                    .build();
            //給每一個類加一個方法
            typeSpecBuilder.addMethod(methodSpec);
        }
        
        //生成一個 TypeSpec 前面定義的java文件  第一個參數是包名 這樣生成的xx.java文件再次包下
        //package animateProxy;
       
        
        
        
        String sourcePath = "C:/Users/"+System.getProperties().getProperty("user.name")+"/Desktop";
        
        JavaFile javaFile = JavaFile.builder("proxy", typeSpecBuilder.build()).build();
        // 爲了看的更清楚,我將源碼文件生成到桌面
       
        javaFile.writeTo(new File(sourcePath));
 
        // 編譯
        JavaCompiler.compile(new File(sourcePath + "/proxy/Proxy.java"));
 
        // 使用反射load到內存
        URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file:C:\\Users\\"+System.getProperties().getProperty("user.name")+"\\Desktop\\") });

        Object obj = null;
        
        //Classloader:類加載器,你可使用自定義的類加載器,咱們的實現版本爲了簡化,直接在代碼中寫死了Classloader。
        
        Class clazz1 = classLoader.loadClass("proxy.Proxy");
        
        System.out.println(clazz1);
        System.out.println(clazz1.getDeclaredConstructors().getClass());
        
        //將生成的TimeProxy編譯成class 使用類加載器加載進內存中 再經過反射或者該類的構造器 
        //再經過構造器將其代理類 TimeProxy 構造出來
        
        //NoSuchException  打印classz信息 發現 剛開始建立類 沒有使用public 
        Constructor constructor = clazz1.getConstructor(InvocationHandler.class);
        System.out.println("constructor" + constructor);
        obj = constructor.newInstance(handler);
        
        return obj;

    }
 
}

 

 執行
        Servlet a = (Servlet) Proxy3.newProxyInstance(Servlet.class, handler);
        
        System.out.println("返回的代理對象" + a);//返回的代理對象proxy.TimeProxy@5b2936fa

        //handler.invoke(a, a.getClass().getDeclaredMethod("uploadFile", null), null);
        //Proxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));
        // 代理了interfaceTimpl類 而且 寫了本身的實現
        a.uploadFile();

若是報NoSuchException錯誤,  將本身桌面上生成的Proxy.java放到項目proxy包下面 web

 

五.JDK動態代理類的用法

上面的只是解釋了動態代理的一種實現思路 , 童鞋們平時只要會用的話能夠不用關注 ,須要關注有3點編程

1.經過jdk反射包 Proxy.newProxyInstance 方法能夠完成 動態代理, 參數第一個是類加載器, 第二個是要代理的類的接口設計模式

第三個是實現了InvocationHandler接口的自定義類 tomcat

 

2.實現InvocationHandler接口, 裏面有3個參數 , app

第一個參數就是生成的動態代理類Proxy.java,框架

原文中說是 :maven

若是你的接口中有方法須要返回自身,若是在invoke中沒有傳入這個參數,將致使實例沒法正常返回。ide

在這種場景中,proxy的用途就表現出來了。簡單來講,這其實就是最近很是火的鏈式編程的一種應用實現。工具

 

這個暫時沒看懂

第二個參數很簡單,就是你要代理的 接口實現類 的方法對象,  經過method.invoke 能夠執行原來的方法不受影響

第三個參數就是代理的方法的參數了

 

3.動態代理的設計思路,爲何要有動態代理, 由於上面的代碼也展現了,在不動用原來代碼邏輯的狀況下, 能夠加入本身的邏輯

這在java中是一種AOP思想, 這種思想的應用場景有 好比 日誌管理, 權限管理, 事務管理 

 

六.動態代理的實際運用

 

示例來自https://blog.csdn.net/yerenyuan_pku/article/details/52598220
李阿昀的博客

在動態代理技術裏,因爲無論用戶調用代理對象的什麼方法,

都是調用開發人員編寫的處理器的invoke方法(這至關於invoke方法攔截到了代理對象的方法調用),

 

好比若是有個方法 c.create(HttpServlet server);須要傳入一個實現了HttpServlet接口的參數 

那麼能夠利用動態代理技術 完成對被代理對象方法的攔截 

 

Proxy.newProxyInstance實際上就是生成了一個 Proxy implements HttpServlet 這樣一個java類

至關於傳入一個c.create(Proxy)

 

而Proxy實現了HttpServlet接口,天然實現了該接口裏的全部方法, 在每一個方法裏實際上執行的是

Method method = staticProxy.Servlet.class.getMethod("xxxx");
this.handler.invoke(this, method, null);

這樣兩句話 

 

真正執行的方法是 xx  implements InvocationHandler 裏的 invoke方法, 而且給你傳入了你要 代理的對象的 方法對象

 

而且開發人員經過invoke方法的參數,還能夠在攔截的同時,

知道用戶調用的是什麼方法,所以利用這兩個特性,就能夠實現一些特殊需求,

例如:攔截用戶的訪問請求,以檢查用戶是否有訪問權限、動態爲某個對象添加額外的功能。


package
動態代理的實際運用; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 利用動態代理技術編寫一個用於處理全站中文亂碼的過濾器 * * 實現了對HttpServlet.getParamter方法的攔截 * * <p>Description: </p> * @author 18045153 * @date 2019年2月28日 */ public class CharacterEncodingFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); // 解決post方式請求下的中文亂碼問題 get:request.getParameter() // servlet-----> requestRroxy.getCookie() //傳入自定義的 ServletRequest 對象 chain.doFilter((ServletRequest) Proxy.newProxyInstance( CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() { //類加載器,你可使用自定義的類加載器 HttpServletRequest接口 , 這樣動態生成的代理類 實現了該接口下的全部方法 //傳入的method對象 實際就表示是 HttpServletRequest接口下的全部方法的對象 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* * 判斷在Servlet那端調用的是否是getParameter方法, * 如不是則不須要攔截,直接放行,直接調用tomcat的request(真的request)幫你把事情幹了, * 而且args參數還要接着往下傳 */ if (!method.getName().equals("getParameter")) { return method.invoke(request, args); // 因爲是內部類,因此request須要聲明爲final(但在Java8中沒這個必要了) } // 判斷客戶端的請求方式是否是get if (!request.getMethod().equalsIgnoreCase("get")) { return method.invoke(request, args); } //執行完 HttpServletRequest 的public abstract String getParameter(String s);方法拿到的返回值 String value = (String) method.invoke(request, args); if (value == null) { return null; } // return new String(value.getBytes("ISO8859-1"), "UTF-8"); return new String(value.getBytes("UTF-8"), "UTF-8"); } }), response); } @Override public void destroy() { // TODO Auto-generated method stub } //在web.xml文件中配置CharacterEncodingFilter。 //<filter> // <filter-name>CharacterEncodingFilter</filter-name> // <filter-class>cn.itcast.web.filter.CharacterEncodingFilter</filter-class> //</filter> //<filter-mapping> // <filter-name>CharacterEncodingFilter</filter-name> // <url-pattern>/*</url-pattern> //</filter-mapping> }
相關文章
相關標籤/搜索