标签:
代理模式
代理模式的定义很简单:给某一对象提供一个代理对象,并由代理对象控制对原对象的引用。
代理模式的结构
有些情况下,一个客户不想活着不能够直接引用一个对象,可以通过代理对象在客户端和目标对象之间起到中介作用。代理模式中的角色有:
1、抽象对象角色
声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象
2、目标对象角色
定义了代理对象所代表的目标对象
3、代理对象角色
代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象
静态代理示例
假如一个接口里面有一个方法,想在调用这个接口的前后都加一点东西,就可以使用代理模式。静态代理是代理模式最简单的实现,先定义一个静态代理接口,里面有一个print()方法,看一下:
public interface StaticHelloWorld { // 定义一个接口,里面有一个打印方法 void print(); }
让一个子类实现它,打印一句"Hello World"出来:
public class StaticHelloWorldImpl implements StaticHelloWorld { public void print() { System.out.println("Hello World"); } }
给这个接口创建一个代理对象,来实现对接口实现类的代理。注意,这里的重点是代理对象和实际对象实现的是同一个接口,因为希望在任何时候让代理对象替代实际对象:
public class StaticProxy implements StaticHelloWorld { private StaticHelloWorld staticHelloWorld; public StaticProxy(StaticHelloWorld staticHelloWorldImpl) { this.staticHelloWorld = staticHelloWorldImpl; } public void print() { System.out.println("Before Hello World!"); staticHelloWorld.print(); System.out.println("After Hello World!"); } }
写一个类去调用代理对象,在代理对象的构造函数中传入一个实际对象即可:
public class StaticTestMain { public static void main(String[] args) { StaticHelloWorld shw = new StaticHelloWorldImpl(); StaticProxy sp = new StaticProxy(shw); sp.print(); } }
运行结果为:
Before Hello World!
Hello World
After Hello World!
这个很明显,就不说了。
静态代理的缺点
静态代理的特点是静态代理的代理类是程序员创建的,在程序运行之前静态代理的.class文件已经存在了。
从静态代理来看,看到静态代理模式确实可以有一个代理对象来控制实际对象的引用,并通过代理对象来使用实际对象。这种模式在代理量较小的时候还可以,但是代理量一大起来,就存在着三个比较大的缺点:
1、如果想换一种代理内容,比如我在"Hello World"前后不想输入"Before XXX"和"After XXX"了,想输出运行前后系统当前时间,就必须新写一个代理对象。这样很容易造成代理对象的膨胀。
2、代理内容无法复用,也就是说"Before XXX"和"After XXX"只可以给某一个类使用,另一个类如果也想使用这个代理内容,必须自己也写一个,同样,造成的后果就是代理类的无限膨胀
3、接口里面如果新增了一个方法,实际对象实现了这个方法,代理对象也必须新增内容,去给这个新增方法增加代理内容(假如需要的话)
利用JDK中的代理类Proxy实现动态代理的示例
由于静态代理的局限性,所以产生了动态代理的概念。看一下,首先还是定义一个动态代理接口:
public interface DynamicHelloWorld { // 动态代理类,有一个print()方法 String print(); }
写一个类去实现它,打印方法打印"Enter DynamicHelloWorldImpl.print()"并返回"DynamicHelloWorldImpl":
public class DynamicHelloWorldImpl implements DynamicHelloWorld { public String print() { System.out.println("Enter DynamicHelloWorldImpl.print()"); return "DynamicHelloWorldImpl"; } }
最关键的一部分,动态代理类,也是不太好理解的一部分。在Java中,动态代理需要实现InvocationHandler接口。
InvocationHandler接口里面只有一个方法invoke(),至于如何实现,完全看使用者自己的喜好,没有固定。可以像下面这样,把newInstance即生成一个动态代理类的过程放到InvocationHandler的实现类中:
public class DynamicProxy implements InvocationHandler { private Object target; public Object newInstance(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before DynamicProxy"); method.invoke(target, args); System.out.println("After DynamicProxy"); return null; } }
也可以像下面这样,让动态代理类在外面生成,只在构造函数中传入一个target:
public class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before DynamicProxy"); method.invoke(target, args); System.out.println("After DynamicProxy"); return null; } }
如果是前者的写法,那么main函数要这么写:
public class DynamicTestMain { public static void main(String[] args) throws Exception { DynamicProxy dp = new DynamicProxy(); DynamicHelloWorld dhwi = new DynamicHelloWorldImpl(); DynamicHelloWorld dhw = (DynamicHelloWorld)dp.newInstance(dhwi); dhw.print(); } }
如果是后者的写法,那么main函数要这么写:
public class DynamicTestMain { public static void main(String[] args) throws Exception { DynamicHelloWorld dhwi = new DynamicHelloWorldImpl(); InvocationHandler ih = new DynamicProxy(dhwi); DynamicHelloWorld dhw = (DynamicHelloWorld)Proxy. newProxyInstance(DynamicHelloWorld.class.getClassLoader(), new Class<?>[]{DynamicHelloWorld.class}, ih); dhw.print(); } }
不管哪种写法,运行结果都是一样的:
Before DynamicProxy
Enter DynamicHelloWorldImpl.print()
After DynamicProxy
动态代理解析
上面两种写法,本质上都是一样的。万变不离其宗,归纳起来,实现一个动态代理可以总结为如下四步:
1、获取要被代理的对象,也就是实际对象
2、实现InvocationHandler接口,生成实际的代理内容
3、利用Proxy.newInstance()方法生成一个代理内容,第三个参数传入InvocationHandler的实现类
4、代理对象调用接口内部的方法
动态代理,利用动态编译+反射技术,把对实际对象的方法调用转换成对传入的InvocationHandler接口实现类的invoke方法的调用,这是动态代理模式实现的关键点。要证明这一点并不难,在程序中打上断点跟一下代码即可:
按F5执行下一步,看到程序走到了InvocationHandler实现类的invoke方法中:
执行完methd.invoke(target, args),看到控制台打印出了"Enter DynamicHelloWorldImpl.print()"这一句语句,说明执行了实际对象DynamicHelloWorldImpl的print()方法:
这证明了我们的结论,把对于实际对象方法的调用转换为对于InvocationHandler实现类的invoke方法的调用
动态代理的优点
1、最直观的,类少了很多
2、代理内容也就是InvocationHandler接口的实现类可以复用,可以给A接口用、也可以给B接口用,A接口用了InvocationHandler接口实现类A的代理,不想用了,可以方便地换成InvocationHandler接口实现B的代理
3、最重要的,用了动态代理,就可以在不修改原来代码的基础上,就在原来代码的基础上做操作,这就是AOP即面向切面编程
标签:
原文地址:http://www.cnblogs.com/xrq730/p/4907999.html