23种设计模式之代理模式

代理模式也称为委托模式,是结构型设计模式中非常重要的模式,也是非常普遍的一个模式,在生活中肯定遇到过老板拖欠工资的情况,这时候,就需要走仲裁。走仲裁需要委托一个律师来代替我们去提请诉讼等事情,这个律师就是我们的代理。在生活中这样的例子很多,咱们言归正传,接着说代理模式。

代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。

代理模式的使用场景
当无法或者不想直接访问某个对象或者访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

适用范围划分
静态代理和动态代理是从Code方面来区分代理模式的两种方式,我们也可以从其适用范围来区分不同类型的代理模式。

  • 远程代理(Remote Proxy):为某个对象在不同的内存地址空间提供局部代理。使系统可以将Server部分的实现隐藏,以便Client可以不必考虑Server的存在。
  • 虚拟代理(Vitrual Proxy):使用一个代理对象表示一个十分耗资源的对象并在真正需要时才创建
  • 保护代理(Protection Proxy):使用代理控制对原始对象的访问。该类型的代理常被用于原始对象有不同访问权限的情况。
  • 智能引用(Smart Reference):在访问原始对象时执行一些自己的附加操作并对指向原始对象的引用计数。
    注意:静态和动态代理都可以应用于上述4种情形,两者是各自独立变化的。

代理模式的UML类图
这里写图片描述
简单思路
这里写图片描述

角色介绍:
1)代理角色(Proxy):

  • 保存一个引用使得代理可以访问实体。若 RealSubject和Subject的接口相同,Proxy会引用Subject。

  • 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。

  • 控制对实体的存取,并可能负责创建和删除它。

  • 其他功能依赖于代理的类型:

    • Remote Proxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。

    • Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。

    • Protection Proxy检查调用者是否具有实现一个请求所必需的访问权限。

2) 抽象主题角色(Subject):
定义真实主题角色RealSubject和抽象主题角色Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。代理主题通过持有真实主题RealSubject的引用,不但可以控制真实主题RealSubject的创建或删除,可以在真实主题RealSubject被调用前进行拦截,或在调用后进行某些操作.
3) 真实主题角色(RealSubject):
定义了代理角色(proxy)所代表的具体对象.

源码实现

//诉讼接口类
public interface ILawsuit {
    // 提交申请
    void submit();

    // 进行举证
    void burden();

    // 开始辩护
    void defend();

    // 诉讼完成
    void finish();
}

// 具体诉讼人
public class XiaoJin implements ILawsuit {
    @Override
    public void submit() {
        System.out.println("老板拖欠工资!特此申请仲裁");
    }

    @Override
    public void burden() {
        System.out.println("这是合同书和过去一年的银行工资流水");
    }

    @Override
    public void defend() {
        System.out.println("证据确凿!不需要再说什么!");
    }

    @Override
    public void finish() {
        System.out.println("诉讼成功!判决老板即日起七天内结算工资");
    }
}

//代理律师
public class Lawyer implements ILawsuit {
    private ILawsuit mLawsuit; // 持有一个具体被代理者的引用

    public Lawyer(ILawsuit lawsuit) {
        this.mLawsuit = lawsuit;
    }

    @Override
    public void submit() {
        mLawsuit.submit();
    }

    @Override
    public void burden() {
        mLawsuit.burden();
    }

    @Override
    public void defend() {
        mLawsuit.defend();
    }

    @Override
    public void finish() {
        mLawsuit.finish();
    }
}

// 客户端
public class Client {
    public static void main(String[] args){
        // 构建一个小金并将这个对象作为参数传递进律师类
        XiaoJin xiaoJin = new XiaoJin();
        Lawyer lawyer = new Lawyer(xiaoJin);

        // 律师的操作
        lawyer.submit();
        lawyer.burden();
        lawyer.defend();
        lawyer.finish();
    }
}

输出结果就不演示了,这里如果再增加一个小蔡,只需要增加一个小蔡的具体类实现诉讼接口并在客户端作为参数传给lawyer律师类。这种是静态代理,我们在运行程序前代理类的class编译文件就已经存在。而动态代理正好相反,通过反射机制动态地生成代理者的对象,也就是我们在Code阶段压根就不需要知道代理谁,代理谁我们将会在执行阶段决定。

静态代理:
优点:

  • 可以做到在不修改目标对象的功能前提下,对目标功能扩展

缺点:

  • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护

如何解决静态代理中的缺点呢?答案是可以使用动态代理方式

动态代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理

JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

    ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
    Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
    InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

java中提供了一个便捷的动态代理接口InvocationHandler,实现该接口需要重写其调用的方法invoke,我们主要通过invoke方法来调用具体的被代理方法,也就是真实的方法。

// 动态代理
public class DynamicProxy implements InvocationHandler {
    private Object obj; // 被代理的类引用

    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(obj, args);
        return result;
    }
}

修改后的动态代理客户端

// 客户端
public class Client {
    public static void main(String[] args){

        XiaoJin xiaoJin = new XiaoJin();
        DynamicProxy proxy = new DynamicProxy(xiaoJin);

        ClassLoader loader = xiaoJin.getClass().getClassLoader();
        ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]{ILawsuit.class}, proxy);

        lawyer.submit();
        lawyer.burden();
        lawyer.defend();
        lawyer.finish();

    }
}

运行结果和之前结果一致,我在这就不粘贴出来了。

总结:
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

Cglib代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

代码示例:
目标对象类:UserDao.java

/**
 * 目标对象,没有实现任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

Cglib代理工厂:ProxyFactory.java

/**
 * Cglib子类代理工厂
 * 对UserDao在内存中动态构建一个子类对象
 */
public class ProxyFactory implements MethodInterceptor{
    //维护目标对象
    private Object target;

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

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始事务...");

        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("提交事务...");

        return returnValue;
    }
}

测试类:

/**
 * 测试类
 */
public class App {

    @Test
    public void test(){
        //目标对象
        UserDao target = new UserDao();

        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //执行代理对象的方法
        proxy.save();
    }
}

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理

总结
Cglib代理我是没有用过,这里只是在网上拿来的,想把代理模式跟大家说全。代理模式应用很广泛,在很多结构型模式中,都可以看到代理模式的身影。而且代理模式几乎没有什么缺点,它是细分化至很小的一种模式,要真的说有缺点,那就是所有设计模式的通病:对类的增加。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值