一、自定义参数解析器需求产生背景
建议了解,也会介绍它是做什么用的
首先在post请求中(content-type=“application/json”)Java后端接受参数从传统角度来讲有两种方法,一是使用Map或者JSONObject,带上@RequestBody注解,可接收任意类型任意数量的传递参数,二是在后端定义一个接收实体也就是Java对象只能接收定义好的参数与参数类型。在后端只需要一个或两个非实体类型参数时,传统方式无疑需要去获取对应的参数,先转换成对应的类型,再设置入具体的类,非常的不友好,故想得出一个能让POST请求与GET请求参数接收一样友好方便的解决方案。
二、准备工作
通过翻阅资料发现,@RequestBody是通过参数解析器工作的,于是便想自定义一个完美的参数解析器用于以上情况的参数解析。
首先新建一个注解类用于解析器判断是否进行自定义解析,并定义一些辅助解析参数
package com.caohua.api.ptom.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface JsonArg {
public String value() default "";// 需解析参数名
public String type() default "";// 参数类型(填bean为实体参数)
}
第二步:新建解析处理类
package com.caohua.api.ptom.annotation.handle;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.caohua.api.ptom.annotation.JsonArg;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
final static Logger log = LoggerFactory.getLogger(JsonArgumentResolver.class);
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 判断是否使用JsonArg注解,也就是,是否使用此解析器解析参数
return parameter.hasParameterAnnotation(JsonArg.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);// 获取body内容(形如{})
// 获取注解设置的属性值
String arg = parameter.getParameterAnnotation(JsonArg.class).value();
String type = parameter.getParameterAnnotation(JsonArg.class).type();
if (StringUtils.isEmpty(arg)) {
arg = parameter.getParameterName();// body取值参数名未指定,使用默认参数名称
}
Object val = null;
try {
JSONObject obj = JSON.parseObject(body);
if (type.equals("bean")) {// 解析实体参数,自动对应参数名称和类型注入实体其他丢弃
val = obj.toJavaObject(parameter.getParameterType());
} else { // 解析非实体参数
val = obj.getObject(arg, parameter.getParameterType());
}
} catch (Exception e) {
log.error("JsonArgumentResolver,参数解析错误,msg:{},body:{}", e.getMessage(), body);
}
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody == null) {
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
第三步将参数解析器放入容器(也可以说是加入所有参数解析器集合中)
此处有两种方法,首先声明亲自实验过使用spring提供的webMvcConfig系列的类去配置自定义参数解析器是无效的,原因是在接收到请求时,参数解析器的执行机制是遍历解析器集合,第一个符合条件的解析器被执行循环终止,显而易见对于我们需求场景中的请求方式肯定是有默认解析器去处理的,而提供的配置类把自定义解析器加入到了集合后方,肯定是永远不会执行的,那就意味着我们必须将自定义的参数解析器放到解析器集合的前面,最好是第一位,这样才能保证我们的解析器会被执行,这里提供两种可行的配置方式,具体实现原理请自行跟源码了解,只有认真了解过的事情才会记忆深刻。
1、配置类形式
package com.caohua.launcher.config;
import com.caohua.api.ptom.annotation.handle.JsonArgumentResolver;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
@Component
public class ArgumentResolverConfig implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// System.out.println("-------------------------------" + beanName);
if(beanName.contains("RequestMappingHandlerAdapter")) {
//对RequestMappingHandlerAdapter进行修改
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
List<HandlerMethodArgumentResolver> argumentResolvers = adapter.getArgumentResolvers();
//添加自定义参数处理器
argumentResolvers = addArgumentResolvers(argumentResolvers);
adapter.setArgumentResolvers(argumentResolvers);
}
return bean;
}
private List<HandlerMethodArgumentResolver> addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
//将自定义的添加到最前面
resolvers.add(new JsonArgumentResolver());// 添加参数解析器
//将原本的添加后面
resolvers.addAll(argumentResolvers);
return resolvers;
}
}
2、配置文件形式
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="resolver.MyResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>