好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring boot中自定义Json参数解析器的方法

一、介绍

用过springmvc/spring boot的都清楚,在controller层接受参数,常用的都是两种接受方式,如下

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/**

   * 请求路径 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,下面列出该类的一个实现方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

@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层方法参数注入值得一个入口。具体的不多说啦!下面看代码

二、实现步骤

要拦截一个参数,肯定得给这个参数一个标记,在拦截的时候,判断有没有这个标记,有则拦截,没有则方向,这也是一种过滤器/拦截器原理,谈到标记,那肯定非注解莫属,于是一个注解类就产生了

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@target (elementtype.parameter)

@retention (retentionpolicy.runtime)

public @interface requestjson {

 

  /**

   * 字段名,不填则默认参数名

   * @return

   */

  string fieldname() default "" ;

 

  /**

   * 默认值,不填则默认为null。

   * @return

   */

  string defaultvalue() default "" ;

}

这个注解也不复杂,就两个属性,一个是fieldname,一个是defaultvalue。有了这个,下一步肯定得写该注解的 解析器 ,而上面又谈到handlermethodargumentresolver接口可以拦截controller层参数,所以这个注解的解析器肯定得写在该接口实现类里,

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

@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等,所有可以先去定义一个转换器接口。

?

1

2

3

4

5

6

7

8

9

10

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类型转换器

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

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类型转换器

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

/**

  * 日期转换器

  * 对于日期校验,这里只是简单的做了一下,实际上还有对闰年的校验,

  * 每个月份的天数的校验及其他日期格式的校验

  * @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中进行包装

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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容器中,

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

@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"}]

所以解析的时候,要对这两种情况分情况解析。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

@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依赖包

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:http://www.cnblogs.com/qm-article/p/10199622.html

查看更多关于Spring boot中自定义Json参数解析器的方法的详细内容...

  阅读:14次