动态代理简介

在不改变原有功能代码的前提下,能够动态的实现方法的增强,通过代理转移部分职责,使得类的代码职责单一,只专注于自己本身的工作,对于非业务代码,如请求用时记录、事务、鉴权、异常处理等,通过代理对象对目标对象的功能进行扩展,同时减少代码冗余,这也体现了open-close principle。生活中有很多代理案例,比如房屋中介或是商品代购或是秘书工作等。代理在java中的应用比如统一的异常处理、MyBatis、SpringAOP等。

静态代理

静态代理实际上是创建了一个代理类,在代理类的内部先执行增强的功能,在执行被代理类的方法,代理类和被代理类会实现同一个接口来规范行为。

缺点:

  1. 代理类和被代理类实现了相同的接口,有较多的代码冗余
  2. 一个代理类只能对一个被代理类的方法进行增强,如果要对另外一个Service类也实现日志打印、执行时间计算等功能,必须在重新创建一个代理类,代码不能复用。

下面是静态代理的一个事务处理的简单案例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 事务操作案例
// 1. 定义接口规范行为
public interface StudentService {
void save(); // 添加学生
void query(Integer id); // 查询操作
}
// 2. 实现类(被代理类)
public class StudentServiceImpl implements StudentService {
public void save() {
System.out.println("save操作");
}
public Student query(Integer id) {
System.out.println("query操作");
return new Student();
}
}
// 3. 事务类
public class Transaction {
public void before(){
System.out.println("开启事务");
}
public void after() {
System.out.println("关闭事务");
}
}
// 4. 代理类
public class ProxyStudent implements StudentService {
private StudentServiceImpl studentService;
private Transaction transaction;

public ProxyStudent(StudentServiceImpl studentService, Transaction transaction) {
this.studentService = studentService;
this.transaction = transaction;
}

public void save() {
transaction.before();
studentService.save();
transaction.after();
}
public Student query(Integer id) {
return studentService.query(id);
}
}

JDK动态代理

JDK动态代理会在运行时为目标类生成一个动态代理类$Proxy*.class

该代理类会实现目标类的接口,并且代理类会实现接口中的所有的方法增强代码

调用时通过代理类先去调用处理类进行增强,在通过反射的方式调用目标方法

Proxy.newProxyInstance()

JDK动态代理通过Proxy.newProxyInstance()来创建代理对象

1
2
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException

该方法的三个参数:

  1. ClassLoader loader:表示类加载器,用于加载生成的代理类。可以通过目标类的 ClassLoader 实例或者其他合适的类加载器来指定。
  2. Class<?>[] interfaces:表示代理类要实现的接口列表。这是一个数组,指定了代理类要实现的接口,通过代理对象调用接口中的方法时,实际上会被转发给 InvocationHandler 的 invoke() 方法处理。
  3. InvocationHandler h:表示代理对象的调用处理程序,也是一个实现 InvocationHandler 接口的对象。InvocationHandler 接口中只有一个方法 invoke(),当代理对象的方法被调用时,会被传递到 invoke() 方法中进行处理。

invoke()

  1. Object proxy:代理对象本身。在 invoke() 方法中,可以使用 proxy 参数来调用代理对象的其他方法,或者在特定情况下使用代理对象本身。
  2. Method method:被调用的方法对象。method 参数表示当前正在调用的方法,通过它可以获取方法的名称、参数等信息。
  3. Object[] args:方法的参数数组。args 参数是一个对象数组,表示传递给方法的参数。通过这个数组,可以获取方法调用时传递的具体参数值。

简单使用案例

下面是一个简单的例子:

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
30
31
public class LogServiceProxy implements InvocationHandler {
private Object targetObj; //委托类的对象

public LogServiceProxy(Object targetObj){
this.targetObj = targetObj;
}

/**
* 重写该方法,在其中完成委托类方法功能的增强
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+",开始执行");
method.invoke(targetObj,args);//使用反射来执行委托类中的方法
System.out.println(method.getName()+",执行成功");
return null;
}
}

// junit测试
@Test
public void test(){
//创建被代理类的对象
IUserService userService = new UserServiceImpl();
//创建代理类的对象
LogServiceProxy logServiceProxy = new LogServiceProxy(userService);
// 创建功能增强后的委托类对象,用委托类实现的接口作为类型来接收
IUserService userServiceProxy= (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),logServiceProxy);
// 之后可以通过代理对象执行方法...
}

CGLIB动态代理

CGLIB的底层是通过ASM在运行时动态的生成目标类的一个子类,代理类继承了被代理类。(还有其他相关类,主要是为了提高调用时效率)会生成多个

并且会重写父类的所有方法增强代码

调用时先通过代理类进行增强,在直接调用父类对应的方法进行调用目标方法,CGLIB是通过继承方式做的动态代理,因此如果某个类被标记为final,那么他是无法使用CGLIB做动态代理的

CGLIB除了生成目标子类代理之外,还有一个FastClass(路由类),通过路由类再次调用代理对象中的方法,可以(但不是必须)让本类方法调用进行增强,而不会像jdk代理那样本类方法之间相互调用增强会失效

使用cglib动态代理之前先导入maven依赖:

1
2
3
4
5
6
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

下面是cglib动态代理的一个简单案例:(此测试案例使用jdk8,使用高版本报错🤡)

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public interface UserService {
void save();
void query();
}

public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存操作");
}

@Override
public void query() {
System.out.println("查询操作");
}
}

public class LogServiceByCglib implements MethodInterceptor {
private Object targetObject;

public LogServiceByCglib(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(method.getName() + "开始执行");
method.invoke(targetObject, objects);
System.out.println(method.getName() + "结束执行");
return null;
}
}

public class test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
LogServiceByCglib logServiceByCglib = new LogServiceByCglib(userService);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // 将代理类作为父类
enhancer.setCallback(logServiceByCglib);
UserService proxyUserService = (UserService)enhancer.create();
proxyUserService.query();
}
}

两者的主要区别

  1. JDK动态代理只能提供接口的代理,不支持类的代理;如果没有实现接口Spring AOP选择cglib进行动态代理。
  2. JDK动态代理是通过jdk内部生成字节码$proxy*.class;cglib通过ASM这样一个开源的字节码生成库生成代理类,除了代理类还会生成其他相关的类。
  3. JDK动态代理中代理类实现目标类的接口;cglib中代理类将目标类作为父类继承。
  4. JDK动态代理调用目标方法是通过反射的方式进行调用;cglib通过子类调用父类方法调用目标方法。