第三十六章:基于SpringBoot架构重写SpringMVC请求参数装载

在国内企业开发项目中大多数都已经偏向Spring家族式的开发风格,在前几年国内项目都是以Structs2作为Web开发的主导,不过由于近几年发生的事情确实让开发者对它失去了以往的信心。与此同时Spring家族发布了SpringMVC,而且完美的整合Spring来开发企业级大型Web项目。它有着比Structs2更强大的技术支持以及更灵活的自定义配置,接下来我们就看看本章的内容,我们自定义实现SpringMVC参数绑定规则,根据业务定制参数装载实现方式。

免费教程专题

恒宇少年在博客整理三套免费学习教程专题,由于文章偏多特意添加了阅读指南,新文章以及之前的文章都会在专题内陆续填充,希望可以帮助大家解惑更多知识点。

本章目标

根据项目定制SpringMVC参数状态并了解SpringMVC的装载过程以及实现方式。

SpringBoot 企业级核心技术学习专题


专题 专题名称 专题描述
001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组件
002 Spring Boot 核心技术章节源码 Spring Boot 核心技术简书每一篇文章码云对应源码
003 Spring Cloud 核心技术 对Spring Cloud核心技术全面讲解
004 Spring Cloud 核心技术章节源码 Spring Cloud 核心技术简书每一篇文章对应源码
005 QueryDSL 核心技术 全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技术 全面讲解SpringDataJPA核心技术
007 SpringBoot核心技术学习目录 SpringBoot系统的学习目录,敬请关注点赞!!!

构建项目

我们先来创建一个SpringBoot项目,添加本章所需的依赖,pom.xml配置文件如下所示:

...//省略部分配置
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring boot tomcat jsp 支持开启 -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <!--servlet支持开启-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>
        <!-- jstl 支持开启 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>
        <!--lombok支持-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--fastjson支持-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.38</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
...//省略部分配置

本章需要JSP相关的依赖支持,所以需要添加对应的依赖,修改application.properties配置文件让JSP生效,配置内容如下所示:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

相关JSP配置可以访问第二章:SpringBoot与JSP间不可描述的秘密查看讲解。

SpringMVC的参数装载

在讲解我们自定义参数装载之前,我们先来看看SpringMVC内部为我们提供的参数装载方式。

添加测试JSP

我们首先来添加一个测试的jsp页面,页面上添加一些输入元素,代码如下所示:

<%--
  Created by IntelliJ IDEA.
  User: hengyu
  Date: 2017/9/17
  Time: 10:33
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form method="post" action="/submit">
        教师姓名:<input type="text" name="name"/><br/><br/>
        学生姓名:<input type="text" name="name"/><br/><br/>
        学生年龄:<input type="text" name="age"/><br/><br/>
        <input type="submit"/>
    </form>
</body>
</html>

index.jsp内添加了三个name的文本输入框,如果我们现在提交到后台SpringMVC为默认为我们解析成一个数组,如果根据描述而言的来处理则是不合理的,当然也可以使用各种手段完成字段参数的装载,比如:为教师的name添加一个数组或者List集合进行接受,这种方式也是可以实现但不优雅

如果你们项目组有严格的开发规范要求,这种方式是不允许出现在Controller方法内的。

那这个问题就让人头疼了,在之前我们使用Struct2的时候是可以根据指定的前缀,如:xxx.xxx来进行映射的,而SpringMVC并没有提供这个支持,不过它提供了自定义参数装载的实现方法,那就没有问题了,我们可以手写。

自定义的参数装载

既然上面的代码实现满足不了我们的需求,那么我接下来就来重写参数装载。

创建ParameterModel注解

对于一直使用SpringMVC的朋友来说,应该对@RequestParam很熟悉,而本章我们自定义的注解跟@RequestParam类似,主要目的也是标识指定参数完成数据的绑定。下面我们先来看看该注解的源码,如下所示:

package com.yuqiyu.chapter36.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 参数实体映射注解
 * 配置该注解的参数会使用 com.yuqiyu.chapter36.resovler.CustomerArgumentResolver类完成参数装载
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/16
 * Time:22:19
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParameterModel
{
}

