内容简介:设计模式之禅在讲解代理模式时用了一个游戏代练的比喻。这个比喻非常的有代表性,对于理解代理模式很有帮助。它大致的思想是:大家都有过玩游戏的经历,也知道游戏代练。那么事实上游戏的代练在帮我的游戏账号打怪升级的时候,和代理模式里面的代理类做的事情不正是一样的事情吗?代理模式的定义如下:Provide a surrogate or placeholder for another object to control acess to it.(为其他对象提供一种代理以控制对这个对象的访问)。其通用类图如下:上面是一个静
设计模式之禅在讲解代理模式时用了一个游戏代练的比喻。这个比喻非常的有代表性,对于理解代理模式很有帮助。它大致的思想是:大家都有过玩游戏的经历,也知道游戏代练。那么事实上游戏的代练在帮我的游戏账号打怪升级的时候,和代理模式里面的代理类做的事情不正是一样的事情吗?
代理模式的定义如下: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动态代理是如何实现的
核心:代理模式加反射。
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});
生成的代理类:
-
会去实现代理的接口,由于 java 不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
-
提供了一个使用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中有多处方法调用,是通过使用了super关键字去拿父类中定义的InvocationHandler实例然后完成调用的。而Super关键字正是在调用父类Prioxy类的方法。
在代理类 Proxy中通过构造方法传入进来,而我们应该还记得,在Proxy的newProxyInstance方法中的第三个参数,正是InvocationHandler的一个实例。
因此这里调用方法时,自然就是使用这个实例去调用invoke方法,同时传入对应的方法参数。这下,所有的东西都串联起来了,问题三的答案事实上也已经有了。
问题3:为什么我们去调用代理类的目标方法,它会去调用invoke方法?
因为JDK生成真正的代理类中,是继承了Proxy类并实现了我们定义的被代理接口,而这个代理类在实现我们定义的接口方法时,是通过反射调用了InvocationHandlerImpl的invoke方法,然后在这个invoke方法当中,我们实现了增强的逻辑以及对被代理方法的真正调用。
问题4:JDK动态代理在哪些地方用到了反射?
经过上面的分析,至少有以下地方用到了反射:
-
- 在调用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方法当中就可以进行逻辑增强,事实上,从技术上讲这里也可以通过反射调用被代理的原方法。
note:不论在哪,身在何职,身居何位,都不要忘记心中那一团似火不灭的璀璨和光芒。
以上所述就是小编给大家介绍的《设计模式(九)代理模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。