美文网首页
05- 代理模式

05- 代理模式

作者: 刘小刀tina | 来源:发表于2020-02-16 13:33 被阅读0次

    2. 什么是代理模式?

    :通过代理来 控制对对象的访问。
    比如:一个明星要开演唱会,明星本人只需要在舞台上唱歌,演唱会之前准备工作和演唱会之后的收尾工作都是交由明星助理做。这就是代理模式。

    真实角色:它只关注真正的业务逻辑,比如歌星唱歌。
    代理角色:在实现真正的业务逻辑前后可以附加一些操作,比如谈合同,布置场地,收钱等等。

    2-1. 静态代理模式【静态代理是我们自己创建一个代理类】
    **
     * 静态代理类:
     *          真实角色 &  代理角色 & 客户端
     **/
    //StaticProxy -- >代理角色
    public class StaticProxy {
        private Singer singer;
        public StaticProxy(Singer singer) {
            this.singer = singer;
        }
        public void sing(){
            System.out.println("准备唱歌的场地等事宜 ~");
            singer.Sing();
            System.out.println("场地收尾工作 ~");
        }
    }
    
    // --->客户端
    class Client{
        public static void main(String[] args) {
            StaticProxy staticProxy = new StaticProxy(new Singer());
            staticProxy.sing();
        }
    }
    
    //歌唱的接口
    interface Sing {
        public void Sing();
    }
    // 歌手实现唱歌的接口 ---> 真实角色
    class Singer implements  Sing{
        @Override
        public void Sing() {
            System.out.println("歌星小凯在唱歌 ~~");
        }
    }
    
    
    
    2-2. 动态代理【动态代理是程序自动帮我们生成一个代理,我们就不用管了,使用的更广泛】

    动态代理一般有两种方式:JDK 动态代理和 CGLIB 动态代理。

    A: JDK 动态代理【基于接口的代理】

    既然动态代理不需要我们去创建代理类,那我们只需要编写一个动态处理器就可以了。真正的代理对象由 JDK 在运行时为我们动态的来创建。

    这里说一下 Proxy.newProxyInstance() 方法,该方法接收三个参数:
    第一个参数指定当前目标对象使用的类加载器,获取加载器的方法是固定的;
    第二个参数指定目标对象实现的接口的类型;
    第三个参数指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。
    **
     * 动态代理之一:
     * 基于Jdk的动态代理,其底层实现是依赖于反射机制实现的代理
     **/
    //-- > 代理角色
    public class JdkProxy implements InvocationHandler {
        private Object  object;
        public JdkProxy(Object  object) {
            this.object = object;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("唱歌之前的准备工作~");
            Object invoke = method.invoke(object, args);
            System.out.println("唱歌之后的收尾工作~~");
            return invoke;
        }
    }
    //---->客户端
    class Clients{
        public static void main(String[] args) {
            Singinger singinger = new Singinger();
            JdkProxy jdkProxy = new JdkProxy(singinger);
            Object obj = Proxy.newProxyInstance(singinger.getClass().getClassLoader(), singinger.getClass().getInterfaces(), jdkProxy);
            //不能用转换为接口的实现类, 只能转换为接口
            Singing s = (Singing)obj;
            s.Sing();
        }
    }
    //--真实角色
    class Singinger implements Singing{
        @Override
        public void Sing() {
            System.out.println("歌星小凯在唱歌 ~~");
        }
    }
    interface  Singing{
        void Sing();
    }
    
    
    
    
    B: CGLIB 动态代理【基于子类的代理】

    其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑(所以不能对final修饰的类进行代理)

    package com.example.mayi.proxy;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    使用 CGLIB 需要实现 MethodInterceptor 接口,并重写intercept 方法,
    在该方法中对原始要执行的方法前后做增强处理。
    该类的代理对象可以使用代码中的字节码增强器来获取。
    **
     *基于 cglib的动态代理 ,其底层是借助asm框架来实现代理
     *依赖asm cglib 包
     **/
    public class CglibProxy implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] arr, MethodProxy methodProxy) throws Throwable {
            System.out.println("饭做好可以吃饭了~~");
            Object invokeSuper = methodProxy.invokeSuper(obj, arr);
            System.out.println("饭吃完了,剩下一些没洗的碗~~");
            return invokeSuper;
        }
    }
    
    //-->client
    class CLien{
        public static void main(String[] args) {
            CglibProxy cglibProxy = new CglibProxy();
            //cglib代理使用的是asm框架
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Eater.class);
            enhancer.setCallback(cglibProxy);
            Object obj = enhancer.create();
            Eat eat =  (Eat)obj;
            eat.Eat();
        }
    }
    
    //真实的角色
    class Eater implements  Eat{
        @Override
        public void Eat() {
            System.out.println("小凯在吃饭");
        }
    }
    interface Eat{
        void Eat();
    }
    
    基于接口的jdk动态代理和 基于子类的cglib动态代理的优缺点:

    动态代理 :
    (1)jdk动态代理 底层是基于java反射机制来实现的。所以jdk动态代理 在生产代理类的时候比较高效,要求必须是基于同一的接口

    (2)cglib动态代理底层是基于asm框架实现的。所以cglib动态代理在相关过程执行的时候效率比较高(原因是因为可以通过将asm生产的类进行缓存,这样解决了asm在生产类过程中低效的问题),要求必须是基于一个类。
    【asm其实就是java字节码控制】

    (1)若目标对象实现了若干接口,

    spring使用JDK的java.lang.reflect.Proxy类代理。
    优点:大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度
    缺点:JDK 实现动态代理需要通过接口定义业务方法由实现类执行,需为每一个目标类创建接口

    (2)若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类

    优点:
    因为代理类与目标类是继承关系,所以不需要有接口的存在;
    CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,所 花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 更为合适一些。

    缺点:
    因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好;
    对于final修饰的方法无法进行代理。


    3. Spring AOP 采用哪种代理?

    JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。
    Spring AOP 中的代理使用逻辑:
    如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。

    说出Spring的通知类型有哪些?
    写出创建代理对象需指定的三要素是什么?

    (1)设定目标对象 (2)设定代理接口 (3)设定拦截器的名字


    针对于这一块内容,我们看一下 Spring 5 中对应的源码是怎么说的。

    //@Overridepublic 
    public  AopProxy createAopProxy(AdvisedSupport config)throws AopConfigException {
    
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
           
            Class<?> targetClass = config.getTargetClass();
            
            if (targetClass == null) {
                thrownew AopConfigException
                ("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
            }
    
            // 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                returnnew JdkDynamicAopProxy (config);
            }
            // 配置了使用CGLIB进行动态代理或者目标类没有接口,那么使用CGLIB的方式创建代理对象
            returnnew ObjenesisCglibAopProxy (config);
            } else {
        
                // 上面的三个方法没有一个为true,那使用JDK的提供的代理方式生成代理对象
                returnnew JdkDynamicAopProxy (config);
            }
    
        }//if的大括号
    
        //其他方法略……
        
        
    }//类的大括号
    
    

    从上述源码片段可以看出,是否使用 CGLIB 是在代码中进行判断的,判断条件是:
    config.isOptimize()、config.isProxyTargetClass()
    和 hasNoUserSuppliedProxyInterfaces(config)。

    其中,config.isOptimize() 与 config.isProxyTargetClass()默认返回都是 false,这种情况下判断结果就由hasNoUserSuppliedProxyInterfaces(config)的结果决定了。

    简单来说,hasNoUserSuppliedProxyInterfaces(config) 就是在判断代理的对象是否有实现接口,有实现接口的话直接走 JDK 分支,即使用 JDK 的动态代理。

    相关文章

      网友评论

          本文标题:05- 代理模式

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