SpringBoot AOP代理学习及使用
2018-03-20 本文已影响0人
wyon
1、基本注解及说明
AOP即面向切面编程,是通过jdk动态代理或cglib代理实现在原有代码前后切入其他业务逻辑的技术,它不会破坏程序原有逻辑,因此可以很好的对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性及开发效率。在SpringBoot中,通过注解方式实现AOP功能,代理的生成,管理及其依赖关系都是由Spring容器负责(不受Spring容器管理的对象是无法使用AOP进行切面控制的),其注解主要包括:
注解 | 注解功能 |
---|---|
@Aspect |
定义一个切面类,配合@Component 表示一个受Spring容器管理的切面bean,配合@Order 表示切面的优先级, 它的值越小,优先级越高。 |
@Pointcut |
定义一个切点,为切入业务提供连接点,切点表达式见第二部分 |
@Before |
在切点开始前执行,切点信息JoinPoint
|
@AfterReturning |
在切点完成后执行,切点信息JoinPoint ,可指定一个returning参数,获取切点返回值 |
@AfterThrowing |
在切点异常时执行,切点信息JoinPoint ,可指定一个throwing参数,获取异常信息 |
@After |
在切点完成后执行,无论目标方法是否成功完成,切点信息JoinPoint
|
@Around |
切点环绕通知,在目标方法完成前后处理,切点信息ProceedingJoinPoint ,此外可以调用其proceed 方法执行原有方法 |
进入目标方法时,先织入@Around
,再织入@Before
,退出目标方法时,先织入@Around
,再织入@AfterReturning
,最后才织入@After
。
2、切入点表达式
- 匹配语法:
* : 匹配任何数量字符;
.. : 匹配任何数量字符的重复。如在类型模式中匹配任何数量子包,而在方法参数模式中匹配任何数量参数。
+ : 匹配指定类型的子类型,仅能作为后缀放在类型模式后面。
&& : ‘与’组合切入点表达式。
|| : ‘或’组合切入点表达式。
! : ‘非’切入点表达式。
- 使用execution匹配容器对象的指定方法:
// 表达式规则
execution(modifiers? result-type declaring-type? method-name(param-type) throws-type?)
// 匹配对象的任意public方法
execution(public * *(..))
// 匹配UserResource对象的任意方法
execution(* com.gwyon.spring.resource.UserResource.*(..))
// 匹配resource包及其子包中对象的任意方法:
execution(* com.gwyon.spring.resource..*.*(..))
- 使用within匹配指定容器对象类型的所有方法:
// 表达式规则,等价于execution(* class-type.*(..))
within(class-type)
// 匹配resource包的对象的任意方法
within(com.gwyon.spring.resource.*)
// 匹配UserResource对象的任意方法
within(com.gwyon.spring.resource.UserResource)
- 使用target匹配指定容器对象类型的自身方法:可提供目标对象信息,与this的区别在于target不会匹配目标对象继承或实现其他接口的方法
// 表达式规则,不支持通配符
target(class-type)
// 匹配UserResource对象自身的方法
target(com.gwyon.spring.resource.UserResource)
- 使用this匹配指定容器对象类型的所有方法:可提供目标对象信息,与target的区别在于this会匹配目标对象继承或实现其他接口的方法
// 表达式规则,不支持通配符
this(class-type)
// 匹配UserResource对象所有的方法
target(com.gwyon.spring.resource.UserResource)
- 使用args匹配容器对象传入参数为指定类型的方法:可提供方法参数信息,注意args属于动态切入点,是匹配传入的参数类型,不是匹配方法签名的参数类型,开销非常大,非特殊情况最好不要使用
// 表达式规则,不支持通配符
args(param-type)
// 匹配对象所有参数为model下对象的方法
args(com.gwyon.spring.model.*)
// 匹配对象所有参数为User的方法
args(com.gwyon.spring.model.User)
- 使用bean匹配指定名称的容器对象的所有方法:为Spring AOP独有的表达式
// 表达式规则
bean(bean-name)
// 匹配名为userResource的对象的所有方法
bean(userResource)
- 使用@annotation匹配容器对象持有指定注解的方法:可提供注解信息
// 表达式规则,不支持通配符
@annotation(enum-type)
// 匹配对象所有持有RequestMapping注解的方法
@annotation(org.springframework.web.bind.annotation.RequestMapping)
- 使用@within匹配持有指定注解的容器对象的所有方法:可提供注解信息
// 表达式规则,不支持通配符
@within(enum-type)
// 匹配持有RestController注解的对象的所有方法
@within(org.springframework.web.bind.annotation.RestController)
- 使用@target匹配持有指定注解的容器对象的自身方法:可提供注解信息
// 表达式规则,不支持通配符
@target(enum-type)
// 匹配持有RestController注解的对象的自身方法
@target(org.springframework.web.bind.annotation.RestController)
- 使用@args匹配容器对象参数持有指定注解的方法:可提供注解信息
// 表达式规则,不支持通配符
@args(enum-type)
// 匹配对象所有持有PathVariable注解的方法
@args(org.springframework.web.bind.annotation.PathVariable)
- 其他Spring AOP目前不支持的切入点指示符不再赘述:
call,get,set,initialization,staticinitialization,preinitialization,handler,adviceexecution,withincode,cflow,cflowbelow,if,@this,@withincode
3、获取参数的方法
- 获取网络请求信息:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
HttpServletResponse response = attributes.getResponse();
- 通过
Joinpoint
或其子类ProceedingJoinPoint
提供方法信息:
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
public interface JoinPoint {
String toString();// 连接点所在位置的相关信息
String toShortString();// 连接点所在位置的简短相关信息
String toLongString();// 连接点所在位置的全部相关信息
Object getThis();// 返回aop代理对象
Object getTarget();// 返回目标对象
Object[] getArgs();// 返回被通知方法参数列表
Signature getSignature();// 返回当前连接点签名
SourceLocation getSourceLocation();// 返回连接点方法所在类文件中的位置
String getKind();// 连接点类型
StaticPart getStaticPart();// 返回连接点静态部分
}
public interface ProceedingJoinPoint extends JoinPoint {
Object proceed() throws Throwable;
Object proceed(Object[] args) throws Throwable;
}
public interface ProceedingJoinPoint.StaticPart {
Signature getSignature();// 返回当前连接点签名
String getKind();// 连接点类型
int getId();// 唯一标识
String toString();// 连接点所在位置的相关信息
String toShortString();// 连接点所在位置的简短相关信息
String toLongString();// 连接点所在位置的全部相关信息
}
- 通过
target
,this
,args
,@annotation
,@within
,@target
,@args
等切点表达式提供:
@Before(value = "target(userResource)&&args(user)&&@annotation(transactional)"
, argNames = "point,userResource,user,transactional")
public void before5(JoinPoint point, UserResource userResource, User user, Transactional transactional) {
// 处理逻辑
}
- 通过
@AfterReturning
的returning
参数提供:
@AfterReturning(value = "target(userResource)", returning = "obj")
public void before5(JoinPoint point, UserResource userResource, Object obj) {
// 处理逻辑
}
- 通过
@AfterThrowing
的throwing
参数提供:
@AfterThrowing(value = "target(userResource)", throwing = "error")
public void before5(JoinPoint point, UserResource userResource, Exception error) {
// 处理逻辑
}
4、使用AOP记录网络请求日志
- 创建标记需要记录日志的方法的注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface KeepLog {
/**
* 设置日志备注
*
* @return 日志备注
*/
String value() default "";
}
- 创建切面类:
@Aspect
@Component
public class KeepLogAspect {
private final LogInfoRepository logInfoRepository;
@Autowired
public KeepLogAspect(LogInfoRepository logInfoRepository) {
this.logInfoRepository = logInfoRepository;
}
/**
* 获取切点
*
* @param keepLog 切点注解
*/
@Pointcut("@annotation(keepLog)")
public void logPoint(KeepLog keepLog) {
}
/**
* 处理方法正常返回
*
* @param joinPoint 方法连接点
* @param keepLog 切点注解
*/
@AfterReturning(value = "logPoint(keepLog)", argNames = "joinPoint,keepLog")
public void afterReturning(JoinPoint joinPoint, KeepLog keepLog) {
LogInfo logInfo = getLogInfo(joinPoint, keepLog);
logInfo.setError("");
logInfoRepository.save(logInfo);
}
/**
* 处理方法报错
*
* @param joinPoint 方法连接点
* @param keepLog 切点注解
* @param throwable 错误信息
*/
@AfterThrowing(pointcut = "logPoint(keepLog)", throwing = "throwable", argNames = "joinPoint,keepLog,throwable")
public void afterThrowing(JoinPoint joinPoint, KeepLog keepLog, Throwable throwable) {
LogInfo logInfo = getLogInfo(joinPoint, keepLog);
logInfo.setError(throwable.getClass().getName() + ":" + throwable.getMessage());
logInfoRepository.save(logInfo);
}
/**
* 解析方法信息
*
* @param joinPoint 方法连接点
* @param keepLog 切点注解
* @return logInfo
*/
@SuppressWarnings("ConstantConditions")
private LogInfo getLogInfo(JoinPoint joinPoint, KeepLog keepLog) {
LogInfo logInfo = new LogInfo();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
logInfo.setRequest(request.getRequestURI());
logInfo.setRemote(request.getRemoteAddr());
logInfo.setMethod(request.getMethod());
}
logInfo.setTimes(new Date());
logInfo.setPoint(joinPoint.getSignature().toString());
logInfo.setNotes(keepLog.value());
return logInfo;
}
}
- 在需要记录日志的方法上添加
@KeepLog
注解即可:
@KeepLog("modifyUser")
@PatchMapping
@Transactional(rollbackOn = Throwable.class)
public NetResult patch(@RequestBody User user) {
return new NetResult(false, userService.patch(user));
}
使用的SpringBoot版本为2.0.0.RELEASE
,点此查看部分代码。