[亿点时间]-重学Java中的代理

327 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Java中的代理模式

1 代理模式

代理模式是一种结构型设计模式, 应用于将类和对象组装成更为庞大的结构, 同时保持结构的灵活与高效

在代理模式中, 我们使用代理对象替换对实际对象的请求, 从而提供额外的操作或功能

代理模式的原则:

  1. 不会修改实际对象的结构
  2. 对于请求方是无感知的
  3. 不依赖于实际对象是否创建

代理模式提供与实际对象相同的接口, 而装饰器模式提供的是增强接口

代理模式的一些应用场景:

  1. 实际对象生命周期管理
  2. 权限控制
  3. 日志记录

代理模式有静态代理动态代理两种实现方式

2 静态代理

静态代理的实现步骤如下:

  1. 定义一个行为接口
  2. 创建行为接口的实现类
  3. 创建代理类实现行为接口, 并存在一个属性用于保存行为接口的实现类对象实例
  4. 将原本请求实际对象的位置, 修改为请求代理对象

以发送消息场景为例:

行为接口:

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就能略感一二