该注解目前没有添加任何一个属性,这个也是可以根据项目的需求已经业务逻辑进行相应添加的,比如@RequestParam内常用的属性requireddefaultValue等属性,由于我们本章内容不需要自定义注解内的属性所以这里就不添加了。

该注解的作用域是在参数上@Target(value = ElementType.PARAMETER),我们仅可以在方法参数上使用。

创建参数接受实体

我们可以回到上面看看index.jsp的内容,我们需要教师的基本信息以及学生的基本信息,那我们就为教师、以及学生创建实体(注意:这个实体可以是对应数据库内的实体)

教师实体
package com.yuqiyu.chapter36.bean;

import lombok.Data;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/17
 * Time:10:40
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class TeacherEntity {
    //教师姓名
    private String name;
}

教师实体内目前为了测试就添加一个跟页面参数有关的字段。

学生实体
package com.yuqiyu.chapter36.bean;

import lombok.Data;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/17
 * Time:10:41
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class StudentEntity {
    //学生姓名
    private String name;
    //年龄
    private String age;
}

学生实体添加与页面参数对应的字段,名称、年龄。

编写CustomerArgumentResolver参数装载

在写参数装载之前,我们需要先了解下它的接口HandlerMethodArgumentResolver,该接口内定义了两个方法:

supportsParameter
boolean supportsParameter(MethodParameter var1);

supportsParameter方法顾名思义,是允许装载的参数,也就是说方法返回true时才会指定装载方法完成参数装载。

resolveArgument
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;

resolveArgument方法是参数状态的实现逻辑方法,该方法返回的值会直接装载到指定的参数上,有木有很神奇啊?下面我们就创建实现类来揭开这位神奇的姑娘的面纱吧!

创建CustomerArgumentResolver实现接口HandlerMethodArgumentResolver内的两个方法,具体实现代码如下所示:

package com.yuqiyu.chapter36.resovler;

