代理模式
代理模式(Proxy Pattern)的定义:为其他对象提供一种代理以控制对这个对象的访问。
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。
生活中,比较常见的代理场景如:火车票代售点代卖火车票了。
在软件设计中,Spring的AOP就是使用代理模式的原理来实现的。
在代理模式中主要有四种角色:
- 客户端(client):使用代理对象和真实对象完成一些工作的调用方。
- 抽象对象(interface):代理对象和真实对象的公共对外方法(如订火车票)
- 代理对象(proxy):用来代理、封装代理对象,代理对象内包含真实对象的引用,从而能够操作真实对象。
- 真实对象(target):真正实现业务逻辑的对象;
业务场景
假设有这样一个业务场景:动物园中有许多小动物,小动物每天都要吃饭,现在需要记录动物园中所有动物的吃饭时间。
抽象对象(Animal),真实对象(Cat,SingleDog),代理对象(xxProxy),接口(吃饭:eat());
public interface Animal {
/**
* 吃饭方法
*/
void eat();
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("吃猫粮");
}
}
public class SingleDog implements Animal{
@Override
public void eat() {
System.out.println("撒狗粮");
}
}
java实现代理的方式有许多,接下来我们着重学习下
- 静态代理:继承方式 + 聚合方式
- 动态代理:jdk动态代理 + cglib动态代理
静态代理
静态代理是指,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
1. 继承方式
public class CatTimeProxy extends Cat {
@Override
public void eat() {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
super.eat();
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");
}
}
public class SingleDogTimeProxy extends SingleDog {
@Override
public void eat() {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
super.eat();
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");
}
}
测试类
//继承方式
public void testCatTimeProxy(){
//1.记录cat的吃饭时间
Animal animal = new CatTimeProxy();
animal.eat();
//2.记录SingleDog的吃饭时间
animal = new SingleDogTimeProxy();
animal.eat();
}
2. 聚合方式
public class AnimalTimeProxy implements Animal {
private Animal target;
public AnimalTimeProxy(Animal animal) {
this.target = animal;
}
@Override
public void eat() {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
target.eat();
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
}
}
测试类:
//聚合的方式(较灵活,因为实现了接口)
public void tesAnimalTimeProxy(){
//1.记录cat的吃饭时间
Animal animal = new AnimalTimeProxy(new Cat());
animal.eat();
//2.记录SingleDog的吃饭时间
animal = new AnimalTimeProxy(new SingleDog());
animal.eat();
}
3. 继承与聚合的比较
聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
举个极端的例子:动物园中有1000中动物, 现在要记录这1000种动物的吃饭时间,如果采用继承,则要为这1000种动物分别创建一个代理类…..
4. 静态代理的劣势
- 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。(ps:jdk8中,支持接口有default方法,可避免此问题)
动态代理
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。
这里着重介绍下jdk自带的动态代理,和CGLIB的动态代理
jdk动态代理
1.实现步骤
实现jdk动态代理,通常有两种常用的方法,但是最终的步骤都如下所示:
a. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
2.代码示例
首先定义InvocationHandler实现类
public class JvmTimeHandler implements InvocationHandler {
private Object target;
public JvmTimeHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
//!!注意proxy为当前调用类即JvmTimeHandler, method.invoke(xxx,zzz)第一个参数不能为proxy,会出现死循环
Object returnVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
return returnVal;
}
}
方法(一)
public void testTimeHandler() {
Animal target = new SingleDog();
Class clazz = target.getClass();
//通过 classloader,代理类的inteface[] ,以及调用处理器对象 直接生成代理对象
Animal proxy = (Animal) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new JvmTimeHandler(target));
proxy.eat();
}
方法(二)
public void testTimeHandler2() throws Exception {
//a.建动态代理类
Class proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Animal.class);
//b.动态代理类的构造函数
Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
//c.构造调用处理器对象
Animal target = new SingleDog();
InvocationHandler handler = new JvmTimeHandler(target);
//d.通过构造函数创建动态代理类对象
Animal proxy = (Animal) constructor.newInstance(handler);
proxy.eat();
}
3.源码解析
1)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
2)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
//构造函数
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
//查找或生成指定的代理类
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
//......省略部分代码......
//如果代理类已经通过实现给定接口的类加载器创建了,则返回缓存中的该类的副本;否则将通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}
//newProxyInstance()
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) {
//......省略部分代码......
final Class<?>[] intfs = interfaces.clone();
//查找或生成指定的代理类
Class<?> cl = getProxyClass0(loader, intfs);
//获得类的构造函数
final Constructor<?> cons = cl.getConstructor(constructorParams);
//newInstance
return cons.newInstance(new Object[]{h});
}
3)java.lang.reflect.Proxy.ProxyClassFactory(内部类)
通过ProxyClassFactory创建代理类.
// 根据给定的类加载器和接口数组生成代理类的工厂类
private static final class ProxyClassFactory
implements BiFunction
{
// 所有代理类名称的前缀
private static final String proxyClassNamePrefix = "$Proxy";
//用于生成唯一代理类名称的下一个序号
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//验证interfaces合法性
//......省略部分代码......
//获取代理类序号
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//生成代理类字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//加载代理类字节码.并返回实例化对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
}
4)生成的代理对象$Proxy0
目前我们只能使用代理对象,但是却获取不到代理对象的字节码,我们可以通过如下两种方式获取:
a.通过ProxyGenerator.generateProxyClass来获取,会在项目根目录下生成com.sun.proxy.$Proxy0.class文件
public static void craeteProxyClazzFile(String clazzName , Class<?> ... interfaces) {
byte[] classFile = ProxyGenerator.generateProxyClass(clazzName, interfaces);
try (FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator+clazzName+".class");){
out.write(classFile);
} catch (Exception e) {
e.printStackTrace();
}
}
b.在Proxy.newInstance之前,增加如下配置,会在项目根目录下创建com/sun/proxy/$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
接下来我们来看下$Proxy0.class的代码构成:
public final class $Proxy0 extends Proxy implements Animal {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void eat() throws {
//调用父类的InvocationHandler.invoke()方法
super.h.invoke(this, m3, (Object[])null);
}
static {
//......省略部分代码......
m3 = Class.forName("pattern.proxy.beans.Animal").getMethod("eat", new Class[0]);
}
}
4.jdk动态代理小结
jdk动态代理必须只针对接口方法的代理,如果业务类没有实现interface则无法使用jdk动态代理。如果业务类实现了接口,在接口新增了方法时,(jdk8中,支持接口default方法,否则会报错)如果代理类么有同步更新则是无法代理的,给之后的维护带来了麻烦。
cglib动态代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,我们可以使用cglib来实现动态代理。
cglib动态代理的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。—这里可以得知final类是无法被代理的。
1.实现步骤
a.定义自己的回调函数类,通常我们使用实现了callback的子接口MethodInterceptor,InvocationHandler
b.enhance设置真实对象.class,设置callback(),生成代理对象;常用的拦截器有:
c.获取代理对象,执行业务方法
2.代码示例
方法(一)
//实现MethodInterceptor接口
public class TimeInterceptorFactory implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method m, Object[] objects, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
//注意调用的方法!! methodProxy.invokeSuper()
Object returnVal = methodProxy.invokeSuper(obj, objects);
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
return returnVal;
}
}
测试类:
public static void testTimeInterceptorFactory() {
TimeInterceptorFactory interceptor = new TimeInterceptorFactory();
SingleDog proxy = (SingleDog) interceptor.getProxy(SingleDog.class);
proxy.eat();
}
方法(二)
public static void testMethodInterceptor2() throws Exception {
Enhancer enhancer = new Enhancer();
Animal target = new SingleDog();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new net.sf.cglib.proxy.InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("start eat-----------------");
//调用的方法
Object returnVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("end eat--------------------" + (endTime - startTime) + "ms");
return returnVal;
}
});
SingleDog proxy = (SingleDog) enhancer.create();
proxy.eat();
}
3.源码解析
如何查看cglib动态代理产生的类呢?我们可以在生成代理类之前增加一个系统变量,即可在工程根目录/net/sf/cglib/proxy 获得产生的代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "net/sf/cglib/proxy");
cglib动态代理会产生多个class文件:
MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class
SingleDog$$EnhancerByCGLIB$$ab95b44d$$FastClassByCGLIB$$5613c2ba.class
SingleDog$$EnhancerByCGLIB$$ab95b44d.class //关注这个
SingleDog$$FastClassByCGLIB$$47f8b6eb.class
1) SingleDog$$EnhancerByCGLIB$$ab95b44d
命名规则:package.真实对象class+$$EnhancerByCGLIB$$+key
//静态语句块
static {
//静态语句中调用
CGLIB$STATICHOOK1();
}
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d");
Class var1; //实际调用类为:SingleDog
//....省略部分.....
CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");
}
//MethodProxy
final void CGLIB$eat$0() {
super.eat();
}
//重写后的eat方法
public final void eat() {
/**
* callback子接口的类型:常见的还有net.sf.cglib.proxy.InvocationHandler;
*/
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if(this.CGLIB$CALLBACK_0 == null) {
//设置this.CGLIB$CALLBACK_0 为 enhance.callbacks[0]
CGLIB$BIND_CALLBACKS(this);
//ca
var10000 = this.CGLIB$CALLBACK_0;
}
if(var10000 != null) {
/**
* 执行callback_0.intercept
* this == 即代理类SingleDog$$EnhancerByCGLIB$$ab95b44d
* CGLIB$eat$0$Method即 被代理的业务方法 == SingleDog.eat();
* CGLIB$eat$0$Method = ReflectUtils.findMethods(new String[]{"eat", "()V"}, (var1 = Class.forName("pattern.proxy.beans.SingleDog")).getDeclaredMethods())[0];
* CGLIB$emptyArgs = 参数
* CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");
*/
var10000.intercept(this, CGLIB$eat$0$Method, CGLIB$emptyArgs, CGLIB$eat$0$Proxy);
} else {
super.eat();
}
}
//由enhance.registerCallbacks(Class generatedClass, Callback[] callbacks) 调用此方法设置CGLIB$STATIC_CALLBACKS[]
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}
//设置callback
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
SingleDog$$EnhancerByCGLIB$$ab95b44d var1 = (SingleDog$$EnhancerByCGLIB$$ab95b44d)var0;
//第一次进来时 var1.CGLIB$BOUND = false
if(!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
//CGLIB$THREAD_CALLBACKS = new ThreadLocal();
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
//第一次进来时var10000 = null;
if(var10000 == null) {
/**
* CGLIB$STATIC_CALLBACKS 由 CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) 设置
*/
var10000 = CGLIB$STATIC_CALLBACKS;
if(CGLIB$STATIC_CALLBACKS == null) {
return;
}
}
//设置this.CGLIB$CALLBACK_0 = enhance.setCallbacks()数组中的第一个
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}
}
2) .net.sf.cglib.proxy.MethodProxy
methodProxy是在上述代理类中生成的:
/**
* 在SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$STATICHOOK1()中调用
* c1 : SingleDog
* c2 : pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d
* desc: "()V",
* name1: "eat"
* name2: "CGLIB$eat$0"
*/
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
//SingleDog.eat()
proxy.sig1 = new Signature(name1, desc);
//SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
private void init()
{
if (fastClassInfo == null)
{
synchronized (initLock)
{
if (fastClassInfo == null)
{
CreateInfo ci = createInfo;
FastClassInfo fci = new FastClassInfo();
//真实对象:SingleDog
fci.f1 = helper(ci, ci.c1);
//代理对象:SingleDog$$EnhancerByCGLIB$$ab95b44d
fci.f2 = helper(ci, ci.c2);
//业务方法SingleDog.eat()
fci.i1 = fci.f1.getIndex(sig1);
//SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
}
}
}
}
//解释:为什么在设置的callback接口实现类中,必须使用methodproxy.invokeSuper。
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
/**
* fci.f2 : SingleDog$$EnhancerByCGLIB$$ab95b44d
* fci.i2 : SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()
* 观察SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()的逻辑为:super.eat()即SingleDog.eat()
* 得知等价于调用SingleDog.eat()
*/
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
4.cglib动态代理小结
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理
参考文献
https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html