美文网首页
手写一个面向接口的动态代理

手写一个面向接口的动态代理

作者: 鸡龙 | 来源:发表于2020-12-16 22:55 被阅读0次

    如题,手写一个面向接口的动态代理。我们需要先了解jdk中的动态代理是怎么实现的。

    理解生成的代码和调用过程

    设置vm参数,-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,可以使jdk动态生成的class文件输出到磁盘中。

    设置vm options

    使用下面代码进行调试

    public interface IService {
        public void service(String name )throws Exception;
    }
    
    public class ServiceImplA implements IService {
        @Override
        public void service(String name) throws Exception {
            System.out.println("ServiceImplA name" + name);
        }
    }
    
    public class DynaProxyServiceA implements InvocationHandler {
        private Object object;
        /**
         *   将目标对象关联到InvocationHandler接口,返回代理对象obj
         *   调用代理对象的方法时,都会自动调用invoke方法
         */
    
        public Object bind(Object object){
            this.object = object;
            return Proxy.newProxyInstance(
                    this.object.getClass().getClassLoader(),
                    this.object.getClass().getInterfaces(),
                    this);
        }
    
        @SuppressWarnings("unchecked")
        public <T> T bindInterface(Class<T> proxyInterface){
            object = proxyInterface;
            return (T)Proxy.newProxyInstance(
                    proxyInterface.getClassLoader(),
                    new Class[]{proxyInterface},
                    this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            System.out.println("log start");
            if(object instanceof Class<?>){
                Class<?> clazz = (Class<?>) object;
                for (Method clazzMethod : clazz.getDeclaredMethods()) {
                    System.out.println(clazzMethod.getName());
                }
            }
            try{
                result = method.invoke(this.object,args);
                Class<?> returnType = method.getReturnType();
                System.out.println(returnType);
            }catch (Exception e){
                throw e;
            }
            System.out.println("log end");
            return result;
        }
        public static void main(String [] args) throws Exception {
            IService service = (IService)new DynaProxyServiceA()
                            .bind(new ServiceImplA());
            service.service("zhjl");
        }
    }
    
    输出结果
    log start
    ServiceImplA namezhjl
    void
    log end
    

    运行完程序后,会在项目的根目录生成一个文件夹com.sun.proxy,里面会生成一个$Proxy0.class的代理类文件。

    打开文件可以看到以下生成的源代码。

    public final class $Proxy0 extends Proxy implements IService {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void service(String var1) throws Exception {
            try {
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (Exception | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("org.example.aop.IService").getMethod("service", Class.forName("java.lang.String"));
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    
    

    很容易可以发现,生成的源代码是有规律可寻的。

    • 固定会重写java.lang.Object中的equalstoStringhashCode三个方法
    • 对比静态变量Method的声明对应方法的位置与static代码块这三块地方,可以发现,声明顺序与静态代码块中反射获取的顺序一致,并且在各方法中执行反射出来的Method也是相等的。这里做到了提前加载被代理类的方法,然后使用到该代理类的方法时将被代理类的方法当参数传到h.invoke中执行。

    进入到被继承的Proxy中,发现在生成的类中使用的h就是InvocationHandler这个类。即我们在使用动态代理时所要实现才那个类!

    image image.png

    所以这个地方是生成一个回调代理类的invoke方法的类,来调用invoke时决定什么时候执行被代理类的service方法就能达到切面增强这个方法的效果

    执行代理类的流程图

    理解完了生成代码的意义和处理流程,剩下的就是怎么构造这些代码并将他编译成.class文件和被类加载器加载并被创建实例被我们所使用了。

    如何构造和加载

    参考Proxy.newProxyInstance的代码,看到getProxyClass0,前面的代码忽略,看注释就知道这里是生成指定代理类的方法。直接点进去就好了。

    生成代码的方法 image image image

    记住这两个变量分别是KeyFactoryProxyClassFactory。然后回到proxyClassCache.get(loader, interfaces)

    image image

    根据实现的接口数量来返回Key

    往下走,最后指向的类都是Factory,并在最后执行get方法。

    image

    最后回到ProxyClassFactory这个类的apply方法。

    image

    最后在方法的底部发现ProxyGenerator.generateProxyClass对应的作用就是构造相当于构造.java源文件。
    defineClass0相当于javac编译成.class文件并loadClass返回对应的类

    image

    这个生成方法较复杂,经过简单的查看源码,已经知道步骤如下:

    • 构造.java源文件
    • 编译成.class后加载类

    笔者的方法比较简单,直接使用freemaker来构造源文件,需要传入以下四个参数。

    • package->生成类所在的包
    • className->生成的代理类名称
    • interface->实现的接口类全类名
    • methodList->需要重写的方法列表

    freemaker模板如下

    package ${package};
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    /**
    * @Author: Jdragon
    * @email: 1061917196@qq.com
    * @Description: jdk动态代理实质
    */
    public class ${className} extends Proxy implements ${interface} {
        public ${className}(InvocationHandler h) {
            super(h);
        }
    <#list 0..(methodList!?size-1) as i>
        @Override
        public final ${methodList[i].retType} ${methodList[i].methodName}(
            <#list methodList[i].paramList as param>
                    ${param} var${param_index}<#if param_has_next>,</#if>
            </#list>) {
            try {
                <#if (methodList[i].retType!="void")>return (${methodList[i].retType})</#if>
                <#if (methodList[i].paramList?size==0)>
                super.h.invoke(this, m${i}, (Object[])null);
                <#else>
                super.h.invoke(this, m${i}, new Object[]{
                    <#list 0..(methodList[i].paramList!?size-1) as k>var${k}
                        <#if k_has_next>,</#if>
                    </#list>});
                </#if>
            } catch (RuntimeException | Error e) {
                throw e;
            }catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    </#list>
    <#list 0..(methodList!?size-1) as i>
        private static Method m${i};
    </#list>
        static{
            try{
                <#list 0..(methodList!?size-1) as i>
                    m${i} = Class.forName("${methodList[i].className}").getMethod("${methodList[i].methodName}"
                    <#list methodList[i].paramList as param>
                        ,Class.forName("${param}")
                    </#list>);
                </#list>
            }  catch (NoSuchMethodException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    主要代码。以下代码不完整,可到gitee获取源码

    public class JdkProxyFactory {
    
        private final static String LN = System.lineSeparator();
    
        private final static AtomicInteger PROXY_INDEX = new AtomicInteger(0);
    
        private final static Boolean SAVE_GENERATED_FILES = Boolean.valueOf(System.getProperty("sun.misc.ProxyGenerator.saveGeneratedFiles"));
    
        private final static String USER_DIR = System.getProperty("user.dir") + "/com/jdragon/proxy/";
    
        private final static String PACKAGE_NAME = "com.jdragon.proxy";
    
        public static Object newProxyInstance(ClassLoader classLoader,
                                              @NotNull Class<?>[] interfaces,
                                              @NotNull InvocationHandler h) {
            try {
                if (interfaces.length == 0) {
                    throw new Exception("至少要实现一个接口");
                }
                //使用被代理类的类名和自增数定义代理类的名字
                String proxyClass = interfaces[0].getSimpleName() + "$Proxy" + PROXY_INDEX.incrementAndGet();
                //加载代理类
                Class<?> loadClass = loadClass(interfaces[0], proxyClass);
                Constructor<?> constructor = loadClass.getDeclaredConstructor(InvocationHandler.class);
                return constructor.newInstance(h);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * @Description: 加载类
        **/
        private static Class<?> loadClass(Class<?> interfaces, String proxyClassName) throws Exception {
            String classPath = PACKAGE_NAME + "." + proxyClassName;
            //构建源代码
            String sourceCode = generateSourceCode(interfaces, proxyClassName);
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            try (JavaFileManager manager = new MemoryFileManager(compiler.getStandardFileManager(null, null, null))) {
                List<JavaFileObject> files = Collections.singletonList(new MemoryJavaFileObject(proxyClassName, sourceCode));
                JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, files);
                if (!task.call()) {
                    throw new Exception("任务调用异常");
                }
                ClassLoader classLoader = manager.getClassLoader(null);
                Class<?> aClass = manager.getClassLoader(null).loadClass(classPath);
                if (SAVE_GENERATED_FILES) {
                    save(proxyClassName, classLoader);
                }
                return aClass;
            }
        }
    
        /**
         * @Description: 构造源代码
        **/
        private static String generateSourceCode(Class<?> interfaces, String proxyClassName) {
            String interfaceName = interfaces.getName();
            List<MethodEntity> methodEntities = new ArrayList<>();
            methodEntities.add(new MethodEntity(Object.class, "toString", null,
                    String.class));
            methodEntities.add(new MethodEntity(Object.class, "hashCode", null,
                    int.class));
            methodEntities.add(new MethodEntity(Object.class, "equals", Collections.singletonList(Object.class.getName()),
                    boolean.class));
    
            for (Method declaredMethod : interfaces.getDeclaredMethods()) {
                MethodEntity methodEntity = new MethodEntity();
                methodEntity.setClassName(interfaces);
                methodEntity.setMethodName(declaredMethod.getName());
                List<String> params = new ArrayList<>();
                for (Parameter parameter : declaredMethod.getParameters()) {
                    String paramTypeName = parameter.getType().getName();
                    params.add(paramTypeName);
                }
                methodEntity.setParamList(params);
                methodEntity.setRetType(declaredMethod.getReturnType());
                methodEntity.setTransferType(declaredMethod.getReturnType());
                methodEntities.add(methodEntity);
            }
    
            //利用定义好的模板传入参数到freemaker进行遍历填充,最后获得源代码
            Map<String, Object> map = new HashMap<>(8);
            map.put("package", PACKAGE_NAME);
            map.put("className", proxyClassName);
            map.put("interface", interfaceName);
            map.put("methodList", methodEntities);
            FreeMakerUtil freeMakerUtil = new FreeMakerUtil("/template/freemaker/", "ftl");
            return freeMakerUtil.printString("proxy", map);
        }
    }
    
    
    

    相关文章

      网友评论

          本文标题:手写一个面向接口的动态代理

          本文链接:https://www.haomeiwen.com/subject/ryfagktx.html