Spring自定義argumentResolver參數解析器


在一個web程序中,當一個HTTP請求進來時,會被容器處理進而轉換成一個servlet請求。
http請求所攜帶的數據,雖然是格式化的但是無類型;而java作為強類型語言,同時為了健壯性考慮,必然要有完善的類型約束。
當然,那么,將數據從servlet請求中轉換到java中,一個很原始的方式是手動處理。
幸好,Spring MVC通過以注解往函數添加額外信息的方式,使得上述的數據轉換過程能夠交由框架自動處理。
從一個角度去看,Controller中的函數聲明及注解定義了此HTTP請求的數據格式和類型,也即規定了對外部暴露的以http協議展現的接口。
不過,有些時候內置注解無法滿足需求的情況。這個時候,就需要自定義自己的注解以聲明參數的格式。

一、自定義注解

現在假設我們需要自定義一種數據,叫做userId。
當一個http請求進入時,我們期望的效果是框架從session取數據,並且放入到controller對應的參數中。
現在,定義了一個叫做UserId的注解:

@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface UserId { boolean required() default true; } 
  1. ElementType.PARAMETER可以看出,這個注解是用於修飾參數的。
  2. 該注解的保留策略要設為RUNTIME,很顯然,因為框架是運行時通過反射拿到注解信息的。
  3. 注解攜帶了個參數required,在這里,是個類似接口的聲明;但是在后面,則要通過此信息決定解析器的行為。
二、參數解析器

首先看位於HandlerMethodArgumentResolver.java的這個接口。
通過實現這個接口的類,就是解析器。按照我們的期望,它中間的函數應該能得到必要信息,從而按照自定義邏輯計算並返回一個值。

 public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; } 
  1. MethodParameter是spring對被注解修飾過參數的包裝,從其中能拿到參數的反射相關信息。
  2. supportsParameter傳入一個參數,用以判斷此參數是否能夠使用該解析器。
  3. resolveArgument就是之前討論的解析函數,傳入必要信息,計算並返回一個值。
  4. 綜合來看,框架會將每一個MethodParameter傳入supportsParameter測試是否能夠被處理,如果能夠,就使用resolveArgument處理。
    對於我們的userId解析器,如下:
public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { public static final String SESSION_USER_CLIENT_ID = "_session_user_clientId"; @Getter @Setter private String noLoginMessage = "未登錄!"; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(UserId.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Annotation[] annotations = parameter.getParameterAnnotations(); // 逐一處理 for (Annotation annotation : annotations) { // userId if (annotation instanceof UserId) { return resolveUserId((UserId) annotation, parameter, mavContainer, webRequest, binderFactory); } } return null; } private Object resolveUserId(ClientId annotation, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { Object attribute = webRequest.getAttribute(SESSION_USER_CLIENT_ID, RequestAttributes.SCOPE_SESSION); if (attribute == null && annotation.required()) { throw new NoLoginException(noLoginMessage); } return attribute; } } 
  1. supportsParameter的實現可以看出,只有被@UserId注解修飾的參數才能被此解析器處理。
  2. 如果參數被@UserId修飾,就會由resolveArgument計算出參數的值。從resolveArgument的實現看出,它從session中取數據作為參數的值。

這個NativeWebRequest是一個關鍵點,看名字像是能拿到http請求的數據,粗略查詢資料可知道,可從中拿到HttpServletRequest

 HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class); 
三、注冊解析器

最后,該把我們的解析器設置到spring MVC中去了:

 @Configuration public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new UserIdArgumentResolver()); } }; } } 

通過定義WebMvcConfigurer這個Bean能夠自定義配置。
看起來像是會把默認配置覆蓋似的,不過實際上只是會和默認配置合並,大膽使用。
最后,我們就能使用@UserId了

@RequestMapping(value = "/api/user", method = RequestMethod.POST) @ResponseBody public ResponseDTO user(@UserId String userId) { return new ResponseDTO(new User()); } 
四、更多信息

通過自定義參數解析器這種方式,我們能夠靈活的實現各種自定義的需求。
上面舉的例子是業務相關的,再舉一個純粹和接口格式有關系的例子:
之前工作中發現前端的axios庫默認的POST方式會將JSON格式的數據作為HTTP請求體傳遞。
顯然后端如果按照標准HTTP協議的方式解析肯定是會出問題的。
那么解決方案出了前端配置axios庫之外,理論上,后端也可以通過自定義參數解析器的方式,來進行擴展。
實際上,spring MVC內置的參數注解是以同以上的方式實現的。以下代碼位於RequestMappingHandlerAdapter.java:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList(); resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); if (this.getCustomArgumentResolvers() != null) { resolvers.addAll(this.getCustomArgumentResolvers()); } resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM