AspectOriented Programing,面向切面编程。
<!--aspect--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>
配置文件开启注解
<!--开启注解切面--> <aop:aspectj-autoproxy proxy-target-class="true" />
AOP主要用于日志记录,性能统计,安全控制(权限控制),事务处理,异常处理等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
Spring AOP织入增强(Advice)的方式有两种 如果连接点实现了接口采用jdk自带的动态代理的形式实现织入,如果连接点没有实现接口则采用动态字节码生成技术(CGLIB)实现织入。
AOP常用术语:
连接点(Joinpoint)
增强程序执行的某个特定位置(要在哪个地方做增强操作)。Spring仅支持方法的连接点,既仅能在方法调用前,方法调用后,方法抛出异常时等这些程序执行点进行织入增强。
切点(Pointcut)
切点是一组连接点的集合。AOP通过“切点”定位特定的连接点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。
增强(Advice)
增强是织入到目标类连接点上的一段程序代码。表示要在连接点上做的操作。
切面(Aspect)
切面由切点和增强(引介)组成(可以包含多个切点和多个增强),它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。
@Slf4j @Aspect @Component public class ServiceLogAspect { /** * 方法执行前打印参数(仅log.debug=true时记录) * @param joinPoint */ @Before("execution(* com.gitub.xl137010393.service.*.*(..))") public void doBefore(JoinPoint joinPoint) { // 如果日志未开启debug模式则跳过 if(!log.isDebugEnabled()) { return; } Object[] args = joinPoint.getArgs(); String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); log.debug("调用{}.{}参数:", className, methodName); if(args.length > 0) { for(int i = 0; i < args.length; i++) { log.debug("第{}个参数: {}", i, args[i]); } } }
}
常用注解:
- @aspect 定义切面
- @pointcut 定义切点
- @before 标注Before Advice定义所在的方法
- @afterreturning 标注After Returning Advice定义所在的方法
- @afterthrowing 标注After Throwing Advice定义所在的方法
- @after 标注 After(Finally) Advice定义所在的方法
- @around 标注Around Advice定义所在的方法
我们如何在定义切点(Pointcut)的时候指定一类Joinpoint呢?有两种方式 简单的方法名指定以及正则表达式两种方式。
常用的@AspectJ形式Pointcut表达式的标志符:
execution:
Spring AOP仅支持方法执行类型的Joinpoint 所以execution将会是我们用的最多的标志符,用它来帮我们匹配拥有指定方法前面的Joinpoint。匹配规则如下:
execution(modifiers-pattern? return-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern)
- modifiers-pattern 修饰符 比如public private这种(可以指定可以不指定)
- return-type-pattern 返回值类型(必须指定)
- declaring-type-pattern 类型(可以是含包名的全路径类型 可以指定可以不指定)
- name-pattern 方法名(必须指定)
- param-pattern 参数类型(必须指定)
方法的返回类型 方法名及参数部分的匹配模式是必须指定的 其他部分可以省略。
我们还可以在表达式中使用两种通配符:*和..
第一:*可以用于任何部分的匹配模式中,匹配相邻的多个字符,即一个Work 。如果放在了方法参数的位置标示参数是任何类型的。
例如:execution(* *(String))
第二:..通配符可以在两个位置使用 一个是declaring-type-pattern的位置,一个是在方法参数匹配模式的位置。
如果是放在了方法类型的位置,可以指定多个层次的类型声明。例如:
execution(void cn.spring.*.doSomething(*)) 指定到cn.spring下的所有类型。
如果是放在了方法参数的匹配位置,则表示该方法可以有0到多个参数。例如:
execution(void *.doSomething(..))
within:
within标志符只接受类型声明,它将匹配指定类型下所有的Joinpoint。
例如:within(cn.spring.aop.target.*) 将会匹配 cn.spring.aop.target包下所有类型的方法级别的Joinpoint。
Aspect 类详解
1.JoinPoint
java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
Signature getSignature() :获取连接点的方法签名对象;
java.lang.Object getTarget() :获取连接点所在的目标对象;
java.lang.Object getThis() :获取代理对象本身;
2.ProceedingJoinPoint
ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:
java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法;
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。