Java代理模式精讲:静态代理+JDK动态代理

在Java设计模式中,代理模式是一种高频且实用的结构型模式,核心价值在于"间接访问+功能增强"------它通过给目标对象创建一个"替身"(代理对象),由代理对象控制对目标对象的访问,同时在不修改目标对象代码的前提下,对原有业务功能进行拓展。

代理模式广泛应用于框架开发(如Spring AOP、MyBatis接口代理)、业务增强(如日志记录、权限校验、事务管理)等场景。很多初学者会混淆静态代理和动态代理的用法,今天这篇博客,将从"定义→核心概念→静态代理(实战+问题)→动态代理(JDK详解)→框架关联",逐一拆解代理模式的核心知识点,结合完整可运行代码、原理分析,让新手也能彻底吃透,同时适配面试高频考点,干货无冗余。

一、代理模式核心认知(定义+目的+相关概念)

1. 核心定义

给目标对象提供一个代理对象,并且由代理对象控制对目标对象的引用------简单说,就是"不直接访问目标对象,通过代理对象间接访问,同时实现额外功能"。

2. 核心目的(两大作用,必记)

  1. 功能增强:在不修改目标对象原有代码的前提下,通过代理对象给目标业务添加额外功能(如前置校验、后置日志、事务控制),符合"开闭原则"。

  2. 控制访问:通过代理对象间接访问目标对象,隐藏目标对象的实现细节,防止直接访问目标对象给系统带来不必要的复杂性(如权限控制,未授权用户无法访问目标对象)。

3. 核心相关概念(3个关键角色,缺一不可)

  • 目标类(真实对象):被代理的原对象,是业务逻辑的实际执行者(比如生产衣服的工厂、生产鞋子的工厂),我们需要通过代理对象控制它的访问、拓展它的功能。

  • 代理类(代理对象):代理模式产生的"替身"对象,持有目标对象的引用,控制对目标对象的访问;同时在目标对象业务的基础上,添加额外的增强逻辑(比如前置调研、后置服务)。

  • 接口(可选,静态代理常用):目标类和代理类共同实现的接口,定义了目标业务的核心方法,是代理类"代理"目标类的基础(保证代理类和目标类有一致的方法,让代理对象可以替代目标对象使用)。

二、静态代理(手动实现,基础入门)

静态代理是代理模式的基础实现方式,核心特点是"代理类需要手动创建,且代理类所代理的目标类是固定的"------简单说,一个静态代理类,通常只能代理一个类型的目标对象(或一个接口的实现类)。

1. 静态代理的核心特点

  • 代理类是开发者手动编写的(不是JDK或第三方工具自动生成的),需要自己创建类文件、实现接口、编写增强逻辑。

  • 代理类所代理的目标类是固定不变的,比如代理"衣服工厂"的静态代理类,无法直接代理"鞋子工厂"(除非修改代理类代码)。

2. 静态代理的实现方式(接口方式,最常用)

静态代理最常用的实现方式是"接口+目标类+代理类":目标类和代理类同时实现同一个接口,代理类持有目标类的引用,在实现接口方法时,调用目标类的对应方法,同时添加增强逻辑。

核心逻辑:接口定义业务方法 → 目标类实现接口(执行核心业务) → 代理类实现接口(持有目标类引用,调用目标方法+增强功能)。

3. 完整代码实战(买衣服场景,可直接运行)

场景说明:用户想买衣服,不直接找衣服工厂(目标类),而是找代理(代理类);代理不仅帮用户找工厂做衣服,还会提供"前置市场调研"和"后置一条龙服务"(功能增强)。

步骤1:定义接口(统一业务方法)

定义买衣服的接口,声明核心业务方法(做衣服),让目标类和代理类共同实现。

java 复制代码
/**
 * 买衣服的接口(核心业务定义)
 * 目标类和代理类都需要实现此接口
 */
public interface ByClothes {
    // 核心方法:制作衣服,参数为衣服尺码
    void clothes(String size);
}
步骤2:定义目标类(真实对象,执行核心业务)

衣服工厂是目标类,实现ByClothes接口,专注于核心业务------制作衣服,不关注额外增强功能。

