最近发现对JDK的动态代理以前看过的东西已经模糊了,发现了自己对这块的理解不够,而且没有记录笔记导致如今呵呵哒,所以今天重新梳理下这块
代理模式
- 使用代理模式创建的代理对象,可以控制对目标对象的访问,以及前置、后置处理,代理模式强调的是代表目标对象。装饰器模式而是针对对象的某个行为进行装饰,通过不同的装饰改变被装饰的行为,强调的是挂载,不断的向行为上挂载行为改变行为增强对象。
- 图片摘自Head First设计模式
静态代理
- 顾名思义,一个代理对象对应一个被代理对象,实现代码
//主题类
public interface Subject {
void request();
}
//真正的主题实现类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("我才是真正的主题,哦也 "+this);
}
}
//没错,在下就是大名鼎鼎的代理,哦也
public class SubjectProxy implements Subject {
private RealSubject realSubject;
public SubjectProxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
realSubject.request();
}
}
缺点
- 目标(被代理的)对象增加新方法时,代理对象要随之改变
- 新增一个目标对象,需要同步增加一个代理对象与之对应
动态代理
常见实现方法
- JDK动态代理:Proxy代理类、InvocationHandler接口。目标对象必须实现接口,代理是代理的接口行为
- Cglib动态代理:补充了JDK的软肋,可以不用实现接口
JDK动态代理
使用方法
// 目标对象的接口
public interface MyBean {
void test();
void setName(String name);
void setAge(Integer age);
}
// 在下正是目标对象
public class SimpleBean implements MyBean {
private String name;
private Integer age;
public void test(){
System.out.println(String.format("name:%s,age:%s", name, age));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
// 吾乃常山赵子龙(动态代理)是也。。。HIAHIAHIA
public class DynamicProxyHandler implements InvocationHandler {
private Object source;
private Object bind(Object source){
this.source = source;
return Proxy.newProxyInstance(source.getClass().getClassLoader(), source.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("source 执行");
return method.invoke(source, args);
}
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
MyBean bean = new SimpleBean();
bean.setAge(22);
bean.setName("superman");
DynamicProxyHandler handler = new DynamicProxyHandler();
MyBean proxy = (MyBean) handler.bind(bean);
proxy.test();
}
}
实现原理
- 根据绑定了目标对象的InvocationHandler接口实现创建代理对象:Proxy.newProxyInstance
- 查找或者创建代理:Proxy.proxyClassCache.get
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
- 代理缓存的subKeyFactory:KeyFactory,valueFactory:ProxyClassFactory
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
- 从缓存中获取代理:get
- 根据cacheKey(即目标对象的classloader实例的弱引用)获取缓存的代理对象集合valuesMap,没有则新建
- 子key工厂中获取subKey:subKeyFactory.apply(key, parameter),其实就是目标对象实现接口的弱引用
- 从valuesMap中获取subKey对应的value提供者Supplier(代理对象提供者),如果不为空则get返回
- 如果工厂(value提供者)为空,则创建
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
- 如果value提供者为空且缓存中没有老的value提供者,则将factory赋值给supplier,否则supplier取缓存中的value提供者实例
- 从工厂中获取代理实例:Factory.get
- 根据key(类加载器),parameters(接口列表)从valueFactory中获取代理实例:ProxyClassFactory.apply
- 根据参数生成class字节码并实例化对象返回,生成类代码如下,其实就是通过静态方法中反射获取对应的方法并通过InvocationHandler的invoker方法调用
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.gallant.dispatch.MyBean;
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 MyBean {
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m5;
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 void setName(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 void test() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 setAge(Integer var1) throws {
try {
super.h.invoke(this, m5, 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"));
m3 = Class.forName("com.gallant.dispatch.MyBean").getMethod("setName", Class.forName("java.lang.String"));
m4 = Class.forName("com.gallant.dispatch.MyBean").getMethod("test");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m5 = Class.forName("com.gallant.dispatch.MyBean").getMethod("setAge", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
总结
- 通过制定该参数可以输出动态代理生成的class文件协助分析排查问题
// JDK输出class文件设置
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 最新版本该配置已发生变更,使用下面的配置
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// cglib输出class文件设置
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D://tmp");
- jdk的动态代理可以看到是通过反射调用实现,所以性能会略差于cglib(通过字节码技术实现)。但是jdk对反射也进行了优化,当反射调用超过一个阈值时,会自动生成字节码来优化反射。所以反射这个性能的开销已经不再是jdk动态代理的软肋了,当前最大软肋还是不能代理类。比如:我想要代理Jedis对象,总不能去修改jedis源码使其实现某个接口吧。为什么要代理jedis对象呢?当然是想要拦截某些方法做些事情。哈哈哈