JFinal的Proxy实现原理

了解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 byteCode; // 编译后的字节码
    private Class clazz; // 字节码被 loadClass 后的 Class
    private List 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 {
public Map 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 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 ret = new HashMap<>();
            // 从Java文件管理者中的引用fileObjects中取出 编译好的字节码
            for (Entry 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动态代理机制了。

你可能感兴趣的:(JFinal的Proxy实现原理)