05- 代理模式

2020-02-16  本文已影响0人  刘小刀tina

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 的动态代理。

上一篇下一篇

猜你喜欢

热点阅读