侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

设计模式--代理模式

林贤钦
2020-04-24 / 0 评论 / 9 点赞 / 653 阅读 / 0 字
温馨提示:
本文最后更新于 2020-05-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

代理模式(Proxy Pattern)

1. 介绍

1.1 模式说明

通俗的来讲代理模式就是我们生活中常见的中介。

  1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.

这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

  1. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

1.2 主要作用

  1. 中介隔离作用
    在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

  2. 开闭原则,增加功能
    理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能

    这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。

    • 代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。

    • 代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。

    • 真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。

    例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

1.3代理模式分类

代理创建的时期来进行分类

静态代理、动态代理

动态代理又很多,比如JDK,CGLIB,javassist,ASM

期中最常用的动态代理技术有两种,一种是JDK动态代理,这是JDK自带的功能,另外一种是CGLIB,这是第三方提供的,目前spring常用JDK和CGLIB,而Mybatis还使用了javassist。

2、 静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

2.1原理类图

2.2 实现代码

接口:

public interface BuyHouse {
    void buyHouse();
}

实现类:

public class BuyHouseImpl implements  BuyHouse{
    public void buyHouse() {
        System.out.println("我要买房");
    }
}

代理类:

public class BuyHouseProxy  implements BuyHouse {
    private BuyHouse buyHouse;
    public BuyHouseProxy(BuyHouse buyHouse) {
        this.buyHouse = buyHouse;
    }

    public void buyHouse() {
        System.out.println("买房前准备");
        buyHouse.buyHouse();
        System.out.println("买房后装修");
    }
}

调用端Client

public class ProxyTest {
    public static void main(String[] args) {
        BuyHouse buyHouse = new BuyHouseImpl();
        BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
        buyHouseProxy.buyHouse();
    }
}

结果

买房前准备
我要买房
买房后装修

2.3 静态代理优缺点

  • 优点:

    在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展

  • 缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类

一旦接口增加方法,目标对象与代理对象都要维护

3、 JDK动态代理

动态代理的主要特点就是能够在程序运行时JVM才为被生成代理对象。

动态代理也叫jdk代理,接口代理,代理对象是由jdk代理api动态的在内存中构建的

静态代理和JDK动态代理都是要求目标对象是实现一个接口的目标对象。

3.1原理类图

3.2 实现代码

接口:

public interface HelloJkdProxy {
    public void sayHelloJkdProxy();
}

实现类:

这是最简单的java接口和实现类的关系,此时就可以开始动态代理了

public class HelloJkdProxyImpl implements HelloJkdProxy {
    public void sayHelloJkdProxy() {
        System.out.println("Hello , JkdProxy");
    }
}

代理类:

首先要建立代理对象和真实服务对象的关系,然后实现代理逻辑,也就是增强方法。

第一步:

建立代理对象和真实对象的关系,这里使用了bind方法去完成,target保存了真实对象,然后通过代码建立并生成代理对象

第二步:

实现代理逻辑方法,invoke方法可以实现代理逻辑,

public class JdkProxyExample implements InvocationHandler {
    //真实对象
    private Object target = null;

    /**
     * 建立代理对象和真实对象的代理关系,并返回代理对象
     * @param target 真实对象
     * @return 代理对象
     */
    public Object bind(Object target){
        this.target=target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 当前调度的方法
     * @param args  当前方法参数
     * @return 代理结果放回
     * @throws Throwable 异常
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入代理逻辑方法");
        System.out.println("在调度真实对象前的服务,或者说是增强方法");
        //其实这里就是利用了反射 方法.invoke(对象,方法的参数数组),返回的obj就是方法放回的内容
        Object obj= method.invoke(target,args);//相当于调用了 sayHelloWorld方法
        System.out.println("在调度真实对象后的服务。");
        return obj;
    }
}

调用端Client

public class TestJdkProxy {
    public static void main(String[] args) {
        JdkProxyExample jdkProxyExample = new JdkProxyExample();
        HelloJkdProxy proxy = (HelloJkdProxy)jdkProxyExample.bind(new HelloJkdProxyImpl());
        proxy.sayHelloJkdProxy();
    }
}

结果

进入代理逻辑方法
在调度真实对象前的服务,或者说是增强方法
Hello , JkdProxy
在调度真实对象后的服务。


根据上面的例子,无论是静态代理还是JDK动态代理,我们都需要实现接口

那假如一个类,它没有接口,我们怎么实现它的动态代理?

4、 Cglib动态代理

  1. 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,

这个时候可使用目标对象子类来实现代理-这就是Cglib代理

  1. Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib 代理归属到动态代理。

  2. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截

  3. Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类

4.1原理类图

4.2实现代码

目标类:(不需要实现接口)

public class CglibTarget {
    public void sayHelloCglib() {
        System.out.println("Hello , Cglib");
    }
}

代理类:

这里用了CGLIB的加强器Enhancer,通过设置超类(它爸)的方法,然后通过setCallBack方法设置哪个类成为它的代理类(儿子)

其中参数this就意味这是当前对象,那就要求当前对象来实现MethodInterceptor的intercept方法,然后返回代理对象。

public class CglibProxyExample implements MethodInterceptor {
    /**
     * 生成CGlib 代理对象
     * @param clazz  --->Class类
     * @return Class 的Cglib代理对象
     */
    public Object getProxy(Class clazz){
        //Gblib enhancer 增强类对象
        Enhancer enhancer = new Enhancer();
        //设置增强类型
        enhancer.setSuperclass(clazz);
        //定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterceptor方法
        enhancer.setCallback(this);
        //生成并放回代理对象
        return enhancer.create();
    }

    /**
     *  代理逻辑方法
     * @param proxy 代理对象
     * @param method 方法
     * @param agrs  方法的参数
     * @param methodProxy 方法代理
     * @return  代理逻辑返回
     * @throws Throwable 异常
     */
    public Object intercept(Object proxy, Method method,
                            Object[] agrs, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用真实对象前");
        //CGlib 反射调用真实对象方法
        Object result = methodProxy.invokeSuper(proxy,agrs);
        System.out.println("调度真实对象后");
        return result;
    }
}

调用端Client

public class TestCglibProxy {
    public static void main(String[] args) {
        //创建目标对象
        CglibProxyExample  cglibProxyExample = new CglibProxyExample();
        //获取代理对象,并且将目标对象传递给代理对象
        CglibTarget obj = (CglibTarget)cglibProxyExample.getProxy(CglibTarget.class);
        //执行代理对象的方法,触发intercept方法,从而实现对目标对象的调用增强
        obj.sayHelloCglib();
    }
}

结果

调用真实对象前
Hello , Cglib
调度真实对象后

JDK动态代理和Cglib代理的对比

jdk动态代理的原理是根据定义好的规则,用传入的接口创建一个新类
CGlib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少
但是CGlib在创建代理对象时所花费的时间却比JDK多的多,比较适合单例
CGlib由于时采用动态创建子类的方法,对于final方法,无法进行代理

在 AOP 编程中如何选择代理模式

  1. 目标对象需要实现接口,用 JDK 代理

  2. 目标对象不需要实现接口,用 Cglib 代理

9

评论区