Java基石--动态代理让你无中生有

前言

"代理"这个词无论是在现实生活还是代码的世界里都广泛存在,本篇将结合两者来阐述代理的真正作用。

通过本篇文章,你想了解到:

  1. 什么是代理?
  2. 什么是静态代理?
  3. 什么是动态代理?
  4. JDK 动态代理的使用与原理
  5. cglib 动态代理的使用与原理
  6. 小结

1. 什么是代理?

生活中的代理

生活中我们常听到的是:销售代理。如药品代理、啤酒代理、汽车代理等。

以比亚迪汽车为例:

  1. 比亚迪厂家生产了汽车,它可以直接售卖汽车
  2. 为了扩大市场,它将汽车的销售权赋予了其他公司,这些公司帮助他销售,这些公司被称为汽车销售代理公司(常见的是4S店)
  3. 比亚迪卖不同的车型,比如汉、唐、宋、元的车型,那么4S店同样可以卖以上车型
  4. 4S店还可以根据自己的营销策略,在销售汽车的时候附带一些服务,比如贴膜、卖保险等

总之,代理是:在事物原有行为的基础上添加额外的操作。

代码中的代理

将上述场景翻译为代码。

卖车的行为抽象为接口:

java 复制代码
public interface CarSeller {
  	//卖唐系列
    void sellTangCar();
  	//卖宋系列
    void sellSongCar();
}

比亚迪自己卖车:

java 复制代码
public class BYDShop implements CarSeller {
    @Override
    public void sellTangCar() {
        System.out.println("比亚迪卖出一辆唐");
    }

    @Override
    public void sellSongCar() {
        System.out.println("比亚迪卖出一辆宋");
    }
}

2. 什么是静态代理?

现在4S店开起来了,它可以卖车,但核心流程还是得走比亚迪销售那块。

定义4S店的卖车逻辑:

java 复制代码
public class FourSShop implements CarSeller {
    
    private CarSeller target;
    
    public FourSShop(CarSeller target) {
        this.target = target;
    }
    
    @Override
    public void sellTangCar() {
        System.out.println("4S店承诺送膜");
        target.sellTangCar();
        System.out.println("4S店执行贴膜");
    }

    @Override
    public void sellSongCar() {
        System.out.println("4S店承诺送保养");
        target.sellTangCar();
        System.out.println("4S店免费办理车贷");
    }
}

如上,4S店可以销售唐、宋汽车,但在销售的过程中他可以附带额外的动作,比如送膜、送保养、办理车贷。

如此一来,4S店就可以帮助比亚迪进行销售了:

java 复制代码
public class ProxyTest {
    public static void main(String[] args) {
        // 测试静态代理
        System.out.println("=======静态代理测试=======");
        CarSeller realSeller = new BYDShop();
        CarSeller carSeller = new FourSShop(realSeller);
        carSeller.sellTangCar();
        carSeller.sellSongCar();
    }
}

最终打印如下:

=======静态代理测试======= 4S店承诺送膜

比亚迪卖出一辆唐

4S店执行贴膜

4S店承诺送保养

比亚迪卖出一辆唐

4S店免费办理车贷

静态代理:编译时就得需要确定代理类(FourSShop)

静态代理优缺点如下:

优点:简单、直观

缺点:1. 每个被代理类都需要对应一个代理类,比较冗余;2. 当接口新增方法时,被代理类和代理类都需要修改,维护成本高;3. 当需要代理其它接口时,需要新增不同的代理类,扩展性差。

3. 什么是动态代理?

我们期望有一种方式可以克服静态代理的缺点,既然静态代理需要在编译时确定代理类,那是否可以在编译时不确定代理类呢?这时候该动态代理出马了。

如果不用事先想好并写好代理类,那么运行时候得知道执行了哪些方法,而运行时确定执行哪些方法我们很容易想到了--反射。

没错,动态大理的基础其实是反射。

动态代理通常有两种实现方式:

  1. JDK动态代理
  2. cglib动态代理

4. JDK 动态代理的使用与原理

使用

