前言
- 一种
结构型设计模式
- 允许通过创建一个代理对象来控制对另一个对象的访问,即为真实对象提供代理,然后供其他对象通过代理访问真实对象
- 常用于在不修改原始对象的情况下增加额外的功能,或者限制对原始对象的访问(
只做增强,不做修改
)
一、静态代理
代理模式的一种形式,在编译时就已经确定了代理类
实现思路
- 代理对象与目标对象实现同一接口(
让用户无法察觉用的是代理对象还是目标对象
) - 在内部
维护一个目标对象的引用
通过构造器注入目标对象
- 代理对象调用与目标类
相同的方法
(让用户无法察觉用的是代理对象还是目标对象),并添加前拦截、后拦截等所需的业务功能
目标类
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();
优
- 解决代理类暴增,动态代理不需要代理类
- 解决代码重复
劣
- 硬编码问题依旧没有解决(前置增强、后置增强依旧写死)
- 会对目标类所有方法增强,无法指定只针对某些方法增强
总结
若不使用代理模式,直接在源码上改,也能实现效果,但是违背开闭原则,且不便于代码维护与扩展,因为如果某一个接口的实现类都要进行相同的增强,则需要对该接口的每一个实现类进行源码的修改,代码可能会重复写,也可能存在遗漏某些是实现类的修改