理解Java动态代理

Posted aspook

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解Java动态代理相关的知识,希望对你有一定的参考价值。

概念理解

动态代理类就是在运行时创建的实现了一系列特定接口的类。

为了更好行文,还需要先明确三个概念:

  1. 代理类——在本文中指动态代理类
  2. 代理接口——在本文中指动态代理类实现的接口
  3. 代理实例——在本文中指动态代理类的一个实例

动态代理的一些特性:

  1. 代理类是publicfinal的,不能是abstract
  2. 代理类均继承自java.lang.reflect.Proxy
  3. 代理类在创建时按顺序实现了所指定的接口
  4. 代理类的名称前缀为$Proxy
  5. 代理类有一个public构造函数,参数为实现了InvocationHandler的对象
  6. 每个代理实例都有一个指定的调用处理器InvocationHandlerInvocationHandler作为其构造函数的参数传入
  7. 代理实例的方法调用会触发InvocationHandler接口的invoke方法

源码简析

Proxy类概览

Proxy类位于java.lang.reflect包中,本文以android SDK中的Proxy类来分析,与Java SDK中的Proxy可能稍有不同。

Proxy提供了一些静态方法用来生成动态代理类及其实例,同时Proxy类是这些动态代理类的父类。

Proxy的主要变量如下,具体请看注释说明:

/** 代理类名称前缀 */
private final static String proxyClassNamePrefix = "$Proxy";

/** 代理类构造函数的参数类型*/
private final static Class[] constructorParams =
     InvocationHandler.class ;

/** 类加载器与对应的代理类缓存映射 */
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache
    = new WeakHashMap<>();

/** 标记某个特定的代理类正在生成 */
private static Object pendingGenerationMarker = new Object();

/** next number to use for generation of unique proxy class names */
private static long nextUniqueNumber = 0;
private static Object nextUniqueNumberLock = new Object();

/** 所有代理类的存储集合,在isProxyClass方法中会用到 */
private static Map<Class<?>, Void> proxyClasses =
    Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>());

/**
 * 代理类实例的调用处理器
 * @serial
 */
protected InvocationHandler h;

检查某个特定类是否为动态代理类:

public static boolean isProxyClass(Class<?> cl) 
    if (cl == null) 
        throw new NullPointerException();
    

    return proxyClasses.containsKey(cl);

可以看到用到了上面定义的proxyClasses

获取代理实例的调用处理器:

public static InvocationHandler getInvocationHandler(Object proxy)
    throws IllegalArgumentException

    /*
     * Verify that the object is actually a proxy instance.
     */
    if (!(proxy instanceof Proxy)) 
        throw new IllegalArgumentException("not a proxy instance");
    
    return ((Proxy) proxy).h;

Proxy类本身还有一个invoke方法:

private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable 
    InvocationHandler h = proxy.h;
    return h.invoke(proxy, method, args);

其他Proxy提供的方法在“动态代理的使用流程分析”一节细说。

InvocationHandler接口

接下来看一下上面多次提到的InvocationHandler接口

/**
 * @code InvocationHandler is the interface implemented by
 * the <i>invocation handler</i> of a proxy instance.
 *
 * <p>Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the @code invoke
 * method of its invocation handler.
 */
public interface InvocationHandler 

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     */ 
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

注释写的非常清楚,每个代理实例都有一个InvocationHandler对象,实例方法调用时会触发该接口的invoke方法,并返回结果。invoke方法的三个参数分别为代理实例、方法实例(此处的方法实质是代理接口中所定义的方法)、方法参数。

通常我们会自定义一个类实现InvocationHandler接口,如上文示例中的MyInvocationHandler

动态代理的使用流程分析

官方文档给出的生成动态代理类的示例:

// 1.为Foo接口生成动态代理类的步骤
InvocationHandler handler = new MyInvocationHandler(...);
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[]  Foo.class );
Foo f = (Foo) proxyClass.getConstructor(new Class[]  InvocationHandler.class ).newInstance(new Object[]  handler );

// 2.更简单的一种调用方式,实际上是对上面几步的整合
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[]  Foo.class , handler);

根据上述两种使用方式,我们来跟踪一下源码。

先看getProxyClass

public static Class<?> getProxyClass(ClassLoader loader,
                                     Class<?>... interfaces)
    throws IllegalArgumentException

    return getProxyClass0(loader, interfaces);

此方法获取一个代理类,参数传入一个类加载器和一个接口数组,最终调到getProxyClass0(ClassLoader loader, Class<?>... interfaces),稍后再来看这个方法的具体实现。

