• 设计模式-代理模式


        本文主要讲解代理模式。

        代理模式:为其他对象提供一种代理以控制对这个对象的访问。

                    代理模式(Proxy)结构图

        Subject类,定义RealSubject和Proxy的共用接口,这样就在任何使用ReadSubject的地方都可以使用Proxy。RealSubject类,定义Proxy所代表的真实实体。Proxy类,保存一个引用使得代理可以访问实体,并提供了一与Subject的接口相同的接口,这样代理就可以用来替代实体。

        下面看看基本的代码实现。

        

    package com.lwx.proxy;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-03
     * Time: 17:44
     */
    public interface Subject {
    
        void request();
    
    }
    package com.lwx.proxy;
    
    import java.util.Random;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-03
     * Time: 17:44
     */
    public class RealSubject implements Subject {
    
        public void request() {
            System.out.println("真实的请求");
            try {
                //用一个随机睡眠模仿处理业务逻辑
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    package com.lwx.proxy;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-03
     * Time: 17:45
     */
    public class Proxy implements Subject {
    
        private RealSubject realSubject;
    
        public void preRequest() {
            System.out.println("方法执行前");
        }
    
        public void afterRequest() {
            System.out.println("方法执行后");
        }
    
        public void request() {
            if (realSubject == null) {
                realSubject = new RealSubject();
            }
            preRequest();
            realSubject.request();
            afterRequest();
        }
    
    }

        然后是测试类和运行结果。

    package com.lwx.proxy;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-03
     * Time: 17:46
     */
    public class SubjectTest {
    
        public static void main(String[] args) {
            Proxy proxy = new Proxy();
            proxy.request();
        }
    
    }

         上面的代码是一个简单静态代理,还有一种代理方式是动态代理,动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询等。

        在上面的代码基础上加一个实现了InvocationHandler,方法调用会被转发到该类的invoke()方法。

    package com.lwx.proxy.example;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-04
     * Time: 21:23
     */
    public class ProxyHandler implements InvocationHandler {
    
        //绑定委托对象,并返回代理类
        private Object target;
    
        public Object bind(Object target) {
            //绑定该实现的所有接口,取得代理类
            this.target = target;
            /**
             * 第一个参数:类加载器
             * 第二个参数:代理需要实现的接口,可以有点多个
             * 第三个参数:方法调用的实际处理者
             */
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            long startTime = System.currentTimeMillis();
            //在调用invoke()方法的前后可以进行所谓的AOP编程,这里我们就简单的打印一下方法调用所花费的时间和代理对象调用方法的名称
            result = method.invoke(target, args);
            long endTime = System.currentTimeMillis();
            System.out.println("方法耗费时间:" + (endTime - startTime));
            System.out.println("实例化" + target.getClass().getName() + ",并调用了" + method.getName() + "方法。");
            return result;
        }
    
    }

        上面代码的newProxyInstance()方法会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方是:

        1.代理对象是在程序运行时产生的,而不是编译期;

        2.对代理对象的所有接口方法调用都会转发到Invocation.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查等。

        下面我们看下JDK动态代码测试类。  

    package com.lwx.proxy.example;
    
    import com.lwx.proxy.RealSubject;
    import com.lwx.proxy.Subject;
    
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-04
     * Time: 21:33
     */
    public class ProxyTest {
    
        public static void main(String[] args) {
            ProxyHandler proxyHandler = new ProxyHandler();
            Subject subject = (Subject) proxyHandler.bind(new RealSubject());
            subject.request();
        }
    
    }

        接着看下JDK动态代理的运行结果。

        

        JDK动态代理为我们提供了非常灵活的代理机制,但Java动态代理是基于接口的,如果对象没有实现接口我们该如何代理呢?下面我们看下另一种动态代码方式CGLIB。

        CGLIB介绍与原理(选自网络)

        一、什么是CGLIB?

        CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。通常可以使用Java的动态代理创建,但当要代理没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

        二、CGLIB原理

        动态生成一个要代理类的子类,子类重写要代理类的所有不是final的方法。在子类中采取方法拦截的技术拦截所有父类方法的调用,顺势织如横切逻辑。它比使用Java反射的JDK动态代理快。

        CGLIB底层使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。 

        下面用一个简单的例子介绍使用CGLIB实现动态代理。

        首先我们引入CGLIB的jar包依赖。

            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>2.2.2</version>
            </dependency>

        先写一个需要代理的目标类,没有实现接口。

    package com.lwx.proxy.example;
    
    import java.util.Random;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-05
     * Time: 21:42
     */
    public class CglibObject {
    
        public void function() {
            System.out.println("调用function方法");
            try {
                //用一个随机睡眠模仿处理业务逻辑
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }

        然后定义一个拦截器。在调用目标方法时,CGLIB会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK动态代理中的InvocationHandler接口。

    package com.lwx.proxy.example;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * Created with IntelliJ IDEA.
     * Description: 目标对象拦截器
     * User: lwx
     * Date: 2019-03-04
     * Time: 21:48
     */
    public class CglibInterceptor implements MethodInterceptor {
    
        /**
         * 重写方法拦截在方法前后加入业务
         * @param o 目标对象
         * @param method 目标方法
         * @param objects 参数
         * @param methodProxy CGlib方法代理对象
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Object result = null;
            long startTime = System.currentTimeMillis();
            result = methodProxy.invokeSuper(o, objects);
            long endTime = System.currentTimeMillis();
            System.out.println("方法耗费时间:" + (endTime - startTime));
            System.out.println("实例化" + o.getClass().getName() + ",并调用了" + method.getName() + "方法。");
            return result;
        }
    
    }

        最后看下CGLIB的测试类和运行结果。

    package com.lwx.proxy.example;
    
    import net.sf.cglib.proxy.Enhancer;
    
    /**
     * Created with IntelliJ IDEA.
     * Description:
     * User: lwx
     * Date: 2019-03-05
     * Time: 21:11
     */
    public class CglibTest {
    
        public static void main(String[] args) {
            //Enhancer是CGLIB中的字节码增强器,它可以方便的对你想要代理的类进行扩展
            Enhancer enhancer = new Enhancer();
            //设置代理目标
            enhancer.setSuperclass(CglibObject.class);
            //设置回调
            enhancer.setCallback(new CglibInterceptor());
            //动态生成一个代理类,并从Object强制转型成父类型CglibObject
            CglibObject cglibObject = (CglibObject) enhancer.create();
            //在代理类上调用方法
            cglibObject.function();
        }
    
    }

        当然CGLIB还有很多的API,这里只是简单的展示了一个比较常用的Enhancer。

        简单总结一下优点和缺点

        优点:可以实现无入侵式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增加一些方法;在方法的前后你可以做任何你想做的事。

        缺点:由于增加了代理对象,因此可能增加处理时间。

        最后附上demo的githup地址:https://github.com/yijinqincai/design_patterns

  • 相关阅读:
    [转]Asp.net中基于Forms验证的角色验证授权
    [转]npm常用命令
    [转]utf8编码引起js输出中文乱码的解决办法
    LEFT JOIN 和 RIGHT JOIN 运算
    [转].NET 数字格式化:忽略末尾零
    [译]Pro ASP.NET MVC 3 Framework 3rd Edition 目录及说明
    微信授权登录
    百度快照更新慢怎么办
    linux爱好者必须掌握的命令,linux基础命令集合
    input输入框只能输入数字、字母相关组合(正则表达式)
  • 原文地址:https://www.cnblogs.com/leisurexi/p/10473709.html
Copyright © 2020-2023  润新知