Java设计模式-代理模式(附手写简易版JDK动态代理实现)
代理模式介绍
代理模式(Proxy Pattern)是一种结构型设计模式,它为另一个对象提供一个替身或者占位符,以控制对这个对象的访问。
在Java中,代理模式通常用于在不修改目标对象代码的情况下,增加一些额外功能,例如权限控制,日志记录,性能监控,失败重试等。这是符合开闭原则,也就是对软件实体应该对扩展开放,对修改关闭,这意味着能够在不修改现有代码的情况下添加新的功能。
下面通过一个生活上的例子,结合代码说明代理模式的使用,最后附上简易版的JDK动态代理的实现代码。
生活中的例子:房产中介
假设您需要买一套二手房。您可以直接跟房主(目标对象)交易,但现实中可能更常见的是通过房产中介(代理对象)来完成这个过程。
- 角色对应
| 角色 | 现实生活对应 | 职责说明 |
|---|---|---|
| 抽象主题 (Subject) | 购房合同签署流程 | 定义房主和中介都应该实现(或拥有的)的共同操作,例如 看房()、商议价格()、签署合同()。 |
| 真实主题 (Real Subject) | 房主 (Owner) | 拥有并执行核心业务逻辑(卖房)的真实对象。 |
| 代理 (Proxy) | 房产中介 (Agent) | 持有真实房主的引用,并实现与房主相同的接口,但在调用真实房主的方法前后可以添加额外操作。 |
- 代理的作用
您作为客户,只需要与中介打交道,中介会替您处理大部分与房主相关的事务。
| 交易环节 | 代理对象(中介)做的额外工作 | 代理模式中的常见用途 |
|---|---|---|
| 看房前 | 询问您的预算、核实您的购房资格。 | 访问控制(Access Control)/ 权限控制: 在访问真实对象前进行校验。 |
| 带您看房 | 提前联系房主、安排时间、准备钥匙、介绍房子的优点。 | 前置处理(Pre-processing): 在核心操作执行前做准备工作。 |
| 价格谈判 | 在您和房主之间来回协商,努力达成一个双方满意的价格。 | 中介/过滤: 控制或优化真实对象方法的输入输出。 |
| 签署合同 | 准备合同文本、陪同您和房主完成签署,确保流程合规。 | 后置处理(Post-processing)/ 日志记录: 在核心操作完成后进行善后、记录交易信息等。 |
代理模式分类
- 静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。- 优点:可以在不修改目标对象的前提下扩展目标对象的功能。
- 缺点:
- 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
- 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
- 动态代理
静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件,而动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。根据实现方式以及使用场景,动态代理又分为两种:- jdk动态代理
- cglib动态代理
静态代理例子
// 1. 抽象主题:定义共同的操作
interface HouseTransaction {
void viewHouse(); // 看房
void signContract(); // 签署合同
}
// 2. 真实主题:房主
class HouseOwner implements HouseTransaction {
@Override
public void viewHouse() {
System.out.println("房主:欢迎来看房,这是我的房子,我很满意。");
}
@Override
public void signContract() {
System.out.println("房主:与买家签署合同,收款。");
}
}
// 3. 代理:房产中介
class PropertyAgent implements HouseTransaction {
private HouseOwner owner; // 持有真实房主的引用
public PropertyAgent(HouseOwner owner) {
this.owner = owner;
}
@Override
public void viewHouse() {
// --- 代理增强:前置处理(权限控制/准备工作)---
System.out.println("中介:为您核实购房资格和预算。");
// 调用真实房主的核心方法
owner.viewHouse();
// --- 代理增强:后置处理(介绍信息)---
System.out.println("中介:这套房子的产权清晰,税费情况我已准备好清单。");
}
@Override
public void signContract() {
// --- 代理增强:确保流程合规 ---
System.out.println("中介:准备合同,进行银行监管,确保资金安全。");
// 调用真实房主的核心方法
owner.signContract();
}
}
测试代码。客户不需要知道中介背后的房主是谁,只需要和中介打交道,但中介会提供和房主一样的服务(HouseTransaction)
public class Client {
public static void main(String[] args) {
// 1. 创建真实主题(房主)
HouseOwner realOwner = new HouseOwner();
// 2. 创建代理(中介),并将房主交由中介代理
PropertyAgent agent = new PropertyAgent(realOwner);
// 3. 客户通过代理执行操作
System.out.println("--- 客户通过中介看房 ---");
agent.viewHouse();
System.out.println("\n--- 客户通过中介签署合同 ---");
agent.signContract();
}
}
从运行结果可以看到,在执行房东操作的前后,增加了中介的操作。

