动态代理的进化 —— 拦截器

上一篇文章《Java 设计模式 —— 动态代理模式》 讨论了 Java 的 JDK 动态代理和 CGLIB 动态代理两种方式。

下一篇文章《Java 设计模式 —— 责任链模式》将描述如何关联多个拦截器,产生新的设计模式 —— 责任链模式


本文紧接上一篇文章,继续分析一下上文中动态代理的实现。

从上文中的两种动态代理,可以看到动态代理具有的一个非常明显的特点,其整体结构都分为三个阶段:

  • 访问真实对象前(包含是否允许访问真实对象)
  • 访问真实对象或者无法访问真实对象时
  • 访问真实对象后。

大致如下图所示:

Created with Raphaël 2.2.0 访问真实对象前 可访问? 真实对象的方法 访问真实对象后 真实对象的替代方法 yes no

仔细观察上流程图,不难看出,这个流程其实恰好就是一个标准的拦截器

Created with Raphaël 2.2.0 拦截器 before() 拦截成功? 拦截 - 指定特殊操作 around() 拦截器 after() 不拦截 - 继续正常操作 yes no

拦截器

一般,程序设计者往往都会设计好一个拦截器接口以供开发者直接使用,这样,开发者就无需明白动态代理内部的实现原理,极大的提高开发效率。

