Spring原理与源码分析系列(六)- Spring AOP入门与概述
一、AOP
1、什么是AOP
AOP :Aspect-Oriented Programming,面向切面编程的简称。
1 2 | 在我们的项目代码中,有大量与日志、事务、权限(AOP称之为横切关注点)相关的代码镶嵌在业务代码当中,造成大量代码的重复与代码的冗余。 虽然可以将这些重复的代码封装起来再进行调用,但是这样的调用方式比较单一,不够灵活,无法更好地以模块化的方式,对这些横切关注点进行组织和实现。 |
1 | AOP提出切面(Aspect)的概念,以模块化的方式对横切关注点进行封装,再通过织入的方式将切面织入到业务逻辑代码当中。这样横切关注点与业务逻辑代码分离,业务逻辑代码中就不再含有日志、事务、权限等代码的调用,可以很好的进行管理。 |
2、AOP基本概念
AOP中的相关术语有:Joinpoint,Pointcut,Advice,Aspect,Introduction,Weaving。
这些概念将在Spring AOP中详细描述,此处略去。
(1)点(Joinpoint)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
(2)切点(Pointcut)
如果说通知定义了切面“是什么”和“何时”的话,那么切点就定义了“何处”。比如我想把日志引入到某个具体的方法中,这个方法就是所谓的切点。
(3)通知(Advice)
在AOP中,切面的工作被称为通知。通知定义了切面“是什么”以及“何时”使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
Spring切面可以应用5种类型的通知:
• 前置通知(Before):在目标方法被调用之前调用通知功能;
• 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
• 返回通知(After-returning):在目标方法成功执行之后调用通知;
• 异常通知(After-throwing):在目标方法抛出异常后调用通知;
• 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为;
(4)切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容———他是什么,在何时和何处完成其功能。
(5)引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。
(6)织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
• 编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
• 类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
• 运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。
(此处术语解释来源:
https://www.jianshu.com/p/5155aabaec3f)
3、AOP的分类
AOP主要分为静态AOP和动态AOP。
(1)静态AOP
1 | 静态AOP,也称为第一代AOP,是指将相应横切关注点以Aspect形式实现之后,在编译阶段,通过特定编译器将Aspect织入到目标类当中。 |
优点:Aspect直接以Java字节码的形式编译到Java Class类中,Java虚拟机可以正常加载类,没有性能损失。
缺点:不够灵活,如果要修改织入的位置时,就需要修改Aspect和重新编译。
静态AOP需要有3个重要因素:
1 2 3 | 1 )共同的接口:定义方法不提供实现,供外部调用; 2 )实现类:上述接口具体实现; 3 )代理类:注入的是实现类,调用接口的方法实际是调用实现类的方法的实现。 |
下面通过代码来看看静态AOP是如何实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //共同接口 public interface CommonService { public void sayHello(); } //接口的具体实现 public class CommonServiceImpl implements CommonService { @Override public void sayHello() { System.out.println( "hello, 静态代理!" ); } } //代理类 public class StaticProxy implements CommonService { private CommonService realService; public StaticProxy(CommonService realService){ this .realService = realService; } @Override public void sayHello() { //织入横切逻辑 System.out.println( "日志输出1.。。" ); realService.sayHello(); //织入横切逻辑 System.out.println( "日志输出2.。。" ); } } //测试 public static void main(String[] args) { CommonService realService = new CommonServiceImpl(); StaticProxy proxy = new StaticProxy(realService); proxy.sayHello(); } |
输出结果:
1 2 3 | 日志输出 1 .。。 hello, 静态代理! 日志输出 2 .。。 |
可以看到,在运行前,即将横切逻辑织入到所需要织入的位置。
我们为RealService类产生了一个代理对象StaticProxy,假如还有RealService2,RealService3….,就要继续生成代理对象StaticProxy2,StaticProxy3,,,,,这样比较麻烦,因此就需要动态AOP来实现。
(2)动态AOP
1 2 | 动态AOP,也称为第二代AOP,是指在类加载或者系统运行阶段,将Aspect代码动态织入到目标类当中。 与静态AOP最大的不同之处在于织入过程发生在系统运行后,而不是预编译阶段。 |
优点:比较灵活,可以动态更改织入逻辑。
缺点:由于发生阶段是在类加载或者系统运行阶段,因此会造成运行时性能损失。
动态AOP可以通过JDK动态代理或者Cglib来实现,下面通过JDK动态代理的方式来实现动态AOP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | //同样需要公共接口 public interface CommonService { public void sayHello(); } //具体实现类 public class CommonServiceImpl implements CommonService { @Override public void sayHello() { System.out.println( "hello, 动态代理!" ); } } //动态代理类 public class DynamicProxy implements InvocationHandler { //要代理的对象 private Object obj; //实际注入的对象 public DynamicProxy(Object realObj){ this .obj = realObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "日志输出1....." ); method.invoke(obj, args); System.out.println( "日志输出1....." ); return null ; } } //测试:通过公共接口的ClassLoader和代理类生成一个代理类对象 public static void main(String[] args) { //产生一个代理类 InvocationHandler handler = new DynamicProxy( new CommonServiceImpl()); //产生代理类对象 CommonService proxyService = (CommonService) Proxy.newProxyInstance(CommonService. class .getClassLoader(), new Class<?>[]{CommonService. class }, handler); proxyService.sayHello(); } |
输出:
1 2 3 | 日志输出 1 ….. hello, 动态代理! 日志输出 2 ….. |
这里有几个需要注意的地方:
- InvocationHandler :InvocationHandler 用来生成动态代理类,它的构造方法注入了具体的实现类,表示被代理的对象;
invoke()方法的几个参数:
1 2 3 | proxy:表示代理类对象本身,作用不大 method:正在被调用的方法; args:方法的参数; |
Proxy.newProxyInstance
该方法是java.lang.reflect包下Proxy类的静态方法,方法定义如下:
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
其中:
1 2 3 | loader:表示被代理对象的类加载器; interfaces:代理类要实现的接口列表,只能是接口类型; h:h的类型为InvocationHandler,它是一个接口,也定义在java.lang.reflect包中,它只定义了一个方法invoke,对代理接口所有方法的调用都会转给该方法 |
4、AOP实现机制
Java平台主要提供了如下两种方式实现AOP:
♦ 动态代理(Dynamic Proxy)—需要有接口
♦ 动态字节码增强(Cglib)—不需要接口
(1)动态代理(Dynamic Proxy)
JDK1.3之后,引入了动态代理(Dynamic Proxy)机制。
1 | 在运行期间为相应接口动态生成对应的代理对象,再将横切关注点逻辑封装到动态代理的InvocationHandler中。在系统运行期间,将横切逻辑织入到代理类指定位置中。 |
缺点:动态代理只针对接口有效。
在上节动态AOP中我们已经看到了如何使用JDK动态代理的方式去实现AOP,本节不再赘述。
(2)动态字节码增强(Cglib)
有的时候我们无法生成接口,自然就无法使用动态代理的方式,这个时候我们就可以使用ASM(字节码增强框架)或者Cglib技术来动态生成字节码的Class文件,而只要符合JVM规范的.class文件都可以被JVM所加载。
1 2 | Cglib就是在系统运行期间,通过动态字节码增强技术,为需要织入横切逻辑的目标类生成对应子类,代理类重写了父类所有 public 非 final 的方法(即横切逻辑加到方法当中)。 当程序调用目标类时,通过拦截方法实际最后执行的是具有横切逻辑的子类。 |
缺点:如果目标类或目标类中的方法是final的话,就无法进行织入(final类不能被继承,final方法不能被重写)。
下面通过代码来看看动态字节码增强(Cglib)是如何实现AOP的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | //需要织入横切逻辑的目标类 public class Login { public void login(String username){ if ( "admin" .equals(username)){ System.out.println( "登录成功!" ); } else { System.out.println( "登录失败!" ); } } } public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //织入横切逻辑 System.out.println( "权限检查1,,," ); //调用目标类方法 Object result = methodProxy.invokeSuper(o, objects); //织入横切逻辑 System.out.println( "权限检查2,,," ); return result; } } public class CglibTest { // 生成代理对象 private static <T> T getProxy(Class<T> clazz){ Enhancer enhancer = new Enhancer(); //将目标类设置为父类 enhancer.setSuperclass(clazz); //设置回调 enhancer.setCallback( new MyMethodInterceptor()); return (T) enhancer.create(); } public static void main(String[] args) { Login loginProxy = getProxy(Login. class ); loginProxy.login( "admin" ); } } |
输出结果:
1 2 3 | 权限检查 1 ,,, 登录成功! 权限检查 2 ,,, |
可以看到,首先重写MethodInterceptor接口中的intercept方法,在该方法中完成横切逻辑的织入以及目标类的方法的调用,
然后通过Enhancer类生成代理对象,将目标类设置为代理类,并完成回调
AOP的内容就介绍到此,下面将介绍Spring AOP的相关理论和实战。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构