spring mvc 是一个非常好用的框架,傻瓜式的操作,深受人们的喜爱。
框架自带的功能基本能够满足市面上百分之八九十的需求,但总有一些奇葩的需求,这时候我们就要自定义一些功能(搞事情)
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类里面定义了参数解析器集合,还有返回值处理,我们这里关注解析器。看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
这个@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;
}
再建一个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(Class<?> clazz,
Field field) throws NoSuchMethodException {
return clazz.getDeclaredMethod("set" + toUpperCaseFirstOne(field.getName()), field.getType());
}
//把一串字符串的第一个字母转大写
private String toUpperCaseFirstOne(String fieldName) {
if (Character.isUpperCase(fieldName.charAt(0))) return fieldName;
else return String.valueOf(Character.toUpperCase(fieldName.charAt(0))) + fieldName.substring(1);
}
————————————————————————————————————————————————
获取基本数据类型的实际值,在把属性值写入字段前要考虑下基本数据类型。
无非是把 字符串 "1"转成int的1,字符串"1.2"转成double的1.2,把字符串"true"转成boolean的true等,这里就不细说
//获取实际值
private Object getArg(Class<?> primitiveClass, Object value) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object result = null;
if (isPrimitive(primitiveClass)) {
String primitiveName = primitiveClass.getSimpleName();
if ("char".equals(primitiveName)) return value;
String firstUpperprimitiveName = toUpperCaseFirstOne(primitiveName);
Class<?> encapsulationClass = "int".equals(primitiveName) ? Class.forName("java.lang." + firstUpperprimitiveName + "eger") : Class.forName("java.lang." + firstUpperprimitiveName);
result = getParseMethod(encapsulationClass, firstUpperprimitiveName).invoke(null, value);
}
return result;
}
private boolean isPrimitive(Class<?> clazz) {
return Integer.class.equals(clazz) ||
Double.class.equals(clazz) ||
Float.class.equals(clazz) ||
Boolean.class.equals(clazz) ||
Long.class.equals(clazz) ||
Byte.class.equals(clazz) ||
Short.class.equals(clazz) ||
clazz.isPrimitive();
}
//获取各个基本数据类型封装类的 parseXXX 方法
private Method getParseMethod(Class<?> encapsulationClass, String firstUpperprimitiveName) throws NoSuchMethodException {
String methodName = "parse" + firstUpperprimitiveName;
return encapsulationClass.getDeclaredMethod(methodName, String.class);
}
查找各个字段并把属性写入字段
for (Field field : fields) {
arg = properties.getProperty(field.getName());
if(arg==null)continue;
if (isPrimitive(field.getType())) {
Class<?> parType = field.getType();
arg = getArg(parType, arg);
}
Method setter = getSetterMethod(parameterType, field);
if (setter != null) setter.invoke(result, arg);
}
最后这是整个实现类,大概就是这个样子
package com.my.web.method.annotation;
import com.my.web.bind.annotation.RequestPropertiesBody;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.objenesis.instantiator.util.ClassUtils;
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 sun.reflect.misc.FieldUtil;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Properties;
/**
* @author : guanzheng
* @since : 2018-12-24 11:56
**/
public class RequestParamMethodPropertiesBodyArgumentConvertObjectResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestPropertiesBody.class);
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> parameterType = parameter.getParameterType();
Properties properties = getProperties(webRequest);
Object arg = null;
//如果是基本数据类型直接返回
if (isPrimitive(parameterType)) {
return getArg(parameterType, properties.getProperty(Objects.requireNonNull(parameter.getParameterName())));
}
//如果是String直接返回
if (String.class.equals(parameterType)) {
return properties.getProperty(Objects.requireNonNull(parameter.getParameterName()));
}
//获取字段
Field[] fields = FieldUtil.getDeclaredFields(parameterType);
//实例化对象
Object result = ClassUtils.newInstance(parameterType);
//往对象写入值
for (Field field : fields) {
arg = properties.getProperty(field.getName());
if(arg==null)continue;
if (isPrimitive(field.getType())) {
Class<?> parType = field.getType();
arg = getArg(parType, arg);
}
Method setter = getSetterMethod(parameterType, field);
if (setter != null) setter.invoke(result, arg);
}
return result;
}
private Method getSetterMethod(Class<?> clazz, Field field) throws NoSuchMethodException {
return clazz.getDeclaredMethod("set" + toUpperCaseFirstOne(field.getName()), field.getType());
}
private boolean isPrimitive(Class<?> clazz) {
return Integer.class.equals(clazz) ||
Double.class.equals(clazz) ||
Float.class.equals(clazz) ||
Boolean.class.equals(clazz) ||
Long.class.equals(clazz) ||
Byte.class.equals(clazz) ||
Short.class.equals(clazz) ||
clazz.isPrimitive();
}
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;
}
private Object getArg(Class<?> primitiveClass, Object value) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object result = null;
if (isPrimitive(primitiveClass)) {
String primitiveName = primitiveClass.getSimpleName();
if ("char".equals(primitiveName)) return value;
String firstUpperprimitiveName = toUpperCaseFirstOne(primitiveName);
Class<?> encapsulationClass = "int".equals(primitiveName) ? Class.forName("java.lang." + firstUpperprimitiveName + "eger") : Class.forName("java.lang." + firstUpperprimitiveName);
result = getParseMethod(encapsulationClass, firstUpperprimitiveName).invoke(null, value);
}
return result;
}
private Method getParseMethod(Class<?> encapsulationClass, String firstUpperprimitiveName) throws NoSuchMethodException {
String methodName = "parse" + firstUpperprimitiveName;
return encapsulationClass.getDeclaredMethod(methodName, String.class);
}
private String toUpperCaseFirstOne(String fieldName) {
if (Character.isUpperCase(fieldName.charAt(0))) return fieldName;
else return String.valueOf(Character.toUpperCase(fieldName.charAt(0))) + fieldName.substring(1);
}
}
完了之后把这个类配置进去,新建一个config包,在config包里新建MyWebMvcConfigurer并实现WebMvcConfigurer,打上@Configuration注解。注意,要把自定义的参数解析器放在首位,这很重要
/**
* REST {@link WebMvcConfigurer} 实现
*
* @author : guanzheng
* @since : 2018-12-17 18:06
**/
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
// 把自己写的参数解析器放在首位,提高命中率
if(resolvers.isEmpty()) resolvers.add(0,new RequestParamMethodPropertiesBodyArgumentConvertObjectResolver());
else resolvers.set(0,new RequestParamMethodPropertiesBodyArgumentConvertObjectResolver());
}
写一个Controller,加上刚写的@RequestPropertiesBody注解
/**
* User {@link RestController}
*
* @author : guanzheng
* @since : 2018-12-17 16:35
**/
@RestController
public class UserRestController {
@RequestMapping(value = "/test3", method = RequestMethod.POST)
//加上 @RequestPropertiesBody 自定义解析器
public String getTest3(@RequestPropertiesBody Person person) {
return "name is: " + person.getName() + ",age is " + person.getAge();
}
}
用Postman请求,body写上Properties数据
打个断点看下结果,成功!
试试别的类,定义一个User类
package com.my.web.pojo;
/**
* @author : guanzheng
* @since : 2018-12-18:30
**/
public class User {
private Long id;
private String name;
private int sex;
private String email;
private String address;
private double height;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
Controller,加上@RequestPropertiesBody注解
@RequestMapping(value = "/test4", method = RequestMethod.POST)
//加上 @RequestPropertiesBody 自定义解析器
public String getTest4(@RequestPropertiesBody User user) {
return "userId:"+user.getId()+"\n"+
"userName:"+user.getName()+"\n"+
"userSex:"+user.getSex()+"\n"+
"userEmail:"+user.getEmail()+"\n"+
"userAddress:"+user.getAddress()+"\n"+
"userHeight:"+user.getHeight();
}
OK,这就是 SpringMVC自定义请求参数解析器
下面是demo的 maven 信息
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.2.BUILD-SNAPSHOTversion>
<relativePath/>
parent>
<groupId>com.baxcallgroupId>
<artifactId>com.baxcall.handlermethodarg.demoartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
<repositories>
<repository>
<id>spring-snapshotsid>
<name>Spring Snapshotsname>
<url>https://repo.spring.io/snapshoturl>
<snapshots>
<enabled>trueenabled>
snapshots>
repository>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
repository>
repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshotsid>
<name>Spring Snapshotsname>
<url>https://repo.spring.io/snapshoturl>
<snapshots>
<enabled>trueenabled>
snapshots>
pluginRepository>
<pluginRepository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
pluginRepository>
pluginRepositories>
project>
(完)