了解Java的动态代理后,动态创建新的代理类通过反射执行。 那JFinal的代理实现有什么不一样的地方呢,据作者介绍是为了
* 追求性能极致:
* 1:禁止使用 JDK 的 Method.invoke(...) 调用被代理方法
* 2:method 存在有效的拦截器才生成代理方法 ProxyMethod
* 3:目标类 target 存在 ProxyMethod 才生成代理类 ProxyClass
作者为了追求性能极致自己设计了一套方式完成了动态代理功能。依据上面的三点意图去看代码。
例如对User.class进行代理
作者利用Enjoy、Class Loader、Dynamic Compile 美妙结合在一起,及其精简的代码量实现代理功能。
其核心功能动态生成代理类是通过ProxyGenerator结合Enjoy生成Java源代码,再通过ProxyCompiler进行编译,最后ProxyClassLoader进行加载。Enjoy生成源代码部分完全可以手工编写,但由于Enjoy的好用程度太高,用模板的方式生成代码太省事了。
ProxyClass: 代理类描述信息,生成的代理类是不可见的,相关的属性由本类管理
private Class<?> target; // 被代理类
private String pkg; // 报名
private String name; // 类名
private String sourceCode; // 生成的Java源代码
private Map<String, byte[]> byteCode; // 编译后的字节码
private Class<?> clazz; // 字节码被 loadClass 后的 Class
private List<ProxyMethod> proxyMethodList = new ArrayList<>(); // 被代理的方法列表
ProxyGenerator: 代理生成器,将ProxyClass中的描述信息以及被代理类分析后通过Enjoy生成Java源代码,这里通过类的注解,给有标注的类和方法生成代理。通过@Before进行注解,
public ProxyClass generate(Class<?> target) {
//1. 用一个Kv对象管理需要在模板中渲染的数据
//2. 给Kv对象填充数据
//2.1 获取被代理类的注解,getMethodUpperInterceptors()
//3 遍历被代理类中的方法,判断此方法是否需要被代理hasInterceptor()
//3.1 生成一个Kv存储被代理的方法信息,用于模板渲染数据
//3.2 产生一个ProxyMethod对象存储到ProxyClass的被代理方法列表中
//4. 通过Enjoy进行渲染
//4.1 生成的源代码设置到ProxyClass中,为后续编译好调用
}
这里会用到一个模板,里面会有个Invocation类,这个是调用信息类,
public class Invocation {
private static final Object[] NULL_ARGS = new Object[0];
private Object target;//代理
private Method method;//被代理的方法
private Object[] args;//方法参数
private Callback callback;//回调
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
}
#--
一般参数情况
--#
#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
#--
只有一个参数,且该参数是数组或者可变参数
--#
#define newInvocationForSingleArrayPara()
Invocation inv = new Invocation(this, #(x.proxyMethodKey)L,
args -> {
#(x.frontReturn) #(name).super.#(x.name)(
p0
);
#(x.backReturn)
}
, p0);
#end
会生成一个这样的类:
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: 自定义编译器,同时自定义了MyJavaFileManager,MyJavaFileObject 便于管理编译好的字节码存放位置,不需要存放到磁盘。
// 继承ForwardingJavaFileManager
// 的目的是将编译后的字节码存在内存中,不用写到磁盘
public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
public Map<String, MyJavaFileObject> fileObjects = new HashMap<>();
public MyJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
// 编译器编译完成后ClassWriter.writeClass()回调此方法,kind 为 CLASS
// 这里返回一个JavaFileObject接口的对象,writeClass()会问他要OutputStream,所以会调用它的openOutputStream
// 返回一个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;
}
// 是否在编译时依赖另一个类的情况下用到本方法 ?
@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文件对象,包含源文件内容、编译后的字节码
public static class MyJavaFileObject extends SimpleJavaFileObject {
private String source;
// 这里巧妙的利用了 内存方式存储字节码,
private ByteArrayOutputStream outPutStream;
// 构建源文件,定义URI文件位置
public MyJavaFileObject(String name, String source) {
super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
this.source = source;
}
// 构建编译后的字节码文件位置
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;
}
// 编译器编译完成后会调用此方法,将字节码写入到outputStream 中
// MyJavaFileManager的getJavaFileForOutput返回的是MyJavaFileObject
// 故此方法会被回调,
// 同时MyJavaFileManager持有fileObjects对本对象的引用,本对象中的outPutStream写入数据后即在内存中
@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<>();
// 创建Java文件管理者
try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) {
// 构建一个Java源文件
MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode());
// 进行编译
Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
// 错出提示
outputCompileError(result, collector);
Map<String, byte[]> ret = new HashMap<>();
// 从Java文件管理者中的引用fileObjects中取出 编译好的字节码
for (Entry<String, MyJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
ret.put(e.getKey(), e.getValue().getByteCode());
}
// 设置到包装类中
proxyClass.setByteCode(ret);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
ProxyClassLoader:类加载器,自定义的目的在于方便从内存中加载编译后的字节码
// 当调用父类的loadClass()方法就会触发查找
// 继承ClassLoader后重写此方法,去哪里找字节码数组
// 这里从byteCodeMap中找到对应的字节码数组然后通过defineClass将字节码数组转为Class实例
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = byteCodeMap.get(name);
if (bytes != null) {
Class<?> ret = defineClass(name, bytes, 0, bytes.length);
// 转换完成后,删掉完成的字节数组
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;
}
//这个方法需要代理
@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
我的名字是:小明
此设计非常巧妙,这样就不需要cglib 、asm和jdk动态代理机制了。
网友评论