设计模式-代理模式-场景应用解析

738 阅读8分钟

代理模式

二十三种设计模式,我们先吃透代理模式,用通俗的图和最通俗的话 来解释什么是代理模式:

代理模式中的重要成员:制作人(代理对象)和 歌星(目标对象),这时候听众(调用服务方) 想听歌星唱一曲《花田里犯了错》,歌星说:最近嗓子不舒服,这首《花田里犯了错》,让制作人传唱给你听。于是制作人把歌星录好的这首《花田里犯了错》传唱给了听众(调用服务方)。

image.png

代理模式的核心点是:制作人(代理对象)与 歌星(目标对象).制作人(代理对象)是对歌星(目标对象)的扩展,并会调用 歌星(目标对象)并且使用 歌星(目标对象)里的方法。

搞清楚了代理模式,我们就来看下代理模式的三种实现方式

  • 静态代理
  • 动态代理
  • Cglib代理 接下来,我们就用上述例子来比较这三种代理的区别,先看静态代理。

1、静态代理

接口:金碧辉煌的大房子

/**
 * 唱片公司-接口
 */
public interface Dafangzi {
    void song();
}

目标对象类:歌星

/**
 *歌星
 * 住在公司里
 **/
public class Girl implements Dafangzi {

    @Override
    public void song() {
        System.out.println("歌星唱歌:《花田里犯了错》");
    }


}

代理对象类:制作人

/**
 *制作人
 *住在公司里
 **/
public class ZhizuoProxy implements Dafangzi{
    //接收保存目标对象
    private Dafangzi dafangzi;
    public ZhizuoProxy(Dafangzi dafangzi){
        this.dafangzi=dafangzi;
    }

    @Override
    public void song() {
        System.out.println("制作人 开始播放 歌星 唱的《花田里犯了错》 ");
        dafangzi.song();//执行目标对象(歌星)的方法,这里是歌星唱的歌曲
        System.out.println("制作人 播放完毕");
    }


}

测试类:客户

/**
 *听众 测试听歌
 * 静态代理
 **/
public class UserTest {
    public static void main(String[] args) {
        //目标对象(歌星)
        Girl star = new Girl();
        //代理对象(制作人),把目标对象(歌星) 传给 代理对象(制作人),建立代理关系
        ZhizuoProxy proxy = new ZhizuoProxy(star);
        //此处执行的是 代理对象的方法 这里是代理对象(制作人)播放的《花田里犯了错》
        //并不是歌星本人唱的
        proxy.song();

    }
}

执行完结果如下

image.png

我们总结一下静态代理的特点

1、(优点)听众可以不用见到歌星本人,就可以听到歌星唱的歌!由制作人来传唱歌星唱的歌!并且制作人可以通过技术手段给歌星的歌曲进行修音、配节奏!让听众听到的歌曲更嗨! (不修改目标对象的功能 前提下,就可以扩展、延伸目标对象的功能。)

2、(缺点)无论是歌星本人、还是制作人 都离不开唱片公司!(目标对象、代理对象 都必须实现同一个接口!!!!!

3、(缺点)如果公司要推出新的业务,比如说唱,那么无论是歌星、制作人 都需要相应的进行录制歌唱歌曲。 (代理对象需要与目标对象实现一样的接口,代理类会很多。如果接口增加方法,目标对象与代理对象都要维护节后新增加的方法。)

知道了静态代理的优点和缺点,我们接下来看下动态代理的特点

2、动态代理

还是以歌星唱歌的demo为例子, 先看图:

image.png

超级制作人 SuperProxy(工厂)

import java.lang.reflect.Proxy;

/**
 *超级制作人SuperProxy
 *不用实现接口
 *独立的类
 **/
public class SuperProxy {
    //维护一个目标对象
    private Object obj;
    public SuperProxy(Object obj){
        this.obj=obj;
    }

    //给目标对象生成代理对象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("制作人 开始播放 歌星 唱的《花田里犯了错》 ");
                    //执行目标对象(歌星)的方法,这里是歌星唱的歌曲
                    Object result = method.invoke(obj, args);
                    System.out.println("制作人 播放完毕");
                    return result;
                }
        );
    }

}

测试类:听众


/**
 *听众 测试听歌
 * 静态代理
 **/
public class UserTest1 {
    public static void main(String[] args) {
        // 目标对象
        Dafangzi target = new Girl();
        //class dailimoshi.jingtai.Girl
        System.out.println(target.getClass());
        // 给目标对象(歌星),创建代理对象(超级制作人SuperProxy)
        //超级制作人SuperProxy不用局限在唱片公司里,不依赖唱片公司,他可以出差去各地播放歌星的歌曲
        Dafangzi proxy = (Dafangzi) new SuperProxy(target).getProxyInstance();
        //class com.sun.proxy.$Proxy0   在内存中 动态生成的 代理对象
        System.out.println(proxy.getClass());
        //此处执行的是 代理对象的方法 这里是代理对象(制作人)播放的《花田里犯了错》
        //并不是歌星本人唱的
        proxy.song();
    }

}

