Java 代理模式:从静态代理到动态代理

前言

代理模式是 Java 中常见的设计模式之一,它的核心思想是通过一个代理对象来控制对真实对象的访问。代理模式不仅可以扩展目标对象的功能,而且在不修改原目标对象的情况下,可以增加一些我们自定义的操作。

1. 代理模式简介

代理模式的核心思想是:通过代理对象来代替对真实对象的访问。这样做的好处是,我们可以在不修改原目标对象的前提下,扩展目标对象的功能。比如,在目标对象的某个方法执行前后,你可以增加一些自定义的操作。

举个例子:

新娘找来了自己的闺蜜来代替自己处理新郎的提问。新娘收到的提问都是经过闺蜜处理过滤之后的。闺蜜在这里就可以看作是新娘的代理对象,代理的行为(方法)是接收和回复新郎的提问。

再举个例子:

你是一个明星,每天有很多粉丝找你签名。但你是大忙人,没时间亲自处理这些请求,所以你找了一个经纪人(代理对象)来帮你处理。粉丝的请求先交给经纪人,经纪人再决定是否转交给你,或者帮你做一些额外的处理(比如记录谁来找你签名)。这样,你既不用亲自处理所有请求,又能保证签名工作顺利进行。

2. 静态代理

2.1 什么是静态代理?

静态代理是指,在编译时就已经确定了代理类和目标类的关系。我们需要手动为每个目标类创建一个代理类,并在代理类中调用目标类的方法。

静态代理的缺点:灵活性较差,一旦接口新增方法,目标类和代理类都需要进行修改。

2.2 静态代理的实现步骤

  1. 定义一个接口及其实现类
  2. 创建一个代理类并实现该接口
  3. 将目标对象注入代理类,并在代理类的方法中调用目标类的方法

2.3 代码示例

1. 定义发送短信的接口
java 复制代码
public interface SmsService {
    String send(String message);
}
2. 实现发送短信的接口
java 复制代码
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
3. 创建代理类并实现发送短信的接口
java 复制代码
public class SmsProxy implements SmsService {

    private final SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    @Override
    public String send(String message) {
        // 调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        // 调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}
4. 实际使用
java 复制代码
public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

运行上述代码之后,控制台打印出:

java 复制代码
before method send()
send message:java
after method send()

可以看到,我们成功地在 SmsServiceImplsend() 方法前后增加了自定义操作。

3. 动态代理

3.1 什么是动态代理?

相比于静态代理,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,而是可以在运行时动态生成代理类。动态代理的实现方式有很多种,比如 JDK 动态代理和 CGLIB 动态代理。

3.2 JDK 动态代理

3.2.1 JDK 动态代理的核心

在 JDK 动态代理中,InvocationHandler 接口和 Proxy 类是核心。Proxy 类的 newProxyInstance() 方法用于生成代理对象,而 InvocationHandler 接口的 invoke() 方法则用于处理代理对象的方法调用。

3.2.2 JDK 动态代理的实现步骤
  1. 定义一个接口及其实现类
  2. 自定义 InvocationHandler 并重写 invoke 方法
  3. 通过 Proxy.newProxyInstance() 方法创建代理对象
3.2.3 代码示例
1. 定义发送短信的接口
java 复制代码
public interface SmsService {
    String send(String message);
}
2. 实现发送短信的接口
java 复制代码
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
3. 定义一个 JDK 动态代理类
java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DebugInvocationHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}
4. 获取代理对象的工厂类
java 复制代码
import java.lang.reflect.Proxy;

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new DebugInvocationHandler(target)
        );
    }
}
5. 实际使用
java 复制代码
public class Main {
    public static void main(String[] args) {
        SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
        smsService.send("java");
    }
}

运行上述代码之后,控制台打印出:

java 复制代码
before method send
send message:java
after method send

3.3 CGLIB 动态代理

3.3.1 CGLIB 动态代理的核心

CGLIB 动态代理通过继承目标类来生成代理类,因此它可以代理未实现任何接口的类。CGLIB 的核心是 MethodInterceptor 接口和 Enhancer 类。

3.3.2 CGLIB 动态代理的实现步骤
  1. 定义一个类
  2. 自定义 MethodInterceptor 并重写 intercept 方法
  3. 通过 Enhancer 类的 create() 方法创建代理类
3.3.3 代码示例
1. 实现一个使用阿里云发送短信的类
java 复制代码
public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
2. 自定义 MethodInterceptor
java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DebugMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        System.out.println("after method " + method.getName());
        return object;
    }
}
3. 获取代理类
java 复制代码
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {
    public static Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new DebugMethodInterceptor());
        return enhancer.create();
    }
}
4. 实际使用
java 复制代码
public class Main {
    public static void main(String[] args) {
        AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
        aliSmsService.send("java");
    }
}

运行上述代码之后,控制台打印出:

java 复制代码
before method send
send message:java
after method send

4. 静态代理和动态代理的对比

  • 灵活性:动态代理更加灵活,不需要针对每个目标类都创建一个代理类,且可以直接代理实现类。
  • JVM 层面:静态代理在编译时生成 class 文件,而动态代理在运行时动态生成类字节码。
相关推荐
dorabighead1 小时前
重构版:JavaScript 的 new 操作符——从“黑箱仪式”到“亲手造物”的认知跃迁
开发语言·javascript·重构
知识浅谈2 小时前
@Validate 注解的使用-分组案例很有用
java·springboot
Humbunklung2 小时前
C#中通过Response.Headers设置自定义参数
开发语言·c#
Trouvaille ~2 小时前
【Java篇】一法不变,万象归一:方法封装与递归的思想之道
java·开发语言·面向对象·javase·递归·方法·基础入门
Zhava2 小时前
MybatisPlus中的customSqlSegment动态拼接where条件
java·mybatis
极客先躯2 小时前
高级java每日一道面试题-2025年2月26日-框架篇[Mybatis篇]-Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式 ?
java·mybatis·嵌套映射·resulttype属性·resultmap属性·results注解·列名别名
wtrees_松阳2 小时前
【编程向导】-JavaScript-基础语法-类型检测
开发语言·javascript·原型模式
qq_529835353 小时前
Java实现死锁
java·开发语言·python
knightkkzboy3 小时前
《C语言中的“三元精灵”:条件操作符的魔法与奥秘》
c语言·开发语言
岱宗夫up3 小时前
C语言零基础入门:嵌入式系统开发之旅
c语言·开发语言·学习