JDK动态代理使用及原理解析

1 篇文章 0 订阅
1 篇文章 0 订阅

JDK动态代理使用及原理解析

一、动态代理的使用

1.1 动态代理简单示例

    
动态代理类图如下:

avatar

    
主体和实际主体代码如下所示:

/**
 * 主体
 */
public interface Subject {
    //主体方法
    void subjectMethod();
}

/**
 * 实际主体
 */
public class RealSubject implements Subject {
    @Override
    public void subjectMethod() {
        System.out.println("执行方法subjectMethod...");
    }
}

    
代理类代码如下所示,可看到代理类中使用了反射调用:

public class ProxyHandler implements InvocationHandler {
    private Subject subject;

    public ProxyHandler(Subject subject) {
        this.subject = subject;
    }

    /**
     * @param proxy  调用方法的代理实例
     * @param method 被代理对象中的方法
     * @param args   调用方法时的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行方法subjectMethod前");
        /**
         * method.invoke(Object obj, Object... args)
         * @param obj  :方法被调用的对象
         * @param args :方法被调用的参数
         * @return Object: 方法调用返回的结果
         */
        Object o = method.invoke(subject, args);
        System.out.println("执行方法subjectMethod后");
        return o;
    }
}

    
请求者中声明了实际主体和调动处理程序:

/**
 * Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
 * @param loader:代理类的类加载器
 * @param interfaces: 代理类要实现的接口列表
 * @param h:将方法调用分派到的调用处理程序
 * @return 返回代理对象,是对被代理对象的增强
 */
Subject proxySubject = (Subject) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
        new Class<?>[]{Subject.class}, new ProxyHandler(new RealSubject()));
proxySubject.subjectMethod();

    
输出如下:

执行方法subjectMethod前
执行方法subjectMethod...
执行方法subjectMethod后

1.2 创建代理实例在代理类中

    
在实际应用中,创建代理实例(Proxy.newProxyInstance)往往是放在代理类中
avatar

    
代码如下所示:

public class ProxyHandlerOther implements InvocationHandler {
    private Subject subject;

    public Subject getProxySubject(Subject subject) {
        this.subject = subject;
        return (Subject) Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
                this.subject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行方法subjectMethod前");
        Object o = method.invoke(subject, args);
        System.out.println("执行方法subjectMethod后");
        return o;
    }
}

    
请求者直接创建代理并调用方法即可:

Subject otherProxySubject = new ProxyHandlerOther().getProxySubject(new RealSubject());
otherProxySubject.subjectMethod();

1.3 公用的代理类

    
可将代理类中的代理对象和通知公用出来,如下所示:
avatar

    
代码如下:

public class ProxyHandlerCommon implements InvocationHandler {
    private Object subject;
    private Advice advice;

    public Object getProxyObject(Object subject, Advice advice) {
        this.subject = subject;
        this.advice = advice;
        return Proxy.newProxyInstance(this.subject.getClass().getClassLoader(),
                this.subject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        advice.before();
        Object o = method.invoke(subject, args);
        advice.after();
        return o;
    }
}

    
通知接口和默认通知类如下:

/**
 * 公用通知接口
 */
public interface Advice {
    void before();
    void after();
}

/**
 * 默认通知
 */
public class AdviceDefault implements Advice{

    @Override
    public void before() {
        System.out.println("Advice before...");
    }

