【Java】代理模式,静态代理和动态代理(基于JDK或CGLib)
当我们需要在一个方法之前或之后添加一段逻辑时,自然会想到使用代理类。代理类帮我们代理了实际类的调用,然后可以在实际调用之前和之后添加一些逻辑,从而不浸入实际类。
拓展:由于代理类能在实际类调用之前和之后添加逻辑,那么可做的事情就多了,常见的有4种,用AOP的术语描述就是:
- 前置增强:在实际方法前添加逻辑。比如,在方法执行前打印入参;在方法执行前判断用户是否有执行此方法的权限
- 后置增强:在实际方法后添加逻辑。比如,在方法执行后打印结果
- 环绕增强:在实际方法之前和之后都添加逻辑。
- 抛出增强:当实际方法发生异常时执行添加的逻辑。
不用代理
有时,需要在一些方法前后都打印一些日志。
这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果。(至于为什么不直接用+号运算,见【Java】Float计算不准确)
package com.nicchagil.study.java.demo.No09代理.No01不用代理; import java.math.BigDecimal; public class FloatCalculator { public float add(float a, float b) { BigDecimal b1 = new BigDecimal(a + ""); BigDecimal b2 = new BigDecimal(b + ""); float f = b1.add(b2).floatValue(); return f; } }
我想在它运行前后打印,最直接的方式就是调用时打印了
package com.nicchagil.study.java.demo.No09代理.No01不用代理; public class Call { public static void main(String[] args) { float f1 = 1f; float f2 = 1f; System.out.println("f1 -> " + f1 + ", f2 -> " + f2); float result = new FloatCalculator().add(f1, f2); System.out.println("result -> " + result); } }
看到这日志,我很欣慰!
f1 -> 1.0, f2 -> 1.0
result -> 2.0
静态代理
随着项目变大,调用此方法的地方变得越来越多,如果有10个调用的地方,我岂不是要写100次打印的方法。
这时,静态代理的方式能帮助我们。
定义个接口
package com.nicchagil.study.java.demo.No09代理.No02静态代理; public interface ICalculator { /** * <p>add</p> */ public float add(float a, float b); }
真实业务类
package com.nicchagil.study.java.demo.No09代理.No02静态代理; import java.math.BigDecimal; public class FloatCalculator implements ICalculator { @Override public float add(float a, float b) { BigDecimal b1 = new BigDecimal(a + ""); BigDecimal b2 = new BigDecimal(b + ""); float f = b1.add(b2).floatValue(); return f; } }
代理类,这个类中,处理执行实际业务,还一并捆绑打印日志的任务。
下面的例子中,代理类持有了被代理类的引用,直接调用被代理类的逻辑执行实际业务。
package com.nicchagil.study.java.demo.No09代理.No02静态代理; public class FloatCalculatorProxy implements ICalculator { ICalculator c = null; /** * 构造方法 * @param c 需被代理的对象 */ public FloatCalculatorProxy(ICalculator c) { super(); this.c = c; } @Override public float add(float f1, float f2) { System.out.println("f1 -> " + f1 + ", f2 -> " + f2); float result = this.c.add(f1, f2); System.out.println("result -> " + result); return result; } }
然后,我们调用时,只需调用代理类,不仅计算得结果,日志也乖乖地出来了
package com.nicchagil.study.java.demo.No09代理.No02静态代理; public class Call { public static void main(String[] args) { System.out.println("代理的对象:"); ICalculator c2 = new FloatCalculatorProxy(new FloatCalculator()); c2.add(1f, 1f); } }
看到日志,我很镇静
代理的对象: f1 -> 1.0, f2 -> 1.0 result -> 2.0
动态代理
JDK基于接口的动态代理
如果现在不仅FloatCalculator这个类需要打印日志,还有其他各种类也需要打印日志,那么我们岂不是要写好多个代理类了?
这时需要使用动态代理。JDK有提供动态代理的实现,通过反射机制为我们实现动态代理。
接口类(ICalculator)、真实业务类(FloatCalculator)如同静态代理,不再重复
调用处理类。这个类实现InvocationHandler,主要任务是:
- 注入被调用对象
- 调用被调用对象的对应方法(在调用方法前后可自行添加逻辑)
package com.nicchagil.study.java.demo.No09代理.No03动态代理; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicProxyHandler implements InvocationHandler { private Object proxied = null; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Clazz -> " + proxy.getClass()); System.out.println("method -> " + method); for (int i = 0; i < args.length; i++) { System.out.println("args[" + i + "] -> " + args[i]); } Object result = method.invoke(proxied, args); System.out.println("result -> " + (result != null ? result.toString() : "")); return result; } }
调用类。这里通过Proxy的newProxyInstance方法生成代理类,这个方法的入参有3个:
- 被代理类的类加载器
- 被代理类实现的接口(这也是JDK动态代理的缺点之一,需实现接口。基于此,Spring的AOP在类有实现接口时,使用JDK动态代理,无实现接口时,使用CGlib)
- 被代理对象
package com.nicchagil.study.java.demo.No09代理.No03动态代理; import java.lang.reflect.Proxy; import com.nicchagil.study.java.demo.No09代理.No02静态代理.FloatCalculator; import com.nicchagil.study.java.demo.No09代理.No02静态代理.ICalculator; public class Call { public static void main(String[] args) { /* 代理的对象 */ System.out.println("代理的对象:"); ICalculator c2 = (ICalculator)Proxy.newProxyInstance(ICalculator.class.getClassLoader(), new Class[] {ICalculator.class}, new DynamicProxyHandler(new FloatCalculator())); c2.add(1f, 1f); } }
日志
代理的对象: Clazz -> class $Proxy0 method -> public abstract float com.nicchagil.study.java.demo.No09代理.No02静态代理.ICalculator.add(float,float) args[0] -> 1.0 args[1] -> 1.0 result -> 2.0
在main方法加入如下代码可生成动态代理类的代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
你会在com.sun.proxy包下发现动态生成的代理类:
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements ICalculator { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { 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 float add(float var1, float var2) throws { try { return (Float)super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("ICalculator").getMethod("add", Float.TYPE, Float.TYPE); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
查看代码,你会发现:
- extends Proxy implements ICalculator
- 有私有的、静态的Method变量,在静态代码块用反射赋予变量具体的引用
- 构造方法的入参为InvocationHandler对象,在本例中就是DynamicProxyHandler对象,也就是具体的方法中的super.h,本代理中会直接调用之前我们重写的invoke方法
- 实现ICalculator的各方法,实现体为用反射调用Method变量、InvocationHandler对象
super.h就是Proxy中的InvocationHandler,构造方法如下:
protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
CGLib动态代理
JDK动态代理有个缺点,只能对实现了接口的类进行代理,如果目标类没有实现接口,我们可以使用CGLib。
引入相关包:
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency> </dependencies>
定义了需被代理的UserService,方法拦截器类,和main方法:
package com.nicchagil.exercise.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.util.Arrays; public class Call { public static void main(String[] args) { UserService userServiceProxy = (UserService) Enhancer.create(UserService.class, new MyMethodInterceptor()); userServiceProxy.getById("123"); userServiceProxy.getById("Nick Huang"); } /** * 测试的方法拦截器 */ static class MyMethodInterceptor implements MethodInterceptor { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before logic, parameter : " + Arrays.toString(objects)); Object result = methodProxy.invokeSuper(o, objects); System.out.println("after logic, result : " + result); return result; } } /** * User业务类 */ static class UserService { public Object getById(String id) { System.out.println("getById"); return new Object(); } public Object getByName(String name) { System.out.println("getByName"); return new Object(); } } }
日志:
before logic, parameter : [123]
getById
after logic, result : java.lang.Object@573fd745
before logic, parameter : [Nick Huang]
getById
after logic, result : java.lang.Object@15327b79
本博客为学习、笔记之用,以笔记形式记录学习的知识与感悟。学习过程中可能参考各种资料,如觉文中表述过分引用,请务必告知,以便迅速处理。如有错漏,不吝赐教。
如果本文对您有用,点赞或评论哦;如果您喜欢我的文章,请点击关注我哦~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用