JAVA代理模式详解

JAVA代理模式详解

什么是代理模式?简单来说就是建立代理对象,替我们访问原对象,这样我们就可以在不修改原对象的前提下,添加额外的功能,从而扩展原对象。

这么说可能会比较抽象,我们来举个栗子:

图中小蓝就是调用方,小绿就是代理对象,小红就是原对象。

静态代理

代理模式分为静态代理和动态代理,我们先来看一下静态代理是如何实现的

定义发送消息的接口

1
2
3
public interface MsgService {
void send(String msg);
}

实现发送消息的接口

1
2
3
4
5
6
public class MsgServiceImpl implements MsgService{
@Override
public void send(String msg) {
System.out.println("MsgService.send msg: "+msg);
}
}

创建代理类并实现发送消息的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MsgServiceProxy implements MsgService{
private final MsgService msgService;

public MsgServiceProxy(MsgService msgService) {
this.msgService = msgService;
}

@Override
public void send(String msg) {
System.out.println("before method");
msgService.send(msg);
System.out.println("after method");
}
}

使用

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
MsgService msgService = new MsgServiceImpl();
MsgService msgServiceProxy = new MsgServiceProxy(msgService);
msgServiceProxy.send("msg");
}
}

执行结果

其实在静态代理中,我们对每个方法的增强都是手动实现的,非常的不灵活;实际的应用场景很少。

动态代理

相对于静态代理,动态代理就灵活多了;

动态代理是在运行时动态生成字节码,并加载到JVM中,而动态代理的实现方式有两种,JDK 动态代理和 CGLIB动态代理;而常见的 Spring AOP 和 RPC 框架都用到了动态代理。

JDK 动态代理

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

我们来看下JDK 动态代理是如何使用的:

我们依旧沿用上面的发送消息接口和实现类,然后定义一个 JDK 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MsgInvocationHandler implements InvocationHandler {
private final Object target;

public MsgInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method");
Object result = method.invoke(target, args);
System.out.println("after method");
return result;
}
}

使用

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
MsgService msgService = new MsgServiceImpl();
MsgService msgServiceProxy = (MsgService) Proxy.newProxyInstance(
msgService.getClass().getClassLoader(),
msgService.getClass().getInterfaces(),
new MsgInvocationHandler(msgService));
msgServiceProxy.send("msg");
}
}

执行结果

从上面的代码可以看到,使用动态代理,我们只要定义一个代理类,就可以对多个服务进行代理,但是JDK动态代理有一个致命的问题是其只能代理实现了接口的类。

CGLIB 动态代理

而为了解决JDK动态代理的问题我们可以使用CGLIB 动态代理机制来避免。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

我们来看下CGLIB 动态代理是如何使用的:

首先引入CGLIB 的包

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

创建一个信息发送服务类

1
2
3
4
5
public class MsgSendService {
public void send(String msg) {
System.out.println("MsgSendService.send msg: " + msg);
}
}

自定义方法拦截器

1
2
3
4
5
6
7
8
9
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before method");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after method");
return result;
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(MsgSendService.class.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(MsgSendService.class);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理类
MsgSendService msgSendServiceProxy = (MsgSendService) enhancer.create();
msgSendServiceProxy.send("msg");
}
}

执行结果

总结

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

作者

ero

发布于

2022-02-28

更新于

2022-06-11

许可协议

评论