JFinal的Proxy實(shí)現(xiàn)原理

了解Java的動(dòng)態(tài)代理后块请,動(dòng)態(tài)創(chuàng)建新的代理類通過反射執(zhí)行肺樟。 那JFinal的代理實(shí)現(xiàn)有什么不一樣的地方呢缴罗,據(jù)作者介紹是為了

 * 追求性能極致:
 * 1:禁止使用 JDK 的 Method.invoke(...) 調(diào)用被代理方法
 * 2:method 存在有效的攔截器才生成代理方法 ProxyMethod
 * 3:目標(biāo)類 target 存在 ProxyMethod 才生成代理類 ProxyClass

作者為了追求性能極致自己設(shè)計(jì)了一套方式完成了動(dòng)態(tài)代理功能宝恶。依據(jù)上面的三點(diǎn)意圖去看代碼乘综。


例如對(duì)User.class進(jìn)行代理

作者利用Enjoy憎账、Class Loader、Dynamic Compile 美妙結(jié)合在一起卡辰,及其精簡(jiǎn)的代碼量實(shí)現(xiàn)代理功能胞皱。
其核心功能動(dòng)態(tài)生成代理類是通過ProxyGenerator結(jié)合Enjoy生成Java源代碼,再通過ProxyCompiler進(jìn)行編譯九妈,最后ProxyClassLoader進(jìn)行加載反砌。Enjoy生成源代碼部分完全可以手工編寫,但由于Enjoy的好用程度太高萌朱,用模板的方式生成代碼太省事了宴树。

ProxyClass: 代理類描述信息,生成的代理類是不可見的晶疼,相關(guān)的屬性由本類管理

    private Class<?> target; // 被代理類
    private String pkg; // 報(bào)名
    private String name; // 類名
    private String sourceCode; // 生成的Java源代碼
    private Map<String, byte[]> byteCode; // 編譯后的字節(jié)碼
    private Class<?> clazz; // 字節(jié)碼被 loadClass 后的 Class
    private List<ProxyMethod> proxyMethodList = new ArrayList<>(); // 被代理的方法列表

ProxyGenerator: 代理生成器酒贬,將ProxyClass中的描述信息以及被代理類分析后通過Enjoy生成Java源代碼又憨,這里通過類的注解,給有標(biāo)注的類和方法生成代理锭吨。通過@Before進(jìn)行注解蠢莺,

public ProxyClass generate(Class<?> target) {
    //1. 用一個(gè)Kv對(duì)象管理需要在模板中渲染的數(shù)據(jù)
    //2. 給Kv對(duì)象填充數(shù)據(jù)
    //2.1 獲取被代理類的注解,getMethodUpperInterceptors()
    //3 遍歷被代理類中的方法零如,判斷此方法是否需要被代理hasInterceptor()
    //3.1 生成一個(gè)Kv存儲(chǔ)被代理的方法信息躏将,用于模板渲染數(shù)據(jù)
    //3.2 產(chǎn)生一個(gè)ProxyMethod對(duì)象存儲(chǔ)到ProxyClass的被代理方法列表中
    //4. 通過Enjoy進(jìn)行渲染
    //4.1 生成的源代碼設(shè)置到ProxyClass中,為后續(xù)編譯好調(diào)用
}

這里會(huì)用到一個(gè)模板考蕾,里面會(huì)有個(gè)Invocation類祸憋,這個(gè)是調(diào)用信息類,

public class Invocation {
    private static final Object[] NULL_ARGS = new Object[0];

    private Object target;//代理
    private Method method;//被代理的方法
    private Object[] args;//方法參數(shù)
    private Callback callback;//回調(diào)
    private Interceptor[] inters;//攔截器組
    private Object returnValue;//方法返回值
   public void invoke() {
   }
}
package #(pkg);
import com.alienjun.aop.Invocation;
public class #(name)#(classTypeVars) extends #(targetName)#(targetTypeVars) {
#for(x : methodList)
    
    public #(x.methodTypeVars) #(x.returnType) #(x.name)(#for(y : x.paraTypes)#(y) p#(for.index)#(for.last ? "" : ", ")#end) #(x.throws){
        #if(x.singleArrayPara)
        #@newInvocationForSingleArrayPara()
        #else
        #@newInvocationForCommon()
        #end
        
        inv.invoke();
        #if (x.returnType != "void")
        
        return inv.getReturnValue();
        #end
    }
#end
}

#--
   一般參數(shù)情況
