0
点赞
收藏
分享

微信扫一扫

深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架


文章目录

  • 一、动手实现一个动态代理框架
  • 1、初识javassist
  • 2、使用javassist实现一个动态代理框架
  • 二、JDK动态代理
  • 1、编码实现
  • 2、基本原理
  • (1)getProxyClass0方法
  • (2)总结
  • 写在后面

一、动手实现一个动态代理框架

1、初识javassist

Java操纵字节码,最底层一般是使用ASM进行操作的,但是ASM上手难度很大,我们可以使用javassist对字节码进行操作,相对来说简单一些。

下面这个实例,就是我们使用javassist对接口动态生成一个实现类:

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

public class JavassistDemo {


    public static void main(String[] args) throws Exception {

        TestService proxy = createProxy();
        proxy.sayHello("zhangsan"); // hello:zhangsan
    }

    /**
     * 生成一个TestService的实现类
     */
    public static TestService createProxy() throws Exception {
        // javassist 底层是ASM,ASM底层是编辑JVM指令码
        ClassPool classPool = new ClassPool();
        // 添加classLoader
        classPool.appendSystemPath();
        // 1.创建一个类
        CtClass class1 = classPool.makeClass("TestServiceImpl");
        class1.addInterface(classPool.get(TestService.class.getName()));
        // 2.创建一个方法
        CtMethod satHelloMethod = CtNewMethod.make(CtClass.voidType, // void返回值
                "sayHello", // 方法名
                new CtClass[]{classPool.get(String.class.getName())}, // 方法参数
                new CtClass[0], // 异常类型
                "{System.out.println(\"hello:\"+$1);}", // 方法体内容,$1表示第一个参数
                class1 // 指定类
                );
        class1.addMethod(satHelloMethod);
        // 3.实例化这个对象
        Class aClass = classPool.toClass(class1);
        // 强制转换
        return (TestService) aClass.newInstance();
    }

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

2、使用javassist实现一个动态代理框架

import javassist.*;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;

public class Javassist3Demo {

    public static void main(String[] args) throws Exception {

        TestService proxy = createProxy(TestService.class, new InvocationHandler() {
            @Override
            public Object invoke(String methodName, Object[] args) {
                // 根据方法判断逻辑
                if(methodName.equals("sayHello3")) {
                    System.out.println("hello" + args[0]);
                    return "aa";
                } else {
                    System.out.println("hello2" + args[0]);
                    return "aa2";
                }
            }
        });
        proxy.sayHello("zhangsan"); // hellozhangsan
        proxy.sayHello2("zz", 1);
        System.out.println(proxy.sayHello3("qq"));
    }

    static int count = 0;
    public static <T> T createProxy(Class<T> classInterface, InvocationHandler handler) throws Exception {
        ClassPool classPool = new ClassPool();
        classPool.appendSystemPath();
        // 1.创建一个类
        CtClass impl = classPool.makeClass("$Proxy" + count ++);
        impl.addInterface(classPool.get(classInterface.getName()));

        // 2.impl类中 添加属性handler
        CtField fie = CtField.make("public com.mydemo.Javassast3Demo.InvocationHandler handler=null;", impl);
        impl.addField(fie);

        // 有返回值类型和无返回值类型的源码
        String src = "return ($r)this.handler.invoke(\"%s\", $args);"; // $args获取所有参数
        String voidSrc = "this.handler.invoke(\"%s\",$args);";

        for (Method method : classInterface.getMethods()) {
            CtClass returnType = classPool.get(method.getReturnType().getName());
            String name = method.getName();
            CtClass[] parameters = toCtClass(classPool, method.getParameterTypes());
            CtClass[] errors = toCtClass(classPool, method.getExceptionTypes());

            // 2.创建一个方法
            CtMethod newMethod = CtNewMethod.make(returnType, // 返回值
                    name, // 方法名
                    parameters, // 方法参数
                    errors, // 异常类型
                    method.getReturnType().equals(Void.class) ? String.format(voidSrc, method.getName()) : String.format(src, method.getName()), // 方法体内容
                    impl // 指定类
            );
            impl.addMethod(newMethod);

        }

        // 生成字节码(辅助学习用)
        //byte[] bytes = impl.toBytecode();
        //Files.write(Paths.get(System.getProperty("user.dir") + "/target/" + impl.getName() + ".class"), bytes);

        // 3.实例化这个对象
        Class aClass = classPool.toClass(impl);
        T t = (T) aClass.newInstance();
        aClass.getField("handler").set(t, handler); // 初始化赋值
        // 强制转换
        return t;
    }


