各位后端搬砖人,咱先聊个生活场景:想买国外的限量球鞋,自己搞不定,找个代购;租房怕踩坑,找个中介 ------ 这些 "帮你办事还加 buff" 的角色,在代码里就叫「代理」。今天咱就从 "中间商" 视角,把代理模式、动态代理、类加载器这堆知识点捋得明明白白,还带可跑的代码示例,看完直接能复用!
一、先搞懂:代理到底是个啥?
简单说,代理就是 "替别人干活,还能偷偷加活" 的中间层。比如你写了个「订单服务」,原本只能下单,现在想加 "下单前打日志" 的功能,但又不想改原代码(怕改崩)------ 这时候找个 "代理",让它先打日志,再调用原订单服务,完美!
核心逻辑就一句话:代理类和目标类做一样的事,但能在 "做事前后" 插额外操作(日志、事务、权限校验都靠这招)。
二、静态代理:"手动包装" 的中间商(装饰者模式 = 包装类)
静态代理是最直白的 "中间商"------ 你得手动写个代理类,把目标类 "包起来"。因为是手动写的,所以叫 "静态",也常被称为「包装类」(本质是装饰者模式的应用)。
举个栗子:给订单服务加日志
比如原订单服务长这样(目标类):
typescript
// 目标接口(代理和目标类要实现同一个接口,保证行为一致)
public interface OrderService {
void createOrder(String orderId);
}
// 目标类:真正干活的
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(String orderId) {
System.out.println("【真实订单服务】创建订单:" + orderId);
}
}
现在要加日志,手动写个代理类(包装类):
java
// 静态代理类:包着目标类干活
public class OrderServiceProxy implements OrderService {
// 持有目标类的引用(知道替谁干活)
private final OrderService target;
// 构造器传入目标类
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void createOrder(String orderId) {
// 代理的额外操作:日志(before)
System.out.println("【代理日志】开始创建订单,订单ID:" + orderId);
// 调用目标类的真实逻辑
target.createOrder(orderId);
// 代理的额外操作:日志(after)
System.out.println("【代理日志】订单创建完成");
}
}
测试一下:
java
public class TestStaticProxy {
public static void main(String[] args) {
// 1. 创建目标对象
OrderService orderService = new OrderServiceImpl();
// 2. 用代理包起来
OrderService proxy = new OrderServiceProxy(orderService);
// 3. 调用代理,自动触发日志+真实逻辑
proxy.createOrder("JD20250829001");
}
}
// 输出结果:
// 【代理日志】开始创建订单,订单ID:JD20250829001
// 【真实订单服务】创建订单:JD20250829001
// 【代理日志】订单创建完成
静态代理的坑:
要是有 100 个服务要加日志,难道要写 100 个代理类?这不是把后端 er 卷到脱发?而且后期改日志逻辑(比如加时间戳),100 个代理类都要改 ------ 这时候「动态代理」就来救场了!
三、动态代理:"自动生成" 的中间商,不用手动写类!
动态代理的核心是:代理类不用你写,程序运行时自动生成!不管有多少个服务要加日志,一个 "代理生成器" 就能搞定,爽到飞起~
后端常用的动态代理有两种:JDK 动态代理、cglib 动态代理,咱一个个说。
1. JDK 动态代理:"接口党" 专属(基于接口生成代理)
JDK 自带的动态代理,不用加额外依赖,但有个要求:目标类必须实现接口(因为它生成的代理类会默认实现目标接口)。
核心靠两个东西:
- InvocationHandler:写 "额外操作"(比如日志)的逻辑
- Proxy.newProxyInstance():运行时生成代理对象
举个栗子:用 JDK 动态代理搞通用日志
先写个 "日志处理器"(所有服务的日志都靠它):
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 通用日志处理器:实现InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
// 持有目标对象(替谁干活)
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
// 核心方法:代理类的逻辑都在这
// proxy:生成的代理对象(一般不用)
// method:当前调用的方法(比如createOrder)
// args:方法参数(比如orderId)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 额外操作:日志(before)
System.out.println("【JDK代理日志】调用方法:" + method.getName() + ",参数:" + args[0]);
// 调用目标类的真实方法(反射实现)
Object result = method.invoke(target, args);
// 额外操作:日志(after)
System.out.println("【JDK代理日志】方法调用完成");
return result;
}
}
再写个工具类生成代理:
typescript
import java.lang.reflect.Proxy;
public class JdkProxyFactory {
// 通用生成代理的方法
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器(后面讲)
target.getClass().getInterfaces(), // 目标类的接口(必须有)
new LogInvocationHandler(target) // 额外操作逻辑
);
}
}
测试:不管是订单还是用户服务,都能代理!
java
public class TestJdkProxy {
public static void main(String[] args) {
// 1. 订单服务代理
OrderService orderProxy = (OrderService) JdkProxyFactory.getProxy(new OrderServiceImpl());
orderProxy.createOrder("JD20250829002");
System.out.println("-------------------");
// 2. 用户服务代理(再写个UserService接口和实现类,直接复用上面的日志处理器)
UserService userProxy = (UserService) JdkProxyFactory.getProxy(new UserServiceImpl());
userProxy.addUser("张三");
}
}
// 输出结果:
// 【JDK代理日志】调用方法:createOrder,参数:JD20250829002
// 【真实订单服务】创建订单:JD20250829002
// 【JDK代理日志】方法调用完成
// -------------------
// 【JDK代理日志】调用方法:addUser,参数:张三
// 【真实用户服务】添加用户:张三
// 【JDK代理日志】方法调用完成
爽吧!一个处理器搞定所有接口服务的日志,不用写 N 个代理类~
2. cglib 动态代理:"无接口党" 救星(基于继承生成代理)
JDK 动态代理必须要接口,要是目标类没实现接口(比如老项目的遗留类),咋办?------ 用 cglib!
cglib 的原理是:通过继承目标类,生成子类作为代理类(所以目标类不能是 final,不然没法继承)。需要加个依赖(Spring 项目自带,不用额外加):
xml
<!-- Maven依赖(非Spring项目需加) -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
核心靠两个东西:
- MethodInterceptor:类似 InvocationHandler,写额外操作
- Enhancer:生成代理类的 "工厂"
举个栗子:代理无接口的服务
比如有个无接口的「商品服务」:
arduino
// 无接口的目标类(没法用JDK代理)
public class GoodsService {
public void updateStock(String goodsId, int num) {
System.out.println("【真实商品服务】更新库存:" + goodsId + ",减少" + num + "件");
}
}
写个 cglib 的日志拦截器:
java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 日志拦截器:实现MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
// 核心方法:代理逻辑
// obj:代理对象(子类)
// method:目标方法(updateStock)
// args:方法参数
// methodProxy:代理方法(优化反射性能)
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 额外操作:日志(before)
System.out.println("【cglib代理日志】调用方法:" + method.getName() + ",商品ID:" + args[0]);
// 调用目标类的真实方法(两种写法,methodProxy更快)
Object result = methodProxy.invokeSuper(obj, args);
// 额外操作:日志(after)
System.out.println("【cglib代理日志】库存更新完成");
return result;
}
}
写个工具类生成代理:
typescript
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass); // 设置父类(目标类)
enhancer.setCallback(new LogMethodInterceptor()); // 设置拦截器
return enhancer.create(); // 生成代理对象
}
}
测试:无接口也能代理!
arduino
public class TestCglibProxy {
public static void main(String[] args) {
// 生成代理对象(不用接口,直接传目标类)
GoodsService goodsProxy = (GoodsService) CglibProxyFactory.getProxy(GoodsService.class);
goodsProxy.updateStock("IPhone16", 10);
}
}
// 输出结果:
// 【cglib代理日志】调用方法:updateStock,商品ID:IPhone16
// 【真实商品服务】更新库存:IPhone16,减少10件
// 【cglib代理日志】库存更新完成
四、JDK vs cglib:动态代理两兄弟 battle
用表格把区别说透,以后面试被问直接怼:
对比维度 | JDK 动态代理 | cglib 动态代理 |
---|---|---|
底层原理 | 基于接口,生成实现类代理 | 基于继承,生成子类代理 |
目标类要求 | 必须实现接口 | 不能是 final 类 / 方法 |
依赖 | JDK 自带,无额外依赖 | 需要引入 cglib 依赖(Spring 自带) |
性能(JDK8+) | 生成代理快,调用略快 | 生成代理慢(需操作字节码),调用略慢 |
适用场景 | 有接口的场景(如 Spring Bean 默认用 JDK) | 无接口的场景(如 MyBatis 的 Mapper 代理) |
五、类加载器:代理的 "幕后推手"
前面写动态代理时,用到了target.getClass().getClassLoader()------ 这玩意儿就是类加载器。它的核心工作是:把硬盘上的.class 文件,加载到 JVM 里,变成可执行的 Class 对象(没有它,你的代码就是一堆字节,跑不起来)。
1. 3 种核心类加载器(JDK8 及之前)
JVM 里类加载器是 "分层" 的,遵循「双亲委派模型」(简单说:儿子先找爹加载,爹加载不了再自己来),核心有 3 种:
类加载器 | 作用 | 加载路径示例 | 特点 |
---|---|---|---|
Bootstrap ClassLoader(启动类加载器) | 加载 JVM 核心类(最顶层) | JRE/lib/rt.jar(如 String、ArrayList) | 用 C++ 写的,Java 代码拿不到引用(getClassLoader () 返回 null) |
Extension ClassLoader(扩展类加载器) | 加载 JVM 扩展类 | JRE/lib/ext/*.jar(如 JDBC 驱动) | Java 写的,父加载器是 Bootstrap |
Application ClassLoader(应用类加载器) | 加载你自己写的代码 / 第三方依赖 | classpath(src/main/java、maven 依赖) | 我们平时用的就是它,父加载器是 Extension |
举个栗子:看不同类的加载器是谁
csharp
public class TestClassLoader {
public static void main(String[] args) {
// 1. String类(JVM核心类,Bootstrap加载)
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println("String的类加载器:" + stringClassLoader); // null(因为是C++写的)
// 2. JDBC驱动类(扩展类,Extension加载)
try {
ClassLoader jdbcClassLoader = Class.forName("com.mysql.cj.jdbc.Driver").getClassLoader();
System.out.println("JDBC驱动的类加载器:" + jdbcClassLoader); // sun.misc.Launcher$ExtClassLoader@xxx
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 3. 自己写的OrderService(应用类,Application加载)
ClassLoader orderClassLoader = OrderService.class.getClassLoader();
System.out.println("OrderService的类加载器:" + orderClassLoader); // sun.misc.Launcher$AppClassLoader@xxx
}
}
2. 类加载器的实际用途
- 动态代理:生成的代理类,需要用和目标类相同的类加载器加载,不然 JVM 不认
- 热部署:比如 Tomcat 的热部署,就是用自定义类加载器重新加载修改后的类
- 插件化:比如 IDEA 插件,每个插件用独立的类加载器加载,避免冲突
六、总结:代理 + 类加载器,后端必用的 "组合拳"
- 代理模式:核心是 "中间层加功能",静态代理手动写类(适合简单场景),动态代理自动生成(适合复杂场景)
- 动态代理选择:有接口用 JDK,无接口用 cglib(Spring、MyBatis 里全是这俩的影子)
- 类加载器:代理类的 "身份证办理员",3 种核心加载器分层工作,双亲委派模型保证安全
最后问一句:你们项目里用代理做过啥骚操作?比如用 cglib 代理做缓存,还是用 JDK 代理做权限控制?评论区唠唠~