描述
代理模式是常用的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一样,动态且批量对接口进行处理
- 待补充