减少10%的代码:自定义参数解析器真的很强大,你不来了解一下?

2b6dff19382e004cc194299bd618138a.jpeg来源:juejin.cn/post/7223705034412015675

👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 /  赠书福利

全栈前后端分离博客项目 2.0 版本完结啦, 演示链接http://116.62.199.48/ ,新项目正在酝酿中。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了239小节,累计38w+字,讲解图:1645张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有1100+小伙伴加入(早鸟价超低)

a72340f8f4bb3e66a7c387322392f273.gif

  • Part1 前言

  • Part2 枚举

    • 1 实现方式

  • Part3 json字符串

    • 2 实现方式

  • Part4 SpringMvc自带解析器

    • 3 普通参数绑定&@RequestParam

    • 4 @PathVariable

    • 5 @RequestBody

    • 6 @ModelAttribute

  • Part5 附录

    • 7 问题1


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就无法自动帮我们绑定参数了,报如下错误:

a8ab637ebc4bdb9bda9f24c1578b6ea8.jpeg
图片

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);然后返回。

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

示例

方法示例:

7f15c2cbf11bb62a8d6e8bc591d52db4.jpeg
图片

请求示例:

3ddf86741b9722b5b518e07029affd7b.jpeg
图片

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

普通对象方法示例:

c60e1de58b45e755700e97aae4cf9748.jpeg
图片

请求示例:

ab4b07d6aac5dce25cc7c830f413fb69.jpeg
图片
示例2

数组对象方法示例:

68b867f5d395ada14e1acb9b8136f937.jpeg
图片

请求示例:

0d3bb735d44a904e696a5d1e4bcdcdc4.jpeg
图片

Part4 SpringMvc自带解析器

3 普通参数绑定&@RequestParam

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

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

如下:

a9bfadea1ea296bcb051f322c500fa52.jpeg
图片
0896db610d0fe8cfc966e6d36c85a88a.jpeg
图片

4 @PathVariable

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

如下:

85cf24bae350c52bb634bc32a8e319b8.jpeg
图片
99f91da347f5f558814d2c2868fd8a72.jpeg
图片

5 @RequestBody

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

ff059e28e28d3274a330e450aef014f4.jpeg
图片
eaba6ee4003e21163394877e09ada37d.jpeg
图片

6 @ModelAttribute

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

be5e427f7d83e3524a0a0e2492aab620.jpeg
图片
c1fc825d3df202a2b0e861222aec2bde.jpeg
图片

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: ['|','{','}','[',']']

👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 /  赠书福利

全栈前后端分离博客项目 2.0 版本完结啦, 演示链接http://116.62.199.48/ ,新项目正在酝酿中。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。目前已更新了239小节,累计38w+字,讲解图:1645张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,已有1100+小伙伴加入(早鸟价超低)

9ee9edd40905a941cf6e0273d58c29cc.gif

9df2dad02943ec16e5d9d818a8966afc.jpeg

 
 

47fecdaa7299136960e34bdc3bb6491a.gif

 
 
 
 
1. 我的私密学习小圈子~
2. Java服务如何优雅的上下线?
3. PO、VO、DAO、BO、DTO、POJO 能分清吗?
4. 本地缓存怎么选型?
 
 
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值