如何手動模擬一個JDK動態代理-創新互聯

今天就跟大家聊聊有關如何手動模擬一個JDK動態代理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

創新互聯公司從2013年開始,是專業互聯網技術服務公司,擁有項目網站設計制作、成都網站建設網站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元呈貢做網站,已為上家服務,為呈貢各地企業和個人服務,聯系電話:18980820575

為哪些方法代理?

實現自己動態代理,首先需要關注的點就是,代理對象需要為哪些方法代理? 原生JDK的動態代理的實現是往上抽象出一層接口,讓目標對象和代理對象都實現這個接口,怎么把接口的信息告訴jdk原生的動態代理呢? 如下代碼所示,Proxy.newProxyInstance()方法的第二個參數將接口的信息傳遞了進去第一個參數的傳遞進去一個類加載器,在jdk的底層用它對比對象是否是同一個,標準就是相同對象的類加載器是同一個

ServiceInterface) Proxy.newProxyInstance(service.getClass().getClassLoader()
        , new Class[]{ServiceInterface.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置通知");
        method.invoke(finalService,args);
        System.out.println("后置通知");
        return proxy;
      }
    });

我們也效仿它的做法. 代碼如下:

public class Test {
  public static void main(String[] args) {
    IndexDao indexDao = new IndexDao();
    Dao dao =(Dao) ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao));
    assert dao != null;
    System.out.println(dao.say("changwu"));
  }
}

拿到了接口的Class對象后,通過反射就得知了接口中有哪些方法描述對象Method,獲取到的所有的方法,這些方法就是我們需要增強的方法

如何將增強的邏輯動態的傳遞進來呢?

JDK的做法是通過InvocationHandler的第三個參數完成,他是個接口,里面只有一個抽象方法如下: 可以看到它里面有三個入參,分別是 代理對象,被代理對象的方法,被代理對象的方法的參數

  public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;
}

當我們使用jdk的動態代理時,就是通過這個重寫這個鉤子函數,將邏輯動態的傳遞進去,并且可以選擇在適當的地方讓目標方法執行

InvocationHandler接口必須存在必要性1:

為什么不傳遞進去Method,而是傳遞進去InvocationHandler對象呢? 很顯然,我們的初衷是借助ProxyUtil工具類完成對代理對象的拼串封裝,然后讓這個代理對象去執行method.invoke(), 然而事與愿違,傳遞進來的Method對象的確可以被ProxyUtil使用,調用method.invoke(), 但是我們的代理對象不能使用它,因為代理對象在這個ProxyUtil還以一堆等待拼接字符串, ProxyUtil的作用只能是往代理對象上疊加字符串,卻不能直接傳遞給它一個對象,所以只能傳遞一個對象進來,然后通過反射獲取到這個對象的實例,繼而有可能實現method.invoke()

InvocationHandler接口必須存在必要性2:

通過這個接口的規范,我們可以直接得知回調方法的名字就是invoke()所以說,在拼接字符串完成對代理對象的拼接時,可以直接寫死它

思路

我們需要通過上面的ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao))方法完成如下幾件事

  1. 根據入參位置的信息,提取我們需要的信息,如包名,方法名,等等

  2. 根據我們提取的信息通過字符串的拼接完成一個全新的java的拼接

  3. 這個java類就是我們的代理對象

  4. 拼接好的java類是一個String字符串,我們將它寫入磁盤取名XXX.java

  5. 通過ProxyUtil使用類加載器,將XXX.java讀取JVM中,形成Class對象

  6. 通過Class對象反射出我們需要的代理對象

  7. ProxyUtil的實現如下:

