在Java设计模式中,代理模式是一种高频且实用的结构型模式,核心价值在于"间接访问+功能增强"------它通过给目标对象创建一个"替身"(代理对象),由代理对象控制对目标对象的访问,同时在不修改目标对象代码的前提下,对原有业务功能进行拓展。
代理模式广泛应用于框架开发(如Spring AOP、MyBatis接口代理)、业务增强(如日志记录、权限校验、事务管理)等场景。很多初学者会混淆静态代理和动态代理的用法,今天这篇博客,将从"定义→核心概念→静态代理(实战+问题)→动态代理(JDK详解)→框架关联",逐一拆解代理模式的核心知识点,结合完整可运行代码、原理分析,让新手也能彻底吃透,同时适配面试高频考点,干货无冗余。
一、代理模式核心认知(定义+目的+相关概念)
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. 静态代理的核心问题(致命缺陷,引出动态代理)
静态代理虽然简单易懂,但存在两个致命缺陷,导致它无法适应复杂项目的需求,这也是动态代理出现的原因:
-
代理类冗余:当目标类增多时,需要手动创建对应的代理类(比如新增鞋子工厂、帽子工厂,就需要新增鞋子代理、帽子代理),导致代理类数量暴增,项目维护成本极高。
-
违反开闭原则:当接口中的方法增多或修改时,所有实现该接口的目标类和代理类,都需要同步修改(比如给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个,必记)
-
InvocationHandler接口:调用处理器,核心是invoke()方法------该方法定义了代理对象需要完成的功能(调用目标方法+增强逻辑),是动态代理的"核心逻辑载体"。 -
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的衣服。
为您提供一条龙的包办服务(送货上门+售后保养)。
结果完美符合预期:
-
同一个调用处理器(LisiFactory),成功代理了两个不同的目标类(ShootFactory、ClothesFactory),解决了静态代理"代理类冗余"的问题。
-
代理对象自动实现了目标类的接口,通过代理对象调用方法,成功触发了前置、后置增强逻辑和目标类的核心业务。
-
如果后续新增目标类(如帽子工厂),只需让帽子工厂实现对应的接口,无需修改代理逻辑,直接复用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);
}
核心关联分析:
-
session.getMapper(StudentDao.class):MyBatis通过JDK动态代理,自动生成StudentDao接口的代理对象(无需我们编写Dao的实现类),代理对象负责执行SQL语句(核心业务)。 -
@Before注解:Spring AOP通过动态代理,在代理对象执行目标方法(如mapper的查询方法)之前,自动执行init()方法(前置增强),实现"初始化资源"的增强功能------这正是代理模式"功能增强"的核心应用。
简单说:框架中的"接口代理""AOP通知",本质都是动态代理的应用,帮我们简化代码、实现功能增强,无需关注代理对象的创建细节。
五、全文核心总结(面试高频考点,必记)
-
代理模式的核心:代理对象控制目标对象的访问,同时实现功能增强,两大目的是"功能增强"和"控制访问"。
-
静态代理:手动创建代理类,代理目标固定,存在"代理类冗余""违反开闭原则"的缺陷,适合简单场景。
-
动态代理:自动生成代理类,可复用,解决静态代理的缺陷;主要分JDK动态代理(原生、需接口)和CGLIB动态代理(第三方、无需接口)。
-
JDK动态代理核心:
InvocationHandler(定义增强逻辑,invoke()方法)和Proxy(生成代理对象,newProxyInstance()方法),目标类必须实现接口。 -
框架应用:MyBatis的Dao接口代理、Spring AOP的通知(@Before等),本质都是动态代理的应用,核心是"功能增强+简化开发"。
对于新手来说,重点掌握JDK动态代理的实现流程和核心参数,多写代码实战(比如给目标方法添加日志、权限校验增强),就能彻底吃透代理模式;理解代理模式的核心,后续学习Spring AOP、MyBatis等框架时,也会更加轻松。