还是以比亚迪为例,想要运行时生成代理类,那么得要在运行时生成CarSeller的实现类,而要生成一个接口的实现类,那得要知道这个接口的具体构造(主要是方法签名),当然这些数据可以通过Class获取。

生成了代理类之后,还得通过这个代理类才能够访问到被代理的类,由于我们并没有显式调用被代理类的方法,因此这一步需要使用反射。

1. 创建工厂类

JDK提供了API可以直接创建动态代理类:

java 复制代码
public class JdkProxyFactory {
    @SuppressWarnings("unchecked")
    //target 为被代理的类
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
          			//代理处理类
                new JdkProxyHandler(target)
        );
    }
}

Proxy.newProxyInstance是JDK提供的方法,返回值为代理对象。

2. 创建代理处理类

java 复制代码
public class JdkProxyHandler implements InvocationHandler {
    //被代理的对象
    private Object target;

    public JdkProxyHandler(Object target) {
        this.target = target;
    }

    //proxy 为代理对象
    //method 为接口方法
    //args 为每个方法的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用实际方法
        if(method.getName().equals("sellTangCar")) {
            System.out.println("4S店承诺送膜");
        }
        if (method.getName().equals("sellSongCar")) {
            System.out.println("4S店承诺送保养");
        }
      	//反射调用被代理的类
        Object result = method.invoke(target, args);
        if(method.getName().equals("sellTangCar")) {
            System.out.println("4S店执行贴膜");
        }
        if (method.getName().equals("sellSongCar")) {
            System.out.println("4S店免费办理车贷");
        }
        return result;
    }
}

此处被代理的对象是Object类型,无需指定具体的类型。

3. 使用代理

java 复制代码
public class ProxyTest {
    public static void main(String[] args) {
        CarSeller realSeller = new BYDShop();
        // 测试JDK动态代理
        System.out.println("=======JDK动态代理测试=======");
        CarSeller jdkProxy = JdkProxyFactory.createProxy(realSeller);
        jdkProxy.sellTangCar();
        jdkProxy.sellSongCar();
    }
}

需要(被)代理的类是:BYDShop,它实现了接口:CarSeller。

JdkProxyFactory.createProxy 创建动态代理类,当调用jdkProxy.sellTangCar()/jdkProxy.sellSongCar()时,会触发代理处理类里的invoke方法,该方法里统一处理CarSeller接口所有的方法调用。

在JdkProxyHandler.invoke()方法里,我们就可以自定义拦截处理不同的方法,此处我们通过判断方法名的不同而执行不同的打印,其结果与静态代理一致。

与静态代理相比,JDK动态代理无需单独书写代理类,只需要关注代理处理类InvocationHandler即可,即使后续对接口、被代理类进行方法的增删,若代理类关注它们的变化,也只需要在InvocationHandler里进行处理即可,若不关注则无需处理。

Retrofit的使用

细心的小伙伴可能会问:一定需要被代理类吗?

答案是:不一定。

典型的场景是:Retrofit接口调用,来看看它的使用。

1. 声明网络接口

java 复制代码
public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

定义了接口,使用Get方法。

2. 调用网络接口