java 复制代码
/**
 * 目标类:衣服工厂(真实对象)
 * 专注于核心业务:制作衣服
 */
public class ClothesFactory implements ByClothes {
    @Override
    public void clothes(String size) {
        // 核心业务逻辑:制作指定尺码的衣服
        System.out.println("已经为您制作好了一整套size为" + size + "的衣服。");
    }
}
步骤3:定义代理类(代理对象,控制访问+功能增强)

代理类实现ByClothes接口,持有目标类(ClothesFactory)的引用,在实现clothes方法时,先执行前置增强、再调用目标类的核心方法、最后执行后置增强。

java 复制代码
/**
 * 代理类:买衣服的代理(替身对象)
 * 持有目标类引用,控制对目标类的访问,同时实现功能增强
 */
public class Proxy implements ByClothes {

    // 持有目标对象(衣服工厂)的引用,通过该引用调用目标方法
    public ClothesFactory factory = new ClothesFactory(); // 修正原资料语法错误

    // 实现接口方法,同时添加增强逻辑
    @Override
    public void clothes(String size) {
        // 1. 前置增强:调用目标方法前执行(比如市场调研)
        frontService();
        // 2. 调用目标对象的核心方法(真正的制作衣服逻辑)
        factory.clothes(size);
        // 3. 后置增强:调用目标方法后执行(比如一条龙服务)
        endService();
    }

    // 前置增强方法:目标方法执行前的额外服务
    public void frontService() {
        System.out.println("根据您的需求进行市场调研,筛选合适的面料和工艺。");
    }

    // 后置增强方法:目标方法执行后的额外服务
    public void endService() {
        System.out.println("为您提供一条龙的包办服务(送货上门+售后保养)。");
    }
}
步骤4:定义测试类(模拟用户买衣服,验证效果)

用户不直接创建ClothesFactory对象,而是创建Proxy对象,通过代理对象间接访问目标类,触发核心业务和增强功能。

java 复制代码
/**
 * 测试类:模拟买衣服的用户
 */
public class Test {
    public static void main(String[] args) {
        // 1. 创建代理对象(用户找代理)
        Proxy proxy = new Proxy();
        // 2. 通过代理对象调用方法(间接访问目标类,触发增强功能)
        proxy.clothes("XXL");
    }
}
运行结果与分析
java 复制代码
根据您的需求进行市场调研,筛选合适的面料和工艺。
已经为您制作好了一整套size为XXL的衣服。
为您提供一条龙的包办服务(送货上门+售后保养)。

结果符合预期:代理对象成功调用了目标类的核心方法(制作衣服),同时执行了前置和后置增强逻辑;用户没有直接访问ClothesFactory,实现了"控制访问+功能增强"的核心目的。

4. 静态代理的核心问题(致命缺陷,引出动态代理)

静态代理虽然简单易懂,但存在两个致命缺陷,导致它无法适应复杂项目的需求,这也是动态代理出现的原因:

  1. 代理类冗余:当目标类增多时,需要手动创建对应的代理类(比如新增鞋子工厂、帽子工厂,就需要新增鞋子代理、帽子代理),导致代理类数量暴增,项目维护成本极高。

  2. 违反开闭原则:当接口中的方法增多或修改时,所有实现该接口的目标类和代理类,都需要同步修改(比如给ByClothes接口新增"定制图案"方法,ClothesFactory和Proxy都要新增该方法的实现),修改成本大,且容易出错。

为了解决静态代理的缺陷,动态代理应运而生------动态代理无需手动创建代理类,由JDK或第三方工具(如CGLIB)自动生成代理对象,且一个代理对象可以代理多个目标类。

三、动态代理(自动生成代理,实战核心)

1. 静态代理与动态代理的核心区别

对比维度 静态代理 动态代理
代理类创建方式 开发者手动编写、创建 JDK或第三方工具(CGLIB)自动生成
代理目标类 固定一个(或一个接口的实现类) 可代理多个不同的目标类(灵活)
维护成本 高(目标类增多,代理类也增多) 低(自动生成,无需手动维护)
是否违反开闭原则 是(接口修改,所有实现类同步修改) 否(接口修改,无需修改代理逻辑)

