• Java设计模式学习记录-代理模式


    代理模式

    代理模式是常见设计模式的一种,代理模式的定义是:为其他对象提供一种代理以控制对这个对象的访问

    在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    静态代理

    理解设计模式是比较枯燥的,所以还是以举例子的方式来进行理解,

    例如:公司开年会想找个明星来表演,那么并不会直接联系明星(主要还是联系不上),而是会联系明星的经纪人,明星就是被代理的对象,而经纪人就是代理对象。明星只需要准备来参加年会时应该表演什么节目就可以,其他的出场费之类的事情就交给经纪人来处理就好了。代理对象可以理解为被代理对象的扩展,能做被代理对象不能做的事情,也可以调用代理对象做事情。

    那么用代码实现这个场景是什么样子的呢?

    执行合作方法的接口

    /**
     * @Description: 经纪公司接口,代理对象和被代理对象都需要实现的接口
     */
    public interface Company {
        /** 合作 */
        void cooperation();
    }

    被代理对象

    /**
     * @Description: 目标对象-明星(被代理对象)
     */
    public class Start implements Company {
    
        @Override
        public void cooperation() {
            System.out.println("is show time");
        }
    }

    代理对象

    /**
     * @Description: 经纪人(代理对象)
     */
    public class Agent implements Company {
    
        private Company company;
    
        public Agent(Company company)
        {
            this.company = company;
        }
    
        @Override
        public void cooperation()
        {
            System.out.println("收出场费,化妆等等");
            company.cooperation();
            System.out.println("收拾行李,打道回府");
        }
    }

    测试类

    import org.junit.Test;
    
    /**
     * @Description: 测试类
     */
    public class ProxyTest {
    
        @Test
        public void AnnualMeeting()
        {
            //目标对象
            Start start = new Start();
            //构建代理对象,生成代理关系
            Agent agent = new Agent(start);
            //用代理对象执行被代理对象的动作
            agent.cooperation();
            
        }
    
    }

    输出结果:

    收出场费,化妆等等
    is show time
    收拾行李,打道回府

    静态代理的特点是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。有时候不方便修改别人的代码或者是引入的一个功能,需要进行功能扩展一下才能适用于自己的业务实现,可以使用代理模式来进行设计。

    但是静态代理的实现基础是一个目标对象对应一个代理对象,并且在编译时就已经维护好了代理关系,如果目标对象是多个那么就会需要多个代理对象,这样在更新目标的对象的时候还需要更新代理对象,当代理对象持续增加时维护成本就变得非常困难。

    针对于这种情况,动态代理应运而生。

    动态代理

    JDK代理

    动态代理的代理对象不需要和目标对象共同实现接口,而是利用JDK的API,动态的在内存中构建代理对象。

    动态生成代理对象需要调用JDK中的java.lang.reflect.Proxy类的newProxyInstance方法,这个方法需要三个参数:

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,]Class<?>[] interfaces,InvocationHandler h)
    ClassLoader loader:类加载器,用来加载目标对象类,因为是在运行时获得目标对象,所以肯定需要用到反射。
    Class<?>[] interfaces:目标对象类实现的接口集合,这些接口中定义目标对象可以执行的方法。
    InvocationHandler h:这个参数代表的是动态代理对象在调用方法的时候,会将方法转发到哪一个invocationHandler对象身上,InvocationHandler是个接口,
    需要自己实现它,然后定义自己的动态代理执行方法。
    创建包含动态代理对象具体执行方法的实现类。
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * @Description: 包含动态代理对象具体执行方法的实现类
     */
    public class MyInvocationHandler implements InvocationHandler {
    
        private Company company;
    
        public MyInvocationHandler(Company company)
        {
            this.company = company;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
    
            System.out.println("收出厂费,化妆等");
            //具体执行方法
            Object result = method.invoke(company,args);
    
            System.out.println("收拾现场,卸妆,打道回府");
    
            return result;
        }
    }

    测试类

    import org.junit.Test;
    import java.lang.reflect.Proxy;
    
    /**
     * @Description: 测试类
     */
    public class DynamicProxyTest {
    
        @Test
        public void AnnualMeeting()
        {
            //创建目标对象
            Company start = new Start();
            //创建代理对象需要执行的方法处理对象
            MyInvocationHandler myInvocationHandler = new MyInvocationHandler(start);
            //获得目标对象的类加载器
            ClassLoader classLoader = start.getClass().getClassLoader();
            //创建动态代理对象
            Company proxy = (Company) Proxy.newProxyInstance(classLoader,start.getClass().getInterfaces(),myInvocationHandler);
            //用动态代理对象执行目标对象的方法
            proxy.cooperation();
        }
    }

    输出结果:

    收出厂费,化妆等
    is show time
    收拾现场,卸妆,打道回府

    JDK动态代理的特点:代理对象不需要实现接口,但是目标对象必须实现接口。

    那么如果在实际的业务中目标对象确实没有实现接口,怎么办呢?

    遇到这种情况的时候就需要时cglib动态代理了。

    Cglib代理

    Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

    • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)。
    • Cglib包的底层是通过使用一个小块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

    在实现cglib代理时需要引入cglib的jar包,但是spring核心功能已经包含了cglib的功能,所以引入spring-core的jar包就可以了。

    需要注意的是:代理的类不能为final,否则报错,目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

    没有实现接口的目标对象类

    /**
     * @Description: 没有经纪公司的明星,就行像最近以个人练习生出道的蔡徐坤
     */
    public class AloneStart {
        /** 合作 */
        public void cooperation() {
            System.out.println("is show time");
        }
    
    }

    生成Cglib代理对象的类

    import org.mockito.cglib.proxy.Enhancer;
    import org.mockito.cglib.proxy.MethodInterceptor;
    import org.mockito.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    
    /**
     * @Description: 生成代理对象的类
     */
    public class CglibProxy implements MethodInterceptor {
    
    
        private AloneStart aloneStart;
    
        public CglibProxy(AloneStart aloneStart)
        {
            this.aloneStart = aloneStart;
        }
    
        /**
         * 创建代理对象
         * @return
         */
        public Object getProxyInstance()
        {
            //动态代理工具类
            Enhancer enhancer = new Enhancer();
            //设置父类
            enhancer.setSuperclass(aloneStart.getClass());
            //设置回调函数调用对象
            enhancer.setCallback(this);
            //返回代理对象
            return enhancer.create();
    
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable
        {
            System.out.println("收出厂费,化妆等");
            //执行代理方法
            methodProxy.invokeSuper(obj,objects);
            System.out.println("卸妆,回家");
            return null;
        }
    }

    测试类

    import org.junit.Test;
    
    /**
     * @Description: 测试类
     */
    public class CglibProxyTest {
    
        @Test
        public void cglibTest()
        {
            //创建目标对象
            AloneStart aloneStart = new AloneStart();
            //创建代理对象
            AloneStart startProxy = (AloneStart) new CglibProxy(aloneStart).getProxyInstance();
            //用代理对象执行目标对象的方法
            startProxy.cooperation();
        }
    }

    输出结果:

    收出厂费,化妆等
    is show time
    卸妆,回家

    jdk采用反射机制调用委托类的方法,而cglib采用类似索引的方式直接调用委托类方法;

    还有需要注意的是:

    在Spring的AOP中

    如果加入容器的目标对象有实现接口,用JDK代理
    如果目标对象没有实现接口,用Cglib代理

     参考:

    Java的三种代理模式: https://www.cnblogs.com/cenyu/p/6289209.html

    说说代理模式:http://www.importnew.com/26116.html

  • 相关阅读:
    EF数据迁移完整步骤
    ajax跨域最全解决方案
    WPF控件与WPF窗体
    WPF模板是把控件MVC模式化
    对象与类型
    Java加权负载均衡策略
    db2列式存储
    linux离线安装mongodb及java调用
    python合并目录下excel数据
    python多线程迁移db2数仓9T数据
  • 原文地址:https://www.cnblogs.com/jimoer/p/8899395.html
Copyright © 2020-2023  润新知