對JDK動態代理的模擬實現

来源:https://www.cnblogs.com/wf614/archive/2019/10/20/11710912.html
-Advertisement-
Play Games

對JDK動態代理的模擬 動態代理在JDK中的實現: 來看看newProxyInstance()這個方法在JDK中的定義 它需要三個參數: ClassLoader loader:類載入器,JDK代理中認為由同一個類載入器載入的類生成的對象相同所以要傳入一個載入器,而且在代理對象生成過程中也可能用到類加 ...


對JDK動態代理的模擬

動態代理在JDK中的實現:

IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader()
                , producer.getClass().getInterfaces(),new InvocationHandler() {
                    
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             Object rtValue = null;
             Float money = (Float)args[0];
             if("saleProduct".equals(method.getName())){
                     rtValue = method.invoke(producer, money * 0.8f);
               }
                return rtValue;
               }
      });

來看看newProxyInstance()這個方法在JDK中的定義

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
   ...
}

它需要三個參數:
ClassLoader loader:類載入器,JDK代理中認為由同一個類載入器載入的類生成的對象相同所以要傳入一個載入器,而且在代理對象生成過程中也可能用到類載入器。

Class<?>[] interfaces:要被代理的類的介面,因為類可以實現多個介面,使用此處給的是Class數組。

InvocationHandler h:代理邏輯就是通過重寫該介面的invoke()方法實現

通過對newProxyInstance()方法的分析我們能可以做出以下分析:

第二個參數Class<?>[] interfaces是介面數組,那麼為什麼需要被代理類的介面?應該是為了找到要增強的方法,因為由JDK實現的動態代理只能代理有介面的類,

2.InvocationHandler h:參數通過重寫其invoke()方法實現了對方法的增強。我們先來看一下invoke()方法是如何定義的

