Java动态代理

代理模式

代理模式顾名思义,就是以你的名义帮你处理一些琐事,例如:用户A 近期准备结婚,他需要预定饭店,布置会场,邀请亲朋,制定菜单等等等等… 这样处理事情就比较麻烦,针对这些琐事就可以找 婚庆公司 来处理,他们可以解决婚礼过程中的大部分事情,这样用户A 只需要面对结婚这件事儿本身就可以,而不需要考虑其他的事情。

上面这个例子中 婚庆公司的定位就是代理,Java 中有些地方也需要使用代理模式进行解决,例如事务,日志等等,我们希望 service 中只需要负责业务逻辑本身而不要掺杂其他的代码,这里就可以用到代理模式

动态代理模式总结起来一句话:可以做到在不修改目标对象的功能前提下,对目标功能扩展

我们需要了解掌握三种代理模式:静态代理,JDK 动态代理,CGLIB 动态代理,我们通过日志的方式进行了解

静态代理模式

我们需要准备 service 接口以及代理类和被代理类:

正常执行添加日志

1
2
3
4
5
// service接口
public interface UserService {
// 添加用户的方法
int insertUser(String username, String password);
}
1
2
3
4
5
6
7
8
9
10
// 实现类
public class UserServiceImpl implements UserService {
@Override
public int insertUser(String username, String password) {
int count = 1;
// 假装有DAO层
// count = userDao.insertUser(username, password);
return count;
}
}
1
2
3
4
5
6
7
// 测试类
public class AppTest{
public static void main(String[] args) {
UserService us = new UserServiceImpl();
System.out.println(us.insertUser("老八", "秘制小汉堡"));
}
}

当我们运行测试类的 main 方法后发现控制台只输出了数字 1,如果我们想要实现日志功能的话,需要在实现类中添加打印语句,如下所示:

1
2
3
4
5
6
7
8
9
10
11
// 【改】实现类
public class UserServiceImpl implements UserService {
@Override
public int insertUser(String username, String password) {
int count = 1;
// 假装有DAO层
// count = userDao.insertUser(username, password);
System.out.println("用户【" + username + "】插入成功!");
return count;
}
}

日志添加完成!

使用代理模式添加日志

但是按照之前的说法,我们不希望在 service 中掺杂其他的代码,它仅仅负责逻辑即可,所以我们要移除掉实现类中的输出语句,新建一个代理类来完成这部分功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 代理类
public class UserServiceProxy implements UserService {
private UserService us;
public UserServiceProxy( UserService us ){
this.us = us;
}
@Override
public int insertUser(String username, String password) {
int count = this.us.insertUser(username, password);
System.out.println("用户【" + username + "】插入成功!");
return count;
}
}
1
2
3
4
5
6
7
// 测试类
public class AppTest{
public static void main(String[] args) {
UserService us = new UserServiceProxy( new UserServiceImpl() );
System.out.println(us.insertUser("老八", "秘制小汉堡"));
}
}

创建代理类的对象,将被代理的实例作为参数传过去,让代理类以自己的名义帮自己实现日志功能,这样实现类中只需要负责书写业务逻辑就可以了。这就是 ==静态代理模式==

JDK动态代理

静态代理可以做到在不修改源代码的情况下添加新的功能,但是如果 service 比较多的话静态代理的弊端就出来了,每个 service 都对应一个代理类,这样代码量就大大增加,这里就可以使用 JDK动态代理来解决这个问题:

代码实现

JDK动态代理依靠 java.lang.reflect.Proxy 实现,保持接口和实现类不动,新建代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class LogProxy {
public static Object getInstance( Object obj){
// 获取被代理类的类加载器
ClassLoader classLoader = obj.getClass().getClassLoader();
// 获取被代理的类实现的所有接口
Class<?>[] classes = obj.getClass().getInterfaces();
// 在这里接管目标对象执行函数的过程
// proxy不用管,method:被执行的函数,args:传递的参数
InvocationHandler invocationHandler =
(Object proxy, Method method, Object[] args)->{
Object result = method.invoke(obj, args);
System.out.println("用户【" + Arrays.asList(args) + "】插入成功!");
return result;
};
Object obj = Proxy.newProxyInstance(classLoader, classes, invocationHandler);
return obj;
}
}

调用 LogProxy.getInstance 传入被代理的对象,通过 Proxy.newProxyInstance 来获取到代理对象,调用这个方法需要传入三个参数:

  • 【classLoader】被代理的类的类加载器
  • 【classes】被代理的类所实现的所有接口
  • 【invocationHandler】是一个支持函数式的接口,直接创建对象即可,当被代理的类调用方法的时候会在这里进行执行,通过 lambda 创建该类的实例需要接受三个形参:
    • 【proxy】暂时不用管…
    • 【method】被代理的类执行的方法会在这里进行处理
    • 【args】被代理类执行方法时传入的参数
    • 最后记得将 invoke 执行函数后的结果返回,不然调用者那边会收不到返回结果

