设计模式之代理模式

代理模式属于结构型模式,代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。如Spring中的AOP。

代理模式有三种形式:静态代理JDK动态代理Cglib动态代理(可以在内存动态的创建对象,而不需要实现接口)

代理模式主要涉及到抽象主题角色、代理主题角色、真实主题角色三个角色:

  • 抽象主题角色:声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以使用代理主题。
  • 真实主题角色:定义了代理角色所代表的真实对象。
  • 代理主题角色:代理主题角色内部含有对真实主题的引用,从而可以在任何时候操作真实主题对象;代理主题角色提供一个与真实主题角色相同的接口,以便可以在任何时候都可以替代真实主体;控制对真实主题的引用,负责在需要的时候创建真实主题对象(和删除真实主题对象);代理角色通常在将客户端调用传递给真实的主题之前或者之后,都要执行某个操作,而不是单纯地将调用传递给真实主题对象。

静态代理

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

在一个学校有一名老师,老师的职责就是教书,但是某一天老师因事请假了,于是找了另一名老师来代课。下面就以这个例子来讲解。

静态代理的UML类图如下:

抽象主题角色:

package com.charon.proxy.staticstate;

/**
 * @className: ITeacherDao
 * @description: 抽象主题角色
 * @author: charon
 * @create: 2022-03-24 23:23
 */
public interface ITeacherDao {

    /**
     * 教书方法
     */
    void teach();
}

具体主题角色:

package com.charon.proxy.staticstate;

/**
 * @className: TeacherDao
 * @description:
 * @author: charon
 * @create: 2022-03-24 23:26
 */
public class TeacherDao implements ITeacherDao{
    @Override
    public void teach() {
        System.out.println("老师在授课中。。。。");
    }
}

代理主题角色:

package com.charon.proxy.staticstate;

/**
 * @className: TeacherDaoProxy
 * @description: 静态代理
 * @author: charon
 * @create: 2022-03-24 23:27
 */
public class TeacherDaoProxy implements ITeacherDao{

    /**
     * 目标对象,聚合
     */
    private ITeacherDao target;

    public TeacherDaoProxy(ITeacherDao target) {
        this.target = target;
    }

    @Override
    public void teach() {
        System.out.println("静态代理开始前的方法。。。。");
        target.teach();
        System.out.println("静态代理结束后的方法。。。。");
    }
}

客户端测试:

package com.charon.proxy.staticstate;

/**
 * @className: Client
 * @description: 
 * @author: charon
 * @create: 2022-03-23 22:52
 */
public class Client {

    public static void main(String[] args) {
        // 创建目标对象(被代理的对象)
        TeacherDao teacherDao = new TeacherDao();
        // 创建代理对象,同时将被代理对象传递给代理对象
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
        // 通过代理对象调用到被代理对象的方法(执行的是代理对象的方法,代理对象再去调用目标对象的方法)
        teacherDaoProxy.teach();
    }
}

打印:
	静态代理开始前的方法。。。。
    老师在授课中。。。。
    静态代理结束后的方法。。。。

静态代理总结:

优点:可以做到在符合“开闭原则”的情况下对目标对象进行功能扩展。

缺点:我们得为每一个目标对象都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

JDK动态代理

在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建;通过java.lang.reflect.Proxy的API实现。

JDK动态代理的UML类图:

JDK动态代理中,抽象主题角色和具体主题角色代码不变。

动态处理器:

package com.charon.proxy.jdkdynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @className: ProxyFactory
 * @description:
 * @author: charon
 * @create: 2022-03-24 23:44
 */
public class ProxyFactory {

    /**
     * 目标对象
     */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 动态代理
     * @return
     */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK动态代理开始前的方法。。。。");
                        // 通过反射机制调用目标对象的方法
                        Object invoke = method.invoke(target, args);
                        System.out.println("JDK动态代理结束后的方法。。。。");
                        return invoke;
                    }
                });
    }
}

