spring 动态代理

文章目录

  • 前言
  • 一、动态代理是什么?
    • [1.1 动态代理是什么:](#1.1 动态代理是什么:)
    • [1.2 动态代理都能干啥:](#1.2 动态代理都能干啥:)
  • 二、静态代理VS动态代理:
    • [2.1 静态代理](#2.1 静态代理)
    • [2.2 动态代理](#2.2 动态代理)
  • 三、动态代理的原理:
    • [3.1 JDK 动态代理 原理](#3.1 JDK 动态代理 原理)
    • [3.2 CGLIB 动态代理 原理](#3.2 CGLIB 动态代理 原理)
    • [3.3 动态代理通用执行流程(两种都适用)](#3.3 动态代理通用执行流程(两种都适用))
  • [四、spring中使用 jdk 动态代理目标类:](#四、spring中使用 jdk 动态代理目标类:)
    • [4.1 spring中代理接口类:](#4.1 spring中代理接口类:)
    • [4.2JDK 动态代理 完整流程图:](#4.2JDK 动态代理 完整流程图:)
  • 总结

前言

本文介绍动态代理的原理以及实现。


一、动态代理是什么?

1.1 动态代理是什么:

如果说目标类:正经干活的员工,那么动态动态代理生成的类就是:前台门卫;所有人找员工办事,必须先过门卫;

门卫可以先登记、校验、分流、拦错,然后在找员工处理问题。以下为动态代理的过程

bash 复制代码
控制器/业务调用方法
        ↓
走到【代理替身】
        ↓
进入拦截器(你写的invoke)
        ↓
前置增强(打印日志、路由、权限、限流)
        ↓
调用真实目标方法
        ↓
后置增强、统一异常处理
        ↓
返回结果

1.2 动态代理都能干啥:

  • 统一异常包装
  • 动态路由(根据参数选不同实现类)
  • 统一日志、耗时统计
  • 事务控制、权限校验
  • Spring AOP 底层就是动态代理
  • Feign、MyBatis Mapper 接口全是动态代理实现

二、静态代理VS动态代理:

2.1 静态代理

你手动写一个代理类,硬编码实现接口,包裹目标类,使用的时候先调用代理类,代理类中在调用目标类; 缺点:每一个接口都要手写代理类,类一多爆炸、重复代码多。

2.2 动态代理

不用手写代理类,程序运行期间自动帮你生成代理类 / 代理对象,通用一套拦截逻辑,能代理任意接口 / 类

三、动态代理的原理:

动态代理 = 程序运行期间,JVM 自动动态生成一个代理类字节码,加载到内存,创建代理对象;所有方法调用先走代理拦截器,再中转调用真实目标方法。实现方式有两种 JDK 动态代理 和 CGLIB 动态代理。

3.1 JDK 动态代理 原理

前提:必须有接口,只能代理接口。底层原理步骤:

  • 运行时根据接口 Class 对象,自动在内存生成全新代理类字节码;
  • 这个自动生成的代理类 实现了你要代理的接口;
  • 代理类里重写了接口所有方法;
  • 你调用接口任意方法,实际调用的是「自动生成代理类」里的方法;
  • 代理类方法内部,固定回调你写的 InvocationHandler.invoke();
  • 在 invoke 里你可以:前置增强、路由、判参数、抓异常、再反射调用真实实现类方法。

如果你有接口:

bash 复制代码
interface UserService { sayHello(); }

JDK 自动生成:

bash 复制代码
public final class $Proxy123 implements UserService {
    InvocationHandler h;

    @Override
    public void sayHello(String name) {
        // 直接回调你的拦截器
        h.invoke(this, 方法对象, 参数数组);
    }
}

你完全看不到这个 $Proxy123,JVM 内存里临时生成、临时加载。

3.2 CGLIB 动态代理 原理

前提:不需要接口,直接代理普通类。底层原理:

  • 依靠字节码技术,运行时继承目标业务类,生成子类;
  • 子类重写父类所有非 final 方法;
  • 调用方法时先走子类重写的方法,进入 CGLIB 方法拦截器;
  • 拦截器里做前置 / 后置 / 异常增强,再调用父类真实方法。

目标类:UserServiceImplCGLIB 动态生成:

bash 复制代码
public class UserServiceImpl$$EnhancerByCGLIB extends UserServiceImpl {
    // 重写所有方法,加入拦截逻辑
}

关键特点:

  • 无需接口,直接代理普通类
  • 基于 继承子类 做代理
  • final 类 /final 方法无法被代理(不能继承、不能重写)
  • 第三方字节码框架,Spring 已内置封装

3.3 动态代理通用执行流程(两种都适用)

  • 程序运行中,代理工厂生成代理类字节码
  • JVM 加载代理类,创建代理对象
  • 开发者注入 / 使用代理对象
  • 外部调用代理对象的方法
  • 进入自定义拦截器(invoke)
  • 拦截器内:
    前置处理(日志、权限、路由)
    反射调用真实目标方法
    后置处理、统一异常转换
  • 返回结果

四、spring中使用 jdk 动态代理目标类:

4.1 spring中代理接口类:

(1) 定义接口类:

bash 复制代码
public interface UserService {
    String sayHello(String name);
}

(2)定义真实目标实现类:

实现类1

bash 复制代码
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public String sayHello(String name) {
        System.out.println("业务逻辑:Hello " + name);
        return "业务逻辑:Hello " + name;
    }
}

实现类2

bash 复制代码
import org.springframework.stereotype.Service;

@Service
public class UserServiceTwoImpl implements UserService {

    @Override
    public String sayHello(String name) {
        System.out.println("业务逻辑 two:Hello " + name);
        return "业务逻辑 two:Hello " + name;
    }
}

(3)定义接口代理类:

bash 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class UserServiceProxy {

    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;
    @Autowired
    @Qualifier("userServiceTwoImpl")
    private UserService userTwoService;

    @Bean
    public UserService userProxyService() {
        // 生成动态代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class[]{UserService.class},
                (InvocationHandler) (p, method, args) -> {
                    System.out.println("正在执行方法:" + method.getClass().getName() + "," + method.getName());
                    System.out.println("代理前置:方法拦截增强");
                    // 执行真实业务方法:getService 获取真实的目标类
                    return method.invoke(getService(args), args);
                }
        );
        return proxy;
    }

    private UserService getService(Object[] args) {
        if (null == args || args.length == 0) {
            return userService;
        }
        String userName = args[0].toString();
        if ("twoUser".equals(userName)) {
            return userTwoService;
        } else {
            return userService;
        }

    }
}

(4) 注入接口代理类完成接口调用:

bash 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/user")
public class StudentController {
    @Autowired
    private  StudentService studentService;

    @GetMapping(value = "/getStudentName")
    public String getStudentUserName(HttpServletRequest req) {
        return studentService.getStudentName(req.getParameter("userName"));
    }
}

4.2JDK 动态代理 完整流程图:

bash 复制代码
┌─────────────────────────────────────────────────────────────┐
│  调用方:Controller / 业务类                                  │
└───────────────┬─────────────────────────────────────────────┘
                │ 调用 接口方法 UserService.sayHello()
                ▼
┌─────────────────────────────────────────────────────────────┐
│  JDK 自动生成【内存代理类 $ProxyXX】(实现 UserService 接口)    │
│  特点:运行时动态生成字节码,磁盘无文件,只在内存中           │
└───────────────┬─────────────────────────────────────────────┘
                │ 代理类内部自动回调
                ▼
┌─────────────────────────────────────────────────────────────┐
│  自定义 InvocationHandler 拦截器(你写的 Lambda 代理逻辑)     │
│  入口:invoke(proxy, method, args)                           │
│  1. 前置增强:日志、路由、权限、打印                         │
│  2. 过滤 Object 方法(toString/equals 避免重复打印)          │
└───────────────┬─────────────────────────────────────────────┘
                │ 反射调用
                ▼
┌─────────────────────────────────────────────────────────────┐
│  真实目标对象:UserServiceImpl 业务实现类                     │
│  执行真正业务逻辑                                             │
└───────────────┬─────────────────────────────────────────────┘
                │ 方法返回结果
                ▼
┌─────────────────────────────────────────────────────────────┐
│  原路逐层返回:实现类 → 拦截器 → 代理类 → 调用方              │
└─────────────────────────────────────────────────────────────┘

总结

  • JDK 动态代理:实现接口、运行时生成接口实现类、只能代理接口
  • CGLIB 动态代理:继承父类、字节码生成子类、可代理普通类
  • 核心原理:运行时动态生成字节码 → 生成代理类 → 拦截所有方法 → 增强 + 中转调用真实方法
  • Spring AOP、MyBatis Mapper、Feign 底层全靠动态代理实现。
相关推荐
gf13211111 小时前
python_【更新已发送的消息卡片】
java·前端·python
WL_Aurora1 小时前
Java字符输入全攻略
java·开发语言
Rust研习社1 小时前
Rust 的 move 语义,一次讲透
后端·rust·编程语言
IT_陈寒1 小时前
用了Vue的动态组件之后,我被坑得找不着北
前端·人工智能·后端
Hello.Reader1 小时前
算法基础(十三)——随机算法为什么有时主动引入随机性
java·数据库·算法
likerhood1 小时前
ConcurrentHashMap底层数据结构和面试常见问题
java·数据结构·面试·hashmap
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【52】Interrupts 中断机制:案例演示
java·人工智能·spring
老鱼说AI2 小时前
现代 LangChain 开发指南:从 LCEL 原理到企业级 RAG 与 Agent 实战
java·开发语言·人工智能·深度学习·神经网络·算法·机器学习
undefinedType2 小时前
深入理解 Rails includes:为什么一个 order(users.xxx) 会导致超级 JOIN 性能问题
后端