Spring MVC 自定义参数处理器
SpringMVC
spring mvc 是一个非常好用的框架,傻瓜式的操作,深受人们的喜爱。
框架自带的功能基本能够满足市面上百分之八九十的需求,但总有一些奇葩的需求,这时候我们就要自定义一些功能(搞事情)
DispatcherServlet
DispatcherServlet这个类在写web.xml的时代使用框架时是要注册到servlet当中的,当servlet3.0出来之后javaEE支持动态增加servlet,因此springBoot里直接加个@Controller注解加个@XXXMapping注解然后main里面SpringApplication.run(XXX.class,args)无脑启动就能直接开启一个服务。如果要自定义一个参数处理器就要看下DispatcherServlet这个类弄了什么幺蛾子。
打开源码,他这里意思是说 用Servlet 3.0+,能支持程序里注册Servlet实例。大概是这个样子,英语渣见谅
然后看到类里面有个 doService方法,肯定是继承上一家的,不用管那么多,反正请求来了他会调这个方法。
doService方法里面调用了一个doDispatch方法
传送到doDispatch方法定义,里面调用了getHandler方法,这里面取出一个handler,这里的handler指的就是我们写了 @RequestMapping("/helloWorld")这种方法,可能也有一些别的handler,比如它里面预定义的"/error",他这里应该是解析地址取出跟请求相对应的handler,这里handlers应该是在启动的时候就准备好了的(也许是在别的时候),存在一个Map容器里。后面调用了getHandlerAdapter方法,取出一个Adapter(适配器)
跳到getHandlerAdapter方法定义for循环,看哪个adapter.supports(handler)返回的是true就选用哪个adapter,这里一定要选中一个adapter,不然程序报错。为了看到取出的是什么Adapter,我在这里打了一个断点,用Postman请求一下看一下选中的适配器,可以看到选中的是RequestMappingHandlerAdapter类
RequestMappingHandlerAdapter
RequestMappingHandlerAdapter类里面定义了参数解析器集合,还有返回值处理,我们这里关注解析器。看HandlerMethodArgumentResolverComposite这个类,这里用了组合模式,具体实现委托这个类去做。
这个类也是有相应的getArgumentResolver(取出解析器)方法,跟前面的getHandler和getHandlerAdapter是一个尿性,这里不再提。可以看到解析器的类是用 HandlerMethodArgumentResolver 表示的。那么自定义请求参数解析器就好办了,写一个符合这样的类
一般我们写一个请求:
/**
* User {@link RestController}
*
* @author : guanzheng
* @since : 2018-12-17 16:35
**/
@RestController
public class UserRestController {
@RequestMapping(value = "/getUser", method = {
RequestMethod.GET, RequestMethod.POST})
public String getUserInfo(@RequestBody Person person) {
return "name is: " + person.getName() + ",age is " + person.getAge();
}
}
然后 Person定义
/**
* @author : guanzheng
* @since : 2018-12-21 16:07
**/
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
用Postman请求,在Body里raw里面填写json数据,返回如下图
回到HandlerMethodArgumentResolver类
按Ctrl/Command + H查看继承链,打了@RequestBody的注解的方法参数是RequestResponseBodyMethodProcessor类支持的处理。
RequestResponseBodyMethodProcessor
打开RequestResponseBodyMethodProcessor
这个@Override
supportsParameter(MethodParameter parameter)
方法体写了只要参数标注了@RequestBody注解就能处理,说明请求参数前标注@RequestBody是这个类来处理的
RequestResponseBodyMethodProcessor这个类应该能处理大部分Body,看下他的支持
有熟悉的json,xml,还有ByteArray,Buffer等不知道干嘛用的233
自定义解析器
接下来可以模仿他写一个自定义的解析器,比如解析Properties格式,可以继续使用RequestResponseBodyMethodProcessor类实现HttpMessageConverter接口,也可以重新实现HandlerMethodArgumentResolver接口。我选择重新实现HandlerMethodArgumentResolver接口。
模仿@RequestBody这种写法,新建一个bind.annotation的包,在bind.annotation包下新建一个RequestPropertiesBody 注解类
package com.my.web.bind.annotation;
import java.lang.annotation.*;
/**
* @author : guanzheng
* @since : 2018-12-24 11:58
**/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPropertiesBody {
boolean required() default true;
}
不要问我为什么这样分,我是抄的spring的。。。
再建一个method.annotation包,包下面新建一个RequestParamMethodPropertiesBodyArgumentConvertObjectResolver类,并继承HandlerMethodArgumentResolver接口,当然,包名和类名都是借鉴(抄)的spring的写法。
supportsParameter方法的实现决定了spring是否选用这个类作为参数解析器,因此这个方法比较重要。配合前面写的RequestPropertiesBody 注解,这里只要方法参数前面加了RequestPropertiesBody 注解就支持解析,跟RequestResponseBodyMethodProcessor类的supportsParameter的方法基本一样的实现。resolveArgument方法是返回添加了RequestPropertiesBody参数的实体
/**
* @author : guanzheng
* @since : 2018-12-24 11:56
**/
public class RequestParamMethodPropertiesBodyArgumentConvertObjectResolver
implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestPropertiesBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
return null;
}
先获取类的Class,他提供了MethodParameter 参数,可以用parameter.getParameterType();获取
Class<?> parameterType = parameter.getParameterType();
再装载Properties类,NativeWebRequest 参数可以获取request,写一个getProperties方法
private Properties getProperties(NativeWebRequest webRequest) throws IOException {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Properties properties = new Properties();
if (request != null) {
// Servlet Request API
String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
// 获取 Content-Type 请求头
MediaType mediaType = MediaType.parseMediaType(contentType);
Charset charset = mediaType.getCharset();
// 当 charset 为空时,就使用UTF-8
charset = charset == null ? StandardCharsets.UTF_8 : charset;
// 字节流
InputStream inputStream = request.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream, charset);
// 加载字符流成为 Properties 对象
properties.load(reader);
inputStream.close();
} else {
throw new IllegalArgumentException();
}
return properties;
}
有了Properties,就可以取出相应的属性。把Properties的属性值写到对象相应的字段里,为了看对象有那些字段,用反射获取
//FieldUtil所在包是 sun.reflect.misc.FieldUtil
Field[] fields = FieldUtil.getDeclaredFields(parameterType);
把要返回的对象准备好
//ClassUtils 所在的类是 org.springframework.objenesis.instantiator.util.ClassUtils
Object result = ClassUtils.newInstance(parameterType);
获取对象的setter方法,没有写getter和setter的类我是不管的,谁让不按照代码规范来写的 (=゚ω゚)傲娇脸
private Method getSetterMethod(