2. 动态代理的两种实现方式

Java中动态代理主要有两种实现方式,适用场景不同,本文重点讲解JDK动态代理(JDK原生支持,无需第三方依赖,面试高频),简要说明CGLIB动态代理:

  • JDK动态代理 :JDK原生支持(java.lang.reflect包下的类和接口),核心依赖InvocationHandler接口和Proxy类;要求目标类必须实现接口,代理对象是接口的实现类。

  • CGLIB动态代理 :第三方工具库(需导入依赖),核心原理是"继承"------通过继承目标类,创建目标类的子类,在子类中重写父类的方法,实现功能增强;无需目标类实现接口,适合目标类没有实现接口的场景。

3. JDK动态代理(重点,实战详解)

JDK动态代理的核心逻辑:开发者无需手动创建代理类,只需编写"调用处理器"(实现InvocationHandler接口),定义增强逻辑;然后通过Proxy类的newProxyInstance()方法,自动生成代理对象,代理对象会自动实现目标类的所有接口。

核心组件(2个,必记)
  1. InvocationHandler接口:调用处理器,核心是invoke()方法------该方法定义了代理对象需要完成的功能(调用目标方法+增强逻辑),是动态代理的"核心逻辑载体"。

  2. Proxy类:核心工具类,唯一作用是自动生成代理对象 ,核心方法是newProxyInstance()(替代手动new代理类的操作)。

完整代码实战(买鞋子场景,可直接运行)

场景说明:用户想买鞋子,找动态代理;代理帮用户找鞋子工厂(目标类)做鞋子,同时提供和买衣服代理一样的前置调研、后置一条龙服务;后续如果新增其他工厂(如帽子工厂),无需新增代理类,可直接复用该动态代理。

步骤1:定义接口(目标类需要实现的接口)
java 复制代码
/**
 * 买鞋子的接口(目标类需要实现的接口)
 */
public interface ByShoot {
    // 核心方法:生产鞋子,参数为鞋子尺码
    void byShoot(String size);
}
步骤2:定义目标类(真实对象,实现接口)
java 复制代码
/**
 * 目标类:鞋子工厂(真实对象)
 * 实现ByShoot接口,执行核心业务:生产鞋子
 */
public class ShootFactory implements ByShoot {
    @Override
    public void byShoot(String size) {
        // 核心业务逻辑:生产指定尺码的鞋子
        System.out.println("已经为您生产出了尺码为" + size + "的鞋子。");
    }
}
步骤3:编写调用处理器(实现InvocationHandler接口,定义增强逻辑)

这是JDK动态代理的核心,该类持有目标对象的引用,在invoke()方法中实现"调用目标方法+前置/后置增强",且该调用处理器可复用(可代理多个不同的目标类)。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JDK动态代理:调用处理器(核心逻辑)
 * 实现InvocationHandler接口,定义代理对象的增强逻辑
 * 可复用:一个调用处理器可以代理多个不同的目标类(只要目标类实现了接口)
 */
public class LisiFactory implements InvocationHandler {

    // 持有被代理的目标对象(泛型Object,支持代理任意类型的目标对象)
    private Object factory;

    // 构造方法:传入目标对象,初始化引用
    public LisiFactory(Object factory) {
        this.factory = factory;
    }

