在Spring引导中定制Json参数解析器的方法
一.导言
任何使用过springMVC/spring boot的人都知道,在控制器层接受参数有两种常用的方法,如下所示
/* * *请求路径http://127 . 0 . 0 . 1:8080/测试提交类型为application/json * test参数{'sid' 33601,' stun ' : ' Reese ' } * @ paramstr */@ request mapping(value='/test ' method=request method。POST)public void testJsonStr(@ request body(必选=false)String str){ system . out . println(str);}/* * *请求路径http://127 . 0 . 0 . 1:8080/TestAccessordinaryParam?Str=123 *测试参数* @ param str */@ request mapping(value='/testaccepordinaryparam ',method={requestmethod.get,requestmethod。POST })public void testaccepterinaryparam(String str){ system . out . println(str);}第一种是在前端传递json参数,后端使用RequestBody注释接受参数。第二种是常见的后台提交数据和接受参数的get/post方法。当然,spring还提供了路径中参数的解析格式,这里就不讨论了
本文主要研究Json参数的前端解析。既然@RequestBody可以接受json参数,那么它的缺点是什么?
尽管最初的spring提供了@RequestBody注释来封装json数据,但它也有很大的局限性。参数可以是jsonObject或javabean类或字符串。
1.如果使用jsonObject进行接收,则进一步获取和分析json中的参数会比较麻烦。
2.如果javabean用于接收,如果接口参数不同,那么每个接口必须对应一个javabean。如果字符串用于接收,json参数必须自己分析
3.因此,就像get/post表单-data的提交方式一样,直接在控制器层的接口写参数名就可以接收到对应的参数值。
关键是在spring将参数注入控制器层方法之前,截取参数并进行一些更改。为此,spring还为开发人员提供了一个扩展自己的接口。这个接口的名字叫HandlerMethodArgumentResolver,是一个接口,主要用于在控制器层提供参数拦截和注入。Spring还提供了很多实现类,这里就不讨论了。这里,我们介绍一个特殊的实现类handlemmethologumentresolvercomposite,并在下面列出这个类的一个实现方法
@ Override @ Nullable public Object resolveArgument(method parameter参数,@ Nullable modelAndviewcontainer MAV container,NativeWebRequest webRequest,@ Nullable WebDataBinderFactory binderFactory)引发异常{ handlermethargumentresolver=getArgumentResolver(参数);if(解析器==null) {引发新的IllegalArgumentException('不支持的参数类型[' parameter.getParameterType()。getName() ']'应该首先调用supportsParameter。);}返回resolver.resolveArgument(参数,mavContainer,webRequest,binderFactory);}您是否感到惊讶,它没有执行自己的resplveArgument方法,而是执行HandlerMethodArgumentResolver接口的其他实现类的方法。具体原因不知道。这个方法是在控制器层的方法参数中注入一个有价值的条目。细节就不多说了!请看下面的代码
二、实施步骤
要截取参数,必须给它一个标记。拦截时,判断是否有这个标签,有就拦截,没有就没有方向。这也是一个过滤器/拦截器原理。当涉及到标签时,它必须是注释,所以产生了一个注释类
@ target (elementtype。参数)@ retention (retentionpolicy。runtime)public @ interface request JSON {/* * *字段名,如果没有填写,默认参数名* @ return */stringfield name()默认为“”;/* * *默认值,如果没有填写,默认为空。* @return */String defaultValue()默认值“”;}这个注释也不复杂,只有两个属性,一个是fieldName,另一个是defaultValue。有了这一点,下一步必须是编写注释的解析器,上面提到HandlerMethodArgumentResolver接口可以截取控制器层的参数,所以这个注释的解析器必须写在接口实现类中。
@ componentpublic类requestjsonhandler实现handlermethodgumentsolver {/* * * JSON类型*/private静态最终字符串JSON _ content _ type=' application/JSON ';@ override public boolean支持参数(method parameter method parameter){//只有被requestjson批注标记的参数才能输入return method parameter . hasparameter annotation(request JSON . class);} @ Override public Object resolveArgument(method parameter method parameter,modelAndviewcontainer modelAndviewcontainer,Native web request Native web request,webdatabinderfactory webdatabinderfactory)引发异常{//解析requestJson注释的代码}已经构建了一个粗略的模型。这里也提到了要达到的初始效果,如图所示
要解析json参数,必须有一些常用的转换器,将json参数对应的值转换成控制器层参数对应的类型。常见类型,如八种基本类型及其封装类、String、Date类型、list/set、javabean等。可以先定义一个转换器接口。
公共接口Converter {/** *将值转换为clazz类型* @ paramclazz * @ param value * @ return */object convert(类型clazz,object value);}使用这个接口,必须有几个实现类。在这里,我把这些转换器分成七个阵营
1.数字类型转换器,负责字节/整数/浮点/双/长/短和基本类型,以及大整数/大十进制
2.日期类型转换器,负责日期类型
3.字符串类型转换器,负责字符和包装类,以及字符串类型
4.收集类型转换器,负责收集类型
5.布尔类型转换器,负责布尔/布尔类型
6.javaBean类型转换器,负责普通的pojo类
7.地图类型转换器,负责地图界面
这里应该介绍一个谷歌的第三方包,将在文章的最后发布。
这里发布的代码有数字类型和日期类型,其余完整的代码将在github上给出,地址是github链接
数字类型转换器
公共类数字转换器实现转换器{ @覆盖公共对象转换(类型类型,对象值){类?clazz=nullif(!(类型类的实例){ }返回null} clazz=(类?)类型;if(clazz==null){ 0引发新的RuntimeException(“”类型不能为空');}else if(值==null){ 0返回null}else if(字符串""的值实例)。等于(字符串。值的(值)){返回空值;}else if(!克拉斯。isprimitive()clazz。getgenericsuperclass()!=number . class){ 0引发新的class castexception(clazz。' gettypename()'无法转换数字类型!');} if(clazz==int。class | | clazz==整数。类){返回整数。(字符串的值。值(value));} else if(clazz==short。class | | clazz==short。class){ return short。(字符串的值。值(value));} else if(clazz==byte。class | | clazz==byte。类){返回字节。(字符串的值。值(value));} else if(clazz==float。class | | clazz==float。class){ return float。(字符串的值。值(value));} else if(clazz==double。class | | clazz==double。class){返回double。(字符串的值。值(value));} else if(clazz==long。class | | clazz==long。class){ return long。(字符串的值。值(value));} else if(clazz==BigDecimal。类){ 0返回新的BigDecimal(字符串。值(value));} else if(clazz==BigInteger。类){ 0返回新的大十进制(字符串。value of(value));}else {抛出新的RuntimeException(“”不支持此类型转换!');} } }日期类型转换器
/** * 日期转换器* 对于日期校验,这里只是简单的做了一下,实际上还有对闰年的校验, * 每个月份的天数的校验及其他日期格式的校验* @author:邱敏* @ create : 2018-12-30 10:43 * */公共类日期转换器实现了转换器{ /** *校验yyyy-mm-DD hh :mm : ss */私有静态最终字符串regex _ date _ time='^\\d{4}([-]\\d{2}){2}[]([0-1][0-9]|[2][0-4])(:[0-5][0-9]){ 2 } $ ';/** * 校验yyyy-MM-dd */私有静态最终字符串regex _ date='^\\d{4}([-]\\d{2}){2}$';/** * 校验hh :mm :s */私有静态最终字符串regex _ time='^([0-1][0-9]|[2][0-4])(:[0-5][0-9]){2}';/** * 校验yyyy-mm-DD hh :mm */private静态最终字符串regex _ date _ time _ not _ contain _ second='^\\d{4}([-]\\d{2}){2}[]([0-1][0-9]|[2][0-4]):[0-5][0-9]$ ';/** * 默认格式*/私有静态最终字符串DEFAULT _ PATTERN=' yyyy-MM-DD hh :MM 3360s ';/** * 存储数据地图*/私人静态最终MapString,String PATTERN _ MAP=new ConcurrentHashMap();静态{ PATTERN_MAP.put(REGEX_DATE,' yyyy-MM-DD ');模式图。put(REGEX _ DATE _ TIME,' yyy-MM-DD hh :MM :s ');PATTERN_MAP.put(REGEX_TIME,' hh :mm :s ');模式图。put(REGEX _ DATE _ TIME _ NOT _ CONTAIN _ SECOND,' yyyy-MM-DD hh :MM ');} @覆盖公共对象转换(类型克拉斯,对象值){ if(clazz==null){ 0引发新的RuntimeException(“”类型不能为' null!');} if(值==null){ 0返回null}else if(').等于(字符串。值的(值)){返回空值;}请尝试{返回新的简单日期格式(GetDatestrPattern(字符串。Valueof(value)).解析(字符串。值的(值));} catch(ParseException e){ throw new RuntimeException(e);} } /** * 获取对应的日期字符串格式* @param值* @ return */private String getdatestrppattern(字符串值){ for (Map).EntryString,String m : PATTERN _ map。entryset()){ if(值。matches(m . getkey())){ return m . getvalue();} }返回DEFAULT _ PATTERN } }具体分析不做过多讨论,详情看代码。
编写转换器后,我们必须从请求中获取前端参数。常用的方法有request.getReader()和request.getInputStream(),但值得注意的是它们是互斥的。也就是一个用在一个请求中,然后另一个得不到想要的结果。你可以试试。如果我们在解析requestJson注释时直接使用这两种方法中的一种,很可能会出错,因为我们不能保证某个方法在spring中使用过它,最好的结果肯定不是使用它或者包装它(提前获取getReader()/getInputStream()中的数据,并将其存储在字节数组中)。后续请求可以使用这两种方法直接从字节数组中获取数据。)如果他们不使用,就必须进一步包装。在java ee中,有一个类HttpServletRequestWrapper,它是httpsevletRequest的子实现类。也就是说,httpservletRequest可以被这个替换。具体来说,您可以查看源代码。spring提供了HttpServletRequestWrapper的几个子类,所以在这里我们不再重复制作轮子。这里我们使用ContentCachingRequestWrapper类。要打包请求,必须将其打包到过滤器中
公共类requestjsonfilter实现筛选器{/* * *用于进一步包装请求中的Body数据* @ param req * @ param response * @ param chain * @抛出ioexception * @抛出ServletException */@ Override public void doFilter(servlet request,ServletResponse,FilterChain)抛出ioexception,ServletException { servlet request wrapper=null;if(req instance of HttpServletrequest){ HttpServletrequest request=(HttpServletrequest)req;/* * *只是为了防止getreader(),getinputStream()和getparameter () *在一个请求中被调用,很明显inputStream没有复用功能,也就是同一个inputStream流被多次读取,*只有第一次有数据,然后在没有数据的情况下再次读取inputStream。*即getReader()只能调用一次,但getParameter()可以调用多次。有关详细信息,请参见contentcachegreequestwrapper */requestwrapper=new contentcachegreequestwrapper(request)的源代码;} chain . dofilter(request wrapper==null?request : request wrapper,响应);}实现过滤器,它必须在spring容器中注册,
@ Configuration @ enablewebmvcppublic类WebConfigure实现WebMvcConfigurer { @ Autowired private request jsonhandler request jsonhandler;//将requestJson解析器交给spring management @ override public void addargumentresolvers(lishandlermethodgumentresolvers){ resolvers。add (0,request jsonhandler);} @ Bean public filterregistration Bean filterRegister(){ filterregistration Bean registration=new filterregistration Bean();registration . setfilter(new RequestjsonFilter());//截取路径注册. addurlspatterns('/');//筛选器名称registration . setname(' request jsonfilter ');//是否注册. setEnabled(false取消Filter的自动注册。set enabled(false);//过滤顺序,应该排第一。registration . Setorder(1);退货登记;} @Bean(名称='requestJsonFilter ')公共筛选器requestFilter(){返回新的request jsonfilter();}}一切都可用,只需要解析器代码。
前端参数大致有两种json参数格式。
一. { '姓名' : '张三' }
二、[{ '姓名' : '张三' },{ '姓名' : '张三1'}]
所以在分析的时候,要把这两种情况分开来分析。
@ Override public Object resolvereargument(方法参数方法参数,modelAndviewcontainer modelAndviewcontainer,NativeWebRequest nativeWebRequest,WebDataBinderFactory WebDataBinderFactory)引发异常{ HttpServletRequest请求=nativebrequest。getnativerequest(HttpServletrequest。类);string ContentType=请求。GetContentType();//不是json if(!JSON _ CONTENT _ TYPe。equalSignorecase(CONTENT TYPe)){ 0返回null}对象对象=request.getAttribute(常量。请求_正文_数据_名称);已同步(RequestjsonHandler。class){ if(obj==null){ resolverequestBody(请求);obj=request.getAttribute(常量。请求_正文_数据_名称);if(obj==null){ 0返回null} } }请求JSON请求JSON=方法参数。getparameter注释(请求JSON。类);if (obj instanceof Map){ MapString,String map=(MapString,String)obj;返回dealWithMap(map,requestJson,方法参数);} else if(List的obj实例){ listmapString,String list=(ListMapString,String)obj;返回处理数组(列表、requestJson、方法参数);}返回null} /** *处理第一层数据结构为数组结构的数据串* 这种结构默认就认为为类似ListJavaBean结构,转数据即为列表映射结构, * 其余情况不作处理,若控制器层为第一种,则数组里的json,转为javabean结构,字段名要对应, * 注意这里默认值不起作用* @ param list * @ param request JSON * @ param方法参数* @ return */private Object dealWithArray(list mapstring,String list,RequestJson requestJson,method parameter method parameter){ Class?参数类型=方法参数。getparameter type();返回转换器实用程序。getconverter(参数类型).convert(方法参数。getgenericparametertype()、jsonutil。convert beantostr(list));} /** * 处理{'':''}第一层数据结构为地图结构的数据串,* @ param map * @ param request JSON * @ param方法参数* @ return */private Object dealWithMap(MapString,String map,RequestJson requestJson,method parameter method parameter){ String字段名=request JSON。字段名();if(').equals(字段名)){字段名=方法参数。get parameter name();}类?参数类型=方法参数。getparameter type();String orDefault=nullif(映射。包含键(字段名)){ orDefault=map。get(字段名);} else if(converter util。IsampTYPe(ParaMeter TYPe)){返回映射;} else if(converter util。ISbeAnTYPe(参数类型)| | converter util。IsCollectionTYPe(参数类型)){ orDefault=JSonutil。转换beAnTostr(地图);} else { orDefault=map。getordefault(字段名,请求JSON。默认值());}返回转换器实用程序。getconverter(参数类型).convert(方法参数。getgenericparametertype()、orDefault);} /** * 解析请求中的身体数据* @ param request */private void resolveRequestBody(servlet请求){ BufferedReader reader=null尝试{ reader=request。getreader();StringBuilder sb=new StringBuilder();字符串行=null while((line=reader . readline())!=null) { sb.append(行);}字符串参数值=sb。tostring();读解析器=new JsonParser();JsonElement元素=解析器。解析(ParaMeter值);if(元素。isjsonarray()){ ListMapString,String list=new ArrayList();list=jsonutil。convertstrtoban(列表。getclass()、参数值);request.setAttribute(常量请求正文数据名称,列表);}else { MapString,String map=new HashMap();map=jsonutil。convertstrtoban(地图。getclass(),参数值);request.setAttribute(常量REQUEST_BODY_DATA_NAME,map);} } catch(IOexception e){ e . print stack trace();}最后{ if (reader!=null){ try { reader。close();} catch(IOexception e){//ignore//e . print stack trace();} } } }整个代码结构就是上面博文,完整代码在开源代码库上,有感兴趣的博友,可以看看地址开源代码库链接,最后贴下专家依赖包
依赖项依赖项groupIdorg.springframework.boot/groupId人工智能春季-启动-启动-网络/人工智能/依赖依赖项groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-Tomcat/artifactId提供的作用域/作用域/相关性groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId作用域测试/作用域/相关性groupIdcom.google.code.gson/groupId artifactIdgson/artifactId版本2。8 .4/版本/依赖/依赖/依赖以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
版权声明:在Spring引导中定制Json参数解析器的方法是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。