JDK动态代理的缺点

我们通过 Proxy.newProxyInstance 传参成功返回了代理对象 ,然后在测试类中成功转换为 UserService 实例进行日志增强,现在我们尝试打印一下转换之前的代理对象的 class 是什么样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 代理类
public class LogProxy {
public static Object getInstance(Object obj) {
// 省略多余代码
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] classes = obj.getClass().getInterfaces();
InvocationHandler invocationHandler =
(Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
// 获取代理对象并进行打印
Object proxy =
Proxy.newProxyInstance(classLoader, classes, invocationHandler);
System.out.println(proxy.getClass());
return proxy;
}
}

打印结果为:class com.sun.proxy.$Proxy0,并不是我们熟悉的类型,那为什么他可以转换为 UserService 类型呢?因为他获取了被代理目标类实现的所有接口:obj.getClass().getInterfaces(),让我们打印一下代理对象实现的接口是什么样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LogProxy {
public static Object getInstance(Object obj) {
// 省略多余代码
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] classes = obj.getClass().getInterfaces();
InvocationHandler invocationHandler =
(Object proxy, Method method, Object[] args) -> method.invoke(obj, args);
// 获取代理对象并进行打印
Object proxy =
Proxy.newProxyInstance(classLoader, classes, invocationHandler);
System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
return proxy;
}
}

打印结果为:[interface site.hanzhe.proxy.jdkProxy.UserService],代理对象实现了和被代理对象一模一样的接口,所以他可以转换为 UserService,那么如果我们被代理的类没有实现任何接口会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
// 修改被代理的类,让他不实现任何接口
public class UserServiceImpl /* implements UserService */ {
// @Override
public int insertUser(String username, String password) {
int count = 1;
// 假装有DAO层
// count = userDao.insertUser(username, password);
System.out.println(username + "--" + password);
return count;
}
}

被代理类修改完成了,代理类保持不变,在测试类中修改强制转换类型,然后进行测试

1
2
3
4
5
6
7
public class AppTest{
public static void main(String[] args) {
UserServiceImpl us =
(UserServiceImpl)LogProxy.getInstance(new UserServiceImpl());
us.insertUser("老八", "秘制小汉堡");
}
}

系统抛出了 java.lang.ClassCastException 异常,详细信息为 com.sun.proxy.$Proxy0 cannot be cast to site.hanzhe.proxy.jdkProxy.UserServiceImpl,告诉我们无法强制转换为 UserServiceImpl 类型的对象,这样一来我们就测出了一个问题,JDK动态代理要求 被代理的对象必须实现至少一个接口

CGLIB动态代理

在之前的静态代理中,代理类与被代理类实现了同一个接口,从而达到对每个函数进行代理的目的,而在 JDK动态代理中要求被代理的类必须实现至少一个接口,那么如果我们被代理的类没有实现接口的话如何代理?

CGLIB动态代理模式,以被代理对象作为父类,动态创建一个子类作为代理对象并返回,CGLIB动态代理底层使用了 ASM字节码框架来生成子类,相比较比 Java反射效率要高

想要使用 CGLIB动态代理需要引入 CGLIB 和 ASM 的 jar 包,这里记录一下两个 jar 包所对应的 maven 坐标:

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.0</version>
</dependency>

代码实现

1
2
3
4
5
6
7
8
9
10
11
// 一个没有实现任何接口的 service 类
public class UserService {
// 添加用户的方法
public int insertUser(String username, String password){
int count = 1;
// 假装有DAO层
// count = userDao.insertUser(username, password);
System.out.println(username + "--" + password);
return count;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 代理类代码
public class LogProxy {
public static Object getInstance(Object obj){
// 创建CGLIB的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(
(InvocationHandler)(Object proxy, Method method, Object[] args)->{
Object result = method.invoke(obj, args);
System.out.println("用户【" + Arrays.asList(args) + "】插入成功!");
return result;
}
);
return enhancer.create();
}
}

代理类代码说明:

  • 【Enhancer】CGLIB中的工具类,通过工具类来动态创建一个新的实例
    • 【setSuperclass】设置对象继承的父类,该方法需要传入被代理类的 class 对象
    • 【setCallback】通过回调的方式实现代理功能,该方法需要传入一个 Callback 对象, 这里使用他下面子接口 InvocationHandler,也就是 JDK动态代理时手动创建的那个对象,由于类型原因在使用的时候使用了强制类型转换
      • 【InvocationHandler】函数式接口的三个参数这里就不解释了,记得将 invoke 结果返回即可

Java动态代理
http://example.com/2022/07/13/设计模式/代理模式/
Author
Shi_Kang
Posted on
July 13, 2022
Licensed under