zoukankan      html  css  js  c++  java
  • JAVA的代理(proxy)模式

      代理(proxy)模式: 是一种设计模式, 提供了间接对目标对象进行访问的方式. 即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能. 有的时候也可以使用代理模式, 在不修改他人代码的情况下, 增强功能(对方法进行扩展)

      举例: 老板的助理, 明星的经纪人等, 这里的助理和经纪人都可以看做是代理对象, 而老板和明星就是目标对象, 别人通过联系助理和经纪人可以达到相同的目的, 通俗一点的解释就是在操作一个目标对象或对象中的方法时,不是直接操作这个对象,而是通过一个代理对象来操作这个实际的目标对象

      关键词: 目标对象, 代理对象. (代理对象就是对目标对象的扩展, 并会调用目标对象)

      图示:

      

       代理分类: 静态代理 和 动态代理以及Cglib代理

       注意: 接下来所有的代理模式示例都将以CURD操作为基础, 只是演示代理模式的方式, 不做真正的数据库操作!!!

     1.静态代理

      说明: 静态代理在使用的时候, 需要定义目标接口,被代理对象(子类, 也就是目标对象)与代理对象都需要一起实现相同的这个目标接口.

         代码示例: 

      目标接口: ICurd.java

    package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;
    
    /**
     * 定义目标接口
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public interface ICurd {
    
        void add();
        
        void delete();
        
        void update();
        
        void find();
    }

      目标对象: CurdImpl.java

    package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;
    
    /**
     * 目标对象, 实现父接口
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public class CurdImpl implements ICurd {
    
        @Override
        public void add() {
            System.out.println("CurdImpl执行了add操作");
        }
    
        @Override
        public void delete() {
            System.out.println("CurdImpl执行了delete操作");
        }
    
        @Override
        public void update() {
            System.out.println("CurdImpl执行了update操作");
        }
    
        @Override
        public void find() {
            System.out.println("CurdImpl执行了find操作");
        }
    }

      代理对象: Proxy.java

    package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;
    
    /**
     * 定义代理对象
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public class Proxy implements ICurd {
    
        private ICurd curd;
    
        public Proxy(ICurd curd) {
            this.curd = curd;
        }
    
        @Override
        public void add() {
            // 扩展, 执行目标对象之前的行为
            System.out.println("执行add操作之前 ...");
            // 执行真正的目标对象的方法
            curd.add();
            // 扩展, 执行目标对象之后的行为
            System.out.println("执行add操作之后 ...");
        }
    
        @Override
        public void delete() {
         // 类似add方法
        }
    
        @Override
        public void update() {
         // 类似add方法
        }
    
        @Override
        public void find() {
         // 类似add方法
        }
    }

      测试类: ProxyTest.java

    package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;
    
    import org.junit.Test;
    
    public class ProxyTest {
    
        @Test
        public void test() {
            // 创建目标对象
            CurdImpl curdImpl = new CurdImpl();
            // 创建代理对象, 代理目标对象
            Proxy proxy = new Proxy(curdImpl);
            // 执行代理对象的add方法
            proxy.add();
        }
    }

      结果输出:

      

    执行add操作之前 ...
    CurdImpl执行了add操作
    执行add操作之后 ...

      静态代理总结: 

        1. 可以做到在不修改目标对象的功能前提下,对目标功能扩展.(执行目标对象方法的前后的输出就是扩展, 可以改为其他方法的执行, 这里为了演示只是输出)

        2. 缺点: 因为代理对象需要与目标对象实现一样的接口, 所以如果都采用这种方式, 久而久之会发现有很多的代理类, 很繁琐, 加入你有两个目标接口, 都需要代理, 那么你就会产生两个对应的目标对象和代理对象, 如果是20个呢, 简直要崩溃呀!!! 而且一旦目标接口改变方法, 例如添加新的方法, 那么目标对象和代理对象都需要相应的进行添加方法, 维护麻烦, 所以, 解决这个问题的方案就是动态代理!!!

     2.动态代理

       动态代理特点: 

        1. 代理对象不需要实现目标接口

        2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

        3. 动态代理也叫作JDK动态代理

      JDK动态代理了解: 

        1. 代理类所在包:java.lang.reflect.Proxy

        2. JDK实现代理只需要使用Proxy的静态方法newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:  

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

         接收的三个参数依次为:      

            ClassLoader loader: 指定当前目标对象使用类加载器,获取加载器的方法是固定的

            Class<?>[] interfaces: 目标对象实现的接口的类型,使用泛型方式确认类型

            InvocationHandler h: 事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

        代码示例:

      目标接口和目标对象依然同上面的静态代理, 这里不再展示, 不同是这里的代理对象不需要定义类来实现目标接口, 而是实现
    JDK的代理类接口MyInvocationHandler中的invoke方法来动态的生成代理对象

        代理对象: MyInvocationHandler.java, 实现JDK的代理类接口 

    package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.jdk.demo;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 实现jdk自带的动态代理类接口并重写invoke方法
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public class MyInvocationHandler implements InvocationHandler {
    
        /**
         * 持有需要被代理的真实目标对象
         */
        private Object obj;
    
        public MyInvocationHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 扩展:执行代理之前的操作
            System.out.println("代理之前 ...");
         // 注意: 这里传入的参数是持有的目标对象obj和参数args Object invoke
    = method.invoke(obj, args); // 扩展:执行代理之后的操作 System.out.println("代理之后 ..."); return invoke; } }

      测试: ProxyTest.java

    package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.jdk.demo;
    
    import java.lang.reflect.Proxy;
    
    import org.junit.Test;
    
    public class ProxyTest {
    
        @Test
        public void test() {
            // 定义目标对象
            CurdImpl curdImpl = new CurdImpl();
            // 执行静态方法获取代理对象, 注意三个参数的获取方式, 也有其他写法, 但是变的都是表面而已, 实际类型都是一样的
            ICurd curd = (ICurd)Proxy.newProxyInstance(curdImpl.getClass().getClassLoader(),
                curdImpl.getClass().getInterfaces(), new MyInvocationHandler(curdImpl));
            //
            curd.add();
        }
    }

      测试结果输出:

        同静态代理输出一样(懒得贴了)

      总结:
        代理对象不需要实现接口而是执行静态方法newProxyInstance来获取的, 但是目标对象一定要实现接口,否则不能用动态代理

     3.Cglig代理

      上面的静态代理和动态代理模式都是要求目标对象实现一个接口(所谓的目标接口), 但是有时候目标对象只是一个单独的对象, 并没有实现任何的接口, 这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

      Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)

      Cglib子类代理实现方法:

        1.需要引入cglib的jar文件,但是如果是Spring项目, 那么核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可. 否则需要cglib-nodep-3.2.4.jar(版本自己视情况而定)

        2.引入功能包后,就可以在内存中动态构建子类

        3.目标对象的方法如果为final/static,那么就不会被代理(本人测试过static方法, 如果采用代理对象调用static方法, 会报出警告, 推荐使用类直接调用, 而且执行了也不会执行代理扩展的方法, 只会执行自身的方法)

        4. 需要一个代理类, 实现接口MethodInterceptor中的invoke方法, 然后还需要有一个生成代理对象的方法

      代码示例:

      没有目标接口, 只是一个类, 作为目标对象

    package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;
    
    /**
     * 单纯的一个类, 作为目标对象
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public class CurdService {
    
        public void add() {
            System.out.println("CurdService -> add");
        }
    
        public String delete() {
            System.out.println("CurdService -> delete");
            return "delete";
        }
    
        public static void update() {
            System.out.println("CurdService -> update");
        }
    }

      代理类: MyMethodInterceptor.java 需要实现MethodInterceptor接口中的invoke方法

    package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    /**
     * cglib代理类, 用来获取代理对象
     * 
     * @author jiangkd
     * @date 2020/06/04
     */
    public class MyMethodInterceptor implements MethodInterceptor {
    
        /**
         * 持有目标对象
         */
        private Object obj;
    
        public MyMethodInterceptor(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //
            System.out.println("before ...");
            Object invoke = method.invoke(obj, args);
            System.out.println("返回:" + invoke);
            System.out.println("after ...");
            return invoke;
        }
    
        /**
         * 创建代理对象的方法
         * 
         * @return
         */
        public Object getProxyObj() {
            //
            Enhancer enhancer = new Enhancer();
            // 设置父类, 就是目标对象类
            enhancer.setSuperclass(obj.getClass());
            // 设置回调, 就是当前代理类
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }
    }

      测试类: ProxyTest.java

    package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;
    
    import org.junit.Test;
    
    public class ProxyTest {
    
        @Test
        public void test() {
            // 目标对象
            CurdService curdService = new CurdService();
            // 获取代理对象
            CurdService proxyObj = (CurdService)new MyMethodInterceptor(curdService).getProxyObj();
            // 
            proxyObj.add();
            // 静态方法, 不会通过代理, 报出警告, 推荐下面的写法
            proxyObj.update();
            // 直接正常调用, 不使用代理
            CurdService.update();
        }
    }

      测试输出:

       不妨自己写一个代理, 测试一下看看啊(好吧, 其实就是我懒得贴了)

  • 相关阅读:
    手机qq2005 没声音
    使用VBS访问外部文本文件一些方法和脚本实例
    sqlserver 备份恢复 学习笔记
    SQL Server中truncate、delete和drop的异同点
    性能诊断
    列整合一例
    XML导入属性数据【经典】
    读取文本行
    利用TcpClient TcpListener 实现发送图片
    德云社的十三香词
  • 原文地址:https://www.cnblogs.com/no-celery/p/13043499.html
Copyright © 2011-2022 走看看