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

摘要: 原创出处 juejin.cn/post/7054441239839506446 「小兵张健」欢迎转载,保留摘要,谢谢!


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

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

网上很多代码都是千篇一律的 cvs,相信我只要你认真看完我写的这篇,你就可以完全掌握这个知识点,这篇文章不适合直接 cvs,一定要先理解。

最近重写个项目遇到个比较棘手的问题,老项目是 PHP 接口,这个接口同时兼容 POST json 和 form 表单,更骚的是连 form-data 也兼容。。。因为写 PHP 请求的对接方代码不严谨。

而在 Java 中,一个接口只支持一种 content-type,json 就用 @RequestBody,form 表单就用 @RequestParam 或不写,form-data 就用 MultipartFile。

兼容版本

如果要把在一个接口中同时兼容三种,比较笨的办法就是获取 HttpServletRequest,然后自己再写方法解析。类似如下:

private Map<String, Object> getParams(HttpServletRequest request) {

String contentType = request.getContentType();
if (contentType.contains("application/json")) {
// json 解析...
return null;
} else if (contentType.contains("application/x-www-form-urlencoded")) {
// form 表单解析 ...
return null;
} else if (contentType.contains("multipart")) {
// 文件流解析
return null;
} else {
throw new BizException("不支持的content-type");
}

}

但是这样写有弊端。

  1. 代码很丑,具体到解析代码又臭又长
  2. 只能返回固定 map 或者自己重新组装参数类
  3. 无法使用 @Valid 校验参数,像我这种几十个参数都要检验的简直是灾难

优雅版本

网上有 form 表单和 json 同时兼容的版本,但是没有兼容 form-data,我在这做一下补充。

1. 自定义注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GamePHP {
}

2. 自定义注解解析

public class GamePHPMethodProcessor implements HandlerMethodArgumentResolver {

private GameFormMethodArgumentResolver formResolver;
private GameJsonMethodArgumentResolver jsonResolver;

public GamePHPMethodProcessor() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
PHPMessageConverter PHPMessageConverter = new PHPMessageConverter();
messageConverters.add(PHPMessageConverter);

jsonResolver = new GameJsonMethodArgumentResolver(messageConverters);
formResolver = new GameFormMethodArgumentResolver();
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
GamePHP ann = parameter.getParameterAnnotation(GamePHP.class);
return (ann != null);
}

@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
ServletRequest servletRequest = nativeWebRequest.getNativeRequest(ServletRequest.class);
String contentType = servletRequest.getContentType();
if (contentType == null) {
throw new IllegalArgumentException("不支持contentType");
}

if (contentType.contains("application/json")) {
return jsonResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
}

if (contentType.contains("application/x-www-form-urlencoded")) {
return formResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
}

if (contentType.contains("multipart")) {
return formResolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
}

throw new IllegalArgumentException("不支持contentType");
}
}

3. 添加到 spring configuration

@Bean
public MyMvcConfigurer mvcConfigurer() {
return new MyMvcConfigurer();
}

public static class MyMvcConfigurer implements WebMvcConfigurer {
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new GamePHPMethodProcessor());
}
}

4. form-data 的特殊处理

引入 jar 包

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

新增解析 bean

@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver(){
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setResolveLazily(true);//resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
resolver.setMaxInMemorySize(40960);
resolver.setMaxUploadSize(50*1024*1024);//上传文件大小 50M 50*1024*1024
return resolver;
}

特殊说明,GameJsonMethodArgumentResolverGameFormMethodArgumentResolver 是我们自定义的 json 和 form 解析,如果你没有自定义的,使用 spring 默认的 ServletModelAttributeMethodProcessorRequestResponseBodyMethodProcessor 也可以。

只需将 @RequestParam 注解改为 @GamePHP,接口即可同时兼容三种 content-type。

其流程为,spring 启动的时候,MyMvcConfigurer 调用 addArgumentResolvers 方法将 GamePHPMethodProcessor 注入,接到请求时,supportsParameter 方法判断是否使用此 resolver,如果为 true,则进入 resolveArgument 方法执行。

「至此我们可以得出一个结论,PHP 是世界上最垃圾的语言。写代码一时爽,维护火葬场。」

文章目录
  1. 1. 兼容版本
  2. 2. 优雅版本
    1. 2.1. 1. 自定义注解
    2. 2.2. 2. 自定义注解解析
    3. 2.3. 3. 添加到 spring configuration
    4. 2.4. 4. form-data 的特殊处理