【转载】模拟实现Java动态代理底层逻辑

原文链接:模拟实现Java动态代理底层逻辑_何奈之的博客-CSDN博客_java动态代理底层

模拟实现Java模拟实现Java动态代理底层逻辑,以期更深入理解Java动态代理。Java动态代理能够实现不编写代理类,也能够获得代理类及其实例。下面以实现一个接口TestService为例,逐步深入Java动态代理底层逻辑。

我们都知道java代码要运行需要大略需要进行这样的工作:

img

这样就实现了一次编译,到处运行。

普通的代理,是通过编写Java源码的方式来实现代理;但是动态代理,则是借助反射,动态编辑.class字节码实现代理,并没有Java源码的产生。由此可见,动态代理底层逻辑的目标就是动态编辑.class字节码:

img

普通代理和动态代理UML图:

img

这里借助Javassist,实现.class的编辑。简单介绍Javassist。Javassist是一个用于编写字节码的库,能够做到在JVM加载一个类的时候修改该类,还能做到在运行期间定义一个新类。Javassist提供两个等级的API:源码(source)级和字节码(bytecode)级:源码级API,是根据Java语言设计,可以在没有字节码规范的知识前提下,进行.class文件的编辑;字节码级API就是直接编辑字节码文件。Javassist官方网站:http://www.javassist.org/。

下面使用eclipse+jdk1.8,尝试实现动态代理底层逻辑。

普通的实现

先看普通的实现,也就是编写Java源码的实现。为了方便,将所有代码写在一个.java文件中。

public class ProxyFactory {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(); // 外部类对象
        ProxyFactory.TestServiceImpl testServiceImpl = proxyFactory.new TestServiceImpl(); // 使用外部类对象创建内部类对象
        testServiceImpl.sayHello("张三");
    }

    // 普通实现
    class TestServiceImpl implements TestService {
        @Override
        public void sayHello(String name) {
            // 自己编写的代理逻辑。接口/被代理类不关系代理逻辑的实现,由我们自己编写
            System.out.println("hello:" + name);
        }
    }

    // TestService接口
    public interface TestService {
        void sayHello(String name);
    }

}

执行结果:

hello:张三

动态代理1.0

以上可见,普通的实现,需要自己编写源码。接下来借助Javassist,看看写死了实现TestService接口的动态代理。为了方便查看代码,所有的异常均通过throw抛出,实际工作中不建议这么做。

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory {

    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
        TestService proxy = createProxy(); // 根据createProxy()获取代理类对象
        proxy.sayHello("张三"); // 运行代理类对象的方法
    }

    /*
     *  javasist工具类:生成TestService的代理类对象
     *  1.创建一个代理类
     *  2.创建一个方法
     *  3.实例化该类对象
     */
    public static TestService createProxy()
            throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {

        ClassPool classPool = new ClassPool(); // 类池
        classPool.appendSystemPath(); // 加载当前类的ClassLoader

        // 1.
        CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建代理类(此处写死代理了名为TestServiceImpl)
        class1.addInterface(classPool.get(TestService.class.getName())); // 将接口加入代理类

        // 2.
        // 根据信息生成代理(此处为重写)的方法sayHello()
        // make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
        // "{System.out.println(\"hello:\"+1);}"就是{System.out.println("hello:"+name);}
        CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
                new CtClass[] { classPool.get(String.class.getName()) }, null, "{System.out.println(\"hello:\"+1);}",
                class1);
        class1.addMethod(sayHello); // 将重写的方法加入代理类中

        // 3.
        Class aClass = classPool.toClass(class1); // 将CtClass转换为Class
        return (TestService) aClass.newInstance(); // 实例化代理类对象并返回(此处只能是TestService)

    }

    // 接口
    public interface TestService {
        void sayHello(String name);
    }

}

执行结果不变:

hello:张三

1.0的问题

从代码可以看出,这样写出来的代码,不仅只能实现TestService接口的代理,而且代理逻辑也只能在 createProxy() 方法中写,还是写在一个被调用方法的参数列表内,不可改变且十分容易出错。那么,为了实现能够代理任意的类,我们是否可以将 createProxy() 方法的返回类型,由TestService改为为泛型,将被代理类作为方法参数传递?为了能够根据需要改变代理逻辑,能否也将代理逻辑作为方法参数传递?

动态代理2.0

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory1 {

    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException {
//      TestService proxy = createProxy(TestService.class, "{System.out.println(\"hello:\"+1);}"); // 利用反射获取接口的信息,下同
//      proxy.sayHello("张三");

        TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+1);}");
        proxy2.sayHello("李四");

    }

    /*
     *  javasist工具类:生成代理类对象。方法参数依次为:被代理类/接口,代理逻辑
     *  1.创建一个代理类
     *  2.创建一个方法
     *  3.实例化该类对象
     */
    public static <T> T createProxy(Class<T> classInterface, String src)
            throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException {

        ClassPool classPool = new ClassPool(); // 类池
        classPool.appendSystemPath(); // 加载当前类的ClassLoader

        // 1.
        CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类
        class1.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中

        // 2.
        // 根据信息生成代理(此处为重写)的方法sayHello()
        // make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
        CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
                new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);
        class1.addMethod(sayHello); // 添加方法到代理类中

        // 3.
        Class aClass = classPool.toClass(class1); // 将代理类由CtClass转换为Class
        return (T) aClass.newInstance(); // 实例化代理类并返回对象

    }

    // 接口。下同
    public interface TestService {
        void sayHello(String name);
    }

    public interface TestService2 {
        void sayHello(String name);

        void sayHello2(String name);
    }
}