/**
* 作用:執行的被代理對象的方法都會經過此方法
* @param proxy  代理對象的引用
* @param method 當前執行的方法的對象
* @param args   被代理對象方法的參數
* @return       被代理對象方法的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

而想要重新該方法並完成對目標對象的代理,就需要使用method對象的invoke()方法(註:這個方法與InvocationHandler 中的invoke方法不同不過InvocationHandler 中的invoke方法主要也是為了聲明使用Method中的invoke()方法)。我們在來看看Method中的invoke()方法

public Object invoke(Object obj, Object... args)

這裡終於看到我們要代理的對象要寫入的位置。

對有以上內容,我們可以做出以下猜想:(說是猜想,但實際JDk的動態代理就是基於此實現,不過其處理的東西更多,也更加全面

Proxy.newProxyInstance(..)這個方法的並不參與具體的代理過程,而是通過生成代理對象proxy來調用InvocationHandler 中的invoke()方法,通過invoke()方法來實現代理的具體邏輯。

所以我以下模擬JDK動態代理的這個過程,就是基於以上猜想實現,需要寫兩個內容,一個是生成代理對象的類(我命名為ProxyUtil),一個實現對代理對象的增強(我命名為MyHandler介面)

Proxy類

package wf.util;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class ProxyUtil {

    /**
     * public UserDaoImpl(User)
     * @param targetInf
     * @return
     */
    public static Object newInstance(Class targetInf,MyHandler h){
        Object proxy = null;
        String tab = "\t";
        String line = "\n";
        String infname = targetInf.getSimpleName();
        String content = "";
        //這裡把代理類的包名寫死了,但JDK實現的過程中會判斷代理的方法是否是public,如果是則其包名是預設的com.sun.Proxy包下,而如果方法類型不是public則生產的Java文件包名會和要代理的對象包名相同,這是和修飾符的訪問許可權有關
        String packageName = "package com.google;"+line;
        String importContent = "import "+targetInf.getName()+";"+line
                +"import wf.util.MyHandler;"+line
                +"import java.lang.reflect.Method;"+line;
        //聲明類
        String clazzFirstLineContent = "public class $Proxy implements "+infname+"{"+line;
        //屬性
        String attributeCont = tab+"private MyHandler h;"+line;
        //構造方法
        String constructorCont = tab+"public $Proxy (MyHandler h){" +line
                                +tab+tab+"this.h = h;"+line
                                +tab+"}"+line;
        //要代理的方法
        String mtehodCont = "";
        Method[] methods = targetInf.getMethods();
        for (Method method : methods) {
            //方法返回值類型
            String returnType = method.getReturnType().getSimpleName();
//            方法名稱
            String methodName = method.getName();
            //方法參數
            Class<?>[] types = method.getParameterTypes();
            //傳入參數
            String argesCont = "";
            //調用目標對象的方法時的傳參
            String paramterCont = "";
            int flag = 0;
            for (Class<?> type : types) {
                String argName = type.getSimpleName();
                argesCont = argName+" p"+flag+",";
                paramterCont = "p" + flag+",";
                flag++;
            }
            if (argesCont.length()>0){
                argesCont=argesCont.substring(0,argesCont.lastIndexOf(",")-1);
                paramterCont=paramterCont.substring(0,paramterCont.lastIndexOf(",")-1);
            }
            mtehodCont+=tab+"public "+returnType+" "+methodName+"("+argesCont+")throws Exception {"+line
                    +tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line;

            if (returnType == "void"){
                mtehodCont+=tab+tab+"h.invoke(method);"+line;
            }else {
                mtehodCont+=tab+tab+"return ("+returnType+")h.invoke(method);"+line;
            }
            mtehodCont+=tab+"}"+line;
        }
        content=packageName+importContent+clazzFirstLineContent+attributeCont+constructorCont+mtehodCont+"}";

//        System.out.println(content);
        //把字元串寫入java文件
        File file = new File("D:\\com\\google\\$Proxy.java");
        try {
            if (!file.exists()){
                file.createNewFile();
            }
            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.flush();
            fw.close();

            //編譯java文件
            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();
            URL[] urls = new URL[]{new URL("file:D:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.google.$Proxy");

            Constructor constructor = clazz.getConstructor(MyHandler.class);
            proxy = constructor.newInstance(h);

        }catch (Exception e){
            e.printStackTrace();
        }
        return proxy;
    }
}

該類會通過String字元串生成一個java類文件進而生成代理對象

補充: 我在實現Java文件時把代理類的包名寫死了,但JDK實現的過程中會判斷代理的方法是否是public,如果是則其包名是預設的com.sun.Proxy包下,而如果方法類型不是public則生產的Java文件包名會和要代理的對象包名相同,這是和修飾符的訪問許可權有關

生成的java類為($Proxy)

package com.google;
import wf.dao.UserDao;
import wf.util.MyHandler;
import java.lang.reflect.Method;
public class $Proxy implements UserDao{
    private MyHandler h;
    public $Proxy (MyHandler h){
        this.h = h;
    }
    public void query()throws Exception {
        Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query");
        h.invoke(method);
    }
    public String query1(String p)throws Exception {
        Method method = Class.forName("wf.dao.UserDao").getDeclaredMethod("query1");
        return (String)h.invoke(method);
    }
}

可以看到代理對象其實起一個中專作用,來調用實現代理邏輯的MyHandler介面

MyHandler實現如下:

package wf.util;

import java.lang.reflect.Method;

public interface MyHandler {
    //這裡做了簡化處理只需要傳入method對象即可
    public Object invoke(Method method);
}

其實現類如下

package wf.util;

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

public class MyHandlerImpl implements MyHandler {
    Object target;
    public MyHandlerImpl(Object target){
        this.target=target;
    }

