⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7223705034412015675 「Aqoo」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

Part1 前言

springMvc中提供了很多好用的参数绑定的方式方法,那枚举呢?或者参数的值是一个json字符串的时候?你是怎么处理的?下面分享一下我的处理方式。

Part2 枚举

普通的枚举类型,比如单列值的那种:one ,two… 。这种事不需要特殊处理的,我们是可以直接接收值并绑定数据的。

要是下面这种枚举类型呢?而且我们的参数传递的是:0,1这种数字,方法参数是枚举类型。spring还能帮我们自动绑定参数嘛?

public enum StatusEnum {
online(1),
offline(0);

private Integer value;

StatusEnum(Integer value) {
this.value = value;
}

public Integer getValue() {
return value;
}
}

这时候spring就无法自动帮我们绑定参数了,报如下错误:

1 实现方式

  • 通过定时枚举参数注解来标记参数:这是一个枚举类型的参数。
  • 通过自定义参数解析器来分析枚举参数注解,来实现参数的绑定。

定义注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnumParam {
String value() default "";
/**
* 赋值调用方法
* 如果为空,默认调用name()方法
* 该方法必须是一个不含参数的方法,否则将会调用失败
*
* @return
*/
String valueMethod() default "";
}
  • value(): value用于绑定请求参数和方法参数名一致时的对应关系。比如user?statusNo=1 。

    • 方法的参数写法如下:getUser(@EnumParam(value="statusNo") int status) 或者 getUser(@EnumParam() int statusNo)
  • valueMethod(): 赋值时调用枚举中的方法。

    • 如果该属性不传值则默认调用枚举类默认提供的 “valueOf()” 方法。
    • 如果自定义一个方法,该方法必须是一个不含参数的方法,否则将会调用失败。比如上述示例枚举 StatusEnum的getValue()方法。

定义枚举参数解析器

核心代码:

// 1
public class EnumParamArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(EnumParam.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 2
EnumParam annotation = parameter.getParameterAnnotation(EnumParam.class);
Object object = null;
if (annotation != null) {
String parameterName = annotation.value();
// 3
if (!StringUtils.hasText(parameterName)) {
parameterName = annotation.name();
}
if (!StringUtils.hasText(parameterName)) {
parameterName = parameter.getParameterName();
}

String value = webRequest.getParameter(parameterName);

if (StringUtils.hasText(value)) {
// 4
Class<?> objectType = parameter.getParameterType();
String method = Objects.equals(annotation.valueMethod(), "") ? "valueOf" : annotation.valueMethod();
Object[] enumConstants = objectType.getEnumConstants();
// 如果方法没了就 抛出异常
Method declaredMethod = objectType.getDeclaredMethod(method);
try {
for (Object enumConstant : enumConstants) {
// 5
Object invoke = method.equals("valueOf") ? declaredMethod.invoke(enumConstant, enumConstant.toString()) : declaredMethod.invoke(enumConstant);
if (invoke != null) {
if (Convert.toStr(invoke).equals(Convert.toStr(value))) {
object = enumConstant;
break;
}
}
}
} catch (Exception e) {
log.error("参数enum转换失败:{}.{}[{}]", parameter.getContainingClass().getName(), parameter.getMethod().getName(), parameterName);
object = null;
}
}
mavContainer.addAttribute(parameterName, object);
}
return object;
}
}
  1. 枚举参数解析器(EnumParamArgumentResolver)实现 spring mvc的扩展接口HandlerMethodArgumentResolver。
  2. 从参数中获取是否标记了 EnumParam 注解,如果是则进行解析。
  3. 处理 EnumParam.value() 的值并进行赋值给parameterName。
  4. 通过返回的方式拿到需要执行的方法和目标枚举类的值。
  5. 通过循环枚举的值然后比较。如果匹配则立即跳出循环并mavContainer.addAttribute(parameterName, object);然后返回。

以上就是实现枚举参数解析器的全部步骤。

示例

方法示例:

请求示例:

Part3 json字符串

我们有时候可能会遇到这种请求:

localhost:8088/prt/jsonParams?user={"age":12,"id":"1","name":"凹凸曼"} 那么这种我们可能一般都是使用String接收,然后在调用转JSON的API进行处理。可是这种代码每个方法都去写的话,太不优雅了。毕竟:「温柔永不落伍, 优雅永不过时 」 。

2 实现方式

  • 通过定时JSON参数注解来标记参数:这是一个JSON字符串的参数。
  • 通过自定义参数解析器来分析JSON字符串参数注解,来实现参数和对象属性的绑定。

