SSM(十四) 基于annotation的http防重插件

SSM(十四) 基于annotation的http防重插件

前言

针对于我们现在常用的RESTful API通常我们需要对请求进行唯一标识,也就是每次都要带上一个请求号,如reqNO

对于入库这种操作数据库的请求我们一般要保证他的唯一性,一个请求号通常只能用一次,所以需要我们对这种请求加上校验机制。

该需求的实现思路是通过自定义annotation,只给需要进行校验的接口加上注解。然后通过切面使用了注解的接口将每次请求号存进Redis,每次都进行判断是否存在这个请求号即可。

来看下加上本次插件的实际效果:


自定义注解

首先我们要自定义一个注解:


1

2

3

4

5

6

7


@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface CheckReqNo {

String desc() default "";

}

(ps:这里并不过多的讲解注解相关的知识)。

首先使用@interface来声明一个注解。接着利用Java为我们提供的三个元注解来定义CheckReqNo注解。

其中@Target表明这个注解被用于什么地方,使用ElementType.METHOD表明被应用到方法上,还有一些其他值可以查看java.lang.annotation.ElementType这个枚举类型。

@Retention注解表明我们的注解在什么范围内有效,这里配置的RetentionPolicy.RUNTIME表明在运行时可以通过反射来获取。

@Documented看字面意思应该也能猜到是用于生成JavaDoc文档的。

其中定义了一个desc()的方法其实并没有用到,但如果需要在使用注解的时候需要自定义一些filed(域)的需求可以按照这样的方式写到这里,通过反射都可以获取到具体的值。
如:@CheckReqNo(desc = "abc")就可以获取到"abc"的值。

切面注解

按照之前的想法是在对所有使用了该注解的方法进行切面:


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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64


@Aspect

@Component

public class ReqNoDrcAspect {

private static Logger logger = LoggerFactory.getLogger(ReqNoDrcAspect.class);

@Value("${redis.prefixReq:reqNo}")

private String prefixReq ;

@Value("${redis.day:1}")

private long day ;

@Autowired

private RedisTemplate<String, String> redisTemplate;

@PostConstruct

public void init() throws Exception {

logger.info("SSM-REQUEST-CHECK init......");

}

@Pointcut("@annotation(com.crossoverJie.request.anotation.CheckReqNo)")

public void checkRepeat(){

}

@Before("checkRepeat()")

public void before(JoinPoint joinPoint) throws Exception {

BaseRequest request;

request = getBaseRequest(joinPoint);

if(request != null){

final String reqNo = request.getReqNo();

if(StringUtil.isEmpty(reqNo)){

throw new RuntimeException("reqNo不能为空");

}else{

try {

String tempReqNo = redisTemplate.opsForValue().get(prefixReq +reqNo);

logger.debug("tempReqNo="+tempReqNo);

if((StringUtil.isEmpty(tempReqNo))){

redisTemplate.opsForValue().set(prefixReq + reqNo, reqNo, day, TimeUnit.DAYS);

}else{

throw new RuntimeException("请求号重复,reqNo="+reqNo);

}

} catch (RedisConnectionFailureException e){

logger.error("redis操作异常",e);

throw new RuntimeException("need redisService") ;

}

}

}

}

public static BaseRequest getBaseRequest(JoinPoint joinPoint) throws Exception {

BaseRequest returnRequest = null;

Object[] arguments = joinPoint.getArgs();

if(arguments != null && arguments.length > 0){

returnRequest = (BaseRequest) arguments[0];

}

return returnRequest;

}

}

使用@Aspect来定义了一个切面。
其中prefixReq,day域可以自定义缓存请求号时的key前缀以及缓存的时间。

最关键的一点是用
@Pointcut("@annotation(com.crossoverJie.request.anotation.CheckReqNo)")
定义了一个切入点,这样所有使用@CheckReqNo的注解都会被拦截。

接下来的逻辑就比较简单了,在每次请求之前进行拦截。

先去Redis中查看这个请求号(ps:反射获取)是否存在,如果不存在则通过并将本次的请求号缓存起来。如果存在则抛出异常。

使用注解

可以在jdbc.properties配置文件中自定义前缀和缓存时间


1

2

3

4


#redis前缀

redis.prefixReq=reqNo

#redis缓存时间 默认单位为天

redis.day=1

不定义也可以,会使用默认值。