    @Override
    public Object invoke(Method method) {
        try {
            System.out.println("MyHandlerImpl");
            return  method.invoke(target);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

這裡對代理對象的傳入是通過構造方法來傳入。

至此模擬完成,看一下以下結果

package wf.test;

import wf.dao.UserDao;
import wf.dao.impl.UserDaoImpl;
import wf.proxy.UserDaoLog;
import wf.util.MyHandlerImpl;
import wf.util.MyInvocationHandler;
import wf.util.ProxyUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) throws Exception {
        final UserDaoImpl target = new UserDaoImpl();
        System.out.println("自定義代理");
        UserDao proxy = (UserDao) ProxyUtil.newInstance(UserDao.class, new MyHandlerImpl(new UserDaoImpl()));
        proxy.query();

        System.out.println("java提供的代理");
        UserDao proxy1 = (UserDao) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{UserDao.class},
                new MyInvocationHandler(new UserDaoImpl()));
        proxy1.query();
    }
}

輸出:

自定義代理

MyHandlerImpl

查詢資料庫

------分界線----

java提供的代理

proxy

查詢資料庫

兩種方式都實現了代理,而實際上JDK的動態代理的主要思想和以上相同,不過其底層不是通過String來實現代理類的Java文件的編寫,而JDK則是通過byte[]實現,其生成Class對象時通過native方法實現,通過c++代碼實現,不是java要討論的內容。

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文連接 https://www.cnblogs.com/aknife/p/11709255.html 一.頁面樣式 二.資料庫 三.前端頁面代碼 controller層 service層 serviceImpl層 dao層 mapper層 本文連接https://www.cnblogs.com/a ...
  • [TOC] 前一陣子遇到一個小問題,在同樣的樣式(主要是寬高邊距之類的)條件下,DIV在移動端和PC端的寬度不一樣,排除了絕大多數樣式的問題,但是有個比較陌生,就是box sinzing,其實經常看到,只不過沒怎麼註意過,連具體的值都不知道有哪些,在開發者工具裡面試了一下,果然和這個樣式有關,因此查 ...
  • 表單內容: form 表單 input 輸入框 name屬性:標簽的名字 value屬性:設置輸入框的預設內容 checked屬性:設置單選或多選的預設項 type屬性:用於確定輸入框的類型 type屬性的屬性值: text:單行文本 password:密碼 email:郵箱 radio:單選 ch ...
  • 前言 在上一篇初識Vue核心中,我們已經熟悉了vue的兩大核心,理解了Vue的構建方式,通過基本的指令控制DOM,實現提高應用開發效率和可維護性。而這一篇呢,將對Vue視圖組件的核心概念進行詳細說明。 什麼是組件呢? 組件可以擴展HTML元素,封裝可重用的HTML代碼,我們可以將組件看作自定義的HT ...
  • B站自動填彈幕(附帶createEvent消息機制) 昨晚看的比賽真的要氣死我。RNG 居然又輸了。。。 為了LPL。。。我寫了一個為LPL加油的腳本。希望大家能和我一起為LPL加油! 腳本代碼如下: 第一步打開瀏覽器並登錄B站 按F12將控制台打開 將代碼粘貼進去然後按回車 請大家觀賞效果 往下就 ...
  • 思路 自定義導航欄高度組成:狀態欄(綠色部分)、導航欄(藍色部分) 狀態欄 通過調用 wx.getSystemInfoSync 獲取 導航欄 通過獲取右上角膠囊的位置信息計算,navBarPadding為導航欄上下的間隙 代碼 app.js: wxml: wxss: js: 最後 setStatus ...
  • 1.什麼是常量? 常量表示一些固定不變的數據 現實生活中人的性別其實就可以看做是常量, 生下來是男孩一輩子都是男孩, 生下來是女孩一輩子都是女孩 2.JavaScript中常量的分類 整型常量其實就是正數, 在JavaScript中隨便寫一個整數都是整型常量1 / 666 / 99 實型常量其實就是 ...
  • 示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 一. 高性能動畫 動畫的流暢程度通常是以 ( Frame Per Second ,每秒 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...