Table of Contents
问题2:为什么JDK动态代理生成的代理类要去继承Proxy类?
问题3:为什么我们去调用代理类的目标方法,它会去调用invoke方法?
代理模式简介
设计模式之禅在讲解代理模式时用了一个游戏代练的比喻。这个比喻非常的有代表性,对于理解代理模式很有帮助。它大致的思想是:大家都有过玩游戏的经历,也知道游戏代练。那么事实上游戏的代练在帮我的游戏账号打怪升级的时候,和代理模式里面的代理类做的事情不正是一样的事情吗?
代理模式的定义如下:Provide a surrogate or placeholder for another object to control acess to it.(为其他对象提供一种代理以控制对这个对象的访问)。其通用类图如下:
- Subject是一个抽象类也可以是一个接口,是一个最普通的业务类型定义无特殊要求
- RealSubject是Subject的子类或实现类,它才是被代理角色。是业务逻辑的具体执行者。
- Proxy叫做委托类,代理类。它负责对真实对象的应用。它在真实对象处理完毕前后做预处理和善后处理工作。
上面是一个静态代理的场景。代理一般实现的模式为JDK静态代理:创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。
代理模式的优点
- 职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
- 高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
- 智能化
这在我们以上讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化,读者有兴趣也可以看看Struts是如何把表单元素映射到对象上的。
动态代理
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理那一个对象,相对的来说,自己写代理类的方式就是静态代理。
现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。
Spring AOP实现动态代理采用了两种方式,即JDK动态代理和CGLIB动态代理。
JDK动态代理是如何实现的
核心:代理模式加反射。继承proxy实现被代理类接口
JDK动态代理与静态代理有相同之处,都要创建代理类,代理类都要实现接口。但是不同之处在于:
JDK静态代理是通过直接编码创建的,而JDK动态代理是利用反射机制在运行时根据指定参数创建代理类的。即JDK动态代理是更加通用的的一种方式,因为我们需要被代理的类往往是不止一个的。
若我们要自己实现一个JDK代理的话,则可以按如下核心代码实现:
public class JDKProxy implements InvocationHandler {
//产生代理对象,被代理的对象必须实现一个接口
public Object newProxy(Object targetObject) {//将目标对象传入进行代理
Object object = Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);//返回代理对象
return object;
}
//实现InvocationHandler的 invoke方法
public Object invoke(Object proxy, Method method, Object[] args)//invoke方法
throws Throwable {
System.out.println("before"); //一般我们进行逻辑处理的函数比如这个地方是模拟检查权限
Object ret = method.invoke(proxy, args); //调用invoke方法,ret存储该方法的返回值,通过反射获取代理方法然后进行调用,而在cglib中则是直接调用的,因为继承的缘故
System.out.println("after"); //后置增强
return ret;
}
}
第一个方法根据被代理类生成代理类,第二个方法实现增强逻辑,第二个方法调用被代理方法是通过反射(method.invoke)实现的。
先来看第一个方法newProxy
这个方法的目标是根据被代理对象去创建一个代理类。关键方法为
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
此方法一共三个参数:
- ClassLoader 对象
- 一组interface接口
- 一个InvocationHandler对象
该方法关键实现如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
...
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
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;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
...
}
}
- getProxyClass0方法的两个参数分别是classloader以及被代理类的接口,这个方法会生成一个Class对象出来。即代理类。
- 然后使用反射获得构造器: final Constructor<?> cons = cl.getConstructor(constructorParams);
- 返回实例:return cons.newInstance(new Object[]{h});
生成的代理类:
- 会去继承proxy类,同时会去实现代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。这里是实现的核心,aop既要执行原本方法又要执行增强方法,因此就通过生产proxy类,去继承jdkproxy获取增强,实现接口获取原本方法,再利用多态就实现了aop
- 提供了一个使用InvocationHandler作为参数的构造方法
- 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。
- 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。
- 代理类实现代理接口的目标方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。
经过我们的反编译,该类大致如下:
import com.dr.designPattern.proxy.dynamicProxy.UserManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy extends Proxy implements UserManager {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void addUser(String var1, String var2) throws {
try {
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
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 delUser(String var1) throws {
try {
super.h.invoke(this, m4, 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)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("addUser", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("delUser", new Class[]{Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
第二个方法是我们自己定义的JDKProxy类实现的InvocationHandler接口的invoke方法。可以看到上面生成的代理类就是通过调用invoke这个方法来进行目标方法的调用。
在动态代理中InvocationHandler是核心,每个代理实例都具有一个关联的调用处理程序(InvocationHandler)。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序(InvocationHandler)的 invoke
方法。所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。invoke方法定义如下:
public Object invoke(Object proxy, Method method, Object[] args)
这个时候我们再回头看生成的代理类$Proxy,并以其中实现的方法addUser方法为例来梳理一遍调用流程:
首先代理类$Proxy继承了Proxy类并实现了被代理类UserManager
->$Proxy类实现了被代理类中的addUser和delUser方法。
->addUser方法的实现为:调用父类Proxy类的Invoke方法->因此,调用addUser方法就变成了调用被代理类的addUser方法->进而变成了调用Proxy类的invoke方法-> 而invoke方法是我们自己实现的->进而变成了调用有我们自己实现的逻辑(增强)的invoke方法
->invoke方法不仅要实现增强,还需调用被代理方法,因此这里传入了一个方法叫m3
->m3定义即为被代理方法addUser
->因此,在最终的invoke方法里面既能实现增强,又能调用被代理方法,并且,由于被代理方法是通过反射加载调用的,因此它可以是在运行时确定的,即动态的。
通过反编译出来的代理类,我们甚至可以直接new一个它的实例出来调用它的方法,如下:
$Proxy p = new $Proxy();
p.addUser("ray","test");
事实上这并没有什么神奇的地方,代理类和我们自己定义的普通的类并没有任何本质上的区别。
方法调用的逻辑图如下:
一些问题
问题1:为什么JDK动态代理只能代理实现了接口的类?
因为JDK动态代理生成的代理类需要去继承Proxy类,而java是单继承的,因此它只能去实现被代理类的接口(实现的接口)
问题2:为什么JDK动态代理生成的代理类要去继承Proxy类?
不仔细看的话,生成的代理类会给人一种继承Proxy类没有用的感觉,因为生成的代理类并没有用什么Proxy类的东西,很有迷惑性,我也是将生成的代理类代码亲自拿来试了一下才发现它的用处。
事实上,在代理类$Proxy中有许多处方法的调用,都是通过使用super关键字去拿父类也就是proxy类中定义的InvocationHandler实例然后完成调用的。Super关键字正是在调用父类Prioxy类的方法。这就是用到proxy类的地方。
在代理类$Proxy中调用父类的方法都是使用了InvocationHandler类的实例,在代理类$Proxy中通过构造方法传入进来,而我们应该还记得,在Proxy的newProxyInstance方法中的第三个参数,正是InvocationHandler的一个实例。
因此这里调用方法时,自然就是使用这个实例去调用invoke方法,同时传入对应的方法参数。这下,所有的东西都串联起来了,问题三的答案事实上也已经有了。
这里同时把oracle介绍代理的官方文档地址放在这里,作为参考。
问题3:为什么我们去调用代理类的目标方法,它会去调用invoke方法?
因为JDK生成真正的代理类中,是继承了Proxy类并实现了我们定义的被代理接口,而这个代理类在实现我们定义的接口方法时,是通过反射调用了InvocationHandlerImpl的invoke方法,然后在这个invoke方法当中,我们实现了增强的逻辑以及对被代理方法的真正调用。
问题4:JDK动态代理在哪些地方用到了反射?
经过上面的分析,至少有以下地方用到了反射:
1. 在调用Proxy.newInstance方法时用反射获取接口传入方法
2.在生成代理类$Proxy时,通过反射获取构造方法生成代理类
3.在invoke方法当中调用被代理方法时,通过反射进行调用
因此,反射对于jdk动态代理的实现至关重要,而代理更多的是起到一个设计思想,代码架构上的作用。
CGLib动态代理是如何实现的
核心:代理模式加继承
具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
方法的调用并不是通过反射来完成的,而是直接对方法进行调用,因为是继承,这一点与jdk动态代理是不一样的
另外JDK代理只能对接口进行代理,Cglib则是对实现类进行代理。重点代码如下:
public class CGLibProxy implements MethodInterceptor {
//根据目标对象生成一个子类作为他的代理类
public Object createProxyObject(Object obj) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());//设置父类为被代理类
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
return proxyObj;// 返回代理对象
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("before");
Object obj = methodProxy.invoke(proxy, args);
System.out.println("after");
return obj;
}
}
总的来说思想和JDK动态代理差别不大,都是根据被代理类生成一个代理类,MethodInterceptor类的角色和JDK动态代理中的InvocationHandler是一样的。但是Cglib的实现逻辑更为复杂。
但是在CGLib代理类中,因为是继承的缘故,因此会重写被代理类的方法,重写的逻辑就是调用我们这里实现的intercept方法,同时传入对应的参数。
第一个方法createProxyObject
这个方法会生成一个代理类,这个代理类当中重写了被代理类中的目标方法,同时,也提供了方法直接去调用被代理类的方法。
第二个方法intercept方法
在我们实现的intercept方法当中,当然也可以使用反射直接调用method方法,但是这里采取的实现是调用了MethodProxy类的方法去调用,这个方法没有用到反射机制。它的实现如下:
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;
}
}
}
可以看到,是用了一个叫做FastClass的类来实现的。
FastClass实现机制简介
FastClass其实就是对Class对象进行特殊处理,提出下标概念index,通过索引保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast,下面通过一个例子了解一下FastClass的实现机制。
1、定义原类
class Test {
public void f(){
System.out.println("f method");
}
public void g(){
System.out.println("g method");
}
}
2、定义Fast类
class FastTest {
public int getIndex(String signature){
switch(signature.hashCode()){
case 3078479:
return 1;
case 3108270:
return 2;
}
return -1;
}
public Object invoke(int index, Object o, Object[] ol){
Test t = (Test) o;
switch(index){
case 1:
t.f();
return null;
case 2:
t.g();
return null;
}
return null;
}
}
在FastTest中有两个方法,getIndex
中对Test类的每个方法根据hash建立索引,invoke
根据指定的索引,直接调用目标方法,避免了反射调用。所以当调用methodProxy.invoke
方法时,实际上是调用代理类的方法,代理类则是直接调用了被代理类的原方法(因为是继承的缘故,可以直接调用)。
在CGLibProxy类中重写的在intercept方法当中就可以进行逻辑增强,事实上,从技术上讲这里也可以通过反射调用被代理的原方法。