面向切面编程,Spring AOP核心概念、使用示例。
1、基础概念
1.1、切面(Aspect)
首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象思维,类定义完成之后(封装)。每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,那个对象就在那里,不卑不亢,不悲不喜,等着被使用,等着被回收。
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。
2、切入点(PointCut)
要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。
3、连接点(JoinPoint)
我们知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。
4、通知(Advice)
通知被织入方法,该如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念,如下面实例中,通用日志处理(切面),@Pointcut规则中指明的方法即为切入点,@Before、@After是连接点,而下面的代码就是对应通知。
1 |
|
5、目标对象(Target Object)
被一个或多个切面所通知的对象,即为目标对象。
6、AOP代理对象(AOP Proxy Object)
AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。
7、织入(Weaving)
将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。
2、示例
背景:利用aop实现日志切面。
步骤:
1、导入切面需要的依赖
1 | <dependency> |
2、自定义注解 AOPLog
Target:描述了注解修饰的对象范围,有如下取值:
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述方法变量
- TYPE:用于描述类、接口或enum类型
Retention:表示注解保留时间长短
- SOURCE:在源文件中有效,编译过程中会被忽略
- CLASS:随源文件一起编译在class文件中,运行时忽略
- RUNTIME:在运行时有效
只有定义为 RetentionPolicy.RUNTIME(在运行时有效)时,我们才能通过反射获取到注解,然后根据注解的一系列值,变更不同的操作。
1 | // 指定注解使用在方法上 |
3、定义切面类
补充:切入点表达式
1)bean表达式
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean(“userServiceImpl”) 指定一个userServiceImpl类中所有方法。
bean(“*ServiceImpl”) 指定所有后缀为ServiceImpl的类中所有方法。
2)within表达式
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
- within(“aop.service.UserServiceImpl”) 指定当前包中这个类内部的所有方法。
- within(“aop.service.*”) 指定当前目录下的所有类的所有方法。
- within(“aop.service…*”) 指定当前目录以及子目录中类的所有方法。
3)execution表达式
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
- execution(void aop.service.UserServiceImpl.addUser()) 匹配addUser方法。
- execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
- execution(* aop.service…*.*(…)) 万能配置。
4)@annotation表达式
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
- @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
- @annotation(anno.RequiredCache) 匹配有此注解描述的方法。
1 |
|
4. 编写测试接口
1 |
|