zoukankan      html  css  js  c++  java
  • 代理模式 静态代理、JDK动态代理、Cglib动态代理

    1 代理模式

      使用代理模式时必须让代理类和被代理类实现相同的接口;

      客户端通过代理类对象来调用被代理对象方法时,代理类对象会将所有方法的调用分派到被代理对象上进行反射执行;

      在分派的过程中还可以添加前置通知和后置处理。

    2 为什么需要使用代理

      2.1 需求

        现有一个HelloApi接口和一个HelloApi接口的实现类Hello,现在需要统计HelloApi接口中say方法的耗时

    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:20
     * @desc
     **/
    public interface HelloApi {
    
        public void say();
    
    }
    Hello.java
    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Hello implements HelloApi {
    
    
        @Override
        public void say() {
            System.out.println("Hello Fury");
        }
    }
    Hello.java

      2.2 思路

        思路01 -> 在实现类中实现

        思路02 -> 利用静态代理实现

        思路03 -> 利用动态代理实现

      2.3 解决

        2.3.1 在实现类中实现

          缺点:

            》需要针对方法进行操作,如果有多个方法需要相同的逻辑时工作量巨大

            》违背了开闭原则:开闭原则是对扩展开放,但是对修改关闭;为了增加功能把每个方法都修改了,不便于维护

            》违背了单一职责原则:每个方法除了完成自己本身的功能,还要计算耗时

    开闭原则
    开闭原则(OCP)是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动
    开闭原则
    单一职责原则
    单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。该原则由罗伯特·C·马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中给出的。马丁表示此原则是基于汤姆·狄马克(Tom DeMarco)和Meilir Page-Jones的著作中的内聚性原则发展出的。
    所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
    单一职责原则
    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Hello implements HelloApi {
    
    
        @Override
        public void say() {
            Long start = System.currentTimeMillis();
            System.out.println("Hello Fury");
            Long end = System.currentTimeMillis();
            System.out.println("耗时为:" + (end - start));
        }
    
    }
    HelloApi.java
    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Test {
    
        public static void main(String[] args) {
    
            Hello hello = new Hello();
    
            // 01 传统调用方式
            hello.say();
    
            // 02 静态调用方式
    //        StaticProxy staticProxy = new StaticProxy(hello);
    //        staticProxy.say();
    
    //        HelloApi proxy = (HelloApi)new DynamicProxy().getProxy(hello);
    //        proxy.say();
    
    
        }
    
    }
    调用测试

        2.3.2 利用静态代理实现

          》修改HelloApi的实现类Hello,将实现类中say方法中的计算耗时逻辑去掉

          》创建一个类,该类实现了HelloApi接口

          》定义一个类型为Hello的成员变量target

          》重写say方法,在say方法中利用target成员变量去调用say方法,并在调用你的前后可以增加逻辑处理

          》》静态代理实现的缺点

            》》》需要针对每一个被代理对象编写一个静态代理类,如果需要进行代理的对象较多,那么工作量将很巨大

            》》》静态代理类实现了被代理对象所属类实现的接口,一旦接口发生改变,静态代理类也必须跟着发生变化

    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Hello implements HelloApi {
    
    
        @Override
        public void say() {
    //        Long start = System.currentTimeMillis();
            System.out.println("Hello Fury");
    //        Long end = System.currentTimeMillis();
    //        System.out.println("耗时为:" + (end - start));
        }
    
    }
    Hello.java
    package demo_test02;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:21
     * @desc 静态代理类
     **/
    public class StaticProxy implements HelloApi {
    
        /**
         * 被代理对象
         */
        private Hello hello;
    
        public StaticProxy(Hello hello) {
            this.hello = hello;
        }
    
        @Override
        public void say() {
            Long start = System.currentTimeMillis();
            hello.say();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start));
        }
    
        public Hello getHello() {
            return hello;
        }
    
        public void setHello(Hello hello) {
            this.hello = hello;
        }
    }
    StaticProxy.java
    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Test {
    
        public static void main(String[] args) {
    
            Hello hello = new Hello();
    
            // 01 传统调用方式
    //        hello.say();
    
            // 02 静态调用方式
            StaticProxy staticProxy = new StaticProxy(hello);
            staticProxy.say();
    
    //        HelloApi proxy = (HelloApi)new DynamicProxy().getProxy(hello);
    //        proxy.say();
    
    
        }
    
    }
    静态代理测试

        2.3.3 利用动态代理实现

          》创建一个动态代理类,该类实现了InvocationHandler

          》定义一个Object类型的target成员变量

          》定义一个getProxy方法,该方法接收一个Object类型对象【就是被代理对象】;方法体中初始化target成员变量,并返回一个动态代理对象

            技巧01:调用Proxy的newProxyInstance 去创建动态代理对象,该方法接收三个参数,分别是:

              ClassLoader loader -> 被代理对象的类加载器【PS: 可以通过被加载对象的类类型去获取类加载器】

              Class<?>[] interfaces -> 被代理对象实现的接口组成的数组【PS: 可以通过被加载对象的类类型去获取实现的接口】

              InvocationHandler h -> 动态代理类实例【PS: 通过this传入自定义的动态代理类实例即可】

        /**
         * Returns an instance of a proxy class for the specified interfaces
         * that dispatches method invocations to the specified invocation
         * handler.
         *
         * <p>{@code Proxy.newProxyInstance} throws
         * {@code IllegalArgumentException} for the same reasons that
         * {@code Proxy.getProxyClass} does.
         *
         * @param   loader the class loader to define the proxy class
         * @param   interfaces the list of interfaces for the proxy class
         *          to implement
         * @param   h the invocation handler to dispatch method invocations to
         * @return  a proxy instance with the specified invocation handler of a
         *          proxy class that is defined by the specified class loader
         *          and that implements the specified interfaces
         * @throws  IllegalArgumentException if any of the restrictions on the
         *          parameters that may be passed to {@code getProxyClass}
         *          are violated
         * @throws  SecurityException if a security manager, <em>s</em>, is present
         *          and any of the following conditions is met:
         *          <ul>
         *          <li> the given {@code loader} is {@code null} and
         *               the caller's class loader is not {@code null} and the
         *               invocation of {@link SecurityManager#checkPermission
         *               s.checkPermission} with
         *               {@code RuntimePermission("getClassLoader")} permission
         *               denies access;</li>
         *          <li> for each proxy interface, {@code intf},
         *               the caller's class loader is not the same as or an
         *               ancestor of the class loader for {@code intf} and
         *               invocation of {@link SecurityManager#checkPackageAccess
         *               s.checkPackageAccess()} denies access to {@code intf};</li>
         *          <li> any of the given proxy interfaces is non-public and the
         *               caller class is not in the same {@linkplain Package runtime package}
         *               as the non-public interface and the invocation of
         *               {@link SecurityManager#checkPermission s.checkPermission} with
         *               {@code ReflectPermission("newProxyInPackage.{package name}")}
         *               permission denies access.</li>
         *          </ul>
         * @throws  NullPointerException if the {@code interfaces} array
         *          argument or any of its elements are {@code null}, or
         *          if the invocation handler, {@code h}, is
         *          {@code null}
         */
        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    View Code

          》重写invoke方法,在方法体通过mehtod的invoke方法去调用被代理对象的方法【PS: 利用反射实现的】

          》》坑01:获取到的动态代理类的类型是Ojbect类型,必须进行类型转换成对应的类型

          》》动态代理类实现的好处

            》》》自定义动态代理类,被代理对象所属类所实现的接口变动时代理类也无需变动,因为动态代理是通过反射的方式实现被代理对象所属类所实现的接口的

            》》》符合开闭原则,通过代理实现新功能,没有侵入到原有代码,也不用破坏原来的代码结构,完美的扩展方案

    package demo_test02;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:26
     * @desc 动态代理类
     **/
    public class DynamicProxy implements InvocationHandler {
    
        /**
         * 被代理对象
         */
        private Object target;
    
        /**
         * 利用JDK的动态代理创建代理对象
         * @param target
         * @return
         */
        public Object getProxy(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 {
            Long start = System.currentTimeMillis();
            Object result = method.invoke(target, args);
            TimeUnit.SECONDS.sleep(1);
            Long end = System.currentTimeMillis();
            System.out.println("花费的时间为:" + (end - start));
            return result;
        }
    
        public Object getTarget() {
            return target;
        }
    
        public void setTarget(Object target) {
            this.target = target;
        }
    }
    DynamicProxy.java
    package demo_test02;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:18
     * @desc
     **/
    public class Test {
    
        public static void main(String[] args) {
    
            Hello hello = new Hello();
    
            // 01 传统调用方式
    //        hello.say();
    
            // 02 静态调用方式
    //        StaticProxy staticProxy = new StaticProxy(hello);
    //        staticProxy.say();
    
            HelloApi proxy = (HelloApi)new DynamicProxy().getProxy(hello);
            proxy.say();
    
    
        }
    
    }
    调用测试

    3 动态代理

      3.1 动态代理类编程步骤

        》通过实现InvocationHandler接口来自定义自己的InvocationHandler;

        》通过Proxy.getProxyClass获得动态代理类
        》通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
        》通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入
        》通过代理对象调用目标方法

      3.2 繁琐方法

    package demo_test02;
    
    import java.lang.reflect.*;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 19:59
     * @desc
     **/
    public class Test02 {
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            StudentImpl student = new StudentImpl();
    
            /**
             * 00 实现InvocationHandler来创建自己的动态代理类
             * 01 获取动态代理类的类类型
             * 02 获取动态代理类的构造器
             * 03 创建动态代理类的实例
             * 04 利用动态代理对象去调用被代理对象的方法
             */
            Class<?> studentProxyClass = Proxy.getProxyClass(Student.class.getClassLoader(), Student.class);
    
            Constructor<?> studentProxyClassConstructor = studentProxyClass.getConstructor(InvocationHandler.class);
    
            Student studentProxy = (Student)studentProxyClassConstructor.newInstance(new StudentDynamicProxy(student));
    
            studentProxy.study();
    
        }
    
        interface Student {
            void study();
        }
    
        static class StudentImpl implements Student {
    
            @Override
            public void study() {
                System.out.println("I am studying dynamic proxy of java.");
            }
        }
    
        /**
         * 自定义动态代理类
         */
        static class StudentDynamicProxy implements InvocationHandler {
    
            /**
             * 被代理对象
             */
            private Object target;
    
            public StudentDynamicProxy(Object target) {
                this.target = target;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("前置处理");
                Object result = method.invoke(target, args);
                System.out.println("后置处理");
                return result;
            }
        }
    
    }
    View Code

      3.3 简便方法

    package demo_test02;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-09 20:13
     * @desc
     **/
    public class Test03 {
    
        public static void main(String[] args) {
            StudentImpl student = new StudentImpl();
            Student studentProxy = (Student)new StudentDynamicProxy().getDynamicProxyObject(student);
            studentProxy.hello();
        }
    
        interface Student {
            void hello();
        }
    
        static class StudentImpl implements Student {
    
            @Override
            public void hello() {
                System.out.println("你好,王杨帅。");
            }
        }
    
        static class StudentDynamicProxy implements InvocationHandler {
    
            private Object target;
    
            public Object getDynamicProxyObject(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 {
                System.out.println("前置处理");
                Object result = method.invoke(target, args);
                System.out.println("后置处理");
                return result;
            }
        }
    
    }
    View Code

      3.4 参考博文

        01  02  03 

     

    4 代理模式

      说明:
        target -> 表示被代理者(目标者)
        proxy -> 表示代理者

      4.1 静态代理

        技巧01:静态代理的目标者和代理者都需要实现相同的接口

        》接口:代理者和目标者需要实现的接口

    package cn.xinagxu.static_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:27
     * @desc
     **/
    public interface ISservice {
        String someMethod01();
        void someMethod02();
    }
    ISservice.java

        》目标者:被代理对象

    package cn.xinagxu.static_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:25
     * @desc
     **/
    public class SomeService implements ISservice {
    
        public String someMethod01() {
            return "someMethod01";
        }
    
        public void someMethod02() {
            System.out.println("someMethod02");
        }
    
    }
    SomeService.java

        》代理者:代理目标者需要用到的代理类

    package cn.xinagxu.static_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:26
     * @desc
     **/
    public class StaticProxy implements ISservice {
    
        SomeService target;
    
        public StaticProxy(SomeService target) {
            this.target = target;
        }
    
        public String someMethod01() {
            String result = target.someMethod01();
            return result.toUpperCase();
        }
    
        public void someMethod02() {
            target.someMethod02();
        }
    }
    StaticProxy.java

        》测试类

    package cn.xinagxu.static_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:32
     * @desc
     **/
    public class StaticProxtTest {
    
        public static void main(String[] args) {
    
            SomeService target = new SomeService();
    
            StaticProxy proxy = new StaticProxy(target);
    
            String result = proxy.someMethod01();
    
            System.out.println(result);
    
            proxy.someMethod02();
    
        }
    
    }
    StaticProxtTest.java

      4.2 JDK动态代理

        技巧01:jdk动态代理不需要自己编写代理类,因为时直接使用Proxy类来创建代理对象的;目标者的类任然需要实现一个接口

        》接口:目标者需要实现的接口

    package cn.xinagxu.dynamic_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:27
     * @desc
     **/
    public interface ISservice {
        String someMethod01();
        void someMethod02();
    }
    ISservice.java

        》目标者

    package cn.xinagxu.dynamic_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:25
     * @desc
     **/
    public class SomeService implements ISservice {
    
        public String someMethod01() {
            return "someMethod01";
        }
    
        public void someMethod02() {
            System.out.println("someMethod02");
        }
    
    }
    SomeService.java

        》测试类

    package cn.xinagxu.dynamic_proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:40
     * @desc
     **/
    public class DynamicProxyTest {
    
        public static void main(String[] args) {
    
            final SomeService target = new SomeService();
    
            ISservice proxy = (ISservice) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            if ("someMethod01".equals(method.getName())) {
                                String str = (String) method.invoke(target, args);
                                return str.toUpperCase();
                            } else {
                                Object result = method.invoke(target, args);
                                return result;
                            }
    
                        }
                    }
            );
    
            System.out.println(proxy.someMethod01());
    
            proxy.someMethod02();
    
    
    
        }
    
    }
    DynamicProxyTest.java

      4.3 Cglib动态代理

        技巧01:Cglib动态代理不需要接口,借助 Enhancer 类来创建代理对象,借助 MethodInterceptor 接口的 intercept 方法来实现增强逻辑

        技巧02:Enhancer 就相当于JDK动态代理中的Proxy,MethodInterceptor 接口的 intercept 方法就相当于JDK动态代理中 InvocationHandler 中的 invoke 方法

        技巧03:使用Cglib动态代理时需要引入外部依赖cglib

            <!-- https://mvnrepository.com/artifact/cglib/cglib -->
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.2.7</version>
            </dependency>

        》目标者

    package cn.xinagxu.cglib_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 20:25
     * @desc
     **/
    public class SomeService {
    
        public String someMethod01() {
            return "someMethod01";
        }
    
        public void someMethod02() {
            System.out.println("someMethod02");
        }
    
    }
    SomeService.java

        》代理类工厂:用于创建代理对象的【PS: 可以不需要的,直接利用内部类实现】

    package cn.xinagxu.cglib_proxy;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import javax.xml.ws.Service;
    import java.lang.reflect.Method;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 21:03
     * @desc
     **/
    public class CglibFactory implements MethodInterceptor {
    
        private SomeService target;
    
        public CglibFactory(SomeService someService) {
            this.target = someService;
        }
    
        public CglibFactory() {
        }
    
        // 创建代理对象的方法
        public SomeService createProxy() {
    
            // 01 创建一个增强器
            Enhancer enhancer = new Enhancer();
    
            // 02 设置增强方
            enhancer.setSuperclass(target.getClass());
    
            // 03 接口回调方法
            enhancer.setCallback(this);
    
            // 04 返回代理对象
            return (SomeService) enhancer.create();
    
        }
    
        // 跟JDK动态代理的invoke方法差不多
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
            if ("someMethod01".equals(method.getName())) {
                String str = (String) method.invoke(target, objects);
                return str.toUpperCase();
            } else {
                Object result = method.invoke(target, objects);
                return result;
            }
    
        }
    
    }
    CglibFactory.java

        》测试类

    package cn.xinagxu.cglib_proxy;
    
    /**
     * @author 王杨帅
     * @create 2018-08-19 21:12
     * @desc
     **/
    public class CglibProxyTest {
    
        public static void main(String[] args) {
    
            SomeService target = new SomeService();
    
            // 创建代理对象
            SomeService proxy = new CglibFactory(target).createProxy();
    
            System.out.println(proxy.someMethod01());
    
            proxy.someMethod02();
    
        }
    
    }
    CglibProxyTest.java

      4.4 三种代理模的总结

        4.4.1 静态代理

          原材料:目标者、代理者、接口

          特点:迅速、目标者和代理者需要实现同一个接口

          比喻:

            目标者 -> 公司

            代理者 -> 法律顾问

        4.4.2 JDK动态代理

          原材料:目标者、接口

          特点:只需要目标者实现接口、目标者必须是final类型、代理者的类型必须是接口类型

            目标者 -> 个人

            代理者 -> 事务所

        4.4.3 Cglib动态代理

          原材料:目标者、cglib依赖

          特点:只需要目标者(目标者类型可以是类也可以是接口)、目标者不能是final类型、需要引入cglib依赖

            目标者 -> 个人

            代理者 -> 儿子

        4.4.4 三种代理方式源代码

          点击前往

     
  • 相关阅读:
    Qt中修改QtoolTip的样式
    字符编码笔记:ASCII、Unicode和UTF-8
    UML类图关系模式(C++代码说明)
    sql标签和include标签的使用
    mybatis动态SQL标签的用法
    <!CDATA[ ....... ]] > 用法详解
    Mybatis 中$与#的区别
    枚举
    ExtJs如何判断form表单是否被修改过详解
    Extjs二级联动combo省城市
  • 原文地址:https://www.cnblogs.com/NeverCtrl-C/p/9451673.html
Copyright © 2011-2022 走看看