了解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)意圖去看代碼乘综。
作者利用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ī)制了。