Spring AOP不生效的原因(切入点表达式错误)
背景
接手了一个写了一半的项目,发现其中的AOP模块不生效,着手开始修改
要实现的功能是在接口调用前判断该接口是否在时间线之内,不是的话则抛出异常从而不执行接口
代码
注解类
package com.neu.annotion;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface TimeAvailableLimit {
/**
* 接口可用的阶段
*/
int[] availableStage();
}
切面类
package com.neu.config.aop;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.neu.annotion.TimeAvailableLimit;
import com.neu.config.exception.BizException;
import com.neu.entity.Timeline;
import com.neu.mapper.TimelineMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
/**
* @Description:
* 附议,取消附议:1
* 反馈:6
* 合并,修改合并后的提案,拟立案:3
* 创建提案,提交提案:0
*/
@Aspect
@Component
public class TimeAvailableAspect {
@Resource
TimelineMapper timelineMapper;
private static final int NOT_STARTED = 0;
private static final int ON_GOING = 1;
private static final int FINISHED = 2;
@Before("@annotation(timeAvailableLimit) && args(sessionId, ..)")
public void checkTimeAvailable(JoinPoint joinPoint, TimeAvailableLimit timeAvailableLimit, String sessionId){
int[] availableStage = timeAvailableLimit.availableStage();
// 反射获取参数sessionId
String[] parameterNames = getParameterNames(joinPoint);
String sessionIdValue = findSessionId(joinPoint, parameterNames);
if(sessionIdValue==null){
throw new BizException("获取session参数失败");
}
Timeline timeline = timelineMapper.selectOne(new LambdaQueryWrapper<Timeline>().eq(Timeline::getSessionId, sessionIdValue));
if(timeline==null){
throw new BizException("当前届次时间线并未创建,请联系管理员创建时间线");
}
for (int oneAvailableStage : availableStage) {
switch (oneAvailableStage){
case 0:
if(timeline.getProposalStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 1:
if(timeline.getApprovalStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 2:
if(timeline.getInitialReviewStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 3:
if(timeline.getProvisionalCaseStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 4:
if(timeline.getFormalCaseStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 5:
if(timeline.getUndertakeStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
case 6:
if(timeline.getFeedbackStatus()!=ON_GOING){
throw new BizException("此功能当前阶段未开放");
}
break;
default:
return;
}
}
System.out.println("aop调用成功");
}
private String[] getParameterNames(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
return discoverer.getParameterNames(method);
}
private String findSessionId(JoinPoint joinPoint, String[] parameterNames) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
if ("sessionId".equals(parameterNames[i])) {
return (String) args[i];
}
}
return null;
}
}
接口
这里我有两个接口,方法名一样,但是参数不一样
@PostMapping("/createProposal")
@ApiOperation(value = "创建并提交一个提案", notes = "")
@TimeAvailableLimit(availableStage = 0)
public Response createProposal(@Valid @NotEmpty @RequestBody CreateProposalDto createProposalDto,@Valid @NotEmpty @RequestParam String sessionId) {
return proposalService.createProposal(createProposalDto);
}
@PostMapping("/submitProposal")
@ApiOperation(value = "使得一个草稿提案成为正式提案", notes = "")
@TimeAvailableLimit(availableStage = 0)
public Response createProposal(@Valid @NotEmpty @RequestParam String proposalId,@Valid @NotEmpty @RequestParam String sessionId) {
return proposalService.submitProposal(proposalId);
}
问题
我调用两个接口,调用createProposal
时aop
直接不发挥作用,没有运行,调用submitProposal
时会抛出异常,但是接口还是正常执行了
网上的解决方式
-
检查pom.xml类中是否依赖。
-
检查启动类中是否有这两个注解
@ComponentScan("com.neu.config.aop") @EnableAspectJAutoProxy(proxyTargetClass = true)
-
检查AOP类中除了@Aspect注解还应该有@Component注解。(这里@Component用于开启组件扫描,使得SpringBoot可以找到它。即交由SpringBoot管理。)
-
检查AOP版本是否过老。
-
检查AOP类是否和启动类在同一等级。如果不在,应该使用2的方法进行指定。(这个方法和2有点重复)
以上方法和我的问题并不一样,小伙伴们可以参考
下面分析我的问题
问题分析
排查了半天,发现是aop
类的切入点表达式写错了
@Before("@annotation(timeAvailableLimit) && args(sessionId, ..)")
这行代码中,args
表达式是基于参数的类型进行匹配的,而不是参数的名字。因此,args(sessionId, ..)
这样的表达式实际上不会尝试匹配名为sessionId
的参数;而是匹配第一个参数类型和sessionId
变量类型相同,且后面可以跟任意类型参数的方法。
即:参数名不是匹配依据
因此将&& args(sessionId, ..)
删掉,或者改为args( .. , String)
都可以解决
切入点表达式
最后在扩展一下切入点表达式的写法
- 执行方法的表达式:
execution(* com.example.service.*.*(..))
: 匹配com.example.service
包下所有类的所有方法。这里*
表示匹配所有的返回类型,..
表示匹配任意参数列表。
- 注解:
@annotation(com.example.MyAnnotation)
: 匹配所有被com.example.MyAnnotation
注解标记的方法。
- 参数:
args(java.io.Serializable)
: 匹配任何接受一个实现了java.io.Serializable
接口的参数的方法。args(.., com.example.MyType)
: 匹配方法的最后一个参数是com.example.MyType
类型的方法。
- 在Bean上:
bean(myBean)
: 匹配Spring上下文中名为myBean
的Bean。
- 指定包或类型:
within(com.example.service.*)
: 匹配指定包下所有类的方法。within(com.example.service..*)
: 匹配指定包及其子包下所有类的方法。within(com.example.service.MyService+)
: 匹配MyService
接口或类及其子类的所有方法。
- 代理对象的类型:
this(com.example.service.MyInterface)
: 匹配实现了MyInterface
接口的Spring Bean的所有方法。target(com.example.service.MyClass)
: 匹配目标类为MyClass
的所有方法。
- 组合使用:
- 你还可以使用
&&
、||
和!
来组合以上元素,实现更复杂的切入点表达式。
- 你还可以使用