美文网首页
代理模式及其应用

代理模式及其应用

作者: 文景大大 | 来源:发表于2022-06-07 16:58 被阅读0次

    一、代理模式简介

    代理模式旨在为服务类和客户类之间插入一个代理类,由代理类为客户类进行服务的代理访问。同时也可以对客户的行为进行增强和控制,但对客户类来说是透明的。现实中的例子有(租客、房屋中介、房子)、(结婚新人、婚庆公司、婚礼)、(业主、物业公司、小区管理)等,可以看到,客户都是通过中介代理来完成对目标资源的控制,同时中介又对资源的管理做了一定程度的增强和控制。

    要想实现代理模式,必须要满足如下的要求:

    • 定义行为接口;
    • 服务类和代理类都要实现行为接口;

    默认情况下实现的代理模式都是静态代理,参见如下章节。

    二、静态代理

    public interface ISubject {
        void request();
    }
    
    @Slf4j
    public class RealSubject implements ISubject{
        @Override
        public void request() {
            log.info("RealSubject!");
        }
    }
    
    @Slf4j
    public class Proxy implements ISubject{
        private ISubject subject;
    
        public Proxy(ISubject subject){
            this.subject = subject;
        }
    
        @Override
        public void request() {
            // 代理类执行服务类职能前后可以进行增强和控制
            before();
            subject.request();
            after();
        }
    
        public void before(){
            log.info("Proxy before...");
        }
    
        public void after(){
            log.info("Proxy after...");
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            Proxy proxy = new Proxy(new RealSubject());
            proxy.request();
        }
    
    }
    

    静态代理的特点就是一个服务类对应一个代理类,但是如果服务类比较多的时候,我们就必须创建很多代理类,显得很冗余,因此才有了动态代理。

    三、动态代理

    相比于静态代理,我们无需为每一个服务类创建一个代理类,可以依靠Java的反射机制,在程序运行期间,动态地为目标服务对象创建代理对象,因此简化了编程工作,提高了系统地可扩展性。

    3.1 JKD动态代理

    @Slf4j
    public class JdkHandler implements InvocationHandler {
        /**
         * 需要代理的目标服务对象
         */
        private Object object;
    
        public JdkHandler(Object object){
            this.object = object;
        }
    
        /**
         * 获取目标服务对象的代理
         * @return
         */
        public Object getProxy(){
            Class<?> clazz = object.getClass();
            return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
        /**
         * 调用目标服务对象的某个方法,同时也可以对服务进行增强和控制
         * @param proxy 代理实例
         * @param method 目标服务对象的某个方法
         * @param args 目标服务对象的方法入参
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 增强行为
            before();
            // 调用目标服务对象的某个方法
            Object result = method.invoke(object, args);
            // 增强行为
            after();
            return result;
        }
    
        public void before(){
            log.info("代理增强行为 before...");
        }
    
        public void after(){
            log.info("代理增强行为 after...");
        }
    }
    
    // 动态代理jdk
    public static void main(String[] args) {
        JdkHandler jdkHandler = new JdkHandler(new RealSubject());
        // 要求目标服务类必须有实现的接口
        ISubject subject = (ISubject)jdkHandler.getProxy();
        subject.request();
    }
    

    可以看到,我们的代理类JdkHandler必须要实现InvocationHandler接口,而其内部引用的目标服务对象则是通用的Object类型,而不是如上具体的ISubject,实现了解耦通用。当执行目标服务对象中的任意方法时,代理类JdkHandler中的invoke方法就会得到执行。

    3.2 Cglib动态代理

    @Slf4j
    public class CglibInterceptor implements MethodInterceptor {
        /**
         * 需要代理的目标服务对象
         */
        private Object obj;
    
        public CglibInterceptor(Object obj){
            this.obj = obj;
        }
    
        /**
         * 获取目标服务对象的代理
         * @return
         */
        public Object getProxy(){
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(obj.getClass());
            // 设置拦截器,回调对象设置为自己
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            // 增强行为
            before();
            // 调用目标服务对象的某个方法
            Object result = methodProxy.invoke(obj, objects);
            // 增强行为
            after();
            return result;
        }
    
        public void before(){
            log.info("代理增强行为 before...");
        }
    
        public void after(){
            log.info("代理增强行为 after...");
        }
    }
    
    // 动态代理cglib
    public static void main(String[] args) throws Exception {
        CglibInterceptor cglibInterceptor = new CglibInterceptor(new RealSubject());
        // 不要求目标服务类有实现的接口
        RealSubject subject = (RealSubject) cglibInterceptor.getProxy();
        subject.request();
    }
    

    如上示例使用的cglib在spring中自带的,因此不用单独引入cglib的maven包。总结下来,使用JDK动态代理和Cglib动态代理的区别如下:

    • JDK动态代理生成的代理对象是实现目标服务对象的接口,因此要求目标服务对象必须有实现的接口;
    • Cglib动态代理生成的代理对象是继承目标服务对象的类,因此不要求目标服务对象必须有实现的接口,但是目标服务对象的类和需要被代理的方法不能是final的,不然没法代理;
    • 它们的原理都是动态地生成一个新的代理类,去实现被代理方法的增强逻辑;
    • JDK动态代理生成效率快,执行效率慢,因为要使用反射;Cglib动态代理生成逻辑复杂,效率慢,但是执行效率快,不需要反射;

    四、应用案例

    4.1 通用案例

    • 延迟加载,通过代理类来控制,当只有真正需要服务类的时候才进行初始化;
    • 访问控制,对客户端调用服务端的请求进行鉴权控制;
    • 屏蔽远程服务,代理类可以将调用远程服务的网络细节隐藏起来,使得客户端以为是在调用本地的服务;
    • 日志记录,代理类可以在调用服务的前后增加日志的记录;
    • 缓存请求结果,代理类可以将调用服务端的结果进行缓存;

    4.2 具体案例

    • Spring中的ProxyFactoryBean的getObject;
    • Spring中实现AOP的AopProxy、CglibAopProxy、JdkDynamicAoproxy;当目标对象有实现接口时Spring会使用后者jdk的动态代理,当目标对象没有实现接口时就会使用Cglib动态代理。当然也可以通过配置来强制每次都使用Cglib动态代理。
    • MyBatis中的MapperProxyFactory和MapperProxy,来生成Dao的动态代理,从而和Mapper配置文件映射来实现数据库的操作;

    五、使用总结

    5.1 优点

    • 在客户端毫无察觉的情况下增强和控制服务对象;
    • 代理类可以对服务类的生命周期进行管理;
    • 即使服务对象还不存在,代理类也可以正常工作;
    • 可以基于服务类创建多个代理类,进行不同的增强逻辑,符合开闭原则;
    • 将代理对象和目标服务对象分离,实现了解耦,增强了可扩展性;

    5.2 缺点

    • 会增加代理类,增强和控制逻辑会将代码变得复杂;
    • 代理类的增强和控制逻辑会导致服务响应延迟;

    5.3 静态代理和动态代理的比较

    • 静态代理需要书写源码来实现代理操作,一旦目标服务类增加了新的方法,就必须同步在代理类中增加代理操作,而动态代理是在运行时动态生成目标服务类的代理对象,无需修改或者增加代理类的源码,符合开闭原则;

    相关文章

      网友评论

          本文标题:代理模式及其应用

          本文链接:https://www.haomeiwen.com/subject/mumemrtx.html