Spring boot中自定义Json参数解析器的方法
这篇文章主要介绍了Spring boot中自定义Json参数解析器的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧一、介绍
用过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层参数,所以这个注解的解析器肯定得写在该接口实现类里,
@component
public 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注解的代码
}
一个大致模型搭建好了。要实现的初步效果,这里也说下,如图
要去解析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}[ ](|)(:){2}$";
/**
* 校验 yyyy-mm-dd
*/
private static final string regex_date = "^\\d{4}([-]\\d{2}){2}$";
/**
* 校验hh:mm:ss
*/
private static final string regex_time = "^(|)(:){2}";
/**
* 校验 yyyy-mm-dd hh:mm
*/
private static final string regex_date_time_not_contain_second = "^\\d{4}([-]\\d{2}){2}[ ](|):$";
/**
* 默认格式
*/
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
@enablewebmvc
public 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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家。
原文链接:http://www.cnblogs.com/qm-article/p/10199622.html
http://www.zzvips.com/article/173719.html
页:
[1]