6.1 学前必备知识
6.1.1 代理模式
下面来看Struts 2的另一个核心知识点:Struts 2的拦截器。要想真正理解Struts 2拦截器的实现机制,必须对Java的反射机制以及动态代理模式有所了解。
首先来看什么是代理模式,代理模式又叫Proxy模式。所谓代理模式,就是一个人或者一个机构代替另一个人或者另一个机构去做一些事情。比如说网上书店,它就是代理了出版社在卖书。
代理模式中有如下最重要的3种角色。
❑ 抽象主题角色:真实主题与代理主题的共同接口。
❑ 真实主题角色:定义了代理角色所代表的真实对象。
❑ 代理主题角色:含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
以网上书店代理卖书为例,各角色如下。
❑ 抽象主题角色:卖书
❑ 真实主题角色:出版社
❑ 代理主题角色:网上书店(如:当当)
网上书店和出版社都是在卖书,所以抽象主题角色就是卖书。而真正出版书籍的是出版社,所以真实主题角色为出版社。网上书店只是代理书店卖书,所以网上书店为代理主题角色。下面用程序来实现代理模式。
步骤如下。
(1)创建抽象主题角色。新建一个抽象类,在该类中有一个抽象方法sailBook()方法,代码如下所示。
//抽象主题角色 public abstract class Subject { //卖书 public abstract void sailBook(); }
(2)创建真实主题角色。新建一个类继承自Subject抽象类,并覆写sailBook()方法,代码如下所示。
//真实主题角色 public class RealSubject extends Subject { //出版社卖书 public void sailBook() { System.out.println("卖书"); } }
(3)创建代理主题角色。新建一个类继承自Subject抽象类,并包含真实主题角色引用。同样覆写sailBook()方法,在该方法中添加其他方法的调用,代码如下所示。
//代理主题角色 public class ProxySubject extends Subject { //包含真实主题角色引用 private RealSubject realSub; //购物网卖书 public void sailBook() { //首先打折 dazhe(); //卖书 if(realSub == null) { realSub = new RealSubject(); } realSub.sailBook(); //送代金券 give(); } public void dazhe() { System.out.println("打折"); } public void give() { System.out.println("送代金券"); } }
(4)新建一个Client类用来测试。在该类中实例化一个代理主题角色,并将引用赋值给其父接口。通过该引用调用sailBook()方法,代码如下所示。
public class Client { public static void main(String[] args) { //实例化一个代理主题角色 Subject s = new ProxySubject(); //从购物网买书 s.sailBook(); } }
程序运行结果如下所示。
打折 卖书 送代金券
实现代理模式的重点在于,在代理主题角色中包含真实主题角色引用。也就是说去网上书店买书,其实就等于是去出版社买书。只是在网上书店买书能够打折还有送代金券。网上书店就是个代理,真正的书还是出自出版社。图6.1所示是这个示例的时序图。
图6.1 代理模式示例时序图
6.1.2 动态代理模式
了解了代理模式,下面再来看什么是动态代理模式。前面是通过手动地添加代理主题角色来实现的代理,其实可以通过JDK提供的Proxy类实现动态地生成代理主题角色。下面通过动态代理模式改写上面那个示例程序。
步骤如下。
(1)创建抽象主题角色。这里和前面示例不同的是,该抽象主题角色不再是一个抽象类,而是一个接口。因为JDK的动态代理只能对实现了接口的实例来生成动态代理,代码如下所示。
package net.hncu.reflection; //抽象主题角色 public interface Subject { //卖书 public void sailBook(); }
(2)创建真实主题角色。该类实现Subject接口,并实现接口中的sailBook()方法,代码如下所示。
package net.hncu.reflection; //真实主题角色 public class RealSubject implements Subject { //出版社卖书 public void sailBook() { System.out.println("卖书"); } }
(3)新建处理类MyHandler,该类实现InvocationHandler接口。InvocationHandler接口是反射中非常重要的一个接口,它可以动态调用目标对象中的方法。在该类中同样包含真实主题角色引用,代码如下所示。
package net.hncu.reflection; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyHandler implements InvocationHandler { //包含真实主题角色引用 private Object realSub; //传入真实主题角色实例 public void setReal(Object realSub){ this.realSub = realSub; } //指定代理主题角色时,该方法会自动调用 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result; //首先打折 dazhe(); //调用真实主题角色 result = method.invoke(realSub, args); //送代金券 give(); return result; } public void dazhe() { System.out.println("打折"); } public void give() { System.out.println("送代金券"); } }
(4)新建一个Client类用来测试。在该类中新建一个真实主题对象实例,并将其设置到代理的处理类中。通过Proxy类的newProxyInstance方法来创建动态代理实例,代码如下所示。
package net.hncu.reflection; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { //真实主题对象实例 RealSubject realSub = new RealSubject(); //代理的处理类实例 MyHandler mh = new MyHandler(); //将真实主题对象设置到代理处理类实例中 mh.setReal(realSub); //创建动态代理实例 Subject proxySub = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(), realSub.getClass().getInterfaces(), mh); //使用动态代理实例调用该方法 proxySub.sailBook(); } }
程序运行结果。
打折 卖书 送代金券
在这个程序里面大量运用了反射方面的知识。如果不熟悉反射方面的知识,最好是进行一下复习。