java 复制代码
public class RetrofitExample {
    public static void main(String[] args) {
        // 创建 Retrofit 实例
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        
        // 创建动态代理实例
        GitHubService service = retrofit.create(GitHubService.class);
        
        // 调用方法,Retrofit 会自动生成代理实现
        Call<List<Repo>> repos = service.listRepos("octocat");
        
        // 执行网络请求
        try {
            Response<List<Repo>> response = repos.execute();
            if (response.isSuccessful()) {
                List<Repo> repoList = response.body();
                // 处理返回的数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

与之前的动态代理使用方式不同的是:retrofit.create(GitHubService.class)此处传入的并不是被代理的对象,而是一个Class对象。那么当执行service.listRepos("octocat")方法时,会调用到InvocationHandler.invoke()方法里,该方法里肯定就不会调用被代理类的方法,因为它压根就没有被代理类。

来看看Retrofit如何处理代理中间类:

java 复制代码
return (T) Proxy.newProxyInstance(
    service.getClassLoader(),  // 类加载器
    new Class<?>[]{service},   // 要代理的接口
    new InvocationHandler() {  // 调用处理器
        private final Platform platform = Platform.get();
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Object类的方法直接调用
            if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
            }
            
            // 核心逻辑:加载或创建 ServiceMethod 并执行
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
        }
    }
);

可以看出InvocationHandler里并没有持有被代理类。

Retrofit关注的是接口的调用,loadServiceMethod里会根据接口里的方法上的注解来确定应该走什么逻辑,比如@GET("users/{user}/repos")用来识别需要调用的API地址,而@Path("user")则用以确定调用API的入参,此种场景下不会涉及到被代理类。

原理

动态代理的关键是自动生成代理类,来看看这个类有什么特征。

java 复制代码
public class ProxyTest {
    public static void main(String[] args) {
        CarSeller realSeller = new BYDShop();
        // 测试JDK动态代理
        System.out.println("\n=======JDK动态代理测试=======");
        CarSeller jdkProxy = JdkProxyFactory.createProxy(realSeller);
        System.out.println("proxy name:" + jdkProxy.getClass().getName());
        System.out.println("proxy super class:" + jdkProxy.getClass().getSuperclass());
        System.out.println("proxy interfaces:" + jdkProxy.getClass().getInterfaces()[0].getName());
        jdkProxy.sellTangCar();
        jdkProxy.sellSongCar();
    }
}

将proxy相关信息打印,结果如下:

=======JDK动态代理测试=======

proxy name:jdk.proxy1.$Proxy0

proxy super class:class java.lang.reflect.Proxy

proxy interfaces:com.fish.myproxy.CarSeller

生成的代理类名字是:$Proxy0,继承自Proxy,实现了CarSeller接口。

JDK生成$Proxy0的Class内容时并没有默认将其保存到本地磁盘,它生成Class的代码是: gen.generateClassFile(),里面是通过JDK自带的ASM工具生成Class。

而保存Class到磁盘的逻辑如下:ProxyGenerator.generateProxyClass(),里面代码如下:

因此只需要设置saveGeneratedFiles=true即可将生成的Proxy保存到本地,而这个值的定义:

java 复制代码
    private static final boolean saveGeneratedFiles =
            java.security.AccessController.doPrivileged(
                    new GetBooleanAction(
                            "jdk.proxy.ProxyGenerator.saveGeneratedFiles"));

我们可以设置JAVA VM的启动参数,最终使得saveGeneratedFiles=true

ini 复制代码
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

编译运行后查看$Proxy0.class:

java 复制代码
public final class $Proxy0 extends Proxy implements CarSeller {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;
    private static final Method m4;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sellTangCar() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sellSongCar() {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.fish.myproxy.CarSeller").getMethod("sellTangCar");
            m4 = Class.forName("com.fish.myproxy.CarSeller").getMethod("sellSongCar");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

h是InvocationHandler类型,存在Proxy里。

可以看出,首先是缓存了反射出来的各个Method,当调用代理类的对应方法的时候通过调用super.h.invoke()将Method传递到 InvocationHandler.invoke里,最终实现动态代理。

5. cglib 动态代理的使用与原理

使用

JDK动态代理的特征是需要实现接口,如果一个被代理的类没有实现接口或者它不是接口,那么JDK动态代理无能为力了,此时cglib出马了,它是一个第三方库。

和使用JDK动态代理类似,cglib使用有如下步骤:

1. 创建工厂类

java 复制代码
public class CglibProxyFactory{
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Class<T> clazz) {
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(clazz);
        //设置回调,监听方法调用
        enhancer.setCallback(new CglibMethodInterceptor());
        return (T) enhancer.create();
    }
}

2. 创建代理处理类

java 复制代码
public class CglibMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 调用实际方法
        if(method.getName().equals("sellTangCar")) {
            System.out.println("4S店承诺送膜");
        }
        if (method.getName().equals("sellSongCar")) {
            System.out.println("4S店承诺送保养");
        }
				//调用父类方法
        Object result = proxy.invokeSuper(obj, args);
        if(method.getName().equals("sellTangCar")) {
            System.out.println("4S店执行贴膜");
        }
        if (method.getName().equals("sellSongCar")) {
            System.out.println("4S店免费办理车贷");
        }
        return result;
    }
}

3. 使用代理

java 复制代码
public class ProxyTest {
    public static void main(String[] args) {
        System.out.println("\n=======CGLIB动态代理测试=======");
        CarSeller cglibProxy = CglibProxyFactory.createProxy(BYDShop.class);
        System.out.println("proxy name:" + cglibProxy.getClass().getName());
        System.out.println("proxy super class:" + cglibProxy.getClass().getSuperclass());
        System.out.println("proxy interfaces:" + cglibProxy.getClass().getInterfaces()[0].getName());
        cglibProxy.sellTangCar();
        cglibProxy.sellSongCar();
    }
}

最终打印结果如下:

=======CGLIB动态代理测试=======

proxy name:com.fish.myproxy.BYDShop <math xmlns="http://www.w3.org/1998/Math/MathML"> E n h a n c e r B y C G L I B EnhancerByCGLIB </math>EnhancerByCGLIBe0855de

proxy super class:class com.fish.myproxy.BYDShop

proxy interfaces:net.sf.cglib.proxy.Factory

4S店承诺送膜

比亚迪卖出一辆唐

4S店执行贴膜

4S店承诺送保养

比亚迪卖出一辆宋

4S店免费办理车贷

代理打印结果与静态代理、JDK动态代理一致。

代理类也是动态生成的,继承自BYDShop,实现了Factory接口。

原理

通过生成目标类(被代理类)的子类(代理类),当调用代理类的方法时会流转到到代理处理类:MethodInterceptor.intercept(),在该方法里可以调用被代理类的方法。

与JDK动态代理类似,生成的代理Class并没有默认保存到磁盘,我们可以设置将其保存下来:

首先设置Java VM 的启动参数:

java 复制代码
--add-opens java.base/java.lang=ALL-UNNAMED

其次在执行动态代理前指定保存.class的位置:

java 复制代码
        String userDir = System.getProperty("user.dir");
        String cglibDir = userDir + "/target/generated-classes/cglib";