客户端测试:

package com.charon.proxy.jdkdynamic;

import com.charon.proxy.staticstate.ITeacherDao;
import com.charon.proxy.staticstate.TeacherDao;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-24 23:53
 */
public class Client {

    public static void main(String[] args) {
        TeacherDao target = new TeacherDao();
        ITeacherDao instance = (ITeacherDao) new ProxyFactory(target).getProxyInstance();
        // 内存中生成的代理对象
        System.out.println("内存中生成的代理对象: "+instance.getClass());
        // 通过代理对象调用目标对象的方法
        instance.teach();
    }
}

打印:
    内存中生成的代理对象: class com.sun.proxy.$Proxy0
    JDK动态代理开始前的方法。。。。
    老师在授课中。。。。
    JDK动态代理结束后的方法。。。。

动态代理总结:

虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

Cglib动态代理

静态代理和JDK代理都要求目标对象实现一个接口,但是有些时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可使用目标对象的子类来实现代理,这就是Cglib动态代理。Cglib动态代理也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展。Cglib是一个强大的高性能的代码生成包,它可以在运行期间扩展java类与实现java接口,其底层是使用字节码处理框架ASM来转换字节码并生成新的类。

使用Cglib需要引入的jar包(cglib3.3.0和asm3.3.1会有jar包冲突):

<dependency>
    <artifactId>asm-parent</artifactId>
    <groupId>asm</groupId>
    <version>3.3.1</version>
</dependency>

<dependency>
    <groupId>asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>3.3.1</version>
</dependency>
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm-tree</artifactId>
    <version>3.3.1</version>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
</dependency>

Cglib动态代理的UML类图:

具体主题角色:

package com.charon.proxy.cglib;

/**
 * @className: TeacherDao
 * @description:
 * @author: charon
 * @create: 2022-03-25 22:40
 */
public class TeacherDao {
    public void teach(){
        System.out.println("老师授课中,这是cglib动态代理,不需要实现接口");
    }
}

动态处理器:

package com.charon.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @className: ProxyFactory
 * @description:
 * @author: charon
 * @create: 2022-03-25 22:41
 */
public class ProxyFactory implements MethodInterceptor {

    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 返回代理对象
     * @return
     */
    public Object getProxyInstance(){
        // 创建一个工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 返回代理对象
        return enhancer.create();
    }

    /**
     * 重写intercept方法,调用目标方法
     * @param o
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib代理模式开始前的方法。。。。");
        Object invoke = method.invoke(target, args);
        System.out.println("cglib代理模式结束后的方法。。。。");
        return invoke;
    }
}

客户端测试:

package com.charon.proxy.cglib;

/**
 * @className: Client
 * @description:
 * @author: charon
 * @create: 2022-03-25 22:56
 */
public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        TeacherDao target = new TeacherDao();
        // 获取到代理对象,并且将目标对象传递给代理对象
        TeacherDao instance = (TeacherDao) new ProxyFactory(target).getProxyInstance();
        // 执行代理方法
        instance.teach();
    }
}

打印:
    cglib代理模式开始前的方法。。。。
    老师授课中,这是cglib动态代理,不需要实现接口
    cglib代理模式结束后的方法。。。。

CGLIB代理总结:

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final/static修饰的方法无法进行代理。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加,增加了系统的复杂度
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢

代理模式的应用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

代理模式的应用场景。

  • 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  • 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate中就存在属性的延迟加载和关联表的延时加载。

代理模式与适配器模式的区别

粗略的看,适配器模式与代理模式很相像,他们都可以视为一个对象提供一种前置的接口。但是,适配器模式的用意是要改变所考虑的对象接口,而代理模式并不能改变代理的对象的接口,在这一点上,两个模式有明显的区别。

posted @ 2022-03-25 23:42  pluto_charon  阅读(103)  评论(0编辑  收藏  举报