SSM(十四) 基於annotation的http防重外掛程式

來源:互聯網
上載者:User

標籤:creat   1.7   web   redist   eof   red   extern   inf   代理   

SSM(十四) 基於annotation的http防重外掛程式

前言

針對於我們現在常用的RESTful API通常我們需要對請求進行唯一標識,也就是每次都要帶上一個請求號,如reqNO

對於入庫這種操作資料庫的請求我們一般要保證他的唯一性,一個請求號通常只能用一次,所以需要我們對這種請求加上校正機制。

該需求的實現思路是通過自訂annotation,只給需要進行校正的介面加上註解。然後通過切面使用了註解的介面將每次請求號存進Redis,每次都進行判斷是否存在這個請求號即可。

來看下加上本次外掛程式的實際效果:



自訂註解

首先我們要自訂一個註解:

1234567 @Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @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"的值。

切面註解

按照之前的想法是在對所有使用了該註解的方法進行切面:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364 @Aspect@Componentpublic class ReqNoDrcAspect { private static Logger logger = LoggerFactory.getLogger(ReqNoDrcAspect.class); @Value("${redis.prefixReq:reqNo}")private String prefixReq ; @Value("${redis.day:1}")private long day ; @Autowiredprivate RedisTemplate<String, String> redisTemplate; @PostConstructpublic 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設定檔中自訂首碼和緩衝時間

1234 #redis首碼redis.prefixReq=reqNo#redis緩衝時間 預設單位為天redis.day=1

不定義也可以,會使用預設值。

由於該註解是需要加到controller層,因此我們得使用CGLIB代理。
這裡有一個坑,需要將開啟CGLIB的配置配置到我們web.xml中的

1234567891011 <!-- 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所在的子容器是無法被父容器所載入的。

使用執行個體:

1234567891011121314151617181920212223 @CheckReqNo@RequestMapping(value = "/createRedisContent",method = RequestMethod.POST)@ResponseBodypublic 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
1234567891011121314151617181920212223242526272829 /** * * ClassName: ErrorController <br/> * Function: 錯誤異常統一處理. <br/> * @author crossoverJie * @version * @since JDK 1.7 */@ControllerAdvicepublic 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在實際開發中的各種好處。
之前的幾篇文章也有應用到:

  • 在JavaWeb應用中使用Redis
  • 動態切換資料來源

不知不覺這個小白入門的SSM系列已經更新了14篇了,在GitHub也有了500多顆星了,期間也和不少朋友有過交流、探討,感謝大家的支援。

接下來可能不太會更新這個系列了,由於博主現在所在的項目組採用的是目前比較流行的SpringBoot+SpringCloudDocker的方式來進行架構的,所以之後的重心肯定會移到這方面,用過SpringBoot之後相信大家肯定也回不去了。

所以之後我會繼續更新SpringBoot+SpringCloud相關的文章,歡迎持續關注,持續拍磚(ps:這個外掛程式也會用springBoot重寫一遍)

SSM(十四) 基於annotation的http防重外掛程式

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.