zoukankan      html  css  js  c++  java
  • 设计模式之代理模式

    代理模式

    一:代理模式概述

    高中的时候遇到一个喜欢的女生,那时候我们都比较害羞,我们的交流全靠传话,有一个姑娘成了我们的中介,每天都给我们传递狗粮,就这样我们开心的过完了高中。然后大学。。。。好了继续代理模式的学习:当我们没办法访问某个对象的时候可以通过一个代理对象来间接访问。

    1.1 什么是代理

    代理是一种设计模式。当我们想要添加或修改现有类的某些功能时,我们创建并使用代理对象。使用代理对象而不是原始代理对象。通常,代理对象具有与原始代理对象相同的方法,并且在Java代理类中通常会扩展原始类。代理有一个原始对象的句柄,可以调用该方法。

    1.2 代理的概念结构图

    结构图:

    1545400167418

    角色说明如下:

    • Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
    • Proxy(代理主题角色):包含了对真实主题的引用,从而可以在任何时候操作真实主题对象
    • RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

    1.3 代理可以做什么

    • 方法启动和停止时记录信息
    • 对参数执行额外检查
    • 模仿原始类的行为
    • 实现对某些资源的延时访问

    在实际应用中,代理类不直接实现该功能。遵循单一责任原则,代理类仅执行代理,并且实际行为修改在处理程序中实现。当调用代理对象而不是原始对象时,代理会决定是否必须调用原始方法或某个处理程序。处理程序可以执行其任务,也可以调用原始方法。

    我们常常接触的代理模式主要分为两种:静态代理模式动态代理模式


    二:静态代理模式

    2.1 静态代理概念

    1. 代理对象需要实现和目标对象相同的接口或者继承相同的父类。
    2. 每一个目标对象都需要对应的代理类。

    2.2 静态代理实战

    模拟:要求更新数据前后记录日志。

    抽象接口:IPerson

    public interface IPerson {
        // 更新信息
        void update();
    }
    

    目标对象Person(要被代理的对象)

    public class Person implements IPerson {
        @Override
        public void update() {
            System.out.println("更新");
        }
    }
    

    代理对象

    public class PersonProxy implements IPerson {
    
        // 接收目标对象(要被代理的对象) 针对抽象编程
        private IPerson target;
        public PersonProxy(IPerson target){
            this.target = target;
        }
        // 扩展的功能 记录日志开始
        private void handleBefore(){
            System.out.println("进入更新方法--获取参数");
        }
        // 扩展的功能 记录日志结束
        private void handleAfter(){
            System.out.println("更新成功");
        }
        // 同样的是更新方法,我们加入了记录日志的功能
        @Override
        public void update() {
           handleBefore();
           target.update();
           handleAfter();
        }
    }
    

    客户端

    public class StaticProxyClient {
        public static void main(String[] args) {
            // 目标对象
            IPerson target = new Person();
            // 代理对象
            PersonProxy proxy = new PersonProxy(target);
            // 执行代理的方法
            proxy.update();
        }
    }
    

    总结:静态代理可以在不侵入目标对象的前提下扩展功能,但是代理对象需要和目标对象实现相同的接口,导致了大量的代理类,会增加维护量,那么便引入接下来的动态代理


    三:动态代理模式

    3.1 动态代理概念

    相较于静态代理,动态代理不再需要写各个静态代理类,只需要简单地指定一组接口以及目标类对象就可以动态的获得对象,可以简单的理解这两个的区别:静态代理的代理类是程序员手动创建的,而动态代理的代理类是由程序创建的

    3.2 JDK动态代理和Cglib动态代理

    3.2.1 JDK动态代理

    1. 编写抽象主题接口
    2. 实现InvocationHandler接口来自定义自己的InvocationHandler。
    3. 通过Proxy.newProxyInstance获得动态代理类

    --------------编码实战 ---------------

    抽象主题角色

    public interface IPerson {
        void update();
    }
    

    真实主题

    public class Person implements IPerson {
        @Override
        public void update() {
            System.out.println("更新");
        }
    }
    

    自定义InvocationHandler

    public class MyInvocationHandler implements InvocationHandler {
        //目标对象
        private Object target;
        public MyInvocationHandler(Object target){
            this.target = target;
        }
        // 横向功能扩展
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------更新前记录日志-------------");
            //执行相应的目标方法
            Object res = method.invoke(target,args);
            System.out.println("------更新后记录日志-------------");
            return res;
        }
    }
    

    客户端调用

    public class client {
        public static void main(String[] args) {
            // 动态获取代理类
            IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(
                    IPerson.class.getClassLoader(), // 加载接口的类加载器
                    new Class[]{IPerson.class}, // 一组接口
                    new MyInvocationHandler(new Person())); // 自定义的InvocationHandler
            proxyInstance.update();
        }
    }
    

    结果打印

    ------更新前记录日志-------------
    更新
    ------更新后记录日志-------------

    ---------------使用静态工厂方法来重构代码----------------

    抽象主题角色和真实主题角色的代理不需要变动,我们新建一个proxyFactory,将MyInvocationHandler的代码和Proxy.newProxyInstance结合起来(其实这两部分就是整个jdk代理的核心要实现的内容)。

    ProxyFactory

    注意点:

    1. effective java 推荐使用静态工厂方法来创建类实例
    2. 对于内部类调用外层方法参数必须要用final修饰
    3. 泛型T和Object的区别
    public class ProxyFactory {
        public static<T> Object  getProxyInstance(final T target){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("------更新前记录日志-------------");
                            Object proxyInstance = method.invoke(target, args);
                            System.out.println("-----更新后记录日志-------------");
                            return proxyInstance;
                        }
                    });
        }
    }
    

    那么客户端的调用

    public class JDKProxyClient {
        public static void main(String[] args) {
            IPerson target = new Person();
            IPerson proxy = (IPerson) ProxyFactory.getProxyInstance(target);
            proxy.update();
        }
    }
    

    看完了小例子,再回顾一下InvocationHandler是干什么的:

    InvocationHandler:

    1. InvocationHandler是由代理实例的调用处理程序实现的接口。 每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并调度到其调用处理程序的invoke方法 。(调用处理程序指的就是我们的MyInvocationHandler类)
    • @Param proxy 调用该方法的代理实例
    • @Param Method 与代理实例上调用的接口方法对应的 Method实例。 Method对象的声明类将是声明方法的接口,它可以是代理接口继承方法的代理接口的超接口
    • @param args 包含在代理实例上的方法调用中传递的参数值的对象数组,如果接口方法不带参数,则为null。 原始类型的参数包含在适当的原始包装类的实例中,例如java.lang.Integer
    • @return 从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是基本类型,则此方法返回的值必须是相应原始包装类的实例; 否则,它必须是可分配给声明的返回类型的类型。 如果此方法返回的值为null且接口方法的返回类型为原始值,则代理实例上的方法调用将抛出 NullPointerException。 如果此方法返回的值与上面描述的接口方法声明的返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException
    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
    }
    

    3.2.2 JDK动态代理总结

    • jdk动态代理不再需要编写许多静态的代理类,但是目标类对象还是必须实现接口,那么当目标类不实现接口的话,jdk就无法再提供动态代理了。

    3.2.3 Cglib动态代理

    jdk提供的代理有个明显的缺点:需要目标对象实现一个或者多个接口。而假如你需要代理没有接口的类,可以使用Cglib库。

    CGLIB是一个强大、高性能的代码生成库,被广泛的运用于AOP框架提供方法拦截,在实现内部,CGLIB库使用了ASM这轻量性能高的字节码操作框架来转化字节码,产生新类。spring的aop默认使用JDK动态代理,除非强制使用CGLIB。但是有个主意点:CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类

    实现的基本步骤如下:

    1. 编写目标对象
    2. 编写MethodInterceptor,当代理对象调用方法时候,会调用该类的intercept方法
    3. 编写Cglib动态代理类

    3.2.4 Cglib实例演示

    目标对象

    public class Hello {
        public String sayHello(String msg){
            return "hello" + msg;
        }
    }
    

    拦截器:类似于jdk代理的InvocationHandler

    public class MyMethodInterceptor implements MethodInterceptor {
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println(" --- 记录日志开始--- ");
            return methodProxy.invokeSuper(o, objects);
        }
    }
    
    public class client {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            // 设置父类,cglib代理将会产生hello的子类
            enhancer.setSuperclass(Hello.class);
            // 设置回调方法,将会执行MyMethodInterceptor的interapt()方法
            enhancer.setCallback(new MyMethodInterceptor());
            // 创建代理对象
            Hello proxy = (Hello) enhancer.create();
            System.out.println(proxy.sayHello("codecarver"));
        }
    }
    

    结果打印

    --- 记录日志开始---
    hello:codecarver

    上述代码通过CGLIB的的Enhancer来生成代理对象,通过将目标对象设置为父类,以及设置回调方法,生成继承自父类的代理类,所以我们的目标对象绝对不能是final类型,我们可以在intercept方法中加入我们要增强的逻辑,通过methodProxy.invokeSuper(o, objects)来将调用转发给目标对象(原始对象)。可以再看看jdk的动态代理,其实很类似,只不过一个是目标对象必须实现接口,一个是目标对象必须可以被继承


    四:总结

    1. 代理可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
    2. 静态代理模式要求代理对象实现和目标对象一样的接口,且由程序员提前写好。会产生大量的代理类。
    3. 动态代理可以动态生成代理类,不会产生大量的静态class文件。
    4. 动态代理也是需要代理对象和目标对象实现一样的接口,但要求是目标对象必须实现接口。
    5. glib代理不要求目标对象实现接口,它是根据目标对象生成子类,让子类作为代理对象去工作的

  • 相关阅读:
    第三话-单一职责原则
    2014辽宁省赛 Repeat Number
    【iOS】Swift字符串截取方法的改进
    Android中特殊图形的生成样例
    Tiny server:小型Web服务器
    C语言复合字面量的使用
    浅析数据库连接池(一)
    答复学习汇编不顺利的准大学生
    Struts2拦截器
    7.数据本地化CCString,CCArray,CCDictionary,tinyxml2,写入UserDefault.xml文件,操作xml,解析xml
  • 原文地址:https://www.cnblogs.com/1314xf/p/10160932.html
Copyright © 2011-2022 走看看