文章目录
Spring AOP
AOP使用场景:权限控制、异常处理、缓存、事务管理、日志记录、数据校验等等
AOP基本概念
- 切面(Aspect): 程序运行过程中的某个的步骤或者阶段
- 连接点(Joinpoint): 程序运行过程中可执行特定处理(增强处理)的点, 如异常处理。而在SpringAOP中,方法调用是连接点。
- Advice(通知、处理、增强处理): 在符合的连接点进行的特定处理 (增强处理)
- 切入点(Pointcut): 可切入进行增强处理的连接点。AOP核心之一就是如何用表达式来定义符合的切入点。在Spring中,默认使用AspectJ的切入点语法。
由于Spring AOP只支持以Spring Bean的方法调用来作为连接点, 所以在这里切入点的定义包括:
- 切入点表达式, 来限制该能作用的范围大小,即是,能匹配哪些bean的方法
- 命名切入点
AspectJ
参考:Spring AOP @Before @Around @After 等 advice 的执行顺序
执行顺序
无异常情况:Around->Before->自己的method->Around->After->AfterReturning
异常情况:Around->Before->自己的method->Around->After->AfterThrowing
多个Aspect作用于一个方法上,如何指定每个 aspect 的执行顺序呢?
方法有两种:
- 实现org.springframework.core.Ordered接口,实现它的getOrder()方法
- 给aspect添加@Order注解,该注解全称为:org.springframework.core.annotation.Order
@Order(5)
@Component
@Aspect
public class Aspect1 {}
@Order(6)
@Component
@Aspect
public class Aspect2 {}
注意点
- 如果在同一个 aspect 类中,针对同一个 pointcut,定义了两个相同的 advice(比如,定义了两个 @Before),那么这两个 advice 的执行顺序是无法确定的,哪怕你给这两个 advice 添加了 @Order 这个注解,也不行。这点切记。
- 对于@Around这个advice,不管它有没有返回值,但是必须要方法内部,调用一下 pjp.proceed();否则,Controller 中的接口将没有机会被执行,从而也导致了 @Before这个advice不会被触发。
使用步骤
引入aspectj的相关jar包
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
在spring中启用aspectj
<aop:aspectj-autoproxy proxy-target-class="true" />
编写注解
import java.lang.annotation.*;
/**
* api拦截器,记录日志
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
public @interface ApiInf {
}
自定义实现Aspect
注意注解@Aspect,如果没有使用@Component,则需要在spring中注册该aspect的bean
<bean class="com.ufgov.util.rest.ApiLogAspect" />
Pointcut
@Pointcut:Pointcut的定义包括两部分:Pointcut表达式(expression:就是@Pointcut后边的参数)和Pointcut签名(就是@Pontcut所在的方法名称),Pointcut签名在下边@Before和@After等中使用(相当于使用Pointcut表达式)。
#execution表示式(方法描述匹配)
表达式类型
标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。
- execution:一般用于指定方法的执行,用的最多。
- within:指定某些类型的全部方法执行,也可用来指定一个包。
- this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- args:当执行的方法的参数是指定类型时生效。
- @target:当代理的目标对象上拥有指定的注解时生效。
- @args:当执行的方法参数类型上拥有指定的注解时生效。
- @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
- @annotation:当执行的方法上拥有指定的注解时生效。
- bean:当调用的方法是指定的bean的方法时生效。
其实execution表示式的定义方式就是方法定义的全量方式
格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
括号中各个pattern分别表示:
- 修饰符匹配(modifier-pattern?)
- 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
- 类路径匹配(declaring-type-pattern?)
- 方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
- 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(…)表示零个或多个任意参数
- 异常类型匹配(throws-pattern?)
其中后面跟着“?”的是可选项
例子
1)execution(* *(..))//表示匹配所有方法
2)execution(public * com.savage.service.UserService.*(..))//表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server..*.*(..))//表示匹配com.savage.server包及其子包下的所有方法
*> 最靠近(…)的为方法名,靠近.(…))的为类名或者接口名,如上例的JoinPointObjP2.(…))
参考:Spring AOP 中pointcut expression表达式解析及配置
方法参数匹配
args()
@args()
//参数带有@MyMethodAnnotation标注的方法.
@args(com.elong.annotation.MyMethodAnnotation)
//参数为String类型(运行时决定)的方法.
args(String)
当前AOP代理对象类型匹配
this()
目标类匹配
target()
@target()
within()
@within()
//pointcutexp包里的任意类.
within(com.test.spring.aop.pointcutexp.*)
//pointcutexp包和所有子包里的任意类.
within(com.test.spring.aop.pointcutexp..*)
//实现了MyInterface接口的所有类,如果MyInterface不是接口,限定MyInterface单个类.
this(com.test.spring.aop.pointcutexp.MyInterface)
***> 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.
标有此注解的方法匹配
@annotation()
//带有@MyTypeAnnotation标注的所有类的任意方法.
@within(com.elong.annotation.MyTypeAnnotation)
@target(com.elong.annotation.MyTypeAnnotation)
//带有@MyTypeAnnotation标注的任意方法.
@annotation(com.elong.annotation.MyTypeAnnotation)
***> @within和@target针对类的注解,@annotation是针对方法的注解
@Before("og()")
这种使用方式等同于以下方式,直接定义execution表达式使用
@Before("execution(* com.savage.aop.MessageSender.*(..))")
Pointcut定义时,还可以使用&&、||、! 这三个运算
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){}
@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}
@Pointcut("logSender() || logReceiver()")
private void logMessage(){}
还可以将一些公用的Pointcut放到一个类中,以供整个应用程序使用,如下:
package com.savage.aop;
import org.aspectj.lang.annotation.*;
public class Pointcuts {
@Pointcut("execution(* *Message(..))")
public void logMessage(){}
@Pointcut("execution(* *Attachment(..))")
public void logAttachment(){}
@Pointcut("execution(* *Service.*(..))")
public void auth(){}
}
在使用上面定义Pointcut时,指定完整的类名加上Pointcut签名就可以了,如:
package com.savage.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class LogBeforeAdvice {
@Before("com.sagage.aop.Pointcuts.logMessage()")
public void before(JoinPoint joinPoint) {
System.out.println("Logging before " + joinPoint.getSignature().getName());
}
}
也可以使用xml配置
<aop:config>
<aop:pointcut id="log" expression="execution(* com.savage.simplespring.bean.MessageSender.*(..))"/>
<aop:aspect id="logging" ref="logBeforeAdvice">
<aop:before pointcut-ref="log" method="before"/>
<aop:after-returning pointcut-ref="log" method="afterReturning"/>
</aop:aspect>
</aop:config>
package com.ufgov.util.rest;
/**
* 记录api访问日志的切面
*/
@Aspect
public class ApiLogAspect{
private static Logger logger = Logger.getLogger(ApiLogAspect.class);
/**
* 定义切入点
*/
@Pointcut("@annotation(ApiInf)")//这里就是表达式
//Pointcut签名
public void controllerAspect() {}
@Around("controllerAspect()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// TODO something
return point.proceed(); // 不调用point.proceed()不会执行目标方法
}
/**
* 进入方法之前处理
*/
@Before("controllerAspect()")
public void doBefore() throws UnsupportedEncodingException{
......
}
/**
* 记录返回信息
*/
@AfterReturning(pointcut="controllerAspect()",returning="ret")
public void doAfterReturn(Object ret) {
//对返回数据进行格式化处理,用于入库
}
/**
* 记录发生的异常信息
* @param e
*/
@AfterThrowing(value="controllerAspect()",throwing="e")
public void doAfterThrow(Throwable e) {
e.printStackTrace();
......
}
}
在目标方法上添加注解
@ApiInfo
....