    /**
     * invoke()方法:代理对象的核心逻辑,JVM会自动调用该方法
     * 三个参数详解(必记,面试高频):
     * 1. Object proxy:代理对象本身(几乎不用,避免递归调用)
     * 2. Method method:被代理的目标方法(即目标对象中正在被调用的方法,反射获取)
     * 3. Object[] args:调用目标方法时传入的实际参数列表(无参数则为null)
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 前置增强:目标方法执行前的额外服务(和静态代理的前置服务一致)
        frontService();
        // 2. 调用目标对象的核心方法(通过反射调用,Method.invoke())
        // 参数1:目标对象(factory);参数2:目标方法的实际参数(args)
        method.invoke(factory, args);
        // 3. 后置增强:目标方法执行后的额外服务
        endService();
        // 无返回值,返回null(如果目标方法有返回值,需返回method.invoke()的结果)
        return null;
    }

    // 前置增强方法(复用,无需重复编写)
    public void frontService() {
        System.out.println("根据您的需求进行市场调研,筛选合适的面料和工艺。");
    }

    // 后置增强方法(复用,无需重复编写)
    public void endService() {
        System.out.println("为您提供一条龙的包办服务(送货上门+售后保养)。");
    }

    /**
     * 生成代理对象的方法(非固定,但推荐封装,简化调用)
     * 核心是Proxy.newProxyInstance()方法,自动生成代理对象
     */
    public Object getProxyInstance() {
        // Proxy.newProxyInstance()三个参数(必记,面试高频):
        // 1. ClassLoader loader:目标对象的类加载器(负责将代理对象加载到内存)
        //    反射获取:factory.getClass().getClassLoader()
        // 2. Class<?>[] interfaces:目标对象实现的所有接口(代理对象会自动实现这些接口)
        //    反射获取:factory.getClass().getInterfaces()
        // 3. InvocationHandler h:调用处理器(即当前类对象,因为当前类实现了InvocationHandler)
        return Proxy.newProxyInstance(
                factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                this
        );
    }
}
步骤4:定义测试类(验证动态代理,复用代理逻辑)

测试两个场景:代理鞋子工厂、代理前面的衣服工厂,验证动态代理的复用性(无需新增代理类,只需传入不同的目标对象)。

java 复制代码
/**
 * JDK动态代理测试类
 * 验证:一个调用处理器,可代理多个不同的目标类
 */
public class Test {
    public static void main(String[] args) {
        // 场景1:代理鞋子工厂(ByShoot接口)
        System.out.println("=== 代理鞋子工厂 ===");
        // 1. 创建目标对象(鞋子工厂)
        ShootFactory shootFactory = new ShootFactory();
        // 2. 创建调用处理器,传入目标对象
        LisiFactory shootProxyHandler = new LisiFactory(shootFactory);
        // 3. 生成代理对象,强转为目标接口类型(ByShoot)
        ByShoot shootProxy = (ByShoot) shootProxyHandler.getProxyInstance();
        // 4. 通过代理对象调用方法(触发增强逻辑+目标方法)
        shootProxy.byShoot("43");

        // 场景2:代理衣服工厂(ByClothes接口),复用同一个调用处理器
        System.out.println("\n=== 代理衣服工厂 ===");
        // 1. 创建目标对象(衣服工厂)
        ClothesFactory clothesFactory = new ClothesFactory();
        // 2. 创建调用处理器,传入目标对象(复用LisiFactory类)
        LisiFactory clothesProxyHandler = new LisiFactory(clothesFactory);
        // 3. 生成代理对象,强转为目标接口类型(ByClothes)
        ByClothes clothesProxy = (ByClothes) clothesProxyHandler.getProxyInstance();
        // 4. 通过代理对象调用方法
        clothesProxy.clothes("XL");
    }
}
运行结果与分析
java 复制代码
=== 代理鞋子工厂 ===
根据您的需求进行市场调研,筛选合适的面料和工艺。
已经为您生产出了尺码为43的鞋子。
为您提供一条龙的包办服务(送货上门+售后保养)。

=== 代理衣服工厂 ===
根据您的需求进行市场调研,筛选合适的面料和工艺。
已经为您制作好了一整套size为XL的衣服。
为您提供一条龙的包办服务(送货上门+售后保养)。

结果完美符合预期:

  1. 同一个调用处理器(LisiFactory),成功代理了两个不同的目标类(ShootFactory、ClothesFactory),解决了静态代理"代理类冗余"的问题。

  2. 代理对象自动实现了目标类的接口,通过代理对象调用方法,成功触发了前置、后置增强逻辑和目标类的核心业务。

  3. 如果后续新增目标类(如帽子工厂),只需让帽子工厂实现对应的接口,无需修改代理逻辑,直接复用LisiFactory类即可,符合"开闭原则"。