import com.yuqiyu.chapter36.annotation.ParameterModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 自定义参数装载
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/16
 * Time:22:11
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
public class CustomerArgumentResolver
    implements HandlerMethodArgumentResolver
{
    /**
     * 日志对象
     */
    private Logger logger = LoggerFactory.getLogger(CustomerArgumentResolver.class);
    /**
     * 该方法返回true时调用resolveArgument方法执行逻辑
     * spring家族的架构设计万变不离其宗啊,在之前event & listener也是用到了同样的方式
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(ParameterModel.class);
    }

    /**
     * 装载参数
     * @param methodParameter 方法参数
     * @param modelAndViewContainer 返回视图容器
     * @param nativeWebRequest 本次请求对象
     * @param webDataBinderFactory 数据绑定工厂
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument (
            MethodParameter methodParameter,
            ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest,
            WebDataBinderFactory webDataBinderFactory
    )
            throws Exception
    {
        String parameterName = methodParameter.getParameterName();
        logger.info("参数名称:{}",parameterName);
        /**
         * 目标返回对象
         * 如果Model存在该Attribute时从module内获取并设置为返回值
         * 如果Model不存在该Attribute则从request parameterMap内获取并设置为返回值
         */
        Object target = modelAndViewContainer.containsAttribute(parameterName) ?
                modelAndViewContainer.getModel().get(parameterName) : createAttribute(parameterName, methodParameter, webDataBinderFactory, nativeWebRequest);;

        /**
         * 返回内容,这里返回的内容才是最终装载到参数的值
         */
        return target;
    }

    /**
     * 根据参数attributeName获取请求的值
     * @param attributeName 请求参数
     * @param parameter method 参数对象
     * @param binderFactory 数据绑定工厂
     * @param request 请求对象
     * @return
     * @throws Exception
     */
    protected Object createAttribute(String attributeName, MethodParameter parameter,
                                     WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
        /**
         * 获取attributeName的值
         */
        String value = getRequestValueForAttribute(attributeName, request);

        /**
         * 如果存在值
         */
        if (value != null) {
            /**
             * 进行类型转换
             * 检查请求的类型与目标参数类型是否可以进行转换
             */
            Object attribute = convertAttributeToParameterValue(value, attributeName, parameter, binderFactory, request);
            /**
             * 如果存在转换后的值,则返回
             */
            if (attribute != null) {
                return attribute;
            }
        }
        /**
         * 检查request parameterMap 内是否存在以attributeName作为前缀的数据
         * 如果存在则根据字段的类型来进行设置值、集合、数组等
         */
        else
        {
            Object attribute = putParameters(parameter,request);
            if(attribute!=null)
            {
                return attribute;
            }
        }
        /**
         * 如果以上两种条件不符合,直接返回初始化参数类型的空对象
         */
        return BeanUtils.instantiateClass(parameter.getParameterType());
    }

    /**
     * 将attribute的值转换为parameter参数值类型
     * @param sourceValue 源请求值
     * @param attributeName 参数名
     * @param parameter 目标参数对象
     * @param binderFactory 数据绑定工厂
     * @param request 请求对象
     * @return
     * @throws Exception
     */
    protected Object convertAttributeToParameterValue(String sourceValue,
                                                     String attributeName,
                                                     MethodParameter parameter,
                                                     WebDataBinderFactory binderFactory,
                                                     NativeWebRequest request) throws Exception {
        /**
         * 获取类型转换业务逻辑实现类
         */
        DataBinder binder = binderFactory.createBinder(request, null, attributeName);
        ConversionService conversionService = binder.getConversionService();
        if (conversionService != null) {
            /**
             * 源类型描述
             */
            TypeDescriptor source = TypeDescriptor.valueOf(String.class);
            /**
             * 根据目标参数对象获取目标参数类型描述
             */
            TypeDescriptor target = new TypeDescriptor(parameter);
            /**
             * 验证是否可以进行转换
             */
            if (conversionService.canConvert(source, target)) {
                /**
                 * 返回转换后的值
                 */
                return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
            }
        }
        return null;
    }

    /**
     * 从request parameterMap集合内获取attributeName的值
     * @param attributeName 参数名称
     * @param request 请求对象
     * @return
     */
    protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
        /**
         * 获取PathVariables参数集合
         */
        Map<String, String> variables = getUriTemplateVariables(request);
        /**
         * 如果PathVariables参数集合内存在该attributeName
         * 直接返回相对应的值
         */
        if (StringUtils.hasText(variables.get(attributeName))) {
            return variables.get(attributeName);
        }
        /**
         * 如果request parameterMap内存在该attributeName
         * 直接返回相对应的值
         */
        else if (StringUtils.hasText(request.getParameter(attributeName))) {
            return request.getParameter(attributeName);
        }
        //不存在时返回null
        else {
            return null;
        }
    }

    /**
     * 获取指定前缀的参数:包括uri varaibles 和 parameters
     *
     * @param namePrefix
     * @param request
     * @return
     * @subPrefix 是否截取掉namePrefix的前缀
     */
    protected Map<String, String[]> getPrefixParameterMap(String namePrefix, NativeWebRequest request, boolean subPrefix) {
        Map<String, String[]> result = new HashMap();
        /**
         * 从PathVariables内获取该前缀的参数列表
         */
        Map<String, String> variables = getUriTemplateVariables(request);

        int namePrefixLength = namePrefix.length();
        for (String name : variables.keySet()) {
            if (name.startsWith(namePrefix)) {

                //page.pn  则截取 pn
                if (subPrefix) {
                    char ch = name.charAt(namePrefix.length());
                    //如果下一个字符不是 数字 . _  则不可能是查询 只是前缀类似
                    if (illegalChar(ch)) {
                        continue;
                    }
                    result.put(name.substring(namePrefixLength + 1), new String[]{variables.get(name)});
                } else {
                    result.put(name, new String[]{variables.get(name)});
                }
            }
        }

        /**
         * 从request parameterMap集合内获取该前缀的参数列表
         */
        Iterator<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasNext()) {
            String name = parameterNames.next();
            if (name.startsWith(namePrefix)) {
                //page.pn  则截取 pn
                if (subPrefix) {
                    char ch = name.charAt(namePrefix.length());
                    //如果下一个字符不是 数字 . _  则不可能是查询 只是前缀类似
                    if (illegalChar(ch)) {
                        continue;
                    }
                    result.put(name.substring(namePrefixLength + 1), request.getParameterValues(name));
                } else {
                    result.put(name, request.getParameterValues(name));
                }
            }
        }

        return result;
    }

    /**
     * 验证参数前缀是否合法
     * @param ch
     * @return
     */
    private boolean illegalChar(char ch) {
        return ch != '.' && ch != '_' && !(ch >= '0' && ch <= '9');
    }

    /**
     * 获取PathVariables集合
     * @param request 请求对象
     * @return
     */
    protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
        Map<String, String> variables =
                (Map<String, String>) request.getAttribute(
                        HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        return (variables != null) ? variables : Collections.emptyMap();
    }

    /**
     * 从request内获取parameter前缀的所有参数
     * 并根据parameter的类型将对应字段的值设置到parmaeter对象内并返回
     * @param parameter
     * @param request
     * @return
     */
    protected Object putParameters(MethodParameter parameter,NativeWebRequest request)
    {
        /**
         * 根据请求参数类型初始化空对象
         */
        Object object = BeanUtils.instantiateClass(parameter.getParameterType());
        /**
         * 获取指定前缀的请求参数集合
         */
        Map<String, String[]> parameters = getPrefixParameterMap(parameter.getParameterName(),request,true);
        Iterator<String> iterator = parameters.keySet().iterator();
        while(iterator.hasNext())
        {
            //字段名称
            String fieldName = iterator.next();
            //请求参数值
            String[] parameterValue = parameters.get(fieldName);
            try {
                Field field = object.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

                //字段的类型
                Class<?> fieldTargetType = field.getType();

                /**
                 * List(ArrayList、LinkedList)类型
                 * 将数组类型的值转换为List集合对象
                 */
                if(List.class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, Arrays.asList(parameterValue));
                }
                /**
                 *Object数组类型,直接将数组值设置为目标字段的值
                 */
                else if(Object[].class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, parameterValue);
                }
                /**
                 * 单值时获取数组索引为0的值
                 */
                else {
                    field.set(object, parameterValue[0]);
                }
            }
            catch (Exception e)
            {
                logger.error("Set Field:{} Value Error,In {}",fieldName,object.getClass().getName());
                continue;
            }
        }
        return object;
    }
}

