代理模式介绍

代理模式(Proxy Pattern)是一种结构型设计模式,它为另一个对象提供一个替身或者占位符,以控制对这个对象的访问。

在Java中,代理模式通常用于在不修改目标对象代码的情况下,增加一些额外功能,例如权限控制,日志记录,性能监控,失败重试等。这是符合开闭原则,也就是对软件实体应该对扩展开放,对修改关闭,这意味着能够在不修改现有代码的情况下添加新的功能。

下面通过一个生活上的例子,结合代码说明代理模式的使用,最后附上简易版的JDK动态代理的实现代码。

生活中的例子:房产中介

假设您需要买一套二手房。您可以直接跟房主(目标对象)交易,但现实中可能更常见的是通过房产中介(代理对象)来完成这个过程。

  1. 角色对应
角色现实生活对应职责说明
抽象主题 (Subject)购房合同签署流程定义房主和中介都应该实现(或拥有的)的共同操作,例如 看房()、商议价格()、签署合同()。
真实主题 (Real Subject)房主 (Owner)拥有并执行核心业务逻辑(卖房)的真实对象。
代理 (Proxy)房产中介 (Agent)持有真实房主的引用,并实现与房主相同的接口,但在调用真实房主的方法前后可以添加额外操作。
  1. 代理的作用
    您作为客户,只需要与中介打交道,中介会替您处理大部分与房主相关的事务。
交易环节代理对象(中介)做的额外工作代理模式中的常见用途
看房前询问您的预算、核实您的购房资格。访问控制(Access Control)/ 权限控制: 在访问真实对象前进行校验。
带您看房提前联系房主、安排时间、准备钥匙、介绍房子的优点。前置处理(Pre-processing): 在核心操作执行前做准备工作。
价格谈判在您和房主之间来回协商,努力达成一个双方满意的价格。中介/过滤: 控制或优化真实对象方法的输入输出。
签署合同准备合同文本、陪同您和房主完成签署,确保流程合规。后置处理(Post-processing)/ 日志记录: 在核心操作完成后进行善后、记录交易信息等。

代理模式分类

  1. 静态代理
    这种代理方式需要代理对象和目标对象实现一样的接口。
    • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。
    • 缺点:
      1. 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
      2. 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
  2. 动态代理
    静态代理在编译时就已经实现,编译完成后代理类是一个实际的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. 动态代理的核心步骤

  1. 定义接口: 必须有一个接口(HouseTransaction)。
  2. 实现 InvocationHandler: 创建一个处理器,它定义了代理需要做什么增强操作。所有对接口方法的调用,都会被转发到这个处理器的 invoke 方法。
  3. 动态生成代理对象: 使用 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 的特点:

  1. 无需接口: 它可以代理没有实现接口的类(这也是它常被用于代理类的主要原因)。

  2. 通过继承实现: CGLIB 是通过继承目标类(HouseOwner)来创建代理子类来实现代理的。

  3. 使用 MethodInterceptor: 代理的增强逻辑定义在 net.sf.cglib.proxy.MethodInterceptor 的 intercept 方法中。

由于CGLIB不需要真实对象实现某个类,下面我们将移除 HouseTransaction 接口,让 HouseOwner 成为一个普通的类。

  1. 添加CGLIB的maven依赖
 <dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>2.2</version>
 </dependency>
  1. 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动态代理的是一样的
在这里插入图片描述

最后

以上就是代理模式的全部内容,希望能帮助大家理解代理模式。

更多推荐