Java实现静态代理、动态代理
一、代理模式
代理模式是设计模式中的一种结构型模式,在设计模式中算比较好理解的一种模式。具体来说就是使用代理对象来代替对真实对象的访问,当我们需要新增额外功能时,不需要修改目标对象就能达到功能扩展的效果。代理模式的关键点--代理对象与目标对象,代理对象是目标对象的扩展,并会调用目标对象。
例子:记得几年前微商很火,小明的高中同学也很多在做微商(听说已经提玛莎拉蒂了!!!),每天朋友圈都被大量的广告刷屏。这些微商,大部分都是从厂家拿货,然后自己通过社交平台宣传,走向财富自由。现实生活中我们购买产品时,很少直接自己跑去找厂家购买,一般的销售模式都是厂家委托给代理商进行销售,而顾客直接向代理商购买即可,不需要与厂家直接联系。这里的代理商就类似我们的代理对象,而厂家就类似目标对象。
优点:1、职责清晰。 2、高扩展性。3、智能化。
缺点:1、在客户端和目标对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
Java实现代理模式主要有静态代理和动态代理两种方式。
二、静态代理
1、基本概念
静态代理从它的名字我们大概就能猜出来它是什么了,即在编译时就将已经将接口,被代理类,代理类确定下来了,在程序运行之前,代理类的.class文件就已经生成了。
静态代理的实现步骤:
a. 定义一个接口及其实现类,即被代理类;
b. 创建一个代理类同样实现这个接口;
c. 将目标对象注入这个代理类,然后在代理对象的对应方法调用目标类中的对应方法,在这期间我们可以在目标方法执行前后做一些自己想做的事情。
2、情景引入
小明的学校附近新开了一家蛋糕店,作为一名资深舔狗,小明已经迫不及待地想去店里买个大蛋糕送给他的女神了。一进店后,看到各种精美的蛋糕,小明就已经忍不住yy女神拿到蛋糕后感谢他的样子了。最后小明忍痛花了299买了个草莓奶油蛋糕准备送给女神。晚上,在女神宿舍楼下等送蛋糕的时候,小明闲得慌,便在脑袋里复习起最近正在学的设计模式,突然想到--“唉,这个草莓奶油蛋糕的制作过程,如何结合设计模式中的代理模式用Java来实现呢?”。
3、代码实现
1)首先应该有一个做蛋糕的机器,因此我们先定义一个蛋糕机器的接口CakeMachine,同时在接口内定义一个制作蛋糕的方法。
public interface CakeMachine {
//制作蛋糕 void makeCake(); }
2)接着我们定义一个CakeMachine接口的实现类CakeMachineImpl,也就是我们的被代理类,通过这个实现类我们能做出奶油味的蛋糕胚。
//制做奶油味蛋糕胚
public class CakeMachineImpl implements CakeMachine { @Override public void makeCake() { System.out.println("奶油蛋糕制作完成!!!"); } }
此时我们通过CakeMachineImpl制作的只是普通的奶油蛋糕胚,那如果我们要做草莓奶油蛋糕的话怎么办?难道专门再做一个制作草莓奶油蛋糕的机器吗?当然不是,如果这样的话,对于草莓巧克力蛋糕我们又得制作一个机器,就变得格外麻烦。此时我们通过代理模式就能很好的解决问题,通过创建代理类,帮我们实现在蛋糕制作完成之后“铺草莓”这个动作,这个代理类能对所有的CakeMachine的实现类都新增这一功能,这样我们就省去了制作多种机器的繁琐过程。
3)创建代理类ProxyCakeMachine,并同样实现CakeMachine接口,这时候我们可以在蛋糕制作完成之后,在表面铺上美味的草莓,这样我们的草莓蛋糕就制作完成啦。
public class ProxyMachine implements CakeMachine{ private CakeMachine cakeMachine; public ProxyMachine(CakeMachine cakeMachine) { this.cakeMachine = cakeMachine; } @Override public void makeCake() { cakeMachine.makeCake(); System.out.println("铺上美味的草莓~~~"); System.out.println("草莓味蛋糕完成啦!!!"); } }
4)最后,当我们去蛋糕店购买时,老板使用的是代理对象进行操作。
public class CakeShop { public static void main(String[] args) { CakeMachine cakeMachine = new CakeMachineImpl(); ProxyMachine proxyMachine = new ProxyMachine(cakeMachine); proxyMachine.makeCake(); } }
运行结果:
蛋糕制作完成!!! 铺上美味的草莓~~~ 草莓味蛋糕完成啦!!!
三、动态代理
相较于静态代理,动态代理更加的灵活,我们不需要针对每个目标类都单独创建一个代理类。上面的例子中,ProxyCakeMachine类是我们自己定义好的,在程序运行前就已经编译完成,然而动态代理,代理类并不是在Java代码中定义好的,而是在运行期间根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一处理,而不用修改每个代理类中方法。
1、JDK动态代理机制
在Java动态代理机制中,InvocationHandler接口和Proxy是核心,上述例子用Java动态代理的实现过程如下:
a. 新建一个类,这个类必须实现InvocationHandler接口,并重写它的invoke方法,在invoke方法中实现我们的铺草莓动作。
public class StrawberryHandler implements InvocationHandler { private Object object; public StrawberryHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o = method.invoke(object, args); System.out.println("铺上美味的草莓~~~"); System.out.println("草莓蛋糕完成啦!!!"); return o; } }
b. 在蛋糕店类中,我们使用Proxy类的newProxyInstance静态方法生成我们的代理对象,通过这个代理对象来制作我们的草莓奶油蛋糕。
public class CakeShop { public static void main(String[] args) { CakeMachine cakeMachine = new CakeMachineImpl(); StrawberryHandler strawberryHandler = new StrawberryHandler(cakeMachine); CakeMachine cakeMachine1 = (CakeMachine)Proxy.newProxyInstance(CakeMachineImpl.class.getClassLoader(), CakeMachineImpl.class.getInterfaces(), strawberryHandler); cakeMachine1.makeCake(); } }
执行结果:
蛋糕制作完成!!! 铺上美味的草莓~~~ 草莓蛋糕完成啦!!!
newProxyInstance()方法的三个参数:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ...... }
1)loader:类加载器,用于加载代理对象;
2)interface:被代理类实现的一些接口;
3)h:实现了InvocationHandler接口的对象。
注意:使用动态代理,我们的“铺草莓”动作,即InvocationHandler,不仅能用在蛋糕机器接口CakeMachine的实现类,也能用于其他的接口实现类,如面包机器接口、饼干机器接口实现类。而在静态代理中,我们的静态代理类ProxyMachine只能对蛋糕机器接口的实现类起作用,若要实现在面包上铺草莓的动作,则需要另外创建面包机器的代理类。
2、CGLIB实现动态代理
JDK动态代理有一个最致命的问题,就是只能代理实现了接口的类,对于没有实现接口的类,JDK动态代理则无能为力。而CGLIB则可以为我们解决这个问题。(Spring的AOP模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理)
在CGLIB动态代理中,MethodInterceptor接口和Enhancer类是核心。具体步骤如下:
1)定义一个类;
2)自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke
方法类似;
3)通过 Enhancer
类的 create()
创建代理类;
CGLIB实现制作草莓奶油蛋糕:
a. 在pom.xml中加入CGLIB的依赖坐标。
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency>
b. 自定义MethodInterceptor,并重写Interceptor方法。
public class StrawberryInterceptor implements MethodInterceptor { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { Object o1 = methodProxy.invokeSuper(o, objects); System.out.println("铺上美味的草莓~~~"); System.out.println("草莓蛋糕完成啦!!!"); return o1; } }
MethodInterceptor接口参数解析:
public interface MethodInterceptor extends Callback{ // 拦截被代理类中的方法 public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable; }
1)obj :被代理的对象(需要增强的对象)
2)method :被拦截的方法(需要增强的方法)
3)args :方法入参
4)methodProxy :用于调用原始方法
c. 蛋糕商店类使用enhancer获取代理类
public class CakeShop { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(CakeMachineImpl.class); enhancer.setCallback(new StrawberryInterceptor()); CakeMachineImpl cakeMachine = (CakeMachineImpl) enhancer.create(); cakeMachine.makeCake(); } }
执行结果:
蛋糕制作完成!!! 铺上美味的草莓~~~ 草莓蛋糕完成啦!!!
----------------
nice,小明很开心前几天学的设计模式还没有忘记。可女神就是迟迟没下楼,明明是炎炎夏日,小明心里却凉飕飕的。