定义注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JsonParam {
String value() default ""

Class<?> objectType() default JsonParam.class;
}
  1. value() : value用于绑定请求参数和方法参数名一致时的对应关系。和 EnumParam中的value定义差不多。
  2. objectType() : 当参数是数组对象时,赋值属性。

定义Json参数解析器

核心代码:

public class JsonParamArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation( JsonParam.class );
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
JsonParam annotation = parameter.getParameterAnnotation( JsonParam.class );
Object object = null;
if (annotation != null) {
String parameterName = annotation.value();
if (StringUtils.isBlank( parameterName )) {
parameterName = annotation.name();
}

if (StringUtils.isBlank( parameterName )) {
parameterName = parameter.getParameterName();
}

String value = webRequest.getParameter( parameterName );
if (StringUtils.isNotBlank( value )) {
// 2
Class<?> objectType = annotation.objectType();
try {
if (objectType != JsonParam.class) {
object = JSON.parseArray( value, objectType );
} else {
object = JSON.parseObject( value, parameter.getParameterType() );
}
} catch (Exception e) {
LoggerFactory.getLogger( JsonParamArgumentResolver.class )
.error( "参数Json转换失败:" + parameter.getContainingClass().getName() + "." + parameter.getMethod().getName() + "[" + parameterName + "]" );
object = null;
}
}
//this.validate( parameter, mavContainer, object, parameterName );
mavContainer.addAttribute( parameterName, object );
}
return object;
}
}
  1. 上述步骤的大部分逻辑和 枚举参数解析器 大体一致。
  2. 步骤2是判断objectType是否是JsonParam类型,如果是则是对象类型;如果不是JsonParam,这是数组对象类型。

以上就是实现Json参数解析器的全部步骤。

示例

示例1

普通对象方法示例:

请求示例:

示例2

数组对象方法示例:

请求示例:

Part4 SpringMvc自带解析器

3 普通参数绑定&@RequestParam

一般我们普通的参数我们无需加任何额外的注解标记,spring既可以给我们自定绑定参数。

这种,当我们的请求参数与方法参数不一致时可以使用@RequestParam

如下:

4 @PathVariable

在Controller方法的参数前面添加@PathVariable注解,将路径参数的值绑定到对应的参数上。

如下:

5 @RequestBody

在Controller方法的参数前面添加@RequestBody注解,将请求体的值绑定到对应的参数上 。注意这种模式不支持: Content-Type: application/x-www-form-urlencoded或 Content-Type: application/x-www-form的请求。

6 @ModelAttribute

在Controller方法的参数前面添加@ModelAttribute注解,将表单参数的值绑定到对应的参数上。同上这种模式不支持 Content-Type: application/json的请求。

Part5 附录

7 问题1

springBoot+tomcat报错:_Invalid character found in the request target \[...\]. The valid characters are defined in RFC 7230 and RFC 3986_

原因是:SpringBoot 2.0.0 以上都采用内置tomcat8.0以上版本,而tomcat8.0以上版本遵从RFC规范添加了对Url的特殊字符的限制,url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~四个特殊字符以及保留字符( ! * ’ ( ) ; : @ & = + $ , / ? # \[ \] ) (26*2+10+4+18=84)这84个字符,请求中出现了{}大括号或者\[\],所以tomcat报错。

处理办法:在application.yml配置文件中如下配置:

server:
tomcat:
relaxed-path-chars: ['|','{','}','[',']']
relaxed-query-chars: ['|','{','}','[',']']
文章目录
  1. 1. Part1 前言
  2. 2. Part2 枚举
    1. 2.1. 1 实现方式
      1. 2.1.0.1. 定义注解
      2. 2.1.0.2. 定义枚举参数解析器
    2. 2.1.1. 示例
  • 3. Part3 json字符串
    1. 3.1. 2 实现方式
      1. 3.1.0.1. 定义注解
      2. 3.1.0.2. 定义Json参数解析器
    2. 3.1.1. 示例
      1. 3.1.1.1. 示例1
      2. 3.1.1.2. 示例2
  • 4. Part4 SpringMvc自带解析器
    1. 4.1. 3 普通参数绑定&@RequestParam
    2. 4.2. 4 @PathVariable
    3. 4.3. 5 @RequestBody
    4. 4.4. 6 @ModelAttribute
  • 5. Part5 附录
    1. 5.1. 7 问题1