亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

Spring boot中自定義Json參數解析器的方法

2024-07-14 08:43:27
字體:
來源:轉載
供稿:網友

一、介紹

用過springMVC/spring boot的都清楚,在controller層接受參數,常用的都是兩種接受方式,如下

/**  * 請求路徑 http://127.0.0.1:8080/test 提交類型為application/json  * 測試參數{"sid":1,"stuName":"里斯"}  * @param str  */ @RequestMapping(value = "/test",method = RequestMethod.POST) public void testJsonStr(@RequestBody(required = false) String str){  System.out.println(str); } /**  * 請求路徑 http://127.0.0.1:8080/testAcceptOrdinaryParam?str=123  * 測試參數  * @param str  */ @RequestMapping(value = "/testAcceptOrdinaryParam",method = {RequestMethod.GET,RequestMethod.POST}) public void testAcceptOrdinaryParam(String str){  System.out.println(str); }

第一個就是前端傳json參數,后臺使用RequestBody注解來接受參數。第二個就是普通的get/post提交數據,后臺進行接受參數的方式,當然spring還提供了參數在路徑中的解析格式等,這里不作討論

本文主要是圍繞前端解析Json參數展開,那@RequestBody既然能接受json參數,那它有什么缺點呢,

原spring 雖然提供了@RequestBody注解來封裝json數據,但局限性也挺大的,對參數要么適用jsonObject或者javabean類,或者string,

1、若使用jsonObject 接收,對于json里面的參數,還要進一步獲取解析,很麻煩

2、若使用javabean來接收,若接口參數不一樣,那么每一個接口都得對應一個javabean若使用string 來接收,那么也得需要自己解析json參數

3、所以琢磨了一個和get/post form-data提交方式一樣,直接在controller層接口寫參數名即可接收對應參數值。

重點來了,那么要完成在spring給controller層方法注入參數前,攔截這些參數,做一定改變,對于此,spring也提供了一個接口來讓開發者自己進行擴展。這個接口名為HandlerMethodArgumentResolver,它呢 是一個接口,它的作用主要是用來提供controller層參數攔截和注入用的。spring 也提供了很多實現類,這里不作討論,這里介紹它的一個比較特殊的實現類HandlerMethodArgumentResolverComposite,下面列出該類的一個實現方法

@Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,   NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {  HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);  if (resolver == null) {   throw new IllegalArgumentException(     "Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +       " supportsParameter should be called first.");  }  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }

是不是感到比較驚訝,它自己不去執行自己的resplveArgument方法,反而去執行HandlerMethodArgumentResolver接口其他實現類的方法,具體原因,我不清楚,,,這個方法就是給controller層方法參數注入值得一個入口。具體的不多說啦!下面看代碼

二、實現步驟

要攔截一個參數,肯定得給這個參數一個標記,在攔截的時候,判斷有沒有這個標記,有則攔截,沒有則方向,這也是一種過濾器/攔截器原理,談到標記,那肯定非注解莫屬,于是一個注解類就產生了

@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public @interface RequestJson { /**  * 字段名,不填則默認參數名  * @return  */ String fieldName() default ""; /**  * 默認值,不填則默認為null。  * @return  */ String defaultValue() default "";}

這個注解也不復雜,就兩個屬性,一個是fieldName,一個是defaultValue。有了這個,下一步肯定得寫該注解的解析器,而上面又談到HandlerMethodArgumentResolver接口可以攔截controller層參數,所以這個注解的解析器肯定得寫在該接口實現類里,

@Componentpublic class RequestJsonHandler implements HandlerMethodArgumentResolver { /**  * json類型  */ private static final String JSON_CONTENT_TYPE = "application/json"; @Override public boolean supportsParameter(MethodParameter methodParameter) {  //只有被reqeustJson注解標記的參數才能進入  return methodParameter.hasParameterAnnotation(RequestJson.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 解析requestJson注解的代碼   }

一個大致模型搭建好了。要實現的初步效果,這里也說下,如圖

Spring,boot,Json,參數解析器

要去解析json參數,那肯定得有一些常用的轉換器,把json參數對應的值,轉換到controller層參數對應的類型中去,而常用的類型如 八種基本類型及其包裝類,String、Date類型,list/set,javabean等,所有可以先去定義一個轉換器接口。

public interface Converter { /**  * 將value轉為clazz類型  * @param clazz  * @param value  * @return  */ Object convert(Type clazz, Object value);}

有了這個接口,那肯定得有幾個實現類,在這里,我將這些轉換器劃分為 ,7個陣營

1、Number類型轉換器,負責Byte/Integer/Float/Double/Long/Short 及基礎類型,還有BigInteger/BigDecimal兩個類

2、Date類型轉換器,負責日期類型

3、String類型轉換器,負責char及包裝類,還有string類型

4、Collection類型轉換器,負責集合類型

5、Boolean類型轉換器,負責boolean/Boolean類型

6、javaBean類型轉換器,負責普通的的pojo類

7、Map類型轉換器,負責Map接口

這里要需引入第三方包google,在文章末尾會貼出來。

代碼在這里就貼Number類型和Date類型,其余完整代碼,會在github上給出,地址  github鏈接

Number類型轉換器

public class NumberConverter implements Converter{ @Override public Object convert(Type type, Object value){  Class<?> clazz = null;  if (!(type instanceof Class)){   return null;  }  clazz = (Class<?>) type;  if (clazz == null){   throw new RuntimeException("類型不能為空");  }else if (value == null){   return null;  }else if (value instanceof String && "".equals(String.valueOf(value))){   return null;  }else if (!clazz.isPrimitive() && clazz.getGenericSuperclass() != Number.class){   throw new ClassCastException(clazz.getTypeName() + "can not cast Number type!");  }  if (clazz == int.class || clazz == Integer.class){   return Integer.valueOf(String.valueOf(value));  }else if (clazz == short.class || clazz == Short.class){   return Short.valueOf(String.valueOf(value));  }else if (clazz == byte.class || clazz == Byte.class){   return Byte.valueOf(String.valueOf(value));  }else if (clazz == float.class || clazz == Float.class){   return Float.valueOf(String.valueOf(value));  }else if (clazz == double.class || clazz == Double.class){   return Double.valueOf(String.valueOf(value));  }else if (clazz == long.class || clazz == Long.class){   return Long.valueOf(String.valueOf(value));  }else if (clazz == BigDecimal.class){   return new BigDecimal(String.valueOf(value));  }else if (clazz == BigInteger.class){   return new BigDecimal(String.valueOf(value));  }else {   throw new RuntimeException("This type conversion is not supported!");  } }}

Date類型轉換器

/** * 日期轉換器 * 對于日期校驗,這里只是簡單的做了一下,實際上還有對閏年的校驗, * 每個月份的天數的校驗及其他日期格式的校驗 * @author: qiumin * @create: 2018-12-30 10:43 **/public class DateConverter implements Converter{ /**  * 校驗 yyyy-MM-dd HH:mm:ss  */ private static final String REGEX_DATE_TIME = "^//d{4}([-]//d{2}){2}[ ]([0-1][0-9]|[2][0-4])(:[0-5][0-9]){2}$"; /**  * 校驗 yyyy-MM-dd  */ private static final String REGEX_DATE = "^//d{4}([-]//d{2}){2}$"; /**  * 校驗HH:mm:ss  */ private static final String REGEX_TIME = "^([0-1][0-9]|[2][0-4])(:[0-5][0-9]){2}"; /**  * 校驗 yyyy-MM-dd HH:mm  */ private static final String REGEX_DATE_TIME_NOT_CONTAIN_SECOND = "^//d{4}([-]//d{2}){2}[ ]([0-1][0-9]|[2][0-4]):[0-5][0-9]$"; /**  * 默認格式  */ private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss"; /**  * 存儲數據map  */ private static final Map<String,String> PATTERN_MAP = new ConcurrentHashMap<>(); static {  PATTERN_MAP.put(REGEX_DATE,"yyyy-MM-dd");  PATTERN_MAP.put(REGEX_DATE_TIME,"yyyy-MM-dd HH:mm:ss");  PATTERN_MAP.put(REGEX_TIME,"HH:mm:ss");  PATTERN_MAP.put(REGEX_DATE_TIME_NOT_CONTAIN_SECOND,"yyyy-MM-dd HH:mm"); } @Override public Object convert(Type clazz, Object value) {  if (clazz == null){   throw new RuntimeException("type must be not null!");  }  if (value == null){   return null;  }else if ("".equals(String.valueOf(value))){   return null;  }  try {   return new SimpleDateFormat(getDateStrPattern(String.valueOf(value))).parse(String.valueOf(value));  } catch (ParseException e) {   throw new RuntimeException(e);  } } /**  * 獲取對應的日期字符串格式  * @param value  * @return  */ private String getDateStrPattern(String value){  for (Map.Entry<String,String> m : PATTERN_MAP.entrySet()){   if (value.matches(m.getKey())){    return m.getValue();   }  }  return DEFAULT_PATTERN; }}

具體分析不做過多討論,詳情看代碼。

那寫完轉換器,那接下來,我們肯定要從request中拿到前端傳的參數,常用的獲取方式有request.getReader(),request.getInputStream(),但值得注意的是,這兩者者互斥。即在一次請求中使用了一者,然后另一個就獲取不到想要的結果。具體大家可以去試下。如果我們直接在解析requestJson注解的時候使用這兩個方法中的一個,那很大可能會出問題,因為我們也保證不了在spring中某個方法有使用到它,那肯定最好結果是不使用它或者包裝它(提前獲取getReader()/getInputStream()中的數據,將其存入一個byte數組,后續request使用這兩個方法獲取數據可以直接從byte數組中拿數據),不使用肯定不行,那得進一步去包裝它,在java ee中有提供這樣一個類HttpServletRequestWrapper,它就是httpsevletRequest的一個子實現類,也就是意味httpservletRequest的可以用這個來代替,具體大家可以去看看源碼,spring提供了幾個HttpServletRequestWrapper的子類,這里就不重復造輪子,這里使用ContentCachingRequestWrapper類。對request進行包裝,肯定得在filter中進行包裝

public class RequestJsonFilter implements Filter { /**  * 用來對request中的Body數據進一步包裝  * @param req  * @param response  * @param chain  * @throws IOException  * @throws ServletException  */ @Override public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException, ServletException {  ServletRequest requestWrapper = null;  if(req instanceof HttpServletRequest) {   HttpServletRequest request = (HttpServletRequest) req;   /**    * 只是為了防止一次請求中調用getReader(),getInputStream(),getParameter()    * 都清楚inputStream 并不具有重用功能,即多次讀取同一個inputStream流,    * 只有第一次讀取時才有數據,后面再次讀取inputStream 沒有數據,    * 即,getReader(),只能調用一次,但getParameter()可以調用多次,詳情可見ContentCachingRequestWrapper源碼    */   requestWrapper = new ContentCachingRequestWrapper(request);  }  chain.doFilter(requestWrapper == null ? req : requestWrapper, response); }

實現了過濾器,那肯定得把過濾器注冊到spring容器中,

@Configuration@EnableWebMvcpublic class WebConfigure implements WebMvcConfigurer { @Autowired private RequestJsonHandler requestJsonHandler; // 把requestJson解析器也交給spring管理 @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {  resolvers.add(0,requestJsonHandler); } @Bean public FilterRegistrationBean filterRegister() {  FilterRegistrationBean registration = new FilterRegistrationBean();  registration.setFilter(new RequestJsonFilter());  //攔截路徑  registration.addUrlPatterns("/");  //過濾器名稱  registration.setName("requestJsonFilter");  //是否自動注冊 false 取消Filter的自動注冊  registration.setEnabled(false);  //過濾器順序,需排在第一位  registration.setOrder(1);  return registration; } @Bean(name = "requestJsonFilter") public Filter requestFilter(){  return new RequestJsonFilter(); }}

萬事具備,就差解析器的代碼了。

對于前端參數的傳過來的json參數格式,大致有兩種。

一、{"name":"張三"}

二、[{"name":"張三"},{"name":"張三1"}]

所以解析的時候,要對這兩種情況分情況解析。

@Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {  HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);  String contentType = request.getContentType();  // 不是json  if (!JSON_CONTENT_TYPE.equalsIgnoreCase(contentType)){   return null;  }  Object obj = request.getAttribute(Constant.REQUEST_BODY_DATA_NAME);  synchronized (RequestJsonHandler.class) {   if (obj == null) {    resolveRequestBody(request);    obj = request.getAttribute(Constant.REQUEST_BODY_DATA_NAME);    if (obj == null) {     return null;    }   }  }  RequestJson requestJson = methodParameter.getParameterAnnotation(RequestJson.class);  if (obj instanceof Map){   Map<String, String> map = (Map<String, String>)obj;   return dealWithMap(map,requestJson,methodParameter);  }else if (obj instanceof List){   List<Map<String,String>> list = (List<Map<String,String>>)obj;   return dealWithArray(list,requestJson,methodParameter);  }  return null; } /**  * 處理第一層json結構為數組結構的json串  * 這種結構默認就認為 為類似List<JavaBean> 結構,轉json即為List<Map<K,V>> 結構,  * 其余情況不作處理,若controller層為第一種,則數組里的json,轉為javabean結構,字段名要對應,  * 注意這里defaultValue不起作用  * @param list  * @param requestJson  * @param methodParameter  * @return  */ private Object dealWithArray(List<Map<String,String>> list,RequestJson requestJson,MethodParameter methodParameter){  Class<?> parameterType = methodParameter.getParameterType();  return ConverterUtil.getConverter(parameterType).convert(methodParameter.getGenericParameterType(),JsonUtil.convertBeanToStr(list)); } /**  * 處理{"":""}第一層json結構為map結構的json串,  * @param map  * @param requestJson  * @param methodParameter  * @return  */ private Object dealWithMap(Map<String,String> map,RequestJson requestJson,MethodParameter methodParameter){  String fieldName = requestJson.fieldName();  if ("".equals(fieldName)){   fieldName = methodParameter.getParameterName();  }  Class<?> parameterType = methodParameter.getParameterType();  String orDefault = null;  if (map.containsKey(fieldName)){   orDefault = map.get(fieldName);  }else if (ConverterUtil.isMapType(parameterType)){   return map;  }else if (ConverterUtil.isBeanType(parameterType) || ConverterUtil.isCollectionType(parameterType)){   orDefault = JsonUtil.convertBeanToStr(map);  }else {   orDefault = map.getOrDefault(fieldName,requestJson.defaultValue());  }  return ConverterUtil.getConverter(parameterType).convert(methodParameter.getGenericParameterType(),orDefault); } /**  * 解析request中的body數據  * @param request  */ private void resolveRequestBody(ServletRequest request){  BufferedReader reader = null;  try {   reader = request.getReader();   StringBuilder sb = new StringBuilder();   String line = null;   while ((line = reader.readLine()) != null) {    sb.append(line);   }   String parameterValues = sb.toString();   JsonParser parser = new JsonParser();   JsonElement element = parser.parse(parameterValues);   if (element.isJsonArray()){    List<Map<String,String>> list = new ArrayList<>();    list = JsonUtil.convertStrToBean(list.getClass(),parameterValues);    request.setAttribute(Constant.REQUEST_BODY_DATA_NAME, list);   }else {    Map<String, String> map = new HashMap<>();    map = JsonUtil.convertStrToBean(map.getClass(), parameterValues);    request.setAttribute(Constant.REQUEST_BODY_DATA_NAME, map);   }  } catch (IOException e) {   e.printStackTrace();  }finally {   if (reader != null){    try {     reader.close();    } catch (IOException e) {     // ignore     //e.printStackTrace();    }   }  } }

整個代碼結構就是上面博文,完整代碼在github上,有感興趣的博友,可以看看地址  github鏈接,最后貼下maven依賴包

<dependencies>  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-tomcat</artifactId>   <scope>provided</scope>  </dependency>  <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-test</artifactId>   <scope>test</scope>  </dependency>  <dependency>   <groupId>com.google.code.gson</groupId>   <artifactId>gson</artifactId>   <version>2.8.4</version>  </dependency> </dependencies>

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲欧美中文日韩在线v日本| 日韩欧美一区视频| 亚洲福利视频专区| 日韩一区在线视频| 欧美激情xxxx性bbbb| 欧美一级视频免费在线观看| 成人黄色在线观看| 欧美性精品220| 欧美成人精品在线| 国产不卡精品视男人的天堂| 国产综合久久久久久| 日韩视频在线观看免费| 97视频在线观看视频免费视频| 这里只有精品视频| 亚洲人精品午夜在线观看| 5566日本婷婷色中文字幕97| 国产精品老女人精品视频| 欧美黄色片视频| 日韩美女在线观看一区| 这里只有精品视频在线| 国产精品一区二区三区免费视频| 久久国产精品影视| 亚洲福利视频在线| 国产精品夜色7777狼人| 日韩激情视频在线播放| xxx一区二区| 国产精品美女久久久免费| 97国产成人精品视频| 7m精品福利视频导航| 日韩电视剧免费观看网站| 欧美制服第一页| 在线看欧美日韩| 亚洲国产精品久久91精品| 欧美日韩精品在线视频| 日韩国产中文字幕| 最近2019年中文视频免费在线观看| 成人黄色av网站| 日韩有码视频在线| 亚洲电影成人av99爱色| 日韩欧美黄色动漫| 欧美丰满少妇xxxxx做受| 另类专区欧美制服同性| 青青草一区二区| 中文字幕自拍vr一区二区三区| 国产成人97精品免费看片| 久久综合免费视频影院| 亚洲午夜精品久久久久久久久久久久| 97精品一区二区三区| 日韩电影免费在线观看| 中文字幕亚洲无线码在线一区| 亚洲国产日韩精品在线| 久久亚洲国产精品成人av秋霞| 一本大道亚洲视频| 久精品免费视频| 日韩精品在线免费| 国产精品成久久久久三级| 日韩在线视频线视频免费网站| 91精品国产91| 九九热精品视频| 久久精品久久久久久国产 免费| 国产视频亚洲视频| 日韩www在线| 81精品国产乱码久久久久久| 国产69精品久久久| 欧美成人午夜激情| 久色乳综合思思在线视频| 日韩高清av一区二区三区| 久久韩国免费视频| 亚洲第一男人av| 久久人人爽人人| 理论片在线不卡免费观看| 亚洲色图第一页| 亚洲精品视频二区| 久久精品国产成人精品| 午夜精品一区二区三区在线视频| 日韩精品免费在线| 欧洲一区二区视频| 国内精品久久久| 中文字幕av一区二区| 国产一区二区久久精品| 国产欧美一区二区三区久久人妖| 成人精品久久av网站| 国产精品日韩在线一区| 久久综合色88| 国产精品久久久久久五月尺| 欧美大全免费观看电视剧大泉洋| 久久久精品免费视频| 日本精品一区二区三区在线| 欧美激情视频在线免费观看 欧美视频免费一| 日韩av一卡二卡| 亚洲一区二区中文字幕| 一区二区av在线| 国产玖玖精品视频| 热久久视久久精品18亚洲精品| 国产亚洲精品久久久久久| 亚洲免费成人av电影| 最近免费中文字幕视频2019| 亚洲图片制服诱惑| 精品久久久久久久久久久久| 秋霞成人午夜鲁丝一区二区三区| 日韩精品日韩在线观看| 欧美成人一区在线| 成人精品一区二区三区电影免费| 欧美日韩国产第一页| 川上优av一区二区线观看| 国产精品夫妻激情| 国产日韩欧美在线看| 欧美精品免费在线| 精品福利在线视频| 国产91精品在线播放| 欧美日韩国产页| 欧美性猛交xxxx乱大交| 国产成人欧美在线观看| 国产一区二区三区在线| 欧美亚洲国产视频小说| 欧美精品久久久久| 91国产精品电影| 中文字幕在线观看亚洲| 精品久久中文字幕| 久久精品中文字幕免费mv| 国产精品亚洲综合天堂夜夜| 日本sm极度另类视频| 国产噜噜噜噜久久久久久久久| 欧美高清自拍一区| 91亚洲国产精品| 欧美另类xxx| 上原亚衣av一区二区三区| 欧美激情影音先锋| 国产精品极品尤物在线观看| 亚洲成人中文字幕| 欧美中在线观看| 日韩欧中文字幕| 欧美在线观看一区二区三区| 91精品国产91久久久久久久久| 欧美在线亚洲在线| 69av成年福利视频| 国产精品高清免费在线观看| 久久久精品影院| 国产欧美日韩中文字幕| 久久亚洲春色中文字幕| 国产欧美一区二区三区久久人妖| 久久高清视频免费| 538国产精品一区二区免费视频| 国产精品久久久久久久一区探花| 91av在线播放| 亚洲国产精品va在线看黑人| 日韩精品极品毛片系列视频| 国产在线观看一区二区三区| 中文在线资源观看视频网站免费不卡| 高清一区二区三区日本久| 成人av在线网址| 欧洲成人免费视频| 欧美最猛性xxxxx亚洲精品| 97香蕉超级碰碰久久免费软件| 久久精品亚洲一区| 日韩精品极品视频免费观看| 国产精品视频成人| 日韩国产精品亚洲а∨天堂免| 日韩精品在线视频| 欧美高清视频在线| 92裸体在线视频网站| 久久久久这里只有精品| 国模精品视频一区二区三区| 国产在线视频2019最新视频|