自定义注解实现请求幂等性demo

实现请求幂等性方式很多:

  1. 数据库建立唯一索引
  2. token机制,每次接口请求前先获取请求携带的token,然后再下次请求的时候加上这个token,后台进行验证,如果验证通过则删除token。
  3. 数据库先查询后判断,如果存在则证明已经请求过了,没有则说明是第一次
  4. 悲观锁或者乐观锁,执行sql操作时,其他sql无法执行update。

本demo实现用自定义注解实现请求幂等性,实例demo代码:点我
具体有两种方式

1 使用自定义注解实现请求幂等性

实现思路:

  1. 创建自定义注解 @IdemAnnotation
  2. 自定义拦截器IdemInterceptor进行注解拦截
  3. 使用WebMvcConfigurer注册自定义拦截器,使请求时拦截生效

具体实现:
目录结构如图:

创建一个springBoot项目,引入redis和spring-web,在application.properties中配置好本地启动的redis的host和port以及密码

1.1 创建自定义注解 @IdemAnnotation

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IdemAnnotation {
}

1.1.1 创建RedisService

RedisService实现三个方法:setEx存放token,判断是否存在token,和执行通过首次请求后的移除token的remove()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component
public class RedisService {

@Autowired
RedisTemplate redisTemplate;

public boolean setEx(String key,Object value,Long expireTime){

boolean result = false;
try {
ValueOperations ops = redisTemplate.opsForValue();
ops.set(key,value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result =true;
} catch (Exception e) {
e.printStackTrace();
}

return result;
};


public boolean exsits(String key){
return redisTemplate.hasKey(key);

}

public boolean remove(String key){
if(exsits(key)){
return redisTemplate.delete(key);
}
return false;
}
}

1.1.2 创建TokenService

TokenService 主要有两个方法:创建token;判断是否存在token参数,没有就抛异常,有则返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class TokenService {

@Autowired
RedisService redisService;

public String createToken(){
String s = UUID.randomUUID().toString();
redisService.setEx(s,s,100L);
return s;
}


public boolean checkTokenExsits(HttpServletRequest request) throws TokenException {
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)){
if (StringUtils.isEmpty(request.getParameter("token"))){
throw new TokenException("token不存在");
}
}

if (!redisService.exsits(token)) {
//可能被移除了,抛出为重复的操作
throw new TokenException("重复的操作");
}

//上述都没抛出,则认为是第一次请求
return redisService.remove(token);

}
}

1.2 创建IdemInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Component
public class IdemInterceptor implements HandlerInterceptor {

@Autowired
TokenService tokenService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//先判断这次handler是不是自定义注解方法handler,因为自定义注解的IdemAnnotion是作用于方法上
if (!(handler instanceof HandlerMethod)) {
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
IdemAnnotation idemAnnotation = method.getAnnotation(IdemAnnotation.class);
//对注解方法进行判断
if (idemAnnotation !=null){
//如果是幂等注解
try {
return tokenService.checkTokenExsits(request);
} catch (TokenException e) {
e.printStackTrace();
//可以用全局异常处理去接受处理
throw e;
}
}
return true;
}

1.3 注册实现拦截器进行注解拦截

1
2
3
4
5
6
7
8
9
10
@Component
public class WebMvcConfig implements WebMvcConfigurer {

@Autowired
IdemInterceptor idemInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(idemInterceptor).addPathPatterns("/**");
}
}

1.4 创建HelloTest进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class HelloTest {

@Autowired
TokenService tokenService;
@RequestMapping("/getToken")
public String getToken(){
return tokenService.createToken();
}

@RequestMapping("/hello")
@IdemAnnotation
public String hello(){
return "hello";
}

@RequestMapping("/hello2")
public String hello2(){
return "hello2";
}
}

hello方法上加了@IdemAnnotation,hello2未加以做比较。
当执行了getToken请求获取了token后,在redis里存入了改token;

我们把token放入请求header中,然后执行hello请求后会进行注解拦截,

当第一次执行hello,成功返回“hello”;再次执行时,会返回“重复的操作”。

总结:
自定义注解的这套流程在实现请求幂等性或实现权限控制等其他流程操作也是一样的道理,可以参考其实现方式进行。

2 AOP方式实现注解生效

实现思路:

  1. maven添加aop依赖,创建aop实现类AnnotationAspect,添加@Component和@Aspect使生效
  2. AnnotationAspect内部实现@PointCut切点和@Before前置操作
  3. 在前置操作中进行request请求的token判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @program: idemcompoent
* @description: 替代拦截器方式;使用AOP 实现注解生效
* @author: Wangts
* @create: 2020-08-21 13:43
**/
@Component
@Aspect
public class AnnotationAspect {
@Autowired
TokenService tokenService;

@Pointcut("@annotation(com.example.idemcompoent.annotation.IdemAnnotation)")
public void pointcut(){

}

@Before("pointcut()")
public void before(JoinPoint joinPoint) throws TokenException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
try {
tokenService.checkTokenExsits(request);
} catch (TokenException e) {
e.printStackTrace();
//可以用全局异常处理去接受处理
throw e;
}

}

}

该种方式同样能实现注解生效。具体两次操作可以参考git提交版本记录进行差异比较。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2021 Wangts
  • 访问人数: | 浏览次数:

加个好友呗~

微信