运行后的结果:

image.png

可以看到 结果和静态代理一样,歌星(目标对象),没有任何变化,但是制作人(代理对象)变化巨大,和静态代理中的制作人不同,这次的超级制作人满足了以下几个特点:

  • 不用待在唱片公司里 (代理对象不需要实现接口,目标对象一定要实现接口,否则动态代理无效)
  • 超级制作人(代理对象)需要声明是哪家唱片公司的制作人,以及歌星唱的歌曲风格。(动态代理使用JDK的API,动态的在内存中构建代理对象,需要我们指定创建代理对象、以及目标对象实现的接口的类型。) Dafangzi proxy = (Dafangzi) new SuperProxy(target).getProxyInstance();
  • 此处的动态代理也被称为:接口代理、JDK代理 JDK中生成代理对象的API 代理类所在包:java.lang.reflect.Proxy
    JDK实现的代理只需要使用newProxyInstance方法,newProxyInstance方法中有三个入参
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
  • ClassLoader loader:指定当前目标对象(歌星)使用类加载器
  • Class<?>[] interfaces:目标对象(歌星) 实现的接口的类型,使用泛型方式
  • InvocationHandler h:事件处理,执行目标对象(歌星)的方法时,会触发事件处理器的方法,会把当前执行目标对象(歌星)的方法作为参数传入返回给代理对象(超级制作人)

接下来我们看代理的第三种实现方式:Cglib代理

3、Cglib代理

我们回顾下 刚才讲过的静态代理 和 动态代理

  • 静态代理要求 :目标对象、代理对象都要实现相同的接口。
  • 动态代理要求:目标对象需要实现接口、代理对象不需要。

问题来了,

  • 动态代理 代理对象不需要实现接口,目标对象是不是也能不实现接口呢???
  • 用歌星的例子来说,我是一个草根歌手 并没有签约任何唱片公司,我能否也被超级制作人代理呢?
  • 答案是可以的!使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。 还是上面最初的唱歌例子: 需要引入spring-core的jar包:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.2.6.RELEASE</version>
</dependency>

目标对象:草根歌星

/**
 *草根歌星
 **/
public class Boy  {
     void shuangjg() {
        System.out.println("草根明星 唱歌:《双节棍》");
    }
}

cglib代理:超级制作人


/**
 *Cglib子类代理
 *对Boy在内存中 动态构建一个子类对象
 **/
public class CgLibProxy implements MethodInterceptor {
    //目标对象
    private Object target;
    public CgLibProxy(Object target) {
        this.target = target;
    }
    //给目标对象创建一个代理对象
    public Object getProxyInstance() {
        //1.代理工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(target.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("制作人 开始播放 歌曲 ");
        //执行目标对象的方法(这里执行的就是草根歌手的自己唱的歌)
        Object result = method.invoke(target, args);
        System.out.println("制作人 播放完毕");
        return result;
    }
}

测试类:听众


/**
 *听众 听歌
 * 静态代理
 **/
public class UserTest2 {
    public static void main(String[] args) {
        //目标对象(草根歌星)
        Boy target = new Boy();
        //代理对象(超级制作人)
        Boy proxy = (Boy)new CgLibProxy(target).getProxyInstance();
        //执行代理对象的方法
        proxy.shuangjg();
    }

}

Cglib是一个强大的高性能的代码生成包。

  • 它广泛的被许多AOP的框架使用,例如:Spring AOP和dynaop,为他们提供方法的interception拦截等;
  • hibernate用Cglib来实现PO(Persistent Object 持久化对象)字节码的动态生成。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

总结

我们在什么情况下使用代理模式:

  • 设计模式的开闭原则,思想是:对修改关闭、对扩展开放,尤其是在代码重构的过程中,我们对其中一些底层代码逻辑无法迅速的下手修改,这时候就可以使用代理类对目标类进行一个扩展、增强。
  • Spring的AOP机制就是采用动态代理的机制来实现切面编程。

每个代理方式适合的场景:

  • 静态代理 :目标对象、代理对象都需要实现同一个接口 使用的场景:我们在代码编译时就确定了目标对象的类是哪一个,这种就可以使用静态代理。比如:架构解耦过程中的拆分对象,一些底层框架的设计;
  • 动态代理 JDK代理:代理对象不需要实现接口、目标对象需要实现,应用的场景比如:RPC框架和Spring AOP机制。
  • Cglib动态代理:目标对象、代理对象都不需要实现接口,使用的场景:不需要频繁创建代理对象的应用,比如Spring中默认的单例bean,只需要在容器启动时生成一次代理对象。