由于该注解是需要加到controller层,因此我们得使用CGLIB代理。
这里有一个坑,需要将开启CGLIB的配置配置到我们web.xml中的


1

2

3

4

5

6

7

8

9

10

11


<!-- Spring MVC servlet -->

<servlet>

<servlet-name>SpringMVC</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:spring-mvc.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

<async-supported>true</async-supported>

</servlet>

这里所定义的spring-mvc.xml文件中,不然springMVC所在的子容器是无法被父容器所加载的。

使用实例:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23


@CheckReqNo

@RequestMapping(value = "/createRedisContent",method = RequestMethod.POST)

@ResponseBody

public BaseResponse<NULLBody> createRedisContent(@RequestBody RedisContentReq redisContentReq){

BaseResponse<NULLBody> response = new BaseResponse<NULLBody>() ;

Rediscontent rediscontent = new Rediscontent() ;

try {

CommonUtil.setLogValueModelToModel(redisContentReq,rediscontent);

rediscontentMapper.insertSelective(rediscontent) ;

response.setReqNo(redisContentReq.getReqNo());

response.setCode(StatusEnum.SUCCESS.getCode());

response.setMessage(StatusEnum.SUCCESS.getMessage());

}catch (Exception e){

logger.error("system error",e);

response.setReqNo(response.getReqNo());

response.setCode(StatusEnum.FAIL.getCode());

response.setMessage(StatusEnum.FAIL.getMessage());

}

return response ;

}

统一异常controller


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


/**

*

* ClassName: ErrorController <br/>

* Function: 错误异常统一处理. <br/>

* @author crossoverJie

* @version

* @since JDK 1.7

*/

@ControllerAdvice

public class ErrorController {

private Logger logger = LoggerFactory.getLogger(this.getClass());

@ExceptionHandler(Exception.class)

@ResponseStatus(HttpStatus.OK)

@ResponseBody

public Object processUnauthenticatedException(NativeWebRequest request, Exception e) {

logger.error("请求出现异常:", e);

BaseResponse<NULLBody> response = new BaseResponse<NULLBody>();

response.setCode(StatusEnum.FAIL.getCode());

if (e instanceof RuntimeException){

response.setMessage(e.getMessage());

} else {

response.setMessage(StatusEnum.FAIL.getMessage());

}

return response ;

}

}

这样当controller层出现异常之后都会进入这里进行统一的返回。

总结

至此整个插件的流程已经全部OK,从中可以看出Spring AOP在实际开发中的各种好处。
之前的几篇文章也有应用到:

不知不觉这个小白入门的SSM系列已经更新了14篇了,在GitHub也有了500多颗星了,期间也和不少朋友有过交流、探讨,感谢大家的支持。

接下来可能不太会更新这个系列了,由于博主现在所在的项目组采用的是目前比较流行的SpringBoot+SpringCloudDocker的方式来进行架构的,所以之后的重心肯定会移到这方面,用过SpringBoot之后相信大家肯定也回不去了。

所以之后我会继续更新SpringBoot+SpringCloud相关的文章,欢迎持续关注,持续拍砖(ps:这个插件也会用springBoot重写一遍)

时间: 2024-10-09 23:16:27

SSM(十四) 基于annotation的http防重插件的相关文章

SSE图像算法优化系列二十四: 基于形态学的图像后期抗锯齿算法--MLAA优化研究。

偶尔看到这样的一个算法,觉得还是蛮有意思的,花了将近10天多的时间研究了下相关代码. 以下为百度的结果:MLAA全称Morphological Antialiasing,意为形态抗锯齿是AMD推出的完全基于CPU处理的抗锯齿解决方案.对于游戏厂商使用的MSAA抗锯齿技术不同,Intel最新推出的MLAA将跨越边缘像素的前景和背景色进行混合,用第2种颜色来填充该像素,从而更有效地改进图像边缘的变现效果,这就是MLAA技术. 其实就是这个是由Intel的工程师先于2009年提出的技术,但是由AMD将

【WPF学习】第二十四章 基于范围的控件