上面我直接贴出了参数装载的全部实现方法,下面我们就开始按照装载的流程进行讲解。

supportsParameter方法实现
 /**
     * 该方法返回true时调用resolveArgument方法执行逻辑
     * spring家族的架构设计万变不离其宗啊,在之前event & listener也是用到了同样的方式
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(ParameterModel.class);
    }

我们只对配置了ParameterModel注解的参数进行装载。

resolveArgument方法实现
 /**
     * 装载参数
     * @param methodParameter 方法参数
     * @param modelAndViewContainer 返回视图容器
     * @param nativeWebRequest 本次请求对象
     * @param webDataBinderFactory 数据绑定工厂
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument (
            MethodParameter methodParameter,
            ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest,
            WebDataBinderFactory webDataBinderFactory
    )
            throws Exception
    {
        String parameterName = methodParameter.getParameterName();
        logger.info("参数名称:{}",parameterName);
        /**
         * 目标返回对象
         * 如果Model存在该Attribute时从module内获取并设置为返回值
         * 如果Model不存在该Attribute则从request parameterMap内获取并设置为返回值
         */
        Object target = modelAndViewContainer.containsAttribute(parameterName) ?
                modelAndViewContainer.getModel().get(parameterName) : createAttribute(parameterName, methodParameter, webDataBinderFactory, nativeWebRequest);;

        /**
         * 返回内容,这里返回的内容才是最终装载到参数的值
         */
        return target;
    }