public static Object newInstance(Class targetInf, MyInvocationHandler invocationHandler) {

  Method methods[] = targetInf.getDeclaredMethods();
  String line = "\n";
  String tab = "\t";
  String infName = targetInf.getSimpleName();
  String content = "";
  String packageContent = "package com.myproxy;" + line;
  //  導包,全部導入接口層面,換成具體的實現類就會報錯
  //  
  String importContent = "import " + targetInf.getName() + ";" + line
              + "import com.changwu.代理技術.模擬jdk實現動態代理.MyInvocationHandler;" + line
              + "import java.lang.reflect.Method;" + line
              + "import java.lang.Exception;" + line;

  String clazzFirstLineContent = "public class $Proxy implements " + infName +"{"+ line;
  String filedContent = tab + "private MyInvocationHandler handler;"+ line;
  String constructorContent = tab + "public $Proxy (MyInvocationHandler handler){" + line
      + tab + tab + "this.handler =handler;"
      + line + tab + "}" + line;
  String methodContent = "";
  // 遍歷它的全部方法,接口出現的全部方法進行增強
  for (Method method : methods) {
    String returnTypeName = method.getReturnType().getSimpleName();     method.getReturnType().getSimpleName());

    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();

    // 參數的.class
    String paramsClass = "";
    for (Class<?> parameterType : parameterTypes) {
      paramsClass+= parameterType.getName()+",";
    }

    String[] split = paramsClass.split(",");

    //方法參數的類型數組 Sting.class String.class
    String argsContent = "";
    String paramsContent = "";
    int flag = 0;
    for (Class arg : parameterTypes) {
      // 獲取方法名
      String temp = arg.getSimpleName();
      argsContent += temp + " p" + flag + ",";
      paramsContent += "p" + flag + ",";
      flag++;
    }
    // 去掉方法參數中最后面多出來的,
    if (argsContent.length() > 0) {
      argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
      paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
    }
    methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
        + tab + tab+"Method method = null;"+line
        + tab + tab+"String [] args0 = null;"+line
        + tab + tab+"Class<?> [] args1= null;"+line

        // invoke入參是Method對象,而不是上面的字符串,所以的得通過反射創建出Method對象
        + tab + tab+"try{"+line
        // 反射得到參數的類型數組
         + tab + tab + tab + "args0 = \""+paramsClass+"\".split(\",\");"+line
         + tab + tab + tab + "args1 = new Class[args0.length];"+line
         + tab + tab + tab + "for (int i=0;i<args0.length;i++) {"+line
         + tab + tab + tab + "  args1[i]=Class.forName(args0[i]);"+line
         + tab + tab + tab + "}"+line
        // 反射目標方法
        + tab + tab + tab + "method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\",args1);"+line
        + tab + tab+"}catch (Exception e){"+line
        + tab + tab+ tab+"e.printStackTrace();"+line
        + tab + tab+"}"+line
        + tab + tab + "return ("+returnTypeName+") this.handler.invoke(method,\"暫時不知道的方法\");" + line; //
         methodContent+= tab + "}"+line;
  }

  content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";

  File file = new File("d:\\com\\myproxy\\$Proxy.java");
  try {
    if (!file.exists()) {
      file.createNewFile();
    }

    FileWriter fw = new FileWriter(file);
    fw.write(content);
    fw.flush();
    fw.close();

    // 將生成的.java的文件編譯成 .class文件
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
    Iterable units = fileMgr.getJavaFileObjects(file);
    JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
    t.call();
    fileMgr.close();

    // 使用類加載器將.class文件加載進jvm
    // 因為產生的.class不在我們的工程當中
    URL[] urls = new URL[]{new URL("file:D:\\\\")};
    URLClassLoader urlClassLoader = new URLClassLoader(urls);
    Class clazz = urlClassLoader.loadClass("com.myproxy.$Proxy");
    return clazz.getConstructor(MyInvocationHandler.class).newInstance(invocationHandler);
  } catch (Exception e) {
    e.printStackTrace();
  }
    return null;
}
}

運行的效果:

package com.myproxy;
import com.changwu.myproxy.pro.Dao;
import com.changwu.myproxy.pro.MyInvocationHandler;
import java.lang.reflect.Method;
import java.lang.Exception;
public class $Proxy implements Dao{
	private MyInvocationHandler handler;
	public $Proxy (MyInvocationHandler handler){
		this.handler =handler;
	}
	public String say(String p) {
		Method method = null;
		String [] args0 = null;
		Class<?> [] args1= null;
		try{
			args0 = "java.lang.String,".split(",");
			args1 = new Class[args0.length];
			for (int i=0;i<args0.length;i++) {
			  args1[i]=Class.forName(args0[i]);
			}
			method = Class.forName("com.changwu.myproxy.pro.Dao").getDeclaredMethod("say",args1);
		}catch (Exception e){
			e.printStackTrace();
		}
		return (String) this.handler.invoke(method,"暫時不知道的方法");
	}
}

解讀

通過newInstance()用戶獲取到的代理對象就像上面的代理一樣,這個過程是在java代碼運行時生成的,但是直接看他的結果和靜態代理差不錯,這時用戶再去調用代理對象的say(), 實際上就是在執行用戶傳遞進去的InvocationHandeler里面的invoke方法, 但是亮點是我們把目標方法的描述對象Method同時給他傳遞進去了,讓用戶可以執行目標方法+增強的邏輯

當通過反射區執行Method對象的invoke()方法時,指定的哪個對象的當前方法呢? 這個參數其實是我們手動傳遞進去的代理對象代碼如下

public class MyInvocationHandlerImpl implements MyInvocationHandler {
  private Object obj;
  public MyInvocationHandlerImpl(Object obj) {
    this.obj = obj;
  }
  @Override
  public Object invoke(Method method, Object[] args) {
    System.out.println("前置通知");
    try {
      method.invoke(obj,args);
    } catch (Exception e) {
      e.printStackTrace();
    } 
    System.out.println("后置通知");
    return null;
  }
}

看完上述內容,你們對如何手動模擬一個JDK動態代理有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注創新互聯行業資訊頻道,感謝大家的支持。

當前文章:如何手動模擬一個JDK動態代理-創新互聯
網頁地址:http://m.kartarina.com/article6/cdcsog.html

成都網站建設公司_創新互聯,為您提供域名注冊面包屑導航微信小程序移動網站建設靜態網站網站設計

廣告

聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯

成都app開發公司
主站蜘蛛池模板: 精品人妻系列无码人妻免费视频| 亚洲爆乳无码一区二区三区| 精品国产性色无码AV网站| 92午夜少妇极品福利无码电影| 中文AV人妻AV无码中文视频| 久久久久无码精品国产h动漫| 18禁超污无遮挡无码免费网站| 97精品人妻系列无码人妻| 波多野结衣AV无码久久一区| 无码中文av有码中文av| 久久精品成人无码观看56| 日韩少妇无码一区二区三区| 日韩免费人妻AV无码专区蜜桃 | 久久久久亚洲精品无码网址| 亚洲国产精品无码av| 曰韩无码AV片免费播放不卡| 亚洲AV无码一区二区三区网址| 无码人妻久久久一区二区三区| 精品人妻系列无码人妻免费视频 | 国产精品无码一区二区在线| 久久亚洲精品AB无码播放| 日韩人妻无码一区二区三区综合部| 无码不卡中文字幕av| 无码熟妇人妻AV在线影院| 中文字幕av无码无卡免费| 久久亚洲AV成人无码国产| 久久青青草原亚洲AV无码麻豆 | 亚洲的天堂av无码| 无码日韩精品一区二区三区免费| 精品人妻少妇嫩草AV无码专区| 亚洲?V无码乱码国产精品 | 国产成人精品无码免费看 | 国产aⅴ无码专区亚洲av麻豆| 国产成人无码精品一区不卡| 国产av激情无码久久| 日韩少妇无码喷潮系列一二三| 特级无码毛片免费视频| 亚洲精品无码成人片在线观看 | 精品久久无码中文字幕| 日韩人妻无码一区二区三区久久 | 无码中文字幕av免费放|