通过springboot+自定义注解+反射+拦截器+token机制进行幂等性校验。
先请求获取token,并将此token放入redis中,并设置1秒过期时间。然后请求新增订单接口,被拦截器拦截,通过反射获取注解类,有该注解的方法进行幂等性校验。
代码示例:
/**
* 幂等性方法注解
* 此幂等性注解校验为无参,通过token(设置过期时间1秒)机制判断是否重复提交
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
//String id();
//Class<?> lockClass();
}
@Component
public class IdempotentInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取方法上的注解
Idempotent annotation = handlerMethod.getMethodAnnotation(Idempotent.class);
if (annotation != null) {
//幂等性校验
System.out.println("幂等性校验");
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
System.out.println("token为空");
return false;
}
}
return true;
}
}
@Component
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private IdempotentInterceptor idempotentInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");
}
}
@Idempotent
@RequestMapping("addUser7")
public String addUser(String id) {
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
}
或通过aop切面进行判断
@Component
@Aspect
public class IdempotentAspect {
@Around("@annotation(com.ldc.springboot_idempotent.annotation.Idempotent)")
public Object idempotent(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
//前置增强
System.out.println("前置增强操作");
String token = request.getHeader("Authorization");
if (StringUtils.isEmpty(token)) {
System.out.println("token为空");
return "token为空";
}
//IdempotentUtil.check();
Object rs = pjp.proceed();
//后置增强
System.out.println("后置增强操作");
return rs;
//此aop切面处理有问题,改用拦截器处理
}
}
通过aop方式代码示例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String id();
Class<?> lockClass();
}
@Component
@Aspect
public class IdempotentAspect {
@Around("@annotation(com.ldc.springboot_idempotent.annotation.Idempotent)")
public Object idempotent(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//获取参数
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = pjp.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
Idempotent annotation = method.getAnnotation(Idempotent.class);
String id = annotation.id();
Class<?> lockClass = annotation.lockClass();
//解析springEL表达式
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(id);
StandardEvaluationContext context = new StandardEvaluationContext();
//添加参数
Object[] args = pjp.getArgs();
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i],args[i].toString());
}
//解析参数
String param = expression.getValue(context).toString();
//前置增强
System.out.println("前置增强操作");
boolean check = IdempotentUtil.check(param, lockClass);
if (!check) {
return "请勿重复提交...";
}
Object rs = pjp.proceed();
//后置增强
System.out.println("后置增强操作");
return rs;
//此aop切面处理有问题,改用拦截器处理
//此问题通过springEL表达式获取动态参数解决,再结合幂等性工具进行判断
}
}
public class IdempotentUtil {
//根据LRU算法淘汰最近最少使用的数据的Map集合,最大容量100个
private static LRUMap<String, Integer> lruMap = new LRUMap<>(100);
/**
* 幂等性判断
*/
public static boolean check(String id, Object lockClass) {
synchronized (lockClass) {
//重复请求判断
if (lruMap.containsKey(id)) {
//重复请求
System.out.println("请勿重复提交..." + id);
return false;
}
//非重复请求,存储ID
lruMap.put(id, 1);
}
return true;
}
}
@RestController
public class DemoController7 {
@Idempotent(id = "#id",lockClass = DemoController7.class)
@RequestMapping("addUser8")
public String addUser8(String id) {
//业务代码
System.out.println("添加用户ID:" + id);
return "请求成功";
}
}
以上幂等性判断未实现,需结合redis进行实现,第一遍请求判断是否存在该id,若不存在则将id放入redis中,正常执行业务;若存在则返回请勿重复提交,整体思路如此,后续需深入理解注解的应用及模式。