在Spring AOP的实现中用到了JDK的代理模式,有必要将代理模式整理一下。
将按照“为什么使用代理模式”、“怎么样使用代理模式”来安排整个文章。
1、为什么要使用代理模式
一个完成的系统有时候可以划分为系统需求和业务需求两种。业务需求即与业务相关的逻辑需求,而系统需求即如安全检查、前期校验等与系统相关的需求,而且系统需求一般穿插于业务需求中,需要在业务开始之前或者是开始之后插入系统需求。这个时候就需要有代理模式。
代理模式是为了实现业务需求与系统需求之间的解耦,使得业务专注于自己的业务逻辑设计、系统需求专注于自己的系统设计,两者互不干涉,当需要结合使用时采用该模式即可实现两者的结合。
举例说明。
有一个业务逻辑,实现的功能是“表演”功能,接口如下:
package com.springframework.aop; public interface IPerformer{ public void perform(); }
其实现类代码如下:
package com.springframework.aop; public class PerformerImpl implements IPerformer{ @Override public void perform(){ System.out.println("perform..."); } }
现在要在“表演”之前观众要“坐下”,“关掉手机”,在“表演”之后要“起立鼓掌”,这些都是系统需求,不管演出者是谁,要演出的节目是什么,观众都要这样做。
观众的实现代码如下:
package com.springframework.aop; public class Audience{ //表演之前 public static void takeSeat(){ System.out.println("before perform, take seat..."); } //表演之前 public static void turnOffCellPhone(){ System.out.println("before perform, turn off CellPhone..."); } //表演之后 public static void applaud(){ System.out.println("after perform, applaud..."); } }
现在需要在表演的时候添加观众的行为,也就是要把两者结合。如果按照一般的写法,我们会这样写:
package com.springframework.aop; public class PerformerImpl implements IPerformer{ @Override public void perform(){ //观众落座 Audience.takeSeat(); //关掉手机 Audience.turnOffCellPhone(); //表演者实际表演内容 System.out.println("perform..."); //观众鼓掌 Audience.applaud(); } }
这样写不是不可以,但是可以发现在为原来的performer实现类中充斥着太多的观众的行为,侵入性很强,耦合很严重,这个时候就需要代理模式来解决。
2、代理模式解决问题
代理模式的实现就是重新写一个代理类,实现和被代理类一样的接口,但是具体的实现方法会发生改变,具体怎么样改变通过代码一目了然。
package com.springframework.aop; public class PerformerImplProxy implements IPerformer{ private IPerformer performer; public PerformerImplProxy(IPerformer performer){ this.performer = performer; } @Override public void perform(){ //观众落座 Audience.takeSeat(); //关掉手机 Audience.turnOffCellPhone(); //表演者实际表演内容 performer.perform(); //观众鼓掌 Audience.applaud(); } }
当我在外部调用时调用的也是实际的代理类,如下所示:
package com.springframework.test; public class Test{ public static void main(String[] args){ IPerformer performer = new PerformerImplProxy(new PerformerImpl()); performer.perform(); } }
如果业务需求(表演者)或者系统需求(观众)发生变化,需要添加或者修改功能,则只需要修改相关的实际实现类即可,调用者和代理类都不需要修改,而且业务需求和系统需求也不会有耦合。
以上是静态代理的实现,静态代理解决了业务需求和系统需求之间的耦合,但是当我表演接口中添加一个需求,假如说是化妆(makeUp),需要在实现类中实现makeUp,并且在代理类中也要实现该具体方法,这样的话就会比较麻烦。因此有了动态代理。
3、动态代理解决问题
静态代理的特点是在前期已经将代理类规定好,具体代理的是哪个类接口,具体代理的是接口的哪个方法,这些都事先已经写好了。
而动态代理是在运行时来决定代理的是哪个类,代理的是哪个方法,事先并不知道。
动态代理需要实现InvocationHandler接口,代码如下:
package com.springframework.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class PerformanceHandler implements InvocationHandler{ //需要被代理的目标类 private Object target; public PerformanceHandler(Object target){ this.target = target; } @Override public Object invoke(Object obj, Method method, Object[] args) throws InvocationTargetException,IllegalAccessException{ Audience.takeSeat(); Audience.turnOffCellPhone(); method.invoke(target, args); Audience.applaud(); return null; } }
可以发现在该接口的实现中并没有指定具体的代理类是什么,它针对的是一个Object类型的目标类。所以具体的目标类需要在调用方指定。
然后在调用端使用Proxy动态创建代理类,然后调用相应功能即可,代码如下:
package com.springframework.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.springframework.aop.*; public class Test{ public static void main(String[] args){ //使用动态代理来实现 //具体的需要代理的目标类 IPerformer target = new PerformerImpl(); InvocationHandler handler = new PerformanceHandler(target); //生成代理实例 IPerformer performer = (IPerformer)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); performer.perform(); } }
这样当我的表演接口中添加化妆(makeUp)方法时,只需要(1)修改IPerformer接口,(2)修改其实现类PerformerImpl类,而代理类不需要做任何修改。
以上介绍的JDK提供的动态代理已经解决了静态代理的问题,但是JDK提供的动态代理方式也有一定的缺点,那就是只能为接口实现代理,无法为没有接口的直接实现类实现代理,这种类型的需要使用cglib代理方式来实现。
4、CGLib动态代理来解决问题
CGLib采用底层字节码技术,为需要代理的目标类创建子类,然后再子类创建的过程中,将需要加入的其他系统需求织入到子类中,就完成了对目标类的代理。
CGLib需要实现MethodInterceptor接口,代码如下:
package com.springframework.aop; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class PerformanceCGLibHandler 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 method, Object[] args, MethodProxy proxy)throws Throwable{ Audience.takeSeat(); Audience.turnOffCellPhone(); proxy.invokeSuper(obj, args); Audience.applaud(); return null; } }
然后调用方调用如下:
package com.springframework.test; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test{ public static void main(String[] args){ PerformanceCGLibHandler cglibProxy = new PerformanceCGLibHandler(); PerformerImpl perfromerimpl = (PerformerImpl)cglibProxy.getProxy(PerformerImpl.class); perfromerimpl.perform(); perfromerimpl.makeUp(); } }
可以发现我没有用接口类,而是用的它的具体实现类,解决了JDK只能代理接口的缺点,但是有一个问题就是,因此CGLib的实现原理是生成目标类的子类,所以如果目标类中有final方法的话是不能被继承的,也就不能用CGLib代理方式来实现。这也是CGLib的一个缺点。
Spring框架的AOP实现原理就是JDK动态代理和CGLib动态代理,默认会使用JDK动态代理,但是如果没有实现接口类的话会使用CGLib动态代理。如果一开始默认想要使用的就是CGLib动态代理,则需进行一下配置,具体在Spring Aop使用中会总结