針對於咱們如今經常使用的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。