一条线理解Java代理技术

问题引入

首先我们看一个问题,假设项目中有一个用户服务接口,定义了一些核心操作:

复制代码
public interface UserService {
    void register(String username, String password);
    void login(String username, String password);
}

以及他的实现类:

复制代码
public class UserServiceImpl implements UserService {
    @Override
    public void register(String username, String password) {
        // 注册逻辑
    }
    @Override
    public void login(String username, String password) {
        // 登录逻辑
    }
}

有一天,产品经理找到你,提出一个新需求:给 UserService 的每个方法都加上日志记录,每次调用时输出方法名和参数。你一听,这还不简单?打开 UserServiceImpl,在每个方法体开头加一行日志不就完了。三分钟搞定,收工。

但问题是真的这么简单吗?你重新审视了一下刚才的做法,发现几个问题:

1.代码侵入:日志代码和业务逻辑耦合在一起。

2.冗余:每个方法都要手动加,如果接口有几十个方法,都要重复加。

3.不易扩展:如果需求继续变化,需要加权限、加监控,只能继续往实现类里塞。

那么有没有一种方式,能在不修改原有实现类的前提下,给这个类的方法"织入"额外的逻辑?答案就是------代理。

静态代理

代理可以理解为:你原来直接调用 UserServiceImpl 的方法,现在我们找一个"中间人"挡在前面。这个中间人长得跟 UserService 一模一样,但它可以在把请求转给真正的 UserServiceImpl 之前或之后,做一些额外的事情,比如记录日志。

java的代理有静态代理和动态代理两种,唯一的区别就是代理类的生成时机。

静态代理的代理类在编译期就已经确定了。也就是说,你需要手动为每一个需要被增强的类,手写一个对应的代理类。回到我们的场景,给 UserService 加日志,用静态代理的做法是这样的。

首先,创建一个代理类,它和 UserServiceImpl 一样实现了 UserService 接口:

复制代码
public class UserServiceProxy implements UserService {
    // 持有真正的实现类对象
    private UserService target;
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    @Override
    public void register(String username, String password) {
        System.out.println("日志:调用register方法,参数:" + username);
        target.register(username, password);
        System.out.println("日志:register方法执行完毕");
    }
    @Override
    public void login(String username, String password) {
        System.out.println("日志:调用login方法,参数:" + username);
        target.login(username, password);
        System.out.println("日志:login方法执行完毕");
    }
}

使用时,只要把原来的实现类"包"进代理里:

复制代码
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.register("张三", "123456");

有没有发现什么问题,我们需要手动生成代理类,每对一个类做增强,都需要手动去写一个代理类,这就是静态代理最大的痛点------代理类数量膨胀、重复代码多、维护成本高。

既然问题出在"每次都要手写代理类"上,那能不能让程序自动帮我们生成代理类?说到这,我们大概能理解动态代理的出场逻辑了------它的代理类是在运行时通过反射动态生成的,不需要我们手动编写。下一节,我们来继续看看 JDK 动态代理是怎么优雅解决这个问题的。

jdk动态代理

这里只关注jdk实现的动态代理,他有个硬性要求就是被代理的对象必须实现接口。

为什么?因为代理本质上就是"模板 + 增强逻辑"。接口就是模板------它定义了有哪些方法可以调用,代理类照着这个模板生成,才能跟原来的实现类长得一模一样。增强逻辑则统一写在 InvocationHandler 中,一个 Handler 就代表一个独立的增强功能。

首先我们写一个增强的逻辑:

复制代码
public class LogInvocationHandler implements InvocationHandler {
    // 持有真正的业务对象
    private Object target;
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 记录日志(前)
        System.out.println("日志:调用" + method.getName() + "方法,参数:" + Arrays.toString(args));
        // 调用真正的业务方法
        Object result = method.invoke(target, args);
        // 记录日志(后)
        System.out.println("日志:" + method.getName() + "方法执行完毕");
        return result;
    }
}

使用的时候需要把模板和增强逻辑组装起来:

复制代码
// 被代理的对象
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
  target.getClass().getClassLoader(),   // 类加载器
  new Class[]{UserService.class},       // 接口
  new LogInvocationHandler(target)      // 增强逻辑
);
// 通过代理调用
proxy.register("张三", "123456");
proxy.login("张三", "123456");

类之间的关系:

复制代码
客户端
  │
  │ 调用
  ▼
┌──────────────────────────┐
│  $Proxy0 (动态生成的代理)  │  ← 实现 UserService 接口(模板)
│  实现了 UserService 接口    │
└──────────┬───────────────┘
           │ 持有
           ▼
┌──────────────────────────┐
│   LogInvocationHandler   │  ← 增强逻辑(集中在一处)
│   - invoke()             │
└──────────┬───────────────┘
           │ 持有
           ▼
┌──────────────────────────┐
│    UserServiceImpl       │  ← 真正的业务对象
└──────────────────────────┘