    private static CtClass[] toCtClass(ClassPool pool, Class[] classes) {
        return Arrays.stream(classes).map(c -> {
            try {
                return pool.get(c.getName());
            } catch (NotFoundException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList()).toArray(new CtClass[0]);
    }

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

    public class InvocationHandlerImpl implements InvocationHandler {

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

    public interface TestService {
        void sayHello(String name);
        void sayHello2(String name, Integer id);
        String sayHello3(String name);
    }
}

这样,我们只要实现InvocationHandler 接口,就可以自定义代理类的核心逻辑了,我们对生成的代理类进行反编译:

public class $Proxy0 implements TestService {
    public InvocationHandler handler = null;

    public String sayHello3(String var1) {
        return (String)this.handler.invoke("sayHello3", new Object[]{var1});
    }

    public void sayHello(String var1) {
        this.handler.invoke("sayHello", new Object[]{var1});
    }

    public void sayHello2(String var1, Integer var2) {
        this.handler.invoke("sayHello2", new Object[]{var1, var2});
    }

    public $Proxy0() {
    }
}

实现了我们的接口,并且重写了接口的所有方法,最终执行的是InvocationHandler的invoke方法。

这也正是JDK动态代理的基本思想。

二、JDK动态代理

1、编码实现

public interface EchoService {
    String echo(String message) throws NullPointerException;
}

public class DefaultEchoService implements EchoService {
    @Override
    public String echo(String message) {
        return "[ECHO] " + message;
    }
}

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK动态代理实例
 */
public class JDKDynamicProxyDemo {

    public static void main(String[] args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        // 真实的对象
        DefaultEchoService realObj = new DefaultEchoService();

        // 代理的对象
        Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("动态前置");
                Object obj = null;
                if (EchoService.class.isAssignableFrom(method.getDeclaringClass())) {
                    // 执行真实方法
                    obj = method.invoke(realObj, args);
                }
                System.out.println("动态后置");
                return obj;
            }
        });
        EchoService echoService = (EchoService) proxy;
        System.out.println(echoService.echo("Hello,World"));
    }
}

我们发现,使用JDK动态代理,基本逻辑和我们上面使用javassist手写的工具类差不多,只不过JDK动态代理底层是使用更复杂的方式实现的,我们这里取巧,使用javassist实现。

注意!这里需要DefaultEchoService 实现EchoService接口,代理的其实是EchoService接口而不是DefaultEchoService 类。

动态代理对原代码没有侵入性,通常可以动态加载。

2、基本原理

为什么 Proxy.newProxyInstance 会生成新的字节码?

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{EchoService.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});

System.out.println(proxy.getClass());// com.sun.proxy.$Proxy0


Object proxy2 = Proxy.newProxyInstance(classLoader, new Class[]{Comparable.class}, (proxy1, method, args1) -> {
            return null;
        });

System.out.println(proxy2.getClass());// com.sun.proxy.$Proxy1

上面代码我们会发现,Java动态代理每生成一个代理,它的class总是com.sun.proxy包下的$Proxy*,从0开始累加,它是如何实现的呢?

我们来分析一下Proxy的newProxyInstance方法:

// java.lang.reflect.Proxy#newProxyInstance
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);
	// 对象克隆
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
     // 先从缓存获取(见 (1))
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
		// 获取代理对象的构造方法,带着InvocationHandler参数的构造方法
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 返回Proxy对象
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

(1)getProxyClass0方法

在getProxyClass0方法中,从proxyClassCache缓存中获取了这个代理类:

// java.lang.reflect.Proxy#getProxyClass0
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}

而proxyClassCache在初始化时,自动创建了KeyFactory和ProxyClassFactory

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

ProxyClassFactory的核心方法apply,隐藏着代理接口的创建逻辑:

// java.lang.reflect.Proxy.ProxyClassFactory
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) { // 遍历我们传入的接口数组
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
            	// 通过classLoader加载我们的接口
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {// 只能代理接口,非接口直接抛异常
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * Verify that this interface is not a duplicate.
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) { // 包名就是com.sun.proxy
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num; // 依次递增

        /*
         * Generate the specified proxy class.
         */
         // 代理类生成器,返回字节数组,就是字节码
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
        	// classLoader加载类,是一个native方法,返回一个Class对象
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

(2)总结

vm options参数设置-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就可以把生成的代理类的源码保存在com.sun.proxy目录下面,或者用arthas来查看运行中的类信息。

JDK动态代理生成的代理类,我们通过反编译,发现其实是这个样子的:

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import com.demo.EchoService;

public final class $Proxy0
extends Proxy
implements EchoService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.demo.EchoService").getMethod("echo", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String echo(String string) throws NullPointerException {
        try {
            return (String)this.h.invoke(this, m3, new Object[]{string});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

InvocationHandler就是Proxy.newProxyInstance传入的最后一个参数。

当调用代理对象的方法时,会执行InvocationHandler的invoke方法。

注意,JDK生成的代理类的包名不总是com.sun.proxy,只有当接口为Public时是这样的,当接口为非public时,生成的代理类与接口所在包名相同。

写在后面

如果本文对你有帮助,请点赞收藏关注一下吧 ~

深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架_动态代理


举报

相关推荐

0 条评论