原文:[WPF学习]第二十四章 基于范围的控件 WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件--ScrollBar.ProgressBar以及Slider--都继承自RangeBase类(该类又继承自Control类).尽管它们使用相同的抽象概念(范围),但工作方式却又很大的区别. 下表显示了RangeBase类定义的属性: 表 RangeBase类的属性 通常不比直接使用ScrollBar控件.更高级的ScrollViewer控件(封装了两个Scro

【转】花开正当时,十四款120/128GB SSD横向评测

原文地址:http://www.expreview.com/19604-all.html SSD横评是最具消费指导意义的评测文章,也是各类热门SSD固态硬盘的决斗疆场.SSD评测在行业内已经有不少网站做过,超能网也从今年开始专注SSD固态硬盘重点产品的评测.随着40nm和25nm新制程的NAND颗粒的 量产,SSD固态硬盘在今年迎来了价格大幅度下降,特别是国内120GB和128GB的SSD固态硬盘价格已经达到非常合理的区间,因此需要阅读SSD评测特别是SSD横评来指导消费的用户,也在呈现爆发式增

第十四篇 现象

第十四篇  现象 "现象"的产生是由宇宙当中各种因素交汇的结果.现象是万物在宇宙中的展现,它可以被人为创造,也可以由宇宙规律自行产生.现象能帮助人类逐步地了解宇宙的本质,也能帮助人类更好地了解自己. 当一个生命对宇宙有足够高度的认识之后就会留意所有的现象,并从这些现象当中去探索自身以及宇宙的奥秘.可以说,生命的成长过程就是不断地分析与探索各种现象,从而总结经验,让自身不断提高探索宇宙奥秘能力的一个历练过程. 随着对各种现象的不断分析与探索,人类会越来越深刻地认识到现象背后的本质,而不会

javascript高级程序设计 第十四章--表单脚本

javascript高级程序设计 第十四章--表单脚本 在HTML中表单由<form>元素表示,在js中表单对应的是HTMLFormElement类型,这个类型也有很多属性和方法:取得表单元素的引用还是为它添加id特性,用DOM操作来获取表单元素:提交表单:把<input>或<button>元素的type特性设置为"submit",图像按钮把<input>元素的type特性设置为"image",也可以调用submit(

Hibernate中,基于Annotation的简单树形结构的实现

在系统设计中,经常用到递归性质的树形结果,比如菜单.多级分类等,一般是在同一个表中定义父子关系实现这种结构. 下面是在Hibernate中,基于Annotation的简单树形结构的实现: 第一步:创建Entity类,并添加注解实现关联关系    ps: 主要是利用@ManyToOne 和 @OneToMany 配置在同一个Entity类中实现树形递归的结构.hibernate注解形式比在xml配置更加简洁 TreeNode.java 1 package com.hfut.hibernate; 2

马哥学习笔记二十四——分布式复制快设备drbd

DRBD: 主从 primary: 可执行读.写操作 secondary: 文件系统不能挂载 DRBD: dual primay, 双主(基于集群文件系统的高可用集群) 磁盘调度器:合并读请求,合并写请求: Procotol:drbd数据同步协议 A: Async, 异步  数据发送到本机tcp/ip协议栈 B:semi sync, 半同步  数据发送到对方tcp/ip协议 C:sync, 同步  数据到达对方存储设备 DRBD Source: DRBD资源 资源名称:可以是除了空白字符外的任意

Chrome浏览器扩展开发系列之十四:本地消息机制Native messagin

Chrome浏览器扩展开发系列之十四:本地消息机制Native messaging 2016-11-24 09:36 114人阅读 评论(0) 收藏 举报  分类: PPAPI(27)  通过将浏览器所在客户端的本地应用注册为Chrome浏览器扩展的"本地消息主机(native messaging host)",Chrome浏览器扩展还可以与客户端本地应用之间收发消息. 客户端的本地应用注册为Chrome浏览器扩展的"本地消息主机"之后,Chrome浏览器会在独立的

使用delphi 开发多层应用(二十四)KbmMW 的消息方式和创建WIB节点

KbmMW 中支持基于UDP的消息广播,也支持TCP/IP hub/spoke 方式,还有 基于UDP或者TCP/IP 的点对点的消息传输. 1.基于UDP的消息广播 根据UDP  的工作原理,在同一个网段里面,可以发布广播包.这样发布者只需要发布一次, 消息就可以被同一网段上的所有订阅者收到.这样大大的降低了网络带宽.这个方式的最大缺点是 无法直接跨越网段,如果要跨越网段,就需要建立一个Gateway. Gateway 就是一个程序,连接两个网段. 它接受第一个网段的广播消息,然后再广播到第二