如上流程图中所示,拦截器接口一般需要三个方法:before()、around()、after()

  • 结合上一文中动态代理的实现,我们尝试着定义一个拦截器接口,对其方法提供四个参数:代理对象、真实对象、真实对象方法、方法参数,如下:

    package com.ambrose.proxypattern.interceptor;
    
    import java.lang.reflect.Method;
    
    /**
     * 定义拦截器接口
     */
    public interface Interceptor {
           
        /**
         * 访问真实对象前调用
         * @param proxy 代理对象
         * @param target    真实对象
         * @param method    方法
         * @param args  运行参数
         * @return
         */
        boolean before(Object proxy, Object target, Method method, Object[] args);
    
        /**
         * 真实对象的方法的替代方法
         *  当访问真实对象之前,调用 before 方法,如果 before 方法执行成功,则调用真实对象的方法;
         *  但如果 before 执行失败,也就是说访问真实对象的方法时被拦截了,此时,就执行 around 方法
         */
        void around(Object proxy, Object target, Method method, Object[] args);
    
        /**
         * 真实对象方法或替代方法执行完成后调用
         */
        void after(Object proxy, Object target, Method method, Object[] args);
    }
    
    
  • 首先,既然要使用 jdk 动态代理来测试,所以我们需要一个接口,和一个实现该接口的类,来作为真实对象。

    package com.ambrose.proxypattern.interceptor;
    
    public interface LeaderGao {
           
        void talkingWithCustomers();
    } 
    
    
    package com.ambrose.proxypattern.interceptor;
    
    public class LeaderGaoImpl implements LeaderGao {
           
        @Override
        public void talkingWithCustomers() {
           
            System.out.println("与客户洽谈中 ...");
        }
    }
    
    
  • 接下来,自定义拦截器实现拦截器接口

    package com.ambrose.proxypattern.interceptor;
    
    import java.lang.reflect.Method;
    import java.util.Random;
    
    /**
     * 自定义拦截器实现类,实现拦截器接口
     */
    public class MyInterceptor implements Interceptor {
           
        @Override
        public boolean before(Object proxy, Object target, Method method, Object[] args) {
           
            // 生成一个随机 boolean 值。
       		// 为 true 时表示通过,未拦截,调用真实对象方法(允许访问高总)
            // 为 false 时表示执行终端,拦截成功,调用替代方法 around (不允许访问高总)
            boolean result = new Random().nextInt(10) % 2 == 1;
            System.out.println("方法前逻辑 —— 小彭接待了客户   " + "小彭认为客户" + (result ? "有权" : "无权") + "访问高总" );
            return result;
        }
    
        @Override
        public void around(Object proxy, Object target, Method method, Object[] args) {
           
            System.out.println("替代方法执行 —— 小彭对客户说:您的项目太小了,请回吧!");
        }
    
        @Override
        public void after(Object proxy, Object target, Method method, Object[] args) {
           
            System.out.println("方法后逻辑 —— 小彭负责送客:您慢走~");
        }
    }
    
  • 在 JDK 动态代理中使用拦截器

    package com.ambrose.proxypattern.interceptor;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 在 JDK 动态代理中使用 [拦截器] 示例
     */
    public class JdkProxyExample implements InvocationHandler {
           
    
        // 真实对象
        private Object target = null;
        // 拦截器全限定名
        private String interceptorClass = null;
    
        /**
         * 构造方法。
         *  在这里,构造方法中,传入真实对象,与拦截器的名称。
         *
         *  至于为什么需要这样一个带参构造方法? 因为创建代理对象时需要使用到拦截器全限定名
         *  详见 bind 方法中的注释
         * @param target
         * @param interceptorClass
         */
        public JdkProxyExample(Object target, String interceptorClass){
           
            this.target = target;
            this.interceptorClass = interceptorClass;
        }
    
        /**
         * 建立代理对象和真实对象的代理关系,并返回代理对象
         *
         *
         * @param target
         * @param interceptorClass
         * @return
         */
        public static Object bind(Object target, String interceptorClass){
           
            /* Proxy.newProxyInstance 方法需要三个参数:
                1、类加载器。
                    这里采用 target 类本身的加载器
                2、表示最终生成的动态代理对象挂载在哪些接口下。
                    这里将其挂在 target 类实现的接口下
                3、定义实现方法逻辑的代理类,必须实现 InvocationHandler 的 invoke 方法
                   在单纯 jdk 动态代理中,我们使用 this 作为方法逻辑类,
                   但是这里既然要使用拦截器,我们就不能直接使用 this 作为方法逻辑类,
                   而要提供一个带参构造方法,将拦截器名作为参数传递给构造方法,然后使用带参构造方法创建该类对象
                ☆ 这里还有一个变化:因为建立代理对象时,直接传入当前类的带参构造对象作为逻辑代理类,
               	   在其他方法中可以无需调用该类的构造方法创建对象,只需要调用 bind 方法获取代理对象即可。
                   所以,这里的 bind 方法可以直接标识为 static
             */
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new JdkProxyExample(target, interceptorClass));
        }
    
        /**
         * 代理方法逻辑。
         * 通过代理对象调用方法,首先会进入该方法。所以对拦截器的处理就应该写在这里。
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           
            // 如果拦截器为空
            if (interceptorClass == null)
                // 直接反射原真实对象方法
                return method.invoke(target, args);
            // 拦截器不为空
            // 通过反射生成拦截器对象
            Interceptor interceptor = (Interceptor)Class.forName(interceptorClass).newInstance();
    
            // 调用拦截器前置方法,返回 before 方法的执行状态,如果返回 false,则说明执行中止,即拦截成功,反之,如果返回 true,则拦截失败
            boolean before = interceptor.before(proxy, target, method, args);
            // 定义一个对象存储执行结果
            Object result = null;
            // 如果拦截失败,调用原真实对象方法
            if (before)
                result = method.invoke(target, args);
            else
            // 如果拦截成功,调用拦截器的 around 方法
                interceptor.around(proxy, target, method, args);
            // 调用拦截器后置方法
            interceptor.after(proxy, target, method, args);
            return result;
        }
    }
    
  • 测试运行

    package com.ambrose.proxypattern.interceptor;
    
    import org.junit.Test;
    
    public class TestInterceptorProxyPattern {
           
        @Test
        public void testInterceptorJdkProxyPattern(){
           
            // 获取代理对象。调用 JdkProxyExample 的静态方法 bind,传入真实对象和拦截器全限定名
            LeaderGao proxy = (LeaderGao) JdkProxyExample.bind(
                    new LeaderGaoImpl(),
                    "com.ambrose.proxypattern.interceptor.MyInterceptor"
            );
            // 运行代理对象的方法
            proxy.talkingWithCustomers();
        }
    }
    
  • 运行结果

你可能感兴趣的:(『Java』,java,设计模式,动态代理,拦截器)