Java代理模式-动态代理

描述

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

Example

// Person.java
public interface Person {  
    void eat(String food);
}

// Main.java
public class Man implements Person {

    private String name;

    public Man(String name) {
        this.name = name;
    }

    @Override
    public void eat(String food) {
        System.out.println(this.name + " eat " + food);
    }
}

// InvokeProxy.java
public class InvokeProxy<T> implements InvocationHandler {

    private T t;

    public InvokeProxy(T t) {
        this.t = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.t, args);
    }
}

// Test.java
Person person = new Man("One");  
Object object = Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class},new InvokeProxy<>(person));  
        if (object instanceof Person) {
            ((Person) object).eat("appl");
        }

原理分析

动态代理最关键方法是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);
        }

        // #1 关键方法,生成新的class文件
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
              // #2 获取新建class的构造函数,生成的class构造函数参数是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;
                    }
                });
            }
            // #3 调用构造函数创建新对象
            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(loader, intfs)

private static Class<?> getProxyClass0(ClassLoader loader,  
                                           Class<?>... interfaces) {
        return proxyClassCache.get(loader, interfaces);
    }

// proxyClassCache 是一个 java.lang.reflect.WeakCache对象, 传递了两个参数KeyFactory, ProxyClassFactory

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 

// 获取和创建内容都在get方法里面, 注意这里key传的classloader, 第二个参数是接口的class对象,里面包含各种缓存机制,我们主要看下创建方法

...
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));  
Supplier<V> supplier = valuesMap.get(subKey);  
Factory factory = null;

while (true) {  
if (supplier != null) {  
   // supplier might be a Factory or a CacheValue<V> instance
   V value = supplier.get();
   if (value != null) {
       return value;
   }
}
...
supplier 是之前构造WeakCache构造函数传过来的ProxyClassFactory对象,supplier.get()执行了 ProxyClassFactory#apply(ClassLoader loader, Class<?>[] interfaces) 

// 然后重点看下java.lang.reflect.Proxy.ProxyClassFactory#apply   
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
                proxyName, interfaces, accessFlags);
try {  
   return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
}
// 主要调用ProxyGenerator.generateProxyClass 生成class文件内容,然后defineClass0写入文件。
private static native Class<?> defineClass0(ClassLoader loader, String name,  
                                                byte[] b, int off, int len);
// defineClass0是一个native方法,没有搜索它的c层的实现。我们来看下ProxyGenerator.generateProxyClass生成的内容到底是咋样的。

系统生成的class直接在内存,所以看不到class文件,我们可以自己写个demo写入文件

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Man.class.getInterfaces());  
        String path = "./Man.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            System.out.println("写文件错误");
        }

文件内容如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.baidu.searchbox.proxy.Person;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
import java.lang.reflect.UndeclaredThrowableException;

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

    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 eat(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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("com.baidu.searchbox.proxy.Person").getMethod("eat", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

一个继承自Proxy实现Person接口的类,里面所有的方法都是通过调用构造函数里面InvocationHandler#invoke来实现的,这就回到了之前我们自己创建的InvocationHandler类,第一个参数是生成的对象,第二个是当前Method,第三个调用方法传递参数的数组,到这里动态代理逻辑就比较清楚了。

模式实践(实现简单的retrofit)

首先实现两个简单的注解

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {  
    String value();
}

@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {  
    String value();
}

写一个接口来定义方法

public interface ServerAPI {  
    @Get("https://www.baidu.com/")
    String getBaiduHome(@Query("type") String type);

    @Get("https://www.qq.com/")
    String getQQHome(@Query("type") String type);
}

生成接口的方法, 简单的输出了接口定义的参数

public class APIManager {

    @NotNull
    public static <T> T create(Class<T> tClass) {
        Object result = Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, (proxy, method, args) -> {
            Get getAnnotation = method.getAnnotation(Get.class);
            if (getAnnotation == null) {
                return null;
            }
            String url = getAnnotation.value();
            String type = (String) args[0];
            return "method=" + method.getName() + ", url=" + url + ", type=" + type;
        });
        return (T) result;
    }
}

调用demo

String response = APIManager.create(ServerAPI.class).getBaiduHome("aaa");  
System.out.println(response);  
response = APIManager.create(ServerAPI.class).getQQHome("bbb");  
System.out.println(response);

// 运行结果
method=getBaiduHome, url=https://www.baidu.com/, type=aaa  
method=getQQHome, url=https://www.qq.com/, type=bbb  

小结

  • 方便在不修改原代码的情况下扩展方法 (需要继承同一个接口)
  • 像retrofit一样,动态且批量对接口进行处理
  • 待补充

参考文档