该方法作为装载参数逻辑的入口,我们从MethodParameter对象内获取了参数的名称,根据该名称检查Model内是否存在该名称的值,如果存在则直接使用并返回,反则需要从ParameterMap内获取对应该参数名称的值返回。
我们下面主要看看从parameterMap获取的方法实现

createAttribute方法实现
/**
     * 根据参数attributeName获取请求的值
     * @param attributeName 请求参数
     * @param parameter method 参数对象
     * @param binderFactory 数据绑定工厂
     * @param request 请求对象
     * @return
     * @throws Exception
     */
    protected Object createAttribute(String attributeName, MethodParameter parameter,
                                     WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
        /**
         * 获取attributeName的值
         */
        String value = getRequestValueForAttribute(attributeName, request);

        /**
         * 如果存在值
         */
        if (value != null) {
            /**
             * 进行类型转换
             * 检查请求的类型与目标参数类型是否可以进行转换
             */
            Object attribute = convertAttributeToParameterValue(value, attributeName, parameter, binderFactory, request);
            /**
             * 如果存在转换后的值,则返回
             */
            if (attribute != null) {
                return attribute;
            }
        }
        /**
         * 检查request parameterMap 内是否存在以attributeName作为前缀的数据
         * 如果存在则根据字段的类型来进行设置值、集合、数组等
         */
        else
        {
            Object attribute = putParameters(parameter,request);
            if(attribute!=null)
            {
                return attribute;
            }
        }
        /**
         * 如果以上两种条件不符合,直接返回初始化参数类型的空对象
         */
        return BeanUtils.instantiateClass(parameter.getParameterType());
    }

该方法的逻辑存在两个分支,首先通过调用getRequestValueForAttribute方法从parameterMap内获取指定属性名的请求值,如果存在值则需要验证是否可以完成类型转换,验证通过后则直接返回值。

上面的部分其实是SpringMVC原有的参数装载的流程,下面我们就来根据需求个性化定制装载逻辑。

putParameters方法实现

该方法实现了自定义规则xxx.xxx方式进行参数装载的逻辑,我们在前台传递参数的时候只需要将Controller内方法参数名称作为传递的前缀即可,如:teacher.namestudent.name

/**
     * 从request内获取parameter前缀的所有参数
     * 并根据parameter的类型将对应字段的值设置到parmaeter对象内并返回
     * @param parameter
     * @param request
     * @return
     */
    protected Object putParameters(MethodParameter parameter,NativeWebRequest request)
    {
        /**
         * 根据请求参数类型初始化空对象
         */
        Object object = BeanUtils.instantiateClass(parameter.getParameterType());
        /**
         * 获取指定前缀的请求参数集合
         */
        Map<String, String[]> parameters = getPrefixParameterMap(parameter.getParameterName(),request,true);
        Iterator<String> iterator = parameters.keySet().iterator();
        while(iterator.hasNext())
        {
            //字段名称
            String fieldName = iterator.next();
            //请求参数值
            String[] parameterValue = parameters.get(fieldName);
            try {
                Field field = object.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

                //字段的类型
                Class<?> fieldTargetType = field.getType();

                /**
                 * List(ArrayList、LinkedList)类型
                 * 将数组类型的值转换为List集合对象
                 */
                if(List.class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, Arrays.asList(parameterValue));
                }
                /**
                 *Object数组类型,直接将数组值设置为目标字段的值
                 */
                else if(Object[].class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, parameterValue);
                }
                /**
                 * 单值时获取数组索引为0的值
                 */
                else {
                    field.set(object, parameterValue[0]);
                }
            }
            catch (Exception e)
            {
                logger.error("Set Field:{} Value Error,In {}",fieldName,object.getClass().getName());
                continue;
            }
        }
        return object;
    }

该方法首先实例化了一个MethodParameter类型的空对象,然后通过getPrefixParameterMap获取PathVariablesParameterMap内前缀为MethodParameter名称的请求参数列表,遍历列表对应设置
object内的字段,用于完成参数的装载,在装载过程中,我这里分别根据CollectionListArraySingle类型进行了处理(注意:这里需要根据项目需求进行调整装载类型)。

