代理模式


前言


  • 一种结构型设计模式
  • 允许通过创建一个代理对象来控制对另一个对象的访问,即为真实对象提供代理,然后供其他对象通过代理访问真实对象
  • 常用于在不修改原始对象的情况下增加额外的功能,或者限制对原始对象的访问(只做增强,不做修改

一、静态代理

代理模式的一种形式,在编译时就已经确定了代理类

实现思路

  • 代理对象与目标对象实现同一接口(让用户无法察觉用的是代理对象还是目标对象
  • 在内部维护一个目标对象的引用
  • 通过构造器注入目标对象
  • 代理对象调用与目标类相同的方法(让用户无法察觉用的是代理对象还是目标对象),并添加前拦截、后拦截等所需的业务功能

目标类

public class BusinessImpl implements BusinessInterface {

    @Override
    public void work() {
        System.out.println("目标对象执行 work 方法");
    }
}

代理类

public class BusinessStaticProxy implements BusinessInterface {
	// 维护一个目标对象
    private BusinessInterface business;
	// 通过构造方法注入目标对象
    public BusinessStaticProxy(BusinessInterface business) {
        this.business = business; 
    }
    // 和目标对象调用相同方法
    @Override
    public void work() {
    	// 进行功能增强操作
        System.out.println("日志开始打印...");
        // 调用目标方法
        business.work();
        // 进行功能增强操作
        System.out.println("日志打印结束...");
    }
}

  当客户端访问目标时,它实际上是通过代理类来访问,而代理类负责在必要时可以对目标进行一些控制或增强。

  • 实现简单,易于理解和使用

  • 存在重复代码
  • 若存在多个接口,就需要编写多个与之对应的代理类,造成代理类暴增,且不便于维护(比如都是打印日志,若需要改成其他的增强操作,所有代理类都需要修改)
  • 若统一接口增加方法,则目标对象和代理类都需要维护新增方法
  • 硬编码在代理类中(即增强操作写在代理类的方法中),不利于后期维护

二、动态代理

实际上,我们不需要编写代理类,只需要代理对象,通过这个代理对象调用目标方法。


  • 可以使用 JDK 提供的 Proxy 类、InvocationHandler 类(AOP 和 Mybatis 的动态代理底层都基于 JDK)
  • 也可以使用第三方: Cglib,Cglib 是一个第三方的代码生成类库,他生成的代理类是基于目标类的子类 ,不要求目标类必须实现接口

对象的创建

大致流程

  • ① .java(源码)经过编译后(javac)转成 .class(字节码文件),字节码文件存储在硬盘上(程序是在内存上运行,由栈、堆、方法区…)
  • ② 使用类加载器 ClassLoader 将硬盘上的字节码文件加载到内存上的方法区,实际上存储的是字节码文件对应类的 Class 对象(叫做 类的模板 )(双亲委派机制)
  • ③ 通过方法区上的 Class 对象的 newInstance() 方法(调用对应类的无参构造方法)在堆上创建我们所需要的目标对象

也就是说,想要获取一个实例,就得先获取他的 Class 对象,且必须要有构造方法

接口类也有字节码文件,可以生产 Class 对象,所以也能到第②步,但不能到第③步,因为接口没有构造函数

所以需要以接口的 Class 对象克隆出一个新的 Class 对象,而 JDK 提供得 java.lang.reflect.Proxy 类可以实现这一操作。(重点)

Version-1

/**
 * ① 以接口的 Class 克隆一个新的 Class 对象,除了包含接口所有的方法,还有构造方法
 * 新的 Class 对象是 Proxy 类的 Class对象
 */
Class newClass = Proxy.getProxyClass(BusinessInterface.class.getClassLoader(), BusinessInterface.class);

// ② 以新的 Class 对象调用构造方法创建对象

/**
 * Object o = newClass.newInstance();
 * 错误,Proxy类的无参构造是私有的,默认情况下不能调用
*/

// 解决上述问题:使用有参构造
// ③ 获得构造器对象:因为有参构造被protected修饰,是受保护的,需要通过 Proxy 的getDeclaredConstructor()方法获取
Constructor c = newClass.getDeclaredConstructor(InvocationHandler.class);
// ④ 通过构造器对象,调用构造方法,创建对象
Object o = c.newInstance(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
});

上述代码步骤大概为以下三步

  • Proxy.getProxyClass() 创建一个新的 Class 对象
  • 得到构造方法
  • 通过构造方法创建代理对象

操作起来过于繁琐

而 Proxy 中的 newProxyInstance() 一个方法就直接获得代理对象,即 newProxyInstance() 底层实现是上述步骤

同时在本节开头提到,创建代理对象的最终目的是调用目标方法,但是接口方法并没有方法体,直接调用毫无意义,所以代理类对象应该要对接口的方法进行实现,如此调用才能有意义

JDK 提供了InvocationHandler接口类来实现补全方法的操作

该接口有一个 invoke(),当代理对象调用目标方法时,会执行 InvocationHandler 的 invoke() 方法,InvocationHandler 做到了 方法体 和 方法 分离

Version-2

public Object getProxy(Object target) {
    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("前置增强...");
            Object rs = method.invoke(target, args); // 调用目标方法
            System.out.println("后置增强...");
            return rs;
        }
    });
}

BusinessInterface target = new BusinessImpl();
// 得到代理对象
BusinessInterface proxy = (BusinessInterface) getProxy(target);
proxy.work();

此时的 invocationHandler 是直接 new 出来的,导致增强代码是硬编码,写死在 invoke() 里

直接根据实际需要的增强处理,构造一个 invocationHandler 对象,将其作为参数进行传递,问题解决

Version-3

public Object getProxy(Object target, InvocationHandler invocationHandler) {
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocationHandler);
}

BusinessInterface target = new BusinessImpl();
InvocationHandler invocationHandler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置增强...");
        Object rs = method.invoke(target, args); // 调用目标方法
        System.out.println("后置增强...");
        return rs;
    }
};
BusinessInterface proxy = (BusinessInterface) getProxy2(target, invocationHandler);
proxy.work();

  • 解决代理类暴增,动态代理不需要代理类
  • 解决代码重复

  • 硬编码问题依旧没有解决(前置增强、后置增强依旧写死)
  • 会对目标类所有方法增强,无法指定只针对某些方法增强

总结

  若不使用代理模式,直接在源码上改,也能实现效果,但是违背开闭原则,且不便于代码维护与扩展,因为如果某一个接口的实现类都要进行相同的增强,则需要对该接口的每一个实现类进行源码的修改,代码可能会重复写,也可能存在遗漏某些是实现类的修改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值