--#
#define newInvocationForCommon()
        Invocation inv = new Invocation(this, #(x.proxyMethodKey)L,
            args -> {
                #(x.frontReturn) #(name).super.#(x.name)(
                        #for(y : x.paraTypes)
                        (#(y.replace("...", "[]")))args[#(for.index)]#(for.last ? "" : ",")
                        #end
                    );
                #(x.backReturn)
            }
            #for(y : x.paraTypes), p#(for.index)#end);
#end
#--
   只有一個(gè)參數(shù)肖卧,且該參數(shù)是數(shù)組或者可變參數(shù)
--#
#define newInvocationForSingleArrayPara()
        Invocation inv = new Invocation(this, #(x.proxyMethodKey)L,
            args -> {
                #(x.frontReturn) #(name).super.#(x.name)(
                        p0
                    );
                #(x.backReturn)
            }
            , p0);
#end

會(huì)生成一個(gè)這樣的類:

package com.alienjun;
import com.alienjun.aop.Invocation;
public class User$$EnhancerByJFinal extends User {
    
    public  void showName() {
        Invocation inv = new Invocation(this, 1L,
            args -> {
                 User$$EnhancerByJFinal.super.showName(
                    );
                return null;
            }
            );
        
        inv.invoke();
    }
}

ProxyCompiler: 自定義編譯器蚯窥,同時(shí)自定義了MyJavaFileManager,MyJavaFileObject 便于管理編譯好的字節(jié)碼存放位置喜命,不需要存放到磁盤。


// 繼承ForwardingJavaFileManager
// 的目的是將編譯后的字節(jié)碼存在內(nèi)存中河劝,不用寫到磁盤
public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
public Map<String, MyJavaFileObject> fileObjects = new HashMap<>();

        public MyJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }

        // 編譯器編譯完成后ClassWriter.writeClass()回調(diào)此方法壁榕,kind 為 CLASS
        // 這里返回一個(gè)JavaFileObject接口的對(duì)象,writeClass()會(huì)問他要OutputStream赎瞎,所以會(huì)調(diào)用它的openOutputStream
        // 返回一個(gè)ByteArrayOutputStream 給它牌里。
        /*
        public JavaFileObject writeClass(ClassSymbol var1) throws IOException, ClassWriter.PoolOverflow, ClassWriter.StringOverflow {
        JavaFileObject var2 = this.fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, var1.flatname.toString(), Kind.CLASS, var1.sourcefile);
        OutputStream var3 = var2.openOutputStream();
        this.writeClassFile(var3, var1);
        var3.close();
        * */
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
            fileObjects.put(qualifiedClassName, javaFileObject);
            return javaFileObject;
        }

        // 是否在編譯時(shí)依賴另一個(gè)類的情況下用到本方法 ?
        @Override
        public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
            JavaFileObject javaFileObject = fileObjects.get(className);
            if (javaFileObject == null) {
                javaFileObject = super.getJavaFileForInput(location, className, kind);
            }
            return javaFileObject;
        }
}

    // java文件對(duì)象,包含源文件內(nèi)容务甥、編譯后的字節(jié)碼
    public static class MyJavaFileObject extends SimpleJavaFileObject {

        private String source;
        // 這里巧妙的利用了 內(nèi)存方式存儲(chǔ)字節(jié)碼牡辽,
        private ByteArrayOutputStream outPutStream;

        // 構(gòu)建源文件,定義URI文件位置
        public MyJavaFileObject(String name, String source) {
            super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
            this.source = source;
        }

        // 構(gòu)建編譯后的字節(jié)碼文件位置
        public MyJavaFileObject(String name, Kind kind) {
            super(URI.create("String:///" + name + kind.extension), kind);
            source = null;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            if (source == null) {
                throw new IllegalStateException("source field can not be null");
            }
            return source;
        }

        // 編譯器編譯完成后會(huì)調(diào)用此方法敞临,將字節(jié)碼寫入到outputStream 中
        // MyJavaFileManager的getJavaFileForOutput返回的是MyJavaFileObject
        // 故此方法會(huì)被回調(diào)态辛,
        // 同時(shí)MyJavaFileManager持有fileObjects對(duì)本對(duì)象的引用,本對(duì)象中的outPutStream寫入數(shù)據(jù)后即在內(nèi)存中

        @Override
        public OutputStream openOutputStream() throws IOException {
            outPutStream = new ByteArrayOutputStream();
            return outPutStream;
        }

        public byte[] getByteCode() {
            return outPutStream.toByteArray();
        }
    }

編譯:

    public void compile(ProxyClass proxyClass) {
        // 獲取jdk提供的Java編譯器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" +
                    "Visit https://jfinal.com/doc/4-8 for details \n");
        }

        // 收集診斷信息列表
        DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();

        // 創(chuàng)建Java文件管理者
        try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) {

            // 構(gòu)建一個(gè)Java源文件
            MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode());

            // 進(jìn)行編譯
            Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
            // 錯(cuò)出提示
            outputCompileError(result, collector);

            Map<String, byte[]> ret = new HashMap<>();
            // 從Java文件管理者中的引用fileObjects中取出 編譯好的字節(jié)碼
            for (Entry<String, MyJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
                ret.put(e.getKey(), e.getValue().getByteCode());
            }

            // 設(shè)置到包裝類中
            proxyClass.setByteCode(ret);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

