后端人必懂的 “中间商” 哲学:代理模式 + 类加载器,从入门到唠明白

各位后端搬砖人,咱先聊个生活场景:想买国外的限量球鞋,自己搞不定,找个代购;租房怕踩坑,找个中介 ------ 这些 "帮你办事还加 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 插件,每个插件用独立的类加载器加载,避免冲突

六、总结:代理 + 类加载器,后端必用的 "组合拳"

  1. 代理模式:核心是 "中间层加功能",静态代理手动写类(适合简单场景),动态代理自动生成(适合复杂场景)
  1. 动态代理选择:有接口用 JDK,无接口用 cglib(Spring、MyBatis 里全是这俩的影子)
  1. 类加载器:代理类的 "身份证办理员",3 种核心加载器分层工作,双亲委派模型保证安全

最后问一句:你们项目里用代理做过啥骚操作?比如用 cglib 代理做缓存,还是用 JDK 代理做权限控制?评论区唠唠~

相关推荐
Eiceblue14 分钟前
Java实现PDF表格转换为CSV
java·python·pdf
自由的疯1 小时前
Java RuoYi整合Magic-Api详解
java·后端·架构
自由的疯1 小时前
Java 实现TXT文件上传并解析的Spring Boot应用
后端·架构
老华带你飞1 小时前
校园二手书交易|基于SprinBoot+vue的校园二手书交易管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·小程序·毕设·校园二手书交易管理系统
hoho不爱喝酒2 小时前
微服务Eureka组件的介绍、安装、使用
java·微服务·eureka·架构
开始学java2 小时前
抽象类和抽象方法
后端
华仔啊2 小时前
接口卡成PPT?这9个优化技巧让系统飞起来,亲测有效!
java
华仔啊2 小时前
别再乱 new ArrayList!8 大 Java 容器选型案例,一篇看懂
java·后端
小码编匠2 小时前
手把手教会设计 WinForm 高DPI兼容程序,告别字体模糊与控件乱飞(.NET 4.6.1/.NET 6.0)
后端·c#·.net