2.0存在的问题

这样就解决1.0的两个问题了。但还有别的问题:

1.代理类一次只能执行一个,否则会编译不通过。因为我们在代码中写死了代理类的类名,会导致代理类名称重复:

CtClass class1 = classPool.makeClass("TestServiceImpl"); // 创建一个代理类,此处代码写死了代
                                                         // 理类的名字为TestServiceImpl
hello:张三
Exception in thread "main" javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/LauncherAppClassLoader): attempted  duplicate class definition for name: "TestServiceImpl"
    at javassist.ClassPool.toClass(ClassPool.java:1170)
    at javassist.ClassPool.toClass(ClassPool.java:1113)
    at javassist.ClassPool.toClass(ClassPool.java:1071)
    at lsb.ProxyFactory1.createProxy(ProxyFactory1.java:46)
    at lsb.ProxyFactory1.main(ProxyFactory1.java:17)
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/LauncherAppClassLoader): attempted  duplicate class definition for name: "TestServiceImpl"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at javassist.ClassPool.toClass2(ClassPool.java:1183)
    at javassist.ClassPool.toClass(ClassPool.java:1164)
    ... 4 more

2.代理类中的方法名也是写死为“sayHello”:

// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
        CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
                new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);

这就会导致我们无法让代理类重写 TestService2() 接口中除了 sayHello() 以外的方法,因为无法找到 sayHello() 以外的方法:

TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
        proxy2.sayHello2("李四"); // 试图重写TestService2的sayHello2(...)方法
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
    at lsb.ProxyFactory1.main(ProxyFactory1.java:18)

3.代理类重写方法的参数列表也被写死了,只有一个 String name:

// make()方法的参数按顺序分别为:代理类返回类型、代理类重写的方法、所重写方法的参数列表、异常、代理逻辑(此处为重写的代码)
        CtMethod sayHello = CtNewMethod.make(CtClass.voidType, "sayHello",
                new CtClass[] { classPool.get(String.class.getName()) }, null, src, class1);

其次,由于Java的多态,若更改接口方法的参数列表,sanHello() 方法就是另外一个方法了,此时再运行,将会产生同样的错误:

TestService2 proxy2 = createProxy(TestService2.class, "{System.out.println(\"hello:\"+$1);}");
        proxy2.sayHello2("李四", 1); // 试图重写TestService2的sayHello(String name, int id)
public interface TestService2 {
        void sayHello(String name, int id); // 增加参数int id

        void sayHello2(String name);
    }
Exception in thread "main" java.lang.AbstractMethodError: TestServiceImpl.sayHello2(Ljava/lang/String;)V
    at lsb.ProxyFactory1.main(ProxyFactory1.java:18)

4.代理逻辑的编码和1.0并无区别,效率依然底下且易读性差。

动态代理3.0

