1. 代理模式详介
1.1 分类和作用
- 分类:静态代理、jdk动态代理(接口代理)、cglib动态代理(子类代理)技术
- 使用代理的原因:实际开发中通常都会调用别人编写的代码/框架来完成业务需求。很多情况是需要对这些代码/框架进行微调或扩展,而如果修改原代码很容易出现错误。这时候就需要使用代理。
- 作用:通过代理访问目标对象。在目标对象实现的基础上,增强额外的功能操作,即在不修改原代码的基础上扩展目标对象的功能。
1.2 静态代理(不推荐)
- 原理:
- 代理类实现与目标对象相同的接口。通过构造器或set方法给代理对象注入目标对象;
- 实现代理对象接口方法时,内部调用目标对象真正实现方法,并且可添加额外的业务控制。
- 实现要求:
- 代理对象需要实现与目标对象一样的接口;
- 代理对象需要维护一个目标对象(需要目标对象调用目标对象自身方法);
- 代理对象一定会调用目标对象的方法。
- 优点:在不修改原有代码的基础上,对指定的目标对象进行扩展
- 缺点:
- 对于代理对象而言,接口的所有方法都要重写,即便需要增强的方法仅是其中的少数几个;
- 目标对象的接口修改后,目标对象和代理对象都得相应修改,不便于维护;
- 每个目标对象至少有一个代理对象,导致代理类过多;
1.2.1 代码实现概述
代理类的代码实现:
- 代理类实现目标对象的接口;
- 代理类中创建目标对象;
- 重写接口方法,调用目标对象的方法,同时添加额外的业务逻辑。
1.3 jdk动态代理(接口代理)
-
概念:代理类在程序运行时动态创建
-
特点:
- 目标对象必须实现接口;
- 程序运行期间,利用jdk的proxy相关api,在内存中动态构建字节码对象,从而生成代理对象,同时让代理对象实现目标对象的接口。
- jdk动态代理对象在内存的名称是
$Proxy加上数字
,如 P r o x y 3 、 Proxy3、 Proxy3、Proxy4等
-
生成动态代理的API:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
- 参数1 loader:当前使用哪个类加载器生成代理对象
- 参数2 interfaces:目标对象实现的接口类型数组
- 参数3 h:事件处理程序(当执行代理方法时候会触发),需要传入一个InvocationHandler接口实现类对象。(详见案例代码)
1.3.1 jdk动态代理的入门案例
1.3.1.1 目标对象接口
public interface IStar {
/*明星的功能*/
void singing(double money);
void act(double money);
}
1.3.1.2 目标对象类,实现接口
public class Star implements IStar {
@Override
public void singing(double money) {
System.out.println("[开演唱会!收费标准:]" + money);
}
@Override
public void act(double money) {
System.out.println("[拍戏!收费标准:]" + money);
}
}
1.3.1.3 测试类中实现动态代理
public class Test_jdk {
public static void main(String[] args) {
//1.定义目标对象
IStar target = new Star();
//2.对目标对象动态生成代理对象
/**
* jdk动态代理
*
* 1.运行时期,利用jdk的api,在内存中动态构建字节码对象,从而生成代理对象。
* 2.要求:目标对象一定要实现接口
* 3.生成代理的Api
* |-->Proxy
* |--> static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
* 参数1:loader 当前使用哪个类加载器生成代理对象
* 参数2:interfaces 目标对象实现的接口类型数组
* 参数3:h 事件处理程序(当执行代理方法时候会触发),需要传入一个InvocationHandler接口实现类对象
* InvocationHandler接口只有一个方法Object invoke(Object proxy, Method method, Object[] args)
* 其中:proxy指当前的代理对象
* method指代理对象调用的方法,使用反射的invoke()来执行方法代码
* args指传入的参数数组
* 4.原理
* 生成代理对象:class $Proxy3 implements IStar
* 通过类加载器生成动态代理对象,通过接口类型数组确定代理对象实现的接口,通过处理程序代码确定代理所要实现的功能
* 5.API的写法套路如下,可以另行定义事件处理程序方法,再把方法传入
*/
IStar proxy = (IStar) Proxy.newProxyInstance(
Test_jdk.class.getClassLoader(),
new Class[]{
IStar.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法参数
Double money = (Double) args[0];
// 添加额外的业务判断
if (money > 100000) {
// 访问目标对象的方法
return method.invoke(target,args);
} else {
System.out.println("档期忙,没空!");
}
return null;
}
}
);
//实行代理方法
proxy.singing(100001);
proxy.act(1);
}
}
1.3.1.4 代理工厂类优化代理类
/**
* 代理工厂
* 1. 对所有的目标对象生成代理对象,使用泛型
* 2. 要求:目标对象一定要实现接口。因为用的是jdk接口代理
*/
public class ProxyFactory<T> {
//创建泛型目标对象
private T target;
//代理工厂构造函数,工厂对象创建时会传入目标对象
public ProxyFactory(T target) {
this.target = target;
}
// 针对传入的目标对象生成代理对象并返回
public T createProxy() {
return (T)Proxy.newProxyInstance(
this.getClass().getClassLoader(), //根据工厂类获取类加载器
target.getClass().getInterfaces(), //根据目标对象获取所实现的所有接口的数组
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法参数
Double money = (Double) args[0];
// 判断
if (money > 100000) {
// 调用目标对象的方法
// 使用method对象的invoke()执行方法代码
return method.invoke(target, args);
} else {
System.out.println("没有调用目标对象方法!不满足条件");
return null;
}
}
});
}
}
1.4 cglib动态代理(子类代理)
- 使用场景:如果目标对象没有实现任何接口,无法使用接口代理。此时就要用到CGLIB子类代理。
- 原理:在内存中动态构建一个子类对象,从而实现对目标对象功能的扩展
- 使用要求:需要导入CGLIB的jar包,或者直接引入Spring-Core核心包。
- 注意事项:
- 代理的类不能为final,否则报错;
- **目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法!!
- 特点:
- cglib动态代理对象在内存的名称是
class com.azure.proxy3_cglib.Star$$EnhancerByCGLIB$$6e7ec13a
,格式是:类全名$$EnhancerByCGLIB$$内存地址
- cglib动态代理对象在内存的名称是
1.4.1 cglib动态代理的入门案例
1.4.1.1 引入依赖
添加spring-core依赖
1.4.1.2 目标类
public class Star {
public void singing(double money) {
System.out.println("[开演唱会!收费标准:]" + money);
}
public void act(double money) {
System.out.println("[拍戏!收费标准:]"