        // 创建目录
        new File(cglibDir).mkdirs();

        // 尝试多种可能的属性设置
        System.setProperty("net.sf.cglib.core.DebuggingClassWriter.DEBUG_LOCATION_PROPERTY", cglibDir);
        System.setProperty("cglib.debugLocation", cglibDir);

最后在当前目录下有如下文件生成:

纵观cglib代码生成过程,其借助了第三方的ASM库,用以生成Class内容。

本质上是通过生成被代理类的子类,而如果被代理类是final修饰或是其方法是private、final修饰的话,那么就无法进行代理,因为他们不能被子类继承或是重写。

6. 小结

代理模式在代码世界里得到了广泛的使用,尤其是动态代理,很多框架简洁(看不懂)的背后都有动态代理的影子,Spring AOP的核心技术之一就是动态代理,了解了动态代理对于理解框架、学习框架、书写框架很有裨益。

按计划,本篇是Java基石系列的最后一篇文章,若后续还有其他基石范畴知识点分享将会再开新章

如果本系列文章对你有帮助,请一键三连~感谢~

相关推荐
咖啡里的茶i2 分钟前
数字化图书管理系统设计实践(java)
java·课程设计
得物技术16 分钟前
营销会场预览直通车实践|得物技术
后端·架构·测试
九转苍翎26 分钟前
Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify
java·thread
曲莫终27 分钟前
正则表达式删除注释和多余换航
java·kotlin
君不见,青丝成雪34 分钟前
浅看架构理论(二)
大数据·架构
Ice__Cai1 小时前
Flask 入门详解:从零开始构建 Web 应用
后端·python·flask·数据类型
武子康1 小时前
大数据-74 Kafka 核心机制揭秘:副本同步、控制器选举与可靠性保障
大数据·后端·kafka
紫穹1 小时前
006.LangChain Prompt Template
后端
whitepure1 小时前
万字详解JavaObject类方法
java·后端
切克呦1 小时前
通过 Cursor CLI 使用 GPT-5 的教程
前端·后端·程序员