JDK动态代理核心细节(面试必问)
  • 代理对象是Proxy类自动生成的,我们无法看到代理类的源码(JVM在运行时动态生成,加载到内存)。

  • invoke()方法是JVM自动调用的,当我们通过代理对象调用任何接口方法时,都会触发invoke()方法。

  • Method.invoke()方法的作用是"通过反射调用目标对象的方法",参数必须是"目标对象"和"方法参数"。

  • JDK动态代理必须要求目标类实现接口------如果目标类没有实现接口,Proxy类无法生成代理对象(会报错),此时需要使用CGLIB动态代理。

四、拓展关联:代理模式在框架中的应用(结合给出的注解代码)

代理模式的核心价值是"功能增强+控制访问",这也是Spring AOP、MyBatis等框架的核心底层原理之一。结合你给出的@Before注解代码,我们可以快速关联框架中的代理应用:

java 复制代码
/**
 * 示例代码(MyBatis/SSM框架中常见)
 * @Before 是Spring AOP的前置通知注解,本质就是动态代理的增强逻辑
 */
@Before  // 前置通知:在目标方法(如查询、新增)执行之前执行
public void init() throws IOException {
    // 加载主配置文件,构建SqlSessionFactory对象
    in = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    // 通过SqlSessionFactory创建SqlSession对象
    session = factory.openSession();
    // 核心:通过Session创建Dao接口的代理对象(MyBatis的动态代理)
    mapper = session.getMapper(StudentDao.class);
}

核心关联分析:

  1. session.getMapper(StudentDao.class):MyBatis通过JDK动态代理,自动生成StudentDao接口的代理对象(无需我们编写Dao的实现类),代理对象负责执行SQL语句(核心业务)。

  2. @Before注解:Spring AOP通过动态代理,在代理对象执行目标方法(如mapper的查询方法)之前,自动执行init()方法(前置增强),实现"初始化资源"的增强功能------这正是代理模式"功能增强"的核心应用。

简单说:框架中的"接口代理""AOP通知",本质都是动态代理的应用,帮我们简化代码、实现功能增强,无需关注代理对象的创建细节。

五、全文核心总结(面试高频考点,必记)

  1. 代理模式的核心:代理对象控制目标对象的访问,同时实现功能增强,两大目的是"功能增强"和"控制访问"。

  2. 静态代理:手动创建代理类,代理目标固定,存在"代理类冗余""违反开闭原则"的缺陷,适合简单场景。

  3. 动态代理:自动生成代理类,可复用,解决静态代理的缺陷;主要分JDK动态代理(原生、需接口)和CGLIB动态代理(第三方、无需接口)。

  4. JDK动态代理核心:InvocationHandler(定义增强逻辑,invoke()方法)和Proxy(生成代理对象,newProxyInstance()方法),目标类必须实现接口。

  5. 框架应用:MyBatis的Dao接口代理、Spring AOP的通知(@Before等),本质都是动态代理的应用,核心是"功能增强+简化开发"。

对于新手来说,重点掌握JDK动态代理的实现流程和核心参数,多写代码实战(比如给目标方法添加日志、权限校验增强),就能彻底吃透代理模式;理解代理模式的核心,后续学习Spring AOP、MyBatis等框架时,也会更加轻松。

相关推荐
悠闲蜗牛�1 小时前
Go语言高并发编程深度实战:从原理到性能优化的完整指南
java·运维·数据库
苡~1 小时前
【openclaw+claude系列02】全景拆解——手机、电脑、AI 三者如何协同工作
java·人工智能·python·智能手机·电脑·ai编程
智塑未来1 小时前
卫星在轨运行5年以上用什么品牌SSD寿命够?航天级存储的长寿命保障技术解析
开发语言·javascript·数据库
Java面试题总结1 小时前
Go-依赖注入
开发语言·后端·golang
Java面试题总结1 小时前
Go 泛型中的 [0]func(T)
开发语言·后端·golang
小二·1 小时前
Go 语言系统编程与云原生开发实战(第19篇)
开发语言·云原生·golang
LSL666_1 小时前
5 Redis通用命令
java·开发语言·redis·命令
rannn_1111 小时前
【Redis|基础篇】初识、Redis的安装与启动、Redis命令、Java客户端
java·redis·后端·缓存·nosql
茶本无香1 小时前
设计模式之十六:状态模式(State Pattern)详解 -优雅地管理对象状态,告别繁琐的条件判断
java·设计模式·状态模式