    @Override
    public void after() {
        System.out.println("Advice after...");
    }
}

    
请求者直接指定主体和通知即可:

//公用的代理类
Object commonProxy = new ProxyHandlerCommon().getProxyObject(new RealSubject(), new AdviceDefault());
((Subject) commonProxy).subjectMethod();

如何进入到代理类中的invoke的?

    
请求者中使用Proxy.newProxyInstance生成了代理实例,那么代理实例是如何进入到代理类中的invoke方法的呢?

    
我们在请求者中设置断点如下图所示:
avatar
    
如上图可看到,代理实例实际上是一个名为$Proxy0的一个类,内部有个h实例(ProxyHandler),h实例内部有一个subject(RealSubject)。

    
猜测:这个$Proxy0也实现了Subject接口,执行主体方法subjectMethod时实际上是去执行h实例里的invoke方法。

拿到$Proxy0这个类,有以下两种方式:

2.1 拿到$Proxy0这个类

方法一:saveGeneratedFiles属性

    
找源码可知打开saveGeneratedFiles属性即可,在请求者中加上该属性:

//保存生成的代理类class文件,默认false
 System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    
运行程序时,会看到项目下生成 P r o x y 0. c l a s s 这 个 类 ( 一 般 位 于 c o m . s u n . p r o x y . Proxy0.class这个类(一般位于com.sun.proxy. Proxy0.class(com.sun.proxy.Proxy.class)。

    
为什么加上该属性就可打印文件,后面会说到。
    

方法二、从内存中把 P r o x y 0 写 入 到 文 件 Proxy0写入到文件 Proxy0Proxy0.class

public static void createProxyClassFile(){
    /**
     * JDK自带的工具,把内存里的字节码转换字节码数组
     * 从内存中把$Proxy0写入到文件$Proxy0.class中
     * 参数1:类名
     * 参数2:该类实现的接口
     */
    byte [] $Proxy0Byte = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Subject.class});
    try{
        //字节码数组写入到文件
        FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
        fileOutputStream.write($Proxy0Byte);
        fileOutputStream.close();
    }catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.2 $Proxy0的解析

    
$Proxy0.class源码如下:

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m3;
    //...省略
    public final void subjectMethod() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        //...省略
        m3 = Class.forName("com.mm.mmblogs.b4.Subject").getMethod("subjectMethod");
        //...省略
    }
}

由上可知:

  • $Proxy0实现了Subject接口,所以它也有subjectMethod方法
  • P r o x y 0 继 承 了 Proxy0继承了 Proxy0Proxy类,里面的变量InvocationHandler即h就是Proxy.newProxyInstance时传入的ProxyHandler实例
  • $Proxy0中subjectMethod方法调用了h中的invoke方法

Proxy.newProxyInstance是如何生成一个代理对象然后返回的?

  • 下面代码经过手动删减

    
进入到Proxy.newProxyInstance方法,代码大致如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        final Class<?>[] intfs = interfaces.clone();

        //生成代理类对象
        Class<?> cl = getProxyClass0(loader, intfs);

        //拿到代理类的构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        
        //返回代理实例
        return cons.newInstance(new Object[]{h});
    }

    
再来看看getProxyClass0方法:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    // 接口数量不可大于065535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 缓存中存在,则直接返回
    // 否则通过ProxyClassFactory创建代理类
    return proxyClassCache.get(loader, interfaces);
}

    
ProxyClassFactory类如下:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
		// 所有代理类名的前缀
        private static final String proxyClassNamePrefix = "$Proxy";

        // 生成唯一代理类名的下一个数字
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        
            //生成指定的代理类的字节码文件
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
    
            //解析字节码,生成$Proxy的Class对象并返回
            return defineClass0(loader, proxyName,
            		proxyClassFile, 0, proxyClassFile.length);

            }
        }
    }

    
继续跟进ProxyGenerator.generateProxyClass方法,这里还看到了sun.misc.ProxyGenerator.saveGeneratedFiles属性:

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    //生成指定的代理类的字节码文件
    final byte[] var4 = var3.generateClassFile();
    //判断sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                //...省略
                Files.write(var2, var4, new OpenOption[0]);
                return null;
            }
        });
    }

    return var4;
}

    
generateClassFile方法中,主要是为代理类组装信息,如组装接口中的所有方法、组装构造函数、组装字段信息等等,然后写入文件,最后以字节数组的方式返回。

    
Proxy.newProxyInstance方法总结如下:

  • 生成代理类对象,如果缓存没有则创建。创建时先生成指定的代理类的字节码文件,然后在解析称class对象返回。
  • 拿到代理类的构造函数,并创建代理实例返回。
  • sun.misc.ProxyGenerator.saveGeneratedFiles属性,如果为true则保存生成的代理类class文件,默认为false。
  • 代理类工厂ProxyClassFactory生成的代理类名前缀为$Proxy,且用AtomicLong保证各个代理类名唯一。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值