为什么要使用代理模式:
比较官方的回答:在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与委托类有同样的接口。代理模式是常用的java设计模式。
看完这个解释依然云里雾里,我觉得代理除了上述功能,最易理解的是,在原有代码的基础上增加功能时,不会修改原有代码,非常符合程序设计的开闭原则。
代理模式一般分为静态代理和动态代理,顾名思义,静态代理是比较死板,不通用的模式,实现的代码是自己实现。
动态代理实现方式有很多种,主要有java提供的InvocationHandler和spring提供的cglib,前者的真实对象必须继承接口,后者不用继承任何接口和类,也可以实现动态代理。spring AOP就是采用cglib技术实现的动态代理。
静态代理:
举例:电影院放映电影,按照以前的方式,电影院从电影开始到电影结束是一套完整的流程。电影院可以播放各种不同的电影,属于一个规范,因此可以把电影院定义为抽象类或者接口:
public interface Movie { // 播放电影 public void play(); }
电影远播开始播放一部具体的电影:
public class RealMovie implements Movie{ @Override public void play() { System.out.println("开始播放:《阿甘正转》"); } }
主程序调用:
public class Test { public static void main(String[] args) { RealMovie realMovie = new RealMovie(); realMovie.playMovie(); }
后来电影院想增加收入,在播放电影的前后两个环节插入零食贩卖的功能,但是又不希望修改既有代码,于是决定使用静态代理来实现:
public class ProxyMovie implements Movie { private RealMovie realMovie = new RealMovie(); @Override public void playMovie() { System.out.println("买点零食看电影吃吧(电影开播前广告)"); realMovie.playMovie(); System.out.println("买点零食回家吃吧(电影结束后广告)"); } }
静态代理的特点:代理类和被代理类都需要继承父接口,实现play方法,这样主程序就无法直接访问被代理类,即使被代理类的play方法中增加了N多复杂的代码,也不影响主程序去调用。
动态代理:java提供的Proxy类
电影1:
public class RealMovie implements Movie{ @Override public void play() { System.out.println("开始播放:《阿甘正转》"); } }
电影2:
public class RealMovie2 implements Movie{ @Override public void play() { System.out.println("开始播放:《复仇者联盟》"); } }
代理类:
public class ProxyMovie implements InvocationHandler { private Object object; public ProxyMovie(Object object){ this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("买点零食看电影吃吧(电影开播前广告)"); // 真实对象的方法都交由InvocationHandler.invoke方法执行 method.invoke(object, args); System.out.println("买点零食回家吃吧(电影结束后广告)"); return null; } }
可以看出代理类不需要继承被代理类的父类,继承的是java提供的InvocationHandler接口,该接口只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
所有被代理的类的方法都会通过该方法调用。且所有扩展功能都在此方法中去实现。
主程序调用代理类:
public class Test { public static void main(String[] args) { // 动态代理(代理类交由Proxy动态去实现接口(加载时实现)) RealMovie realMovie = new RealMovie(); InvocationHandler proxyMovie = new ProxyMovie(realMovie); Movie movie = (Movie) Proxy.newProxyInstance( realMovie.getClass().getClassLoader(), realMovie.getClass().getInterfaces(), proxyMovie); movie.play(); RealMovie2 realMovie2 = new RealMovie2(); InvocationHandler proxyMovie2 = new ProxyMovie(realMovie2); Movie movie2 = (Movie)Proxy.newProxyInstance( realMovie.getClass().getClassLoader(), realMovie.getClass().getInterfaces(), proxyMovie2); movie2.play(); } }
test主程序执行结果:
有点类似于面向切面编程的感觉。Proxy是java提供的生成动态代理实例的类,此类在程序运行时,会继承被代理类的父类,从方法newProxyInstance传入的参数可以看出,无论是传realMovie.getClass().getInterfaces(),还是realMovie2.getClass().getInterfaces(),
都可以实现代理的功能。应为这两个实例的父类是同一个。另外realMovie.getClass().getClassLoader()获取的都是AppClassLoader ,所以只要是同一个类加载器,传什么实例并不是重点。
java提供的动态代理有一个缺点,就是若类既没有继承父类,也没有实现接口,是无法被代理的,cglib提供的动态代理却可以实现代理
cglib动态代理:
public class RealMovie { public void playMovie(){ System.out.println("开始播放:《阿甘正传》"); } }
此类没有继承或者实现任何类。
代理类:
public class MyProxy implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("买点零食看电影吃吧(电影开播前广告)"); methodProxy.invokeSuper(o, objects); System.out.println("买点零食回家吃吧(电影结束后广告)"); return null; } }
test主程序调用”
public class Test { public static void main(String[] args) { // cglib动态代理 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(RealMovie.class); enhancer.setCallback(new MyProxy()); RealMovie realMovie = (RealMovie)enhancer.create(); realMovie.playMovie(); } }
执行结果:
代理成功。
在spring framework 中,AOP就是使用cglib实现的动态代理。