配置Spring托管CustomerArgumentResolver

我们将CustomerArgumentResolver托管交付给Spring框架,我们来创建一个名叫WebMvcConfiguration的配置类,该类继承抽象类WebMvcConfigurerAdapter,代码如下所示:

/**
 * springmvc 注解式配置类
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/16
 * Time:22:15
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Configuration
public class WebMvcConfiguration
    extends WebMvcConfigurerAdapter
{
    /**
     * 添加参数装载
     * @param argumentResolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        /**
         * 将自定义的参数装载添加到spring内托管
         */
        argumentResolvers.add(new CustomerArgumentResolver());
    }

    /**
     * 配置静态请求视图映射
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("index");
    }
}

我们重写了WebMvcConfigurerAdapter抽象类内的两个方法addArgumentResolversaddViewControllers,其中addArgumentResolvers方法完成了参数装载的托管。

addViewControllers配置了视图控制器映射,这样我们访问/index地址就可以请求到index.jsp页面。

创建测试控制器

创建名为IndexController的控制器并添加数据提交的方法,具体代码如下所示:

/**
 * 表单提交控制器
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/16
 * Time:22:26
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class IndexController
{
    /**
     * 装载参数测试
     * @return
     */
    @RequestMapping(value = "/submit")
    public String resolver(@ParameterModel TeacherEntity teacher, @ParameterModel StudentEntity student)
    {
        return "教师名称:"+ JSON.toJSON(teacher.getName()) +",学生名称:"+student.getName()+",学生年龄:"+student.getAge();
    }
}

可以看到我们为TeacherEntityStudentEntity分别添加了注解@ParameterModel,也就证明了这两个实体需要使用我们的CustomerArgumentResolver完成参数装载。

运行测试

在运行测试之前,我们需要修改下index.jsp内的参数映射前缀,修改后代码如下所示:

<form method="post" action="/submit">
        教师姓名:<input type="text" name="teacher.name"/><br/><br/>
        学生姓名:<input type="text" name="student.name"/><br/><br/>
        学生年龄:<input type="text" name="student.age"/><br/><br/>
        <input type="submit"/>
    </form>

测试单值装载

我们为教师名称、学生名称、学生年龄都分别添加了前缀,下面我们来启动项目,访问项目根下路径/index,如下图1所示:

图1

在上图1中输入了部分请求参数,点击“提交”按钮查看界面输出的效果,图下所示:

教师名称:王老师,学生名称:张小跑,学生年龄:23

可以看到参数已经被正确的装载到了不同的实体类内。

上面的例子只是针对实体内的单个值的装载,下面我们来测试下List类型的值是否可以装载?

测试List装载

我们先来修改下教师实体内的名称为List,字段名称不需要变动,如下所示:

//教师姓名
private List<String> name;

再来修改下index.jsp输入框,如下所示:

    <form method="post" action="/submit">
        语文老师姓名:<input type="text" name="teacher.name"/><br/><br/>
        数学教师姓名:<input type="text" name="teacher.name"/><br/><br/>
        学生姓名:<input type="text" name="student.name"/><br/><br/>
        学生年龄:<input type="text" name="student.age"/><br/><br/>
        <input type="submit"/>
    </form>

在上代码中我们添加了两位老师的名称,接下来重启项目,再次提交测试,查看是不是我们想要的效果?
修改后的界面如下图2所示:

图2

界面输出内容如下所示:

教师名称:["王老师","李老师"],学生名称:张小跑,学生年龄:24

可以看到我们已经拿到了两位老师的名称,这也证明了我们的CustomerArgumentResolver是可以完成List的映射装载的。

总结

以上内容就是本章的全部讲解内容,本章简单实现了参数的状态,其中还有很多细节性质的逻辑,如:@Valid注解的生效、文件的上传等。在下一章我们会降到如果通过参数装载实现接口服务的安全认证。

本章代码已经上传到码云:
SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter

作者个人 博客
使用开源框架 ApiBoot 助你成为Api接口服务架构师

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容