上面得到代理类之后通过getConstructor得到其构造方法,上面说过代理类有一个public构造函数,参数为实现了InvocationHandler的对象,得到构造函数后,然后调用newInstance得到一个代理实例,并将其转为对应的接口对象。

再看第2种简单的实现方式,Proxy.newProxyInstance的实现:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException

    // 先检验调用处理器
    if (h == null) 
        throw new NullPointerException();
    

    /*
     * 获取代理类,也是通过调用getProxyClass0
     */
    Class<?> cl = getProxyClass0(loader, interfaces);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try 
        // 获取构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        return newInstance(cons, h);
     catch (NoSuchMethodException e) 
        throw new InternalError(e.toString());
    

newInstance(cons, h)的实现为:

private static Object newInstance(Constructor<?> cons, InvocationHandler h) 
    try 
        // 最终还是走Constructor的newInstance方法
        return cons.newInstance(new Object[] h );
     catch (IllegalAccessException | InstantiationException e) 
        throw new InternalError(e.toString());
     catch (InvocationTargetException e) 
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) 
            throw (RuntimeException) t;
         else 
            throw new InternalError(t.toString());
        
    

上面两种方式本质都是一样的,只不过第2种方式对用户使用来说更加简单。

上述两种方式的流程很清楚,接下来看最关键的地方,即动态代理类的生成过程,主要是getProxyClass0的实现,其源码较长,具体看注释吧,如下:

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) 
    // 校验接口数组的长度
    if (interfaces.length > 65535) 
        throw new IllegalArgumentException("interface limit exceeded");
    

    Class<?> proxyClass = null;

    /* collect interface names to use as key for proxy class cache */
    // 用于存储代理类实现的所有接口的名称
    String[] interfaceNames = new String[interfaces.length];

    // for detecting duplicates
    // 存储不重复的接口类对象
    Set<Class<?>> interfaceSet = new HashSet<>();

    // 循环遍历所有接口
    for (int i = 0; i < interfaces.length; i++) 
        /*
         * Verify that the class loader resolves the name of this
         * interface to the same Class object.
         */
        // 接口名
        String interfaceName = interfaces[i].getName();
        Class<?> interfaceClass = null;
        try 
            // 通过接口名及类加载器得到一个接口类对象
            interfaceClass = Class.forName(interfaceName, false, loader);
         catch (ClassNotFoundException e) 
        
        // 校验
        if (interfaceClass != interfaces[i]) 
            throw new IllegalArgumentException(
                interfaces[i] + " 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.contains(interfaceClass)) 
            throw new IllegalArgumentException(
                "repeated interface: " + interfaceClass.getName());
        
        interfaceSet.add(interfaceClass);

        // 存储接口名称到数组
        interfaceNames[i] = interfaceName;
    

    /*
     * Using string representations of the proxy interfaces as
     * keys in the proxy class cache (instead of their Class
     * objects) is sufficient because we require the proxy
     * interfaces to be resolvable by name through the supplied
     * class loader, and it has the advantage that using a string
     * representation of a class makes for an implicit weak
     * reference to the class.
     */
    // 上面已经有英文注释了,简单解释一下:使用字符串作(代理接口的名称)作为代理类缓存的key是足够的,因为我们使用类加载器加载时也是使用名称,使用字符串的另一个优势是它对类是隐式的弱引用。
    List<String> key = Arrays.asList(interfaceNames);

    /*
     * Find or create the proxy class cache for the class loader.
     */
    // 为类加载器查找或创建代理类缓存
    Map<List<String>, Object> cache;
    synchronized (loaderToCache) 
        cache = loaderToCache.get(loader);
        if (cache == null) 
            cache = new HashMap<>();
            loaderToCache.put(loader, cache);
        
        /*
         * This mapping will remain valid for the duration of this
         * method, without further synchronization, because the mapping
         * will only be removed if the class loader becomes unreachable.
         */
    

    /*
     * Look up the list of interfaces in the proxy class cache using
     * the key.  This lookup will result in one of three possible
     * kinds of values:
     *     null, if there is currently no proxy class for the list of
     *         interfaces in the class loader,
     *     the pendingGenerationMarker object, if a proxy class for the
     *         list of interfaces is currently being generated,
     *     or a weak reference to a Class object, if a proxy class for
     *         the list of interfaces has already been generated.
     */
    // 3种查找结果:(1)null(2)pendingGenerationMarker对象(3)一个类对象的弱引用
    synchronized (cache) 
        /*
         * Note that we need not worry about reaping the cache for
         * entries with cleared weak references because if a proxy class
         * has been garbage collected, its class loader will have been
         * garbage collected as well, so the entire cache will be reaped
         * from the loaderToCache map.
         */
        // 如果一个代理类被GC了,它对应的类加载器也会被GC
        do 
            Object value = cache.get(key);
            // 结果1:返回弱引用,从中得到代理类
            if (value instanceof Reference) 
                proxyClass = (Class<?>) ((Reference) value).get();
            
            if (proxyClass != null) 
                // proxy class already generated: return it
                // 代理类已经生成,并返回
                return proxyClass;
             else if (value == pendingGenerationMarker) 
                // 结果2:返回pendingGenerationMarker,表示代理类正在生成
                // proxy class being generated: wait for it
                try 
                    cache.wait();
                 catch (InterruptedException e) 
                    /*
                     * The class generation that we are waiting for should
                     * take a small, bounded time, so we can safely ignore
                     * thread interrupts here.
                     */
                
                continue;
             else 
                // 结果3:返回null,表示目前还没生成代理类,先在缓存中存储一个标记
                /*
                 * No proxy class for this list of interfaces has been
                 * generated or is being generated, so we will go and
                 * generate it now.  Mark it as pending generation.
                 */
                cache.put(key, pendingGenerationMarker);
                break;
            
         while (true);
    

    try 
        // 代理类的包名
        String proxyPkg = null;     // package to define proxy class in

        /*
         * 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 (int i = 0; i < interfaces.length; i++) 
            int flags = interfaces[i].getModifiers();
            if (!Modifier.isPublic(flags)) 
                String name = interfaces[i].getName();
                int n = name.lastIndexOf('.');
                // 举个列子,如果name=com.abc.Testinterface,则pkg=com.abc.
                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) 
            // if no non-public proxy interfaces, use the default package.
            // 默认包名为com.sun.proxy
            proxyPkg = "";
        

        
            // Android-changed: Generate the proxy directly instead of calling
            // through to ProxyGenerator.
            // 请注意上面注释,Android中有变动,直接调用Native方法生成代理类而不是通过ProxyGenerator
            // 下面方法通过递归获取接口中的所有方法
            List<Method> methods = getMethods(interfaces);
            // 根据前面及类型排序
            Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
            // 对方法返回类型进行校验,Throws if any two methods in @code methods have the same name and parameters but incompatible return types.
            validateReturnTypes(methods);
            // Remove methods that have the same name, parameters and return type. This computes the exceptions of each method; this is the intersection of the exceptions of equivalent methods.
            List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);

            // 转成方法数组
            Method[] methodsArray = methods.toArray(new Method[methods.size()]);
            Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);

            /*
             * Choose a name for the proxy class to generate.
             */
            final long num;
            synchronized (nextUniqueNumberLock) 
                num = nextUniqueNumber++;
            
            // 生成的动态代理类名,如com.sun.proxy.$Proxy0,com.sun.proxy.$Proxy1,com.sun.proxy.$Proxy2
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            // generateProxy为Native方法
            proxyClass = generateProxy(proxyName, interfaces, loader, methodsArray,
                    exceptionsArray);
        
        // add to set of all generated proxy classes, for isProxyClass
        proxyClasses.put(proxyClass, null);

     finally 
        /*
         * We must clean up the "pending generation" state of the proxy
         * class cache entry somehow.  If a proxy class was successfully
         * generated, store it in the cache (with a weak reference);
         * otherwise, remove the reserved entry.  In all cases, notify
         * all waiters on reserved entries in this cache.
         */
        // 代理类生成之后,清除之前Cache中的标记状态,并将其以弱引用的方式保存
        synchronized (cache) 
            if (proxyClass != null) 
                cache.put(key, new WeakReference<Class<?>>(proxyClass));
             else 
                cache.remove(key);
            
            // 唤醒前面cache.wait()
            cache.notifyAll();
        
    
    // 返回动态代理类
    return proxyClass;

动态代理示例

示例1

先定义一个代理接口,描述如何”上网冲浪“:

package com.aspook.dynamicproxy2;

public interface SurfInternet 
    void surf(String content);

再定义一个委托类:

public class Normal implements SurfInternet 
    @Override
    public void surf(String content) 
        System.out.println("上网: " + content);
    

创建一个自定义的InvocationHandler

public class VPNInvocationHandler implements InvocationHandler 

    private SurfInternet base;

    public VPNInvocationHandler(SurfInternet obj) 
        base = obj;
    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        System.out.println("invoke start");

        // 会调用委托类的对应方法,从而实现代理
        method.invoke(base, args);

        System.out.println("invoke end");
        return null;
    

测试类:

public class Test 
    public static void main(String[] args) 
        // 被代理类,或叫做委托类
        Normal normal = new Normal();
        InvocationHandler h = new VPNInvocationHandler(normal);
        SurfInternet vpn = (SurfInternet) Proxy.newProxyInstance(normal.getClass().getClassLoader(),
                normal.getClass().getInterfaces(),
                h);

        vpn.surf("VPN方式,可以访问Google了");
    

输出结果:

invoke start
上网: VPN方式,可以访问Google了
invoke end

注意第2行的输出,实质上是被代理类的功能,代理类一般用于不想将被代理类暴露出去,而是转用代理类来实现被代理类的功能,即所谓的代理。在上面的例子有一个被代理类Normal,与静态代理模式相比(本质上是代理类持有一个被代理类的实例,代理类与被代理类均实现相同接口),动态代理在运行时才会生成。

示例2

先定义一个代理接口:

package com.aspook.dynamicproxy;

public interface HelloService 
    void sayHello(String content);

注意此接口为public,位于com.aspook.dynamicproxy包下,按上文分析,生成的动态代理类应该位于默认包中,包名为com.sun.proxy,稍后验证。

再来实现一个逻辑类ProxyClassTest,具体请看注释:

public class ProxyClassTest 
    private InvocationHandler h;

    // 初始化一个InvocationHandler
    public void prepareInvocationHandler() 
        h = new MyInvocationHandler();
    

    // 只有一个代理接口示例
    public <T> T create(Class<T> service) 
        return (T) Proxy.newProxyInstance(service.getClassLoader(),
                new Class[]service, h);
    

    // 这里有两个代理接口,GoodService代表另一个接口,源码未给出
    public <T> T create() 
        return (T) Proxy.newProxyInstance(HelloService.class.getClassLoader(),
                new Class[]HelloService.class, GoodService.class, h);
    

    // 代理实例方法调用触发invoke回调后可执行的个人逻辑
    private void doLogic(String firstArg) 
        System.out.println(firstArg);
    

    // InvocationHandler的实现类,当然也可以用匿名内部类
    class MyInvocationHandler implements InvocationHandler 
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
            System.out.println("invoke");
            System.out.println("proxy: " + proxy.getClass());
            System.out.println("method: " + method.getName());
            System.out.println("args length: " + args.length);
            // 由于只是示例,仅供说明,因此下面两行未做越界校验
            System.out.println("args : " + args[0].toString());
            doLogic(args[0].toString());

            // 注意返回值要跟接口方法返回值相对应
            return null;
        
    

最后看下调用类:

public class Test 
    public static void main(String[] args) 
        ProxyClassTest test = new ProxyClassTest();
        test.prepareInvocationHandler();
        HelloService service = test.create();
        service.sayHello("hahaha");
    

最终输入结果如下:

invoke
proxy: class com.sun.proxy.$Proxy0
method: sayHello
args length: 1
args : hahaha
custom logic: hahaha

由输出结果可以看到代理全路径名称为com.sun.proxy.$Proxy0,跟前文中的理论一致,同时可以得到方法名和方法参数,并可执行自定义逻辑。

此示例并没有实现被代理类,多个接口的响应均统一在invoke中处理,然后实现具体逻辑,而不必像静态代理那样单独实现每一个接口。Retrofit也是利用了此原理。

注意事项

  1. 如果代理接口有多个,如下面代码有HelloServiceGoodService两个接口

    public <T> T create() 
       return (T) Proxy.newProxyInstance(HelloService.class.getClassLoader(),
               new Class[]HelloService.class, GoodService.class, h);
    

    只要有一个接口为非public的,则生成的动态代理类就跟接口同包,如com.aspook.dynamicproxy.$Proxy0

  2. 如果多个接口返回类型不同,可以在invoke中根据方法的返回类型来return不同的值。

  3. 如果跨包引用一个代理接口,则该接口必然为public的,如果只有这一个接口则动态代理类位于默认包中;如果还有其他同包的代理接口,只要有一个为非public的,则生成的动态代理类就跟非public的接口同包,如果无论跨包同包所有接口均为public,则生成的动态代理类在默认包中。

  4. 由于动态代理类本身已经继承了Proxy,根据Java单继承的限制,因此只能实现多个接口了。

以上是关于理解Java动态代理的主要内容,如果未能解决你的问题,请参考以下文章

彻底理解JAVA动态代理

彻底理解JAVA动态代理

Java 动态代理的简单使用和理解

理解java动态代理

啰里吧嗦式讲解java静态代理动态代理模式

深入理解java动态代理的实现机制