声明:本文中通过Java的动态代理实现的拦截器只是能够在目标方法执行之前、之后调用拦截器方法,属于比较初级的应用。
在软件开发领域,有一条很重要的规则:Don’t Repeat Yourself,即DRY规则,意思是不要书写重复的代码。此处所说的重复的代码是指功能性的代码,比如在很多方法里面用到排序,就可以将排序方法提取出来,写成一个功能方法,在需要用到的地方调用一下即可。这样做,比重复书写(或者使用复制、粘贴)相同的代码节省时间,而且更有益于软件后期的升级、维护中。
拦截器更是对调用方法的改进。拦截器只是一种行为,从代码的角度,拦截器是一个类,包含方法,只是这个方法可以在目标方法调用之前“自动”执行(所谓自动,是指无需开发者关心,由系统来驱动调用)。
首先定义一个Dog接口,因为JDK动态代理只能对实现了接口的实例生成代理。
public interface Dog { // info方法声明 public void info(); // run方法声明 public void run(); // stand方法声明 public void stand(); }
为了正常使用该Dog实例,需提供该接口的实现类。
public class DogImpl implements Dog{ public void info() { System.out.println("猎狗"); } public void run() { System.out.println("飞速移动中。。。"); } public void stand(){ System.out.println("站立不动。"); } }
上面代码没有丝毫特别,比较常见的定义接口,并实现接口。
下面实现拦截Dog实例的拦截器类:
public class DogIntercepter { public void method1() { System.out.println("=======模拟通用方法一======"); } public void method2() { System.out.println("=======模拟通用方法二======"); } public void method3() { System.out.println("=======模拟通用方法三======"); } public void method4() { System.out.println("=======模拟通用方法四======"); } }
可以发现,上面的拦截器类只是一个普通的Java类。所以要想该拦截类实现其拦截的目的,就需要通知系统。关键就在下面的ProxyHandler类了。该类需要实现InvocationHandler接口,InvocationHandler是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
public class ProxyHandler implements InvocationHandler{ // 需要代理的目标对象 private Object target; // 创建拦截器实例 DogIntercepter di = new DogIntercepter(); // 执行代理的目标方法时,该invoke方法会被自动调用。 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 如果被调用方法的方法名为info if(method.getName().equals("info")){ di.method1(); result = method.invoke(target, args); di.method2(); } else if ("run".equals(method.getName())){ di.method3(); result = method.invoke(target, args); di.method4(); } else { result = method.invoke(target, args); } return result; } // 用于设置传入目标对象的方法 public void setTarget(Object o) { this.target = o; } }
可以看到,ProxyHandler类与Dog类没有丝毫关联之处(如果说info是Dog的方法名,但是info方法是否属于Dog实例,却没有限定,因此,可以认为ProxyHandler与Dog实例没有丝毫关联),这样就实现了Dog实例与拦截器类的解耦(解耦这个词用在这个地方只是显得很很牛的样子,实际上在ProxyHandler中的info方法使用的是字符串,相对于直接调用,不易排错,仁者见仁智者见智吧)。
通过ProxyHandler类,系统实现了在执行info方法之前,调用拦截器method1方法,在执行info方法之后,调用拦截器method2方法(run方法类似)。
到了此时,系统还需要一个能够根据目标对象生成一个代理对象的代理工厂。代理工厂负责根据目标对象和对应的拦截器生成新的代理对象,代理对象里的方法时目标方法和拦截器方法的组合。
public class MyProxyFactory { /** * 实例Service对象 */ public static Object getProxy(Object objext) throws Exception { // 代理的处理类 ProxyHandler handler = new ProxyHandler(); // 把该Dog实例托付给代理操作 handler.setTarget(objext); // return Proxy.getProxyClass(DogImpl.class.getClassLoader(), objext.getClass().getInterfaces()) // .getConstructor(new Class[]{InvocationHandler.class}) // .newInstance(new Object[]{handler}); // 等价于: // 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 // 第一个参数是用来创建动态代理的ClassLoader对象,只要该对象能访问Dog接口即可 // 第二个参数是接口数组,代理该接口数组。代理类要实现的接口列表。 // 第三个参数是代理包含的处理实例,指派方法调用的调用处理程序 。 return Proxy.newProxyInstance(DogImpl.class.getClassLoader(), objext.getClass().getInterfaces(), handler); } }
仔细研读上面代理工厂的返回对象是Proxy.newProxyInstance方法根据数组动态创建代理类实例的,第二个参数是获取代理类的接口数组,创建的代理类是JVM在内存中动态创建,该类实现参数里接口数组中的全部接口。因此,本文中的动态代理要求被代理的必须是接口的实现类,否则无法为其构造相应的动态实例。
下面编写主程序进行测试:
public class TestDog { public static void main(String[] args) throws Exception { // 创建一个Dog实例,该实例将被作为代理的目标对象 Dog targetObject = new DogImpl(); Dog dog = null; // 以目标对象创建代理 Object proxy = MyProxyFactory.getProxy(targetObject); if(proxy instanceof Dog){ dog = (Dog)proxy; } // 测试代理的方法 dog.info(); dog.run(); dog.stand(); } }
在主程序中,先创建一个Dog实例作为目标对象,然后以该目标对象为基础,创建该对象的代理对象(将拦截器方法和目标方法拼接在一起),生成的代理对象就能够有拦截器的效果了。
效果如图:
以上便是实现拦截器功能的动态代理的简单实现。