为了解决上面的4个问题,将2.0更改为3.0:

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class ProxyFactory3 {
    static int count = 0; // 作为生成的代理类后缀,避免重名

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NotFoundException,
            CannotCompileException, IllegalArgumentException, NoSuchFieldException, SecurityException {

        // 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
        TestService proxy1 = createProxy(TestService.class, new InvocationHandler() {

            @Override
            public Object invoke(String methodName, Object[] args) {
                // 需要程序员自己写的代理逻辑
                System.out.println("hello:" + args[0]);
                return null;
            }

        });
        proxy1.sayHello("张三");

        // 匿名类实现 InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService2 接口
        TestService2 proxy2 = createProxy(TestService2.class, new InvocationHandler() {

            @Override
            public Object invoke(String methodName, Object[] args) {
                System.out.println("hello:" + args[0]);
                return null;
            }

        });
        proxy2.sayHello("李四");
        proxy2.sayHello2("王五");
        proxy2.sayHello3("赵六", 1);

        // 非匿名类实现InvokationHandler接口,重写 invoke(...)方法,动态代理实现 TestService 接口
        ProxyFactory3 proxyFactory3 = new ProxyFactory3();
        Handler handler = proxyFactory3.new Handler();
        TestService proxy3 = createProxy(TestService.class, handler);
        proxy3.sayHello("孙七");

    }

    /*
     * 创建代理对象:方法参数依次为:代理的方法/接口、handler
     * 1.创建一个代理类
     * 2.添加一个InvocationHandler类型的属性,用以保存增强代码
     * 3.创建接口下的所有方法实现
     * 4.实例化该代理类对象
     */
    public static <T> T createProxy(Class<T> classInterface, InvocationHandler handler)
            throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, NoSuchFieldException, SecurityException {

        ClassPool classPool = new ClassPool(); // 类池
        classPool.appendSystemPath(); // 加载当前类的ClassLoader

        // src分为void和return两种。该字符串为代理类最后执行的invoke()方法代码
        String src = "return (r)this.handler.invoke(\"%s\",args);";
        String voidSrc = "this.handler.invoke(\"%s\",args);";

        // 1.创建一个代理类
        CtClass impl = classPool.makeClass("proxy" + count++); // 代理类impl,加count后缀防止类重名
        impl.addInterface(classPool.get(classInterface.getName())); // 添加被代理接口/类的方法到代理类中

        // 2.添加一个InvocationHandler类型的属性,用以保存增强代码
        // 编译"public lsb.ProxyFactory3.InvocationHandler
        // handler=null;",代码这里用了全权限定名
        // 这样就可以创建一个InvocationHandler接口的空对象handler,并将其加入代理类impl
        // 相当于代理类中声明了一个成员变量 handler:public InvocationHandler handler = null;
        // 这样代理类就可以存储该方法传递过来的参数 handler(createProxy(..., InvocationHandler
        // handler))
        CtField field = CtField.make("public lsb.ProxyFactory3.InvocationHandler handler=null;", impl);
        impl.addField(field); // 将上一句代码创建的属性(变量)加入代理类

        // 3.创建接口下的所有方法实现
        // 循环被代理接口/类的方法,分别获取方法信息,添加到代理类中
        for (Method method : classInterface.getMethods()) {
            CtClass returnType = classPool.get(method.getReturnType().getName()); // 方法类型
            String methodName = method.getName(); // 方法名
            CtClass[] parameters = toCtClass(classPool, method.getParameterTypes()); // 方法参数
            CtClass[] errors = toCtClass(classPool, method.getExceptionTypes()); // 方法的异常(try-catch)

            // 判断代理逻辑是否需要return
            String srcImpl = "";
            if (method.getReturnType().equals(Void.class)) {
                srcImpl = voidSrc;
            } else {
                srcImpl = src;
            }

            // 根据信息生成对应的方法
            CtMethod newMethod = CtNewMethod.make(returnType, methodName, parameters, errors, srcImpl, impl);

            impl.addMethod(newMethod); // 将方法添加到代理类中
        }

        // 4.实例化该代理类对象
        Class clazz = classPool.toClass(impl); // 将CtClass转换为Class。将代理类加载到当前ClassLoader中
        Object object = clazz.newInstance(); // 实例化
        clazz.getField("handler").set(object, handler); // 将handler赋值到代理类中

        return (T) object; // 返回最终获得的代理类对象
    }

    // 接口InvocationHandler
    public interface InvocationHandler {
        // invoke(...)方法,用来接受被代理方法的名称及参数列表
        // 实现该方法时,重写的代码即是代理逻辑。所以该方法用来调用代理之后的方法
        // 例子见下个方法
        Object invoke(String methodName, Object args[]);
    }

    // InvocationHandler接口实现例子(正常实现,在main方法中的实现为匿名类方式)
    public class Handler implements InvocationHandler {
        @Override
        public Object invoke(String methodName, Object[] args) {
            // 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
            System.out.println(args[0] + "执行了增强代码!");

            return null;
        }
    }

    // 工具方法:将Class[]转换为CtClass[]
    private static CtClass[] toCtClass(ClassPool classPool, Class[] classes) {
        return Arrays.stream(classes).map(c -> {
            try {
                return classPool.get(c.getName());
            } catch (NotFoundException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList()).toArray(new CtClass[0]);
    }

    // 被代理接口。下同
    public interface TestService {
        void sayHello(String name);
    }

    public interface TestService2 {
        String sayHello(String name);

        void sayHello2(String name);

        void sayHello3(String name, int id);
    }

}

运行结果:

hello:张三
hello:李四
hello:王五
hello:赵六
孙七执行了增强代码!

关于匿名类的直接理解:

可以看到,上面的代码中,在方法 createProxy(Class classInterface, InvocationHandler handler) 中的第二个参数 handler 处,我们直接new了一个 InvocationHandler 对象(其实就是 handler),并且直接在里面写了方法体,则就是匿名类。匿名类的效果可以简单粗暴地理解成这样:

// 接口InvocationHandler
public interface InvocationHandler {
    Object invoke(String methodName, Object args[]);
}

TestService proxy = createProxy(TestService.class, 

    public class handler implements InvocationHandler {
        @Override
        public Object invoke(String methodName, Object[] args) {
            // 自己写的增强代码,InvocationHandler接口不关心增强代码是什么
            System.out.println("执行了增强代码!");
            return null;
        }
    }

);
proxy.sayHello("张三");

当然实际上不能这样说,只不过简单粗暴地如此理解,可以比较快地得到一个感性直观的理解,从而避免在不必要的方面纠结,而耽误了对动态代理的理解。后面会针对内部类、匿名类等专门写一篇文章。

附虚拟代理类简单的类UML关系图:

img

发表评论