一、代理模式概述
1.生活中的代理案例
- 房屋中介
- 商品代购
2.为什么要使用代理
- 对于消费者而言,可以减少成本,只需要关心自己需要的商品,不需要去寻找渠道或者找房源
3.代理模式在Java中的应用
- 统一异常处理
- Mybatis使用了代理
- Spring aop实现原理
- 日志框架
4.概述
- 代理模式(Proxy pattern):是23中设计模式中的一种,属于结构性的模式。指一个对象本身不做实际的操作,而是通过其他对象来得到自己想到的的结果
- 意义:目标对象只需要关心自己的实现目标,通过代理对象来实现功能的增强,可以扩展目标对象的功能
- 体现了一种重要的编程思想:不能随便修改源码,如果需要修改源码,可以通过代理的方式实现功能的扩展
二、代理的实现方式
2.1 Java中的代理图示
client通过访问代理类实现具体的功能
三、静态代理的实现
3.1 案例
通过代理模式实现事务操作
创建domain对象
1
2
3
4public class Student {
private String name;
private int age;
}创建service接口,定义规范
1
2
3
4
5
6public interface IStudentService {
//添加学生
void save();
//查询学生信息
Student query(Long id);
}创建实现类
1
2
3
4
5
6
7
8
9
10
11
12public class StudentServiceImpl implements IStudentServiceice{
public void save(){
System.out.println("保存学生信息");
}
public Student query(Long id){
Student student = new Student();
student.setAge(20);
student.setName("Tommy");
return student;
}
}创建事务类对象
1
2
3
4
5
6
7
8
9public class DaoTransaction {
public void before(){
System.out.println("开启事务操作");
}
public void after(){
System.out.println("关闭事务");
}
}创建代理类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class ProxyStudent implements IStudentService {
//通过构造器获取目标类和增强类对象
public ProxyStudent(StudentServiceImpl studentService, DaoTransaction daoTransaction){
this.studentService = (StudentServiceImpl) studentService;
this.daoTransaction = daoTransaction;
}
//目标类对象
private final StudentServiceImpl studentService;
//需要做的增强对象
private final DaoTransaction daoTransaction;
public void save() {
//开始事务
daoTransaction.before();
//目标类操作
studentService.save();
//关闭事务
daoTransaction.after();
}
public Student query(Long id) {
return null;
}
}测试代理对象
1
2
3
4
5
6
7
8
9
10public class Main {
public static void main(String[] args) {
DaoTransaction daoTransaction = new DaoTransaction();
StudentServiceImpl studentService = new StudentServiceImpl();
//获取代理类对象
ProxyStudent proxyStudent = new ProxyStudent(studentService, daoTransaction);
proxyStudent.save();
proxyStudent.query(220L);
}
}
3.2 静态代理存在的问题
- 不利于代码的扩展,比如接口中新添加一个抽象方法的时候,所有实现类都必须重写
- 代理类对象需要创建很多,这种设计很不方便
四、JDK动态代理
4.1 概述
在不改变原有功能代码的前提下,能够动态地实现方法的增强
4.2 JDK动态代理简单实现
【JDK动态代理】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去创建的动态代理对象,我们这里也使用这个方法去创建一个简单的【动态代理对象】以便于理解他的核心原理。
定义Subject接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public interface Subject {
/**
接口方法
*/
void doSomething();
/**
*@Desc:
*@Params:
*@Retuen:
*/
void sayHello(String str);
}创建Subject接口的实现类RealSubject
1
2
3
4
5
6
7
8
9
10
11
12
13public class RealSubject implements Subject{
public void doSomething() {
System.out.println("RealSubject doing somthing...");
}
public String sayHello(String str) {
System.out.println("RealSubject saying Hello...");
return "hello" + str;
}
}定义代理对象创建工厂
- 定义一个工厂类,该工厂类用于为target对象生产代理对象
- 该工厂类借助
Proxy.newProxyInstance
来为目标对象创建代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class DynamicProxyFactory {
/**
* 创建target类的代理对象
* 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
*
* @param <T> 泛型
* @return 代理对象
*/
public static <T> T getProxy(Object target) {
// 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
Object proxyInstance = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行目标方法前");
// 执行目标方法
Object result = method.invoke(target, args);
System.out.println("执行目标方法后");
// 返回目标方法的执行结果
return result;
}
});
// 返回代理对象
return (T) proxyInstance;
}
}创建测试类,为target创建代理对象
- 该测试类会将内存中的动态代理对象保存到磁盘上,以便于我们后续分析生成的动态代理类的具体结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Client {
public static void main(String[] args) {
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 目标对象
RealSubject target = new RealSubject();
// 使用JDK动态代理为【target对象】创建代理对象
Subject proxy = DynamicProxyFactory.getProxy(target);
// 调用代理对象的方法
proxy.doSomething();
System.out.println("=====================华丽的分割线=====================");
proxy.sayHello("shelby");
}
}
4.3 JDK动态代理原理分析
在上面一步中我们使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样
1 | // 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject |
- 可以看到代理类【继承了Proxy类】,并且【实现了目标接口Subject】,覆写了接口中的【每一个方法】
- 这也说明了为什么JDK代理需要实现接口,因为Java是单继承的,既然代理类继承了Proxy类那么就无法再继承其他类了
- 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来(主要是为了方便后面的【反射调用】)
- 在调用动态代理对象的某个方法时(比如:调用doSomething方法),实质上是调用的【Proxy类】的【h属性】的invoke方法
- 所以我们要重点去看看这个【Proxy.h】到底是什么,其实它就是创建代理对象时我们传入的【InvocationHandler】
1.Proxy.newProxyInstance()是如何创建代理对象的
下面的源代码做了一些删减,只留下了最核心的部分
- 通过下面代码我们就明确了使用【newProxyInstance】方法创建代理对象时所做的几件事情
- 首先通过【目标接口】 + 【类加载器】创建一个Proxy类的【Class对象】
- 然后通过这个【Class对象】获取到他的有参构造器,并且传入我们自定义的【InvocationHandler】作为构造函数参数,并且通过反射的方式调用有参构造器创建一个【Proxy对象】
- 在【Proxy的有参构造器】中,会将传入的【InvocationHandler】保存到 【h属性】上(方便后面的supper.h.invoke调用)
- 代理对象创建完毕
1 | public class Proxy implements java.io.Serializable { |
2.问题分析
现在再来看上面抛出的三个问题:为什么创建代理对象时需要传入以下三个参数
加载target类的类加载器:
- 因为动态代理类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而动态代理类则是在【运行过程中动态生成的类】。那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】
target类实现的接口
为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行
- 从上面的【动态生成的代理类的结构】来看,代理类继承了Proxy类,由于【Java是单继承】的,所以无法再通过继承的方式来继承【目标类】了。所以动态代理类需要【实现目标接口】,来重写接口的方法
InvocationHandler
- 从【动态生成的代理类的结构】可以看出,我们传入的InvocationHandler最终会被作为一个属性保存到Proxy对象的【h属性】上。并且【动态代理对象】会覆写【目标接口的所有方法】,在方法中会使用 supper.h.invoke的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler动态代理的每个方法调用都会先走InvocationHandler.invoke()方法。因此InvocationHandler就可以起到方法增强或者方法过滤的作用。
五、CGLIB动态代理
5.1 AOP面向切面编程
面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。
主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。
主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP底层使用动态代理实现。包括两种方式:
使用JDK动态代理实现。
使用cglib来实现
由AOP的概念我们将AOP视为一种横向的拦截器,也就是在执行业务逻辑之前和之后都可以进行一些额外的操作。
5.2 CGLIB动态代理实现
1.cglib包结构:
- net.sf.cglib.core 底层字节码处理类。
- net.sf.cglib.transform 该包中的类用于class文件运行时转换或编译时转换。
- net.sf.cglib.proxy 该包中的类用于创建代理和方法拦截。
- net.sf.cglib.reflect 该包中的类用于快速反射,并提供了C#风格的委托。
- net.sf.cglib.util 集合排序工具类。
- net.sf.cglib.beans JavaBean工具类。
2.cglib动态代理相关的基础类:
- net.sf.cglib.proxy.Enhancer 主要的增强类。
- net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
- net.sf.cglib.proxy.MethodProxy JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。
3.常用API
- net.sf.cglib.proxy.Callback接口在CGLIB包中是一个重要的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。
net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception)需要。对有些情况下可能过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型:
net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。
net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。
net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
net.sf.cglib.proxy.Dispatcher Dispathcer回调和LazyLoader回调有相同的特点,不同的是,当代理方法被调用时,装载对象的方法也总要被调用。
net.sf.cglib.proxy.ProxyRefDispatcher ProxyRefDispatcher回调和Dispatcher一样,不同的是,它可以把代理对象作为装载对象方法的一个参数传递。
4.实现
创建一个普通类作为我们的被代理类
1
2
3
4
5
6
7
8
9
10public class Person {
public void eat(){
System.out.println("I am eating...");
}
public void play(){
System.out.println("I am playing...");
}
}创建一个拦截器MyApiInterceptor,实现MethodInterceptor
1
2
3
4
5
6
7
8
9
10
11class MyApiInterceptor implements MethodInterceptor{
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//此处可以做一些操作
System.out.println("please wash your hand before eating");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("be careful when you play"); //调用方法之后的操作
return result;
}
}Object result=proxy.invokeSuper(o, object); 表示调用原始类的被拦截到的方法。这个方法的前后添加需要的过程。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。
由于性能的原因,对原始方法的调用使用CGLIB的net.sf.cglib.proxy.MethodProxy对象,而不是反射中一般使用java.lang.reflect.Method对象。
创建类加强器Enhancer来生成代理对象
net.sf.cglib.proxy.Enhancer中有几个常用的方法:
- void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
- void setCallback(Callback callback) 设置CallBack接口的实例。
- void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
- void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
- Object create() 使用默认无参数的构造函数创建目标对象。
- Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。
好了,至此,我们完成了一个简单的cglib实现动态代理。但是看起来还不够,比如说,我只想在吃饭这个api的前后进行一些额外的操作(比如洗手和休息),但是玩耍前后不需要增加额外的操作,那么怎么实现呢?
CallbackFilter可以实现不同的方法使用不同的回调方法。CallbackFilter中的accept方法, 根据不同的method返回不同的值i, 这个值是在callbacks中的顺序, 就是调用了callbacks[i]
完整代码:
1 | package com.pak; |
参考资料
1.https://blog.csdn.net/Hellowenpan/article/details/123482681
2.https://www.bilibili.com/video/BV1tY411Z799
3.https://blog.csdn.net/qq_25827845/article/details/87513102