23种设计模式7_代理模式之二动态代理
1 基本介绍
代理模式:为其他对象提供一种代理以控制对这个对象的访问
代理模式也叫委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上都是在更加特殊的场合采用了代理模式。在日常开发应用种,代理模式能够提供非常好的访问控制。
代理可分为两种:一种是静态代理,由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的**.class**文件就已经存在了;另一种是动态代理:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理参考:23种设计模式7_代理模式之一静态代理
静态代理事先知道自己要代理的是什么,而动态代理不知道要代理什么,只有在运行时才知道。
动态代理的两种实现方式:
①JDK动态代理*** 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler*来处理
②CGlib动态代理*** 利用ASM开源包(开源的Java字节码编辑库,操作字节码),将代理对象类的class文件*加载进来,通过修改其字节码生成子类来处理。
2 JDK动态代理
继续通过游戏代练这个案例来演示动态代理,UML类图如下:
被代理接口:
public interface IGamePlayer {
// 登录
void login(String playName, String password);
// 打怪
void killMonster();
// 升级
void levelUp();
}
被代理类:
public class GamePlayer implements IGamePlayer{
// 游戏玩家名称
private String playerName;
public GamePlayer(String playerName) {
this.playerName = playerName;
}
@Override
public void login(String playName, String password) {
System.out.println(this.playerName + "登录游戏成功!");
}
@Override
public void killMonster() {
System.out.println(this.playerName + "正在打怪!");
}
@Override
public void levelUp() {
System.out.println("恭喜" + this.playerName + "又升了一级!");
}
}
动态代理类GamePlayerIH实现InvocationHandler
public class GamePlayerIH implements InvocationHandler {
// 被代理的实例
Object obj = null;
// 我要代理谁
public GamePlayerIH(Object _obj) {
this.obj = _obj;
}
// 调用被代理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.obj, args);
}
}
客户端
public class Client {
public static void main(String[] args) {
IGamePlayer player = new GamePlayer("zhangsan");
// 定义一个Handler
InvocationHandler handler = new GamePlayerIH(player);
// 获取类加载器
ClassLoader cl = player.getClass().getClassLoader();
// 动态产生一个代理者
IGamePlayer playerProxy = (IGamePlayer) Proxy.newProxyInstance(cl, new Class[]{IGamePlayer.class}, handler);
playerProxy.login("zhangsan","123456");
playerProxy.killMonster();
playerProxy.levelUp();
}
}
演示结果:
Spring的核心之一AOP就是通过这种方式实现的,这种方式对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。
主题还用IGamePlayer
接口和GamePlayer
实现类;动态代理的Handler类还是GamePlayerIH
,不过可以在invoke方法种添加详细的业务逻辑代码,下面对动态代理类进行优化:
前置处理:
public interface IAdvice {
void exec();
}
public class BeforeAdvice implements IAdvice{
@Override
public void exec() {
System.out.println("我是前置通知,我被执行了!!!!");
}
}
动态代理类:
public class DynamicProxy {
public static <T> T newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler handler) {
// 寻找JoinPoint连接点,AOP框架使用元数据定义
if (true) {
// 执行前置通知
new BeforeAdvice().exec();
}
return (T) Proxy.newProxyInstance(loader, interfaces, handler);
}
// 可以添加具体业务的代理,如IGamePlayer
public static <T> T newProxyInstance(IGamePlayer gamePlayer) {
// 获得ClassLoader
ClassLoader loader = gamePlayer.getClass().getClassLoader();
// 获得接口数组
Class<?>[] interfaces = gamePlayer.getClass().getInterfaces();
// 获取handler
GamePlayerIH handler = new GamePlayerIH(gamePlayer);
return newProxyInstance(loader, interfaces, handler);
}
}
客户端:
public class Client {
public static void main(String[] args) {
IGamePlayer player = new GamePlayer("zhangsan");
// 动态产生一个代理者
IGamePlayer playerProxy = DynamicProxy.newProxyInstance(player);
playerProxy.login("zhangsan","123456");
playerProxy.killMonster();
playerProxy.levelUp();
}
}
演示结果:
3 CGlib动态代理
3.1 简单介绍
CGLIB是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理。
3.2 CGLIB的核心类
net.sf.cglib.proxy.Enhancer
– 主要的增强类
net.sf.cglib.proxy.MethodInterceptor
– 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy
– JDK的java.lang.reflect.Method
类的代理类,可以方便的实现对源对象方法的调用,如使用:Object o = methodProxy.invokeSuper(proxy, args);
//虽然第一个参数是被代理对象,也不会出现死循环的问题。
3.3 Enhancer
Enhancer
是增强器,它对你想代理的类进行扩展
它的两个主要方法:
setSuperclass
设置需要代理/增强的类(相当于父类)
setCallback
设置回调的拦截器,正常是MethodInterceptor
的实现类
3.4 MethodInterceptor
MethodInterceptor
方法拦截器,在调用目标方法的时候会调用它的实现类进行拦截。它最重要方法为intercept,进行拦截并执行。
intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
第一个参数是增强类;
第二个参数是拦截的方法;
第三个参数是拦截的方法的参数;
第四个参数是Method
的代理类。
3.5 两种方式
cglib有两种可选方式,继承和引用。第一种是基于继承实现的动态代理,所以可以直接通过super调用target方法,但是这种方式在spring中是不支持的,因为这样的话,这个target对象就不能被spring所管理,所以cglib还是采用类似jdk的方式,通过持有target对象来达到拦截方法的效果。两种方式其一是通过使用java.lang.reflect.Method
对象的一般反射调用;另一种就是使用net.sf.cglib.proxy.MethodProxy
对象调用。通常使用第二种,因为它更快。
3.6 代码演示
创建一个普通类:
public class Animal {
public void run() {
System.out.println("动物在奔跑!!!!");
}
}
创建cglib代理类,实现MethodInterceptor
接口
public class CglibProxy implements MethodInterceptor {
// 增强器
private Enhancer enhancer = new Enhancer();
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-------------before-----------------");
System.out.println("method.getName():" + method.getName());
System.out.println("methodProxy.getSuperName():" + methodProxy.getSuperName());
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("-------------after-----------------");
return obj;
}
public Object getProxy(Object obj) {
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
main
public class Client {
public static void main(String[] args) {
Animal animalProxy = (Animal) new CglibProxy().getProxy(new Animal());
animalProxy.run();
}
}
运行发现报错:
报错内容是缺少类,发现我引入的包是cglib-3.1.jar
,后面又导入了asm-all-4.1.jar
,就能够正常运行了。
cglib资源包(包含cglib-3.1.jar和asm-all-4.1.jar)
3.7 拓展
被cglib代理的类只要是普通的类就行,但是要确保类和方法上没有被**final
关键字修饰。用final
修饰的类会直接抛出异常,但是修饰方法就不会抛出异常,但是此方法不会被代理,但是不影响代理其他没有被final
**修改的方法。
3.7.1 死循环演示
对CglibProxy
进行修改:
public class CglibProxy implements MethodInterceptor {
// 创建增强器
private Enhancer enhancer = new Enhancer();
// 被代理对象,如果使用invoke会使用到
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("-------------before-----------------");
System.out.println("method.getName():" + method.getName());
System.out.println("methodProxy.getSuperName():" + methodProxy.getSuperName());
Object obj = methodProxy.invoke(o, objects);
//Object obj = methodProxy.invoke(this.target, objects);
//Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("-------------after-----------------");
return obj;
}
public Object getProxy(Object obj) {
this.target = obj;
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
演示结果
可以看到一直在打印,导致**StackOverflowError
**。注释掉的两个使用方式都是能够正常使用的。
3.7.2 原理分析
首先,我们需要获取到代理类的字节文件,然后对其反编译。在main方法种添加下面这句话:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\classes");
再运行一遍你会发现有生成的代理class文件有三个,我们主要看**Animal$$EnhancerByCGLIB$$e01ba0a1
**代理类!
反编译后(代码很长,我截取需要演示的部分,其他省略号):
public class Animal$$EnhancerByCGLIB$$e01ba0a1 extends Animal implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$run$0$Method;
private static final MethodProxy CGLIB$run$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("cn.test.Animal$$EnhancerByCGLIB$$e01ba0a1");
Class var1;
CGLIB$run$0$Method = ReflectUtils.findMethods(new String[]{"run", "()V"}, (var1 = Class.forName("cn.test.Animal")).getDeclaredMethods())[0];
CGLIB$run$0$Proxy = MethodProxy.create(var1, var0, "()V", "run", "CGLIB$run$0");
Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = var10000[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = var10000[1];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[2];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[4];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
}
final void CGLIB$run$0() {
super.run();
}
public final void run() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$run$0$Method, CGLIB$emptyArgs, CGLIB$run$0$Proxy);
} else {
super.run();
}
}
// ......
}
从反编译后的源码来看,可以看到被代理类的方法会生成两个代理方法,一个是跟我Animal种run方法同名的run方法,另一个是CGLIB$run$0()
。
1.CGLIB$run$0()
是调用被代理的方法,可以看到别的啥都没干。
2.同名的**run()
** 首先判断有没有设置callback,我们在代码中设置为CglibProxy,所以就会调用CglibProxy的**intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
**方法
下面再看一下**MethodProxy
**的源码,invoke和invokeSuper的具体执行流程。
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
} catch (IllegalArgumentException var5) {
if (this.fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " + this.sig1);
} else {
throw var5;
}
}
}
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
this.init();
MethodProxy.FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException var4) {
throw var4.getTargetException();
}
}
invoke和invokeSuper第一步都会执行**init()
**这个方法
private void init() {
if (this.fastClassInfo == null) {
synchronized(this.initLock) {
if (this.fastClassInfo == null) {
MethodProxy.CreateInfo ci = this.createInfo;
MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(this.sig1);
fci.i2 = fci.f2.getIndex(this.sig2);
this.fastClassInfo = fci;
this.createInfo = null;
}
}
}
}
这个init方法就是加载**MethodProxy.FastClassInfo
**。
c1
指的是我们的代理类,即**Animal
**
c2
代理类,即**Animal$$EnhancerByCGLIB$$e01ba0a1.class
**
f1
代理类,即**Animal$$FastClassByCGLIB$$b2e76df5.class
**
f2
代理类,即**Animal$$EnhancerByCGLIB$$e01ba0a1$$FastClassByCGLIB$$9a34f4cc.class
**
上述三个class文件即**E:\\classes
**这个路径下(当然还有你的包路径)生成出来的三个class文件
i1
即**run()
**
i2
即**CGLIB$run$0()
**
为什么会生成f1和f2这两个代理类,这里就涉及到invoke和invokeSuper两个方法的区别,invoke就是f1.invoke(),invokeSuper就是f2.invoke()。
这也就解释了为什么会发生死循环了,我们用的是invoke()方法,但是传入的是一个代理对象,从上面的代理类的代码可以看到还是会回到invoke()这个方法,所以就造成了死循环。
3.7.3 总结
cglib代理比jdk代理快很多,但是被代理的类和方法不能被final修改,而且有invoke和invokeSuper这两个方法,使用的时候需要谨慎。
× Object obj = methodProxy.invoke(o, objects);
√ Object obj = methodProxy.invoke(this.target, objects);
√ Object obj = methodProxy.invokeSuper(o, objects);