JAVA---JDK动态代理原理

本文将明白以下几个问题。

一、思考

问题一:如何使用JDK动态代理?

问题二:JDK动态代理的原理是什么?如何生成代理类?

 二、分析

2.1基于JDK动态代理的一般过程

代理过程的实现,必然要涉及原生对象,代理逻辑,代理对象的生成。我们结合用户登录服务,在登录前后打印登录日志功能来说明。

原生对象

//基于JDK的动态代理,原生对象必须实现接口,具体原因稍后分析
public class UserServiceImpl implements UserService{
    @Override
    public void login() {
        System.out.println("登录成功");
    }
}

代理逻辑

//继承自InvocationHandler,增强原生对象
public class UserServiceInvocationHandler implements InvocationHandler {

    private UserService userService;

    public UserServiceInvocationHandler(UserService userService) {
        this.userService = userService;
    }

    //该方法的参数来源请看下文代理类结构分析
    //参数一:proxy,代理类对象。
    //参数二:method,代理方法。
    //参数三:args,方法调用参数。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("登录前处理...");
        Object result = method.invoke(userService, args);
        System.out.println("登录后处理...");
        return result;
    }
}

代理对象生成

//通过Proxy类生成代理对象
public class ProxyDemo {
    public static void main(String[] args) {
        //该方法源码请看下文分析
        UserService userService = (UserService) Proxy.newProxyInstance
                (UserService.class.getClassLoader(), new Class[]{UserService.class},
                new UserServiceInvocationHandler(new UserServiceImpl()));
        userService.login();
    }
}

2.2基于JDK动态代理的源码分析

上述基于JDK动态代理的实现方式相信大家并不陌生,接下来我们将从源码角度分析其代理对象的生成过程。

Proxy.newProxyInstance原理

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.
         */
        //核心:生成传入接口的代理类字节码文件,并通过传入的类加载器加载生成的字节码
        //具体请看下文getProxyClass0分析
        Class cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过代理类的构造函数,传入我们的代理逻辑InvocationHandler,生成代理对象。
            //请看下文代理对象的类结构。
            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);
        }
    }

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
        
        //该方法从缓存中获取代理类,如果缓存中没有则通过ProxyClassFactory生成代理类
        return proxyClassCache.get(loader, interfaces);
    }

从缓存中获取并生成代理类的详细过程我们不深入探究,这里我们看一下其过程是怎样的:

步骤一:在我们调用Proxy.newProxyInstance静态方法时,会进行Proxy类加过过程,初始化静态字段proxyClassCache:

//字段初始化
//KeyFactory:缓存key生成工厂
//ProxyClassFactory:代理类生成工厂
private static final WeakCache[], Class>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());


//WeakCache构造函数
public WeakCache(BiFunction subKeyFactory,
                     BiFunction valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

步骤二:当缓存中无代理类时,通过ProxyClassFactory生成代理类,并通过传入的类加载器加载该类:

具体方法请看ProxyClassFactory.apply,其主要为:

1.确保传入的接口是通过传入的类加载所加载。

2.确保代理的类是接口。这也在代码层面解释了为什么基于JDK的代理类要实现接口。

3.确保代理类没有被重复加载。

4.确定代理类的包访问路径。如果接口是non-public类型,代理类包路径为当前接口包路径,否在为com.sun.proxy。

5.确定代理类名称:代理类包路径+$Proxy+(从0开始依次递增的编号)。

6.通过ProxyGenerator.generatorProxyClass方法生成代理类字节码文件。

7.通过传入的类加载器加载对应的字节码文件生成代理类。

 UserService代理类结构

通过上文分析,我们了解了代理类字节码的生成过程,接下来,我们结合UserService接口的代理类看一下其具体的结构是什么样子:

注意:默认情况下,代理类文件只是临时文件,如果想要查看代理类文件,可以添加如下代码: System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")。

代理类字节码文件路径:工程路径/com/sun/proxy/$Proxy0.class(public访问类型举例)

//因为该类是public访问类型,所以包路径为com.sun.proxy
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 net.xz.go.pattern.proxy.UserService;

//代理类实现接口,代理的是接口
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    //代理类的构造函数,接收一个InvocatoinHandler对象,类处理我们的代理逻辑
    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 login() throws  {
        try {
            //通过调用InvocationHandler的invoke方法,走代理逻辑
            //这里也解释了InvocationHandler的invoke方法参数:
            //参数一:proxy,代理类对象。
            //参数二:method,代理方法。
            //参数三:args,方法调用参数。
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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("net.xz.go.pattern.proxy.UserService").getMethod("login");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

 

 

三、总结 

本文主要剖析了基于JDK动态代理的实现过程,总结如下:

  1. JDK在运行时为我们传入的指定接口生成代理类字节码文件并通过指定的类加载器加载该类到内存。
  2. 生成的代理类接收一个InvocationHandler对象,用于接收代理逻辑。
  3. 当我们通过代理类调用接口方法时,最终都是调用InvocationHandler.invoke方法。我们需要在该方法中实现我们的代理逻辑。

但是上面的动态代理使用方法,在实际的项目中很少用到,而Spring的AOP编程思想中大量用到了动态代理,如果你感兴趣的话,请移步博文Spring---AOP实现原理(整理中...)。 

你可能感兴趣的:(杂记---框架)