JDK代理例子
静态代理需要为每一个真实主题类手动编写一个代理类,而 JDK 动态代理则是在程序运行时,动态地在内存中生成一个代理类。
它的核心是使用 ***java.lang.reflect.Proxy***类和 ***java.lang.reflect.InvocationHandler***接口。
1. 动态代理的核心步骤
- 定义接口: 必须有一个接口(HouseTransaction)。
- 实现 InvocationHandler: 创建一个处理器,它定义了代理需要做什么增强操作。所有对接口方法的调用,都会被转发到这个处理器的 invoke 方法。
- 动态生成代理对象: 使用 Proxy.newProxyInstance() 方法,根据接口动态地生成代理对象。
2. Java代码实现
// 买房接口
public interface HouseTransaction {
void viewHouse(); // 看房
void signContract(); // 签署合同
}
// 真实房东,实现买房接口的方法
public class HouseOwner implements HouseTransaction {
@Override
public void viewHouse() {
System.out.println("房主:欢迎来看房,这是我的房子,我很满意。");
}
@Override
public void signContract() {
System.out.println("房主:与买家签署合同,收款。");
}
}
//中介,对房东做增强
public class AgentHandler implements InvocationHandler {
private final Object target;
public AgentHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("viewHouse".equals(methodName)) {
System.out.println("【中介 Pre-Check】: 为您核实购房资格和预算。");
} else if ("signContract".equals(methodName)) {
System.out.println("【中介 Pre-Action】: 准备合同文本,进行流程监管。");
}
Object result = method.invoke(target, args);
if ("viewHouse".equals(methodName)) {
System.out.println("【中介 Post-Info】: 这套房子的产权清晰,税费情况已准备好清单。");
}
return result;
}
}
测试代码:
public class JDKDynamicProxyClient {
public static void main(String[] args) {
// 1. 创建真实主题对象
HouseTransaction realHouseOwner = new HouseOwner();
// 2. 创建 InvocationHandler (传入真实房主)
InvocationHandler agentHandler = new AgentHandler(realHouseOwner);
// 3. 动态生成代理对象
HouseTransaction agentProxy = (HouseTransaction)Proxy.newProxyInstance(realHouseOwner.getClass().getClassLoader(),
realHouseOwner.getClass().getInterfaces(),
agentHandler);
// 4. 客户端通过代理对象执行操作
System.out.println("--- 客户通过动态代理 (中介) 看房 ---");
agentProxy.viewHouse();
System.out.println("\n--- 客户通过动态代理 (中介) 签署合同 ---");
agentProxy.signContract();
}
}
从运行结果可到,调用agentProxy对象的两个方法,会自动回调中介的代码。

通过 JDK 动态代理,我们只创建了一个 AgentHandler 处理器,就能为所有实现了 HouseTransaction 接口的对象提供代理服务,无需手动创建像 PropertyAgent 这样的静态代理类,大大提升了代码的灵活性和可维护性。
注意: JDK 动态代理要求被代理的对象必须至少实现一个接口。如果目标对象没有实现接口,则需要使用 CGLIB 等第三方库来实现。
CGLib代理
CGLIB 的全称是:Code Generation Library。
CGLIB 的特点:
-
无需接口: 它可以代理没有实现接口的类(这也是它常被用于代理类的主要原因)。
-
通过继承实现: CGLIB 是通过继承目标类(HouseOwner)来创建代理子类来实现代理的。
-
使用 MethodInterceptor: 代理的增强逻辑定义在 net.sf.cglib.proxy.MethodInterceptor 的 intercept 方法中。
由于CGLIB不需要真实对象实现某个类,下面我们将移除 HouseTransaction 接口,让 HouseOwner 成为一个普通的类。
- 添加CGLIB的maven依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
- Java实现代码
public class HouseOwner {
public void viewHouse() {
System.out.println("房主:欢迎来看房,这是我的房子,我很满意。");
}
public void signContract() {
System.out.println("房主:与买家签署合同,收款。");
}
}
public class CglibAgentInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
// --- 代理增强:前置处理 ---
if ("viewHouse".equals(methodName)) {
System.out.println("【CGLIB 中介 Pre-Check】: 为您核实购房资格和预算。");
} else if ("signContract".equals(methodName)) {
System.out.println("【CGLIB 中介 Pre-Action】: 准备合同文本,进行流程监管。");
}
// 核心逻辑:调用真实对象 (父类) 的方法
// 使用 MethodProxy.invokeSuper(obj, args) 是 CGLIB 性能最高的方式
Object result = proxy.invokeSuper(obj, args);
// --- 代理增强:后置处理 ---
if ("viewHouse".equals(methodName)) {
System.out.println("【CGLIB 中介 Post-Info】: 这套房子的产权清晰,税费情况已准备好清单。");
}
return result; // 返回真实方法执行结果
}
}
测试代码
public class CglibProxyClient {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HouseOwner.class);
enhancer.setCallback(new CglibAgentInterceptor());
HouseOwner agentProxy = (HouseOwner) enhancer.create();
System.out.println("--- 客户通过 CGLIB 代理 (中介) 看房 ---");
agentProxy.viewHouse();
System.out.println("\n--- 客户通过 CGLIB 代理 (中介) 签署合同 ---");
agentProxy.signContract();
}
}
运行结果,与上面两种方式类似

