针对于咱们如今经常使用的RESTful API
一般咱们须要对请求进行惟一标识,也就是每次都要带上一个请求号,如reqNO
。javascript
对于入库这种操做数据库的请求咱们通常要保证他的惟一性,一个请求号一般只能用一次,因此须要咱们对这种请求加上校验机制。java
该需求的实现思路是经过自定义
annotation
,只给须要进行校验的接口加上注解。而后经过切面使用了注解的接口将每次请求号存进Redis
,每次都进行判断是否存在这个请求号便可。git
来看下加上本次插件的实际效果:
github
首先咱们要自定义一个注解:web
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckReqNo {
String desc() default "";
}复制代码
(ps:这里并不过多的讲解注解相关的知识)。redis
首先使用@interface
来声明一个注解。接着利用Java
为咱们提供的三个元注解来定义CheckReqNo
注解。spring
其中@Target
代表这个注解被用于什么地方,使用ElementType.METHOD
代表被应用到方法上,还有一些其余值能够查看java.lang.annotation.ElementType
这个枚举类型。数据库
@Retention
注解代表咱们的注解在什么范围内有效,这里配置的RetentionPolicy.RUNTIME
代表在运行时能够经过反射来获取。spring-mvc
@Documented
看字面意思应该也能猜到是用于生成JavaDoc
文档的。缓存
其中定义了一个desc()
的方法其实并无用到,但若是须要在使用注解的时候须要自定义一些filed(域)
的需求能够按照这样的方式写到这里,经过反射均可以获取到具体的值。
如:@CheckReqNo(desc = "abc")
就能够获取到"abc"
的值。
按照以前的想法是在对全部使用了该注解的方法进行切面:
@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
配置文件中自定义前缀和缓存时间
#redis前缀
redis.prefixReq=reqNo
#redis缓存时间 默认单位为天
redis.day=1复制代码
不定义也能够,会使用默认值。
因为该注解是须要加到controller
层,所以咱们得使用CGLIB
代理。
这里有一个坑,须要将开启CGLIB
的配置配置到咱们web.xml
中的
<!-- 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
所在的子容器是没法被父容器所加载的。
使用实例:
@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 ;
}复制代码
/** * * 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+SpringCloud
和Docker
的方式来进行架构的,因此以后的重心确定会移到这方面,用过SpringBoot
以后相信你们确定也回不去了。
因此以后我会继续更新SpringBoot+SpringCloud
相关的文章,欢迎持续关注,持续拍砖(ps:这个插件也会用springBoot重写一遍
)
我的博客地址:crossoverjie.top。