小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Java中的代理模式
1 代理模式
代理模式是一种结构型设计模式, 应用于将类和对象组装成更为庞大的结构, 同时保持结构的灵活与高效
在代理模式中, 我们使用代理对象替换对实际对象的请求, 从而提供额外的操作或功能
代理模式的原则:
- 不会修改实际对象的结构
- 对于请求方是无感知的
- 不依赖于实际对象是否创建
代理模式提供与实际对象相同的接口, 而装饰器模式提供的是增强接口
代理模式的一些应用场景:
- 实际对象生命周期管理
- 权限控制
- 日志记录
代理模式有静态代理和动态代理两种实现方式
2 静态代理
静态代理的实现步骤如下:
- 定义一个行为接口
- 创建行为接口的实现类
- 创建代理类实现行为接口, 并存在一个属性用于保存行为接口的实现类对象实例
- 将原本请求实际对象的位置, 修改为请求代理对象
以发送消息场景为例:
行为接口:
public interface IMessageAction {
void send(String message);
}
行为接口的实现类:
public class MessageService implements IMessageAction {
@Override
public void send(String message) {
System.out.printf("手动发送消息: %s%n", message);
}
}
代理类:
public class MessageProxy implements IMessageAction {
private final MessageService target;
public MessageProxy(MessageService target) {
this.target = target;
}
@Override
public void send(String message) {
System.out.printf("代理日志: 发送消息[%s]%n", message);
target.send(message);
}
}
测试:
public class Test {
public static void main(String[] args) {
MessageService messageService = new MessageService();
MessageProxy messageProxy = new MessageProxy(messageService);
messageService.send("Java");
messageProxy.send("Java");
}
}
输出为:
手动发送消息: Java
代理日志: 发送消息[Java]
手动发送消息: Java
静态代理的操作是由编码人员手动完成的
这种设计会产生一个很严重的问题: 如果实际对象发生变化, 那么代理对象需要同步进行修改
静态代理的实际应用场景极少, 只有在实际对象不能修改, 并需要固定的额外功能(真的存在不会变的需求吗)时可以尝试使用
3 动态代理
从JVM角度, 动态代理是在程序运行过程中动态生成并加载类字节码
不需要单独创建代理类, 不需要在意实际对象的变化, 甚至都不需要实际对象
动态代理有两种实现方式: JDK动态代理和CGLIB动态代理
3.1 JDK动态代理
下面展示利用JDK动态代理, 重构静态代理中的例子
创建动态代理类:
public class MessageInvocationHandler implements InvocationHandler {
private final Object target;
public MessageInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException,
InvocationTargetException {
System.out.printf("日志: 发送消息[%s]%n", args[0]);
return method.invoke(target, args);
}
}
创建代理工厂:
public class MessageProxyFactory {
public static IMessageAction getProxy(Object target) {
return (IMessageAction) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MessageInvocationHandler(target));
}
}
这里需要注意, 工厂返回的代理类型应为接口IMessageAction,
如果使用实现类MessageService, 会抛出类型转换错误异常
由此我们可以得知, JDK动态代理的要求是必须实现接口
测试:
public class Test {
public static void main(String[] args) {
MessageProxyFactory.getProxy(new MessageService()).send("Java");
}
}
输出为:
日志: 发送消息[Java]
手动发送消息: Java
在例子中, 我们并没有让代理类手动实现send方法, 实现了代理与实际对象之间的解耦
3.2 CGLIB动态代理
在JDK动态代理中, 存在一个约束, 即只可以代理实现了接口的类
CGLIB基于ASM字节码生成库, 帮助我们实现了对未实现接口的类的代理
在Spring的AOP模块中, 如果目标对象实现了接口, 则使用JDK动态代理, 反之使用CGLIB
使用CGLIB, 需要在Maven中加入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
继续重构之前的例子
首先删除IMessageAction接口, 直接创建服务类:
public class MessageService {
public void send(String message) {
System.out.printf("手动发送消息: %s%n", message);
}
}
创建方法拦截器:
public class MessageMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.printf("日志: 发送消息[%s]%n", args[0]);
return methodProxy.invokeSuper(target, args);
}
}
修改代理工厂:
public class MessageProxyFactory {
public static MessageService getProxy(Class<MessageService> messageServiceClass) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(messageServiceClass.getClassLoader());
enhancer.setSuperclass(messageServiceClass);
enhancer.setCallback(new MessageMethodInterceptor());
return (MessageService) enhancer.create();
}
}
测试:
public class Test {
public static void main(String[] args) {
MessageProxyFactory.getProxy(MessageService.class).send("Java");
}
}
输出为:
日志: 发送消息[Java]
手动发送消息: Java
CGLIB通过继承的方式实现代理, 在方法拦截器中调用的invokeSuper就能略感一二