ProxyClassLoader:類加載器挺尿,自定義的目的在于方便從內(nèi)存中加載編譯后的字節(jié)碼

    // 當(dāng)調(diào)用父類的loadClass()方法就會(huì)觸發(fā)查找
    // 繼承ClassLoader后重寫此方法奏黑,去哪里找字節(jié)碼數(shù)組
    // 這里從byteCodeMap中找到對(duì)應(yīng)的字節(jié)碼數(shù)組然后通過defineClass將字節(jié)碼數(shù)組轉(zhuǎn)為Class實(shí)例
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = byteCodeMap.get(name);
        if (bytes != null) {
            Class<?> ret = defineClass(name, bytes, 0, bytes.length);
            // 轉(zhuǎn)換完成后,刪掉完成的字節(jié)數(shù)組
            byteCodeMap.remove(name);
            return ret;
        }
        return super.findClass(name);
    }

示例:

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //這個(gè)方法需要代理
    @Before(MyInterceptor.class)
    public void showName() {
        System.out.println("我的名字是:"+name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User() {
    }
}
public class MyInterceptor implements Interceptor {
    @Override
    public void intercept(Invocation inv) {
        System.out.println("攔截方法:"+inv.getMethodName());
        inv.invoke();
    }
}
public static void main(String[] args) {
    User s = com.alienjun.proxy.Proxy.get(User.class);
    s.setName("小明");
    s.showName();
}

輸出:
攔截方法:showName
我的名字是:小明

此設(shè)計(jì)非常巧妙编矾,這樣就不需要cglib 熟史、asm和jdk動(dòng)態(tài)代理機(jī)制了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窄俏,一起剝皮案震驚了整個(gè)濱河市蹂匹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凹蜈,老刑警劉巖限寞,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忍啸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昆烁,警方通過查閱死者的電腦和手機(jī)吊骤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來静尼,“玉大人白粉,你說我怎么就攤上這事∈竺欤” “怎么了鸭巴?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拦盹。 經(jīng)常有香客問我鹃祖,道長(zhǎng),這世上最難降的妖魔是什么普舆? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任恬口,我火速辦了婚禮,結(jié)果婚禮上沼侣,老公的妹妹穿的比我還像新娘祖能。我一直安慰自己,他們只是感情好蛾洛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布养铸。 她就那樣靜靜地躺著,像睡著了一般轧膘。 火紅的嫁衣襯著肌膚如雪钞螟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天谎碍,我揣著相機(jī)與錄音鳞滨,去河邊找鬼。 笑死蟆淀,一個(gè)胖子當(dāng)著我的面吹牛太援,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扳碍,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼提岔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了笋敞?” 一聲冷哼從身側(cè)響起碱蒙,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后赛惩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哀墓,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年喷兼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了篮绰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡季惯,死狀恐怖吠各,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情勉抓,我是刑警寧澤贾漏,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站藕筋,受9級(jí)特大地震影響纵散,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隐圾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一伍掀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暇藏,春花似錦蜜笤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)啊胶。三九已至甸各,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焰坪,已是汗流浹背趣倾。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留某饰,地道東北人儒恋。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像黔漂,于是被迫代替她去往敵國(guó)和親诫尽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼炬守,是存儲(chǔ)格式發(fā)展的一小步牧嫉,確實(shí)編譯語(yǔ)言發(fā)展的一大步。 虛擬機(jī)把描述類的數(shù)據(jù)從...
    胡二囧閱讀 954評(píng)論 0 0
  • 一、基礎(chǔ)理論知識(shí) 1酣藻、java虛擬機(jī)的生命周期: Java虛擬機(jī)的生命周期 一個(gè)運(yùn)行中的Java虛擬機(jī)有著一個(gè)清晰...
    ipfs布道者閱讀 314評(píng)論 0 1
  • 類加載器是 Java 語(yǔ)言的一個(gè)創(chuàng)新曹洽,也是 Java 語(yǔ)言流行的重要原因之一。它使得 Java 類可以被動(dòng)態(tài)加載到...
    CHSmile閱讀 1,598評(píng)論 0 12
  • 本文目的: 深入理解Java類加載機(jī)制; 理解各個(gè)類加載器特別是線程上下文加載器; Java虛擬機(jī)類加載機(jī)制 虛擬...
    czwbig閱讀 722評(píng)論 0 3
  • 心要碎了辽剧,卻還是要裝作沒事的樣子送淆。
    櫻花落羽閱讀 69評(píng)論 0 0