CGLIB的本质是对目标类进行继承。所以不再需要目标对象实现某个接口,但是final方式对方法无法进行代理。理由也显而易见,因为不能重写。
手写简易JDK动态代理
为了对JDK动态代理原理有一个比较直观的感受,下面手写一个简易JDK动态代理。
JDK动态代理主要要到1个类和1个接口:
- Proxy
- InvocationHandler
手写Proxy类
首先,我们先看到使用JDK动态代理,关键是这一行代码:
HouseTransaction agentProxy = (HouseTransaction)Proxy.newProxyInstance(realHouseOwner.getClass().getClassLoader(),
realHouseOwner.getClass().getInterfaces(),
agentHandler);
这个agentProxy对象的类是在运行时动态生成,怎么理解呢?
我们可以理解为:在运行时,Java进程写一个临时.java文本文件,然后编译这个java文件,得到class文件,然后加载进内存。
接下来手写一个我们自己的Proxy类,以示区分,这里命名为MyProxy:
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 用来生成源代码的工具类
*/
public class MyProxy {
public static final String ln = "\r\n";
public static Object newProxyInstance(MyClassLoader classLoader, Class<?> [] interfaces, MyInvocationHandler h){
try {
//1、动态生成源代码.java文件
String src = generateSrc(interfaces);
System.out.println(src);
//2、Java文件输出磁盘
String filePath = MyProxy.class.getResource("").getPath();
// System.out.println(filePath);
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//3、把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
Iterable iterable = manage.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
task.call();
manage.close();
//4、编译生成的.class文件加载到JVM中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
f.delete();
//5、返回字节码重组以后的新的代理对象
return c.newInstance(h);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
private static String generateSrc(Class<?>[] interfaces){
StringBuffer sb = new StringBuffer();
sb.append(MyProxy.class.getPackage() + ";" + ln);
sb.append("import " + interfaces[0].getName() + ";" + ln);
sb.append("import java.lang.reflect.*;" + ln + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append(" MyInvocationHandler h;" + ln);
sb.append(" public $Proxy0(MyInvocationHandler h) { " + ln);
sb.append(" this.h = h;" + ln);
sb.append(" }" + ln + ln);
for (Method m : interfaces[0].getMethods()){
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if(i > 0 && i < params.length-1){
paramNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append(" public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
sb.append(" try{" + ln);
sb.append(" Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
sb.append(" " + (hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})",m.getReturnType()) + ";" + ln);
sb.append(" }catch(Error _ex) {" + ln);
sb.append(" }catch(Throwable e){" + ln);
sb.append(" throw new UndeclaredThrowableException(e);" + ln);
sb.append(" }" + ln);
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append(" }" + ln);
}
sb.append("}" + ln);
return sb.toString();
}
private static Map<Class,Class> mappings = new HashMap<Class, Class>();
static {
mappings.put(int.class,Integer.class);
}
private static String getReturnEmptyCode(Class<?> returnClass){
if(mappings.containsKey(returnClass)){
return "return 0;";
}else if(returnClass == void.class){
return "";
}else {
return "return null;";
}
}
private static String getCaseCode(String code,Class<?> returnClass){
if(mappings.containsKey(returnClass)){
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
private static boolean hasReturnValue(Class<?> clazz){
return clazz != void.class;
}
private static String toLowerFirstCase(String src){
char [] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
通过看到上面newProxyInstance的方法,不难理解它的动态生成类的步骤,其中我们需要关注的方法是generateSrc,所谓用"用Java代码写Java代码“,这里有一种用记事本写代码的感觉,因为没有了IDE的辅助,不容易及时发现代码有没有语法错误。
封装ClassLoader
接下来我们看到上面有这么一行
//4、编译生成的.class文件加载到JVM中来
Class proxyClass = classLoader.findClass("$Proxy0");
那么这里到底是怎么加载.class文件到内存呢?这里的classLoader对象是从外面传进来的。这里我们基于JDK原生的ClassLoader,封装一个自己的ClassLoader,以示区分,这里命名为MyClassLoader
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader {
private File classPathFile;
public MyClassLoader(){
String classPath = MyClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = MyClassLoader.class.getPackage().getName() + "." + name;
if(classPathFile != null){
File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
if(classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte [] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1){
out.write(buff,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
}
可以看到,findClass方法是从磁盘上找到class文件读入内存,装入一个byte[],调用父类的defineClass方法得到类对象。
自定义InvocationHandler接口
在上面的MyProxy类中,我们用到了自定义的InvocationHandler接口,命名为MyInvocationHandler,这里给出定义,比较简单:
import java.lang.reflect.Method;
public interface MyInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
客户端测试
import com.example.pattern.proxy.myDynamicProxy.myJdkProxy.core.MyClassLoader;
import com.example.pattern.proxy.myDynamicProxy.myJdkProxy.core.MyInvocationHandler;
import com.example.pattern.proxy.myDynamicProxy.myJdkProxy.core.MyProxy;
public class Client {
public static void main(String[] args) {
// 1. 创建真实主题对象
HouseTransaction realHouseOwner = new HouseOwner();
// 2. 创建 InvocationHandler (传入真实房主)
MyInvocationHandler agentHandler = new AgentHandler(realHouseOwner);
// 3. 动态生成代理对象
HouseTransaction agentProxy = (HouseTransaction)MyProxy.newProxyInstance(new MyClassLoader(),
realHouseOwner.getClass().getInterfaces(),
agentHandler);
// 4. 客户端通过代理对象执行操作
System.out.println("--- 客户通过动态代理 (中介) 看房 ---");
agentProxy.viewHouse();
System.out.println("\n--- 客户通过动态代理 (中介) 签署合同 ---");
agentProxy.signContract();
}
}
中介代码:
import com.example.pattern.proxy.myDynamicProxy.myJdkProxy.core.MyInvocationHandler;
import java.lang.reflect.Method;
public class AgentHandler implements MyInvocationHandler {
private final Object target;
public AgentHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("viewHouse".equals(methodName)) {
System.out.println("【中介 Pre-Check】: 为您核实购房资格和预算。");
} else if ("signContract".equals(methodName)) {
System.out.println("【中介 Pre-Action】: 准备合同文本,进行流程监管。");
}
Object result = method.invoke(target, args);
if ("viewHouse".equals(methodName)) {
System.out.println("【中介 Post-Info】: 这套房子的产权清晰,税费情况已准备好清单。");
}
return result;
}
}
房东代码,这个没有变化:
public class HouseOwner implements HouseTransaction {
@Override
public void viewHouse() {
System.out.println("房主:欢迎来看房,这是我的房子,我很满意。");
}
@Override
public void signContract() {
System.out.println("房主:与买家签署合同,收款。");
}
}
房东实现的接口,也没有变化
public interface HouseTransaction {
void viewHouse(); // 看房
void signContract(); // 签署合同
}
运行效果,为了便于观察Java写出来的代码是长什么样子的,加了一个打印

为了方便理解,我把运行时生成的java代码放到下面:
public class $Proxy0 implements com.example.pattern.proxy.myDynamicProxy.myJdkProxy.client.HouseTransaction{
MyInvocationHandler h;
public $Proxy0(MyInvocationHandler h) {
this.h = h;
}
public void viewHouse() {
try{
Method m = com.example.pattern.proxy.myDynamicProxy.myJdkProxy.client.HouseTransaction.class.getMethod("viewHouse",new Class[]{});
this.h.invoke(this,m,new Object[]{});
}catch(Error _ex) {
}catch(Throwable e){
throw new UndeclaredThrowableException(e);
}
}
public void signContract() {
try{
Method m = com.example.pattern.proxy.myDynamicProxy.myJdkProxy.client.HouseTransaction.class.getMethod("signContract",new Class[]{});
this.h.invoke(this,m,new Object[]{});
}catch(Error _ex) {
}catch(Throwable e){
throw new UndeclaredThrowableException(e);
}
}
}
可以看到,每个方法的最后是执行下面代码,其中h就是中介,也就是回调InvocationHandler的实现者的invoke方法。
this.h.invoke(this,m,new Object[]{});
当然,在运行结果的最后,可以看到跟原生的JDK动态代理的是一样的

最后
以上就是代理模式的全部内容,希望能帮助大家理解代理模式。
更多推荐




所有评论(0)