• 设计模式系列:代理模式


    一、问题引入

    现在有这样一个场景:假设我们想邀请一位明星,那么一般我们不是直接联系明星,而是联系明星的经纪人,通过经纪人建立和明星的合作。在这里,明星就是一个目标对象,他只要负责活动演出就行了,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理模式在现实中的一个应用场景。

    同样地,假如我们想要租房,我们不可能每个小区每个住户去敲门问业主是否要出租,这样是不可行的,太浪费时间和精力了。我们只是想要租房,为什么要额外做这么多事呢?很简单,我直接通过中介公司来租房,由中介帮我们找房源,帮我们办理租房流程,签合同。我们只负责挑选自己喜欢的房源,然后付钱入住就可以了。这也是代理模式在现实中的一个应用。

    二、模式定义

    代理(Proxy)是一种设计模式,又叫委托模式,提供了对目标对象另外的访问方式,即通过代理对象去访问目标对象,由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。这样做的好处是:可以在目标对象实现的基础上,增加额外的功能,即扩展目标对象的功能。同时还能起到隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,方法就是代理类和委托类实现相同的接口。

    开闭原则,增加功能:代理类除了是委托类的中介之外,还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果进行处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共服务。例如,我们想给项目加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

    代理模式可以分为静态代理和动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。动态代理可以分为jdk动态代理和cglib动态代理。

    1、静态代理

    主题接口,具体类和代理类都需要实现的接口,里面的接口方法是要被代理的方法。如下:

    //主题接口
    public interface Subject {
        /**
         * 接口方法
         */
        public void request();
    }

    具体的实现类,是要被代理的类。如下:

    //具体实现类
    public class Concrete implements Subject {
        /**
         * 具体的业务逻辑实现
         */
        @Override
        public void request() {
            //业务处理逻辑
        }
    }

    代理类,相当于中介。如下:

    //代理类
    public class Proxy implements Subject {
    
        /**
         * 要代理的实现类
         */
        private Subject subject = null;
    
        public Proxy(Subject subject) {
            this.subject = subject;
        }
    
        /**
         * 实现接口方法
         */
        @Override
        public void request() {
            log.info("其他业务处理");
            
            this.subject.request();
            
            log.info("其他业务处理");
        }
    }

    测试类,客户端调用类。如下:

    public class Client {
        public static void main(String[] args) {
            Subject subject = new ConcreteSubject();
            Proxy proxy = new Proxy(subject);
            proxy.request();
        }
    }

    2、JDK动态代理

    定义一个和代理类相关联的InvacationHandler,如下:

    //定义和代理类相关联的InvacationHandler
    public class ProxyInvocationHandler<T> implements InvocationHandler
    {
        //invocationHandler持有的被代理对象
        T target;
        
        public ProxyInvocationHandler(T target) {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            log.info("其他业务处理");
    
            Object result = method.invoke(target, args);
            
            return result;
        }
    }

    测试类,客户端调用类。如下:

    //调用测试类
    public class Client {
        public static void main(String[] args) {
            Subject subject = new ConcreteSubject();
            
            //创建一个与代理对象相关联的InvocationHandler
            InvocationHandler invocationHandler = new ProxyInvocationHandler<Subject>(subject);
            
            //创建一个代理对象proxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
            Subject proxy = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, invocationHandler);
            
            //执行方法代理方法
            proxy.request();
        }
    }

    3、cglib动态代理

    JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

    定义代理类,如下:

    public class CglibProxy implements MethodInterceptor {
    
        private Object target;
        
        public Object getInstance(final Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            log.info("其他业务处理");
            Object result = methodProxy.invoke(object, args);
    
            return result;
        }
    }

    定义测试类,如下:

    public class CglibProxyTest {
        public static void main(String[] args){
        
            Subject subject = new ConcreteSubject();
            
            CglibProxy cglibProxy = new CglibProxy();
            
            Subject subjectProxy = (Subject) cglibProxy.getInstance(subject);
            
            subjectProxy.request();
        }
    }

    CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

    三、总结

    代理解决的问题:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可。代理模式还可以让我们完成与另一个类之间的关系的统一管理。但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

    代理模式使用了编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

    参考资料:

    1、https://www.cnblogs.com/leeego-123/p/10995975.html

    2、https://www.cnblogs.com/daniels/p/8242592.html

  • 相关阅读:
    ubuntu---系统备份
    Linux---基础篇学习记录
    ubuntu---如何进入高级选项
    ubuntu---【nvidia-smi】命令参数含义
    ubuntu---卡顿,强制关机,引发的一系列问题
    CUDA---安装提示“Not enough space on parition mounted at /”
    C++ ---usleep()功能
    ubuntu---常用命令
    yolo---训练时,命令行中加入-map 和 -dont_show
    jQuery火箭图标返回顶部代码
  • 原文地址:https://www.cnblogs.com/alan6/p/13172769.html
Copyright © 2020-2023  润新知