可以发现,JDK 动态代理抽象出了一层 InvocationHandler,所有增强逻辑都集中在这里,不像静态代理那样散落在每个代理类的每个方法中,减少了逻辑冗余。

那么jdk代理有没有什么缺点呢?

  1. 代理类需要手动生成,每次都要手动调用 Proxy.newProxyInstance(),写一堆样板代码。项目里有几个需要代理的类,就得写几遍,没有自动化的创建和管理机制。
  2. 以及多个增强逻辑的编排需要手动完成,一个 InvocationHandler 代表一个增强功能。日志一个 Handler,事务一个 Handler,权限又一个 Handler。当多个功能需要组合时,只能手动层层嵌套。
  3. 方法路由靠硬编码,匹配规则太简单,一个 Handler 代理了整个接口,所有方法调用都进同一个 invoke。想对不同方法做不同处理,只能在 invoke 里手写判断:

spring aop代理

回顾一下核心结论------代理就做了两件事:拿模板 + 增强逻辑。这里只讲 JDK 实现的代理方式。

在 Spring AOP 中,增强逻辑进一步被拆分为切面(Aspect),一个切面就是 "匹配规则 + 增强逻辑" 的组合:

复制代码
切面(Aspect)= Pointcut(在哪切)+ Advice(切了干啥)

代理对象的生成过程是Spring 通过 Bean 后置处理器(BeanPostProcessor,简称 BPP)在 Bean 初始化之后介入,判断是否需要为这个 Bean 生成代理对象。不再需要我们手动创建。

复制代码
容器启动
    │
    ├── 扫描所有 @Aspect 类
    │      └── 解析成一个个 Advisor(匹配规则 + 增强逻辑)
    │
    └── 初始化 Bean(比如 UserService)
           │
           └── BPP 拦截
                 │
                 ├── 遍历所有 Advisor,用 Pointcut 匹配这个 Bean 的方法
                 │
                 ├── 有匹配 → 创建代理对象(JDK 方式:拿接口做模板)
                 │
                 └── 无匹配 → 返回原对象

spring BPP会在bean初始化之后,判断是否需要生成代理对象。核心的BPP:找到所有切面,判断匹配规则pointcut是否匹配,创建代理对象。

代理对象:spring 容器创建

路由的逻辑:invocationhandler自动匹配+组织切面

代理对象的核心是 JdkDynamicAopProxy,它实现了 InvocationHandler------这直接证明了 Spring AOP 底层和 JDK 动态代理是同一套逻辑:模板 + 增强。

当调用代理对象的方法时,入口就是它的 invoke 方法,路由的逻辑全部由 JdkDynamicAopProxy.invoke() 自动完成------自动匹配 + 自动编排切面,不再需要我们手写 if-else 判断方法名。

接口和实现类保持不变,定义切面:

复制代码
@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.service.UserService.*(..))")
    public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        System.out.println("日志:调用" + methodName + "方法,参数:" + Arrays.toString(args));
        Object result = joinPoint.proceed();
        System.out.println("日志:" + methodName + "方法执行完毕");

        return result;
    }
}

使用:

复制代码
@RestController
public class UserController {

    @Autowired
    private UserService userService;  // 注入的已经是代理对象

    @PostMapping("/register")
    public String register(@RequestParam String username, @RequestParam String password) {
        userService.register(username, password);
        return "注册成功";
    }

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        userService.login(username, password);
        return "登录成功";
    }
}

spring aop实现代理的调用过程:

复制代码
容器启动
    │
    ├── 扫描 @Aspect → 解析成 Advisor(匹配规则 + 增强逻辑)
    │
    └── 初始化 Bean(UserService)
            │
            └── BPP 拦截
                  │
                  ├── 拿切面的匹配规则去匹配这个Bean的方法
                  ├── 匹配上 → 生成代理对象(模板)
                  └── 没匹配上 → 返回原对象

行时调用代理方法
    │
    └── JdkDynamicAopProxy.invoke()
          │
          ├── 用 Pointcut 匹配当前方法,找到所有匹配的 Advisor
          ├── 将这些 Advisor 的 Advice 包装成拦截器链
          ├── 构建 ReflectiveMethodInvocation 责任链
          └── 调用 invocation.proceed(),拦截器链依次执行

至此,JDK 动态代理的三大痛点都有了答案:
- 代理需手动创建?→ Spring 容器通过 BPP 自动生成
- 多 Handler 需手动嵌套?→ JdkDynamicAopProxy 内部用拦截器链串联
- 方法路由靠硬编码?→ Pointcut 表达式自动匹配

总结

直接改实现类 → 业务侵入

静态代理 → 解决侵入,但类爆炸 + 逻辑重复

动态代理 → 解决类爆炸,但代理需手动创建 + 多逻辑手动套娃

Spring AOP → 全自动:代理自动生成、容器自动注入、切面自由编排

本博客内容仅为个人技术学习与分享,文中观点仅代表个人。由于个人水平有限,文章难免有疏漏或错误之处,欢迎在评论区指正交流。