AOP有两种实现方式:静态代理和动态代理。
- 静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
- 缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
- 动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
Spring
的AOP
实现原理其实很简单,就是通过动态代理实现的。如果我们为Spring
的某个bean
配置了切面,那么Spring
在创建这个bean
的时候,实际上创建的是这个bean
的一个代理对象,我们后续对bean
中方法的调用,实际上调用的是代理类重写的代理方法。
而Spring
的AOP
使用了两种动态代理,分别是JDK的动态代理,以及CGLib的动态代理。
JDK动态代理
如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler
接口和Proxy
类。
缺点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。
CGLIB动态代理
通过继承实现。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final
,那么它是无法使用CGLIB做动态代理的。
优点:目标类不需要实现特定的接口,更加灵活。
什么时候采用哪种动态代理?(jdk 多少以后,默认全都是 cglib 了)
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库
两者的区别:
- jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来动态生成字节码,不需要导入其他依赖。cglib需要引入相关依赖:
asm.jar
,它使用字节码增强技术来实现。 - 当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。
- 反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效。
JDK 动态代理详解
代理模式
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。
通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间。
动态代理步骤:
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
- 通过Proxy的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理对象
- 通过代理对象调用方法
1、需要动态代理的接口
package jiankunking; /** * 需要动态代理的接口 */ public interface Subject { /** * 你好 * * @param name * @return */ public String SayHello(String name); /** * 再见 * * @return */ public String SayGoodBye(); }
2、需要代理的实际对象(上面接口的实现)
package jiankunking; /** * 实际对象 */ public class RealSubject implements Subject { /** * 你好 * * @param name * @return */ public String SayHello(String name) { return "hello " + name; } /** * 再见 * * @return */ public String SayGoodBye() { return " good bye "; } }
3、代理类关联的 handler 对象。实现 InvocationHandler 接口,实现 invoke 方法,构造方法传入真实类。
它的 invoke 方法用来替代理类做真正的工作:invoke() 里除了调用真实对象的方法可以在其前后做些别的
package jiankunking; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 调用处理器实现类 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象 */ public class InvocationHandlerImpl implements InvocationHandler { /** * 这个就是我们要代理的真实对象 */ private Object realSubject; /** * 构造方法,给我们要代理的真实对象赋初值 * * @param subject */ public InvocationHandlerImpl(Object realSubject) { this.realSubject = realSubject; } /** * 该方法负责集中处理动态代理类上的所有方法调用。 * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 * * @param proxy 代理类实例 * @param method 被调用的方法对象 * @param args 调用参数 * @return * @throws Throwable */ @override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在代理真实对象前我们可以添加一些自己的操作 System.out.println("在调用之前,我要干点啥呢?"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 Object returnValue = method.invoke(realSubject, args); //在代理真实对象后我们也可以添加一些自己的操作 System.out.println("在调用之后,我要干点啥呢?"); return returnValue; } }
4、生成代理类,用代理类进行方法调用
JDK生成的最终真正的代理类,它继承自Proxy并和真实类实现了同一个接口(前面定义的 Subject 接口)。
在代理类实现的Subject接口方法的内部,Proxy.newInstance 通过反射调用了InvocationHandlerImpl的invoke方法。
package jiankunking; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * 动态代理演示 */ public class DynamicProxyDemonstration { public static void main(String[] args) { //代理的真实对象 Subject realSubject = new RealSubject(); /** * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用. * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法 */ InvocationHandler handler = new InvocationHandlerImpl(realSubject); ClassLoader loader = realSubject.getClass().getClassLoader(); Class[] interfaces = realSubject.getClass().getInterfaces(); /** * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 */ Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler); System.out.println("动态代理对象的类型:"+subject.getClass().getName()); String hello = subject.SayHello("jiankunking"); System.out.println(hello); } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器