Spring MVC 自定义参数解析器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanzhengyinqin/article/details/85255840

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实例。大概是这个样子,英语渣见谅
DispatcherServlet说明

然后看到类里面有个 doService方法,肯定是继承上一家的,不用管那么多,反正请求来了他会调这个方法。
doService方法里面调用了一个doDispatch方法
DispatcherServlet的doService方法

传送到doDispatch方法定义,里面调用了getHandler方法,这里面取出一个handler,这里的handler指的就是我们写了 @RequestMapping("/helloWorld")这种方法,可能也有一些别的handler,比如它里面预定义的"/error",他这里应该是解析地址取出跟请求相对应的handler,这里handlers应该是在启动的时候就准备好了的(也许是在别的时候),存在一个Map容器里。后面调用了getHandlerAdapter方法,取出一个Adapter(适配器)
doDispatch方法定义

跳到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(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);
        }

RequestParamMethodPropertiesBodyArgumentConvertObjectResolver

最后这是整个实现类,大概就是这个样子

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();
    }

启动,Postman定义PropertiesBody
在这里插入图片描述

成功
在这里插入图片描述

OK,这就是 SpringMVC自定义请求参数解析器

Maven信息

下面是demo的 maven 信息

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.baxcall</groupId>
    <artifactId>com.baxcall.handlermethodarg.demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </pluginRepository>
    </pluginRepositories>

</project>

(完)

猜你喜欢

转载自blog.csdn.net/guanzhengyinqin/article/details/85255840