第三十六章:基于SpringBoot架構(gòu)重寫SpringMVC請求參數(shù)裝載

在國內(nèi)企業(yè)開發(fā)項目中大多數(shù)都已經(jīng)偏向Spring家族式的開發(fā)風(fēng)格窒舟,在前幾年國內(nèi)項目都是以Structs2作為Web開發(fā)的主導(dǎo),不過由于近幾年發(fā)生的事情確實讓開發(fā)者對它失去了以往的信心映跟。與此同時Spring家族發(fā)布了SpringMVC,而且完美的整合Spring來開發(fā)企業(yè)級大型Web項目扬虚。它有著比Structs2更強大的技術(shù)支持以及更靈活的自定義配置努隙,接下來我們就看看本章的內(nèi)容,我們自定義實現(xiàn)SpringMVC參數(shù)綁定規(guī)則孔轴,根據(jù)業(yè)務(wù)定制參數(shù)裝載實現(xiàn)方式剃法。

免費教程專題

恒宇少年在博客整理三套免費學(xué)習(xí)教程專題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會在專題內(nèi)陸續(xù)填充贷洲,希望可以幫助大家解惑更多知識點收厨。

本章目標(biāo)

根據(jù)項目定制SpringMVC參數(shù)狀態(tài)并了解SpringMVC的裝載過程以及實現(xiàn)方式。

SpringBoot 企業(yè)級核心技術(shù)學(xué)習(xí)專題


專題 專題名稱 專題描述
001 Spring Boot 核心技術(shù) 講解SpringBoot一些企業(yè)級層面的核心組件
002 Spring Boot 核心技術(shù)章節(jié)源碼 Spring Boot 核心技術(shù)簡書每一篇文章碼云對應(yīng)源碼
003 Spring Cloud 核心技術(shù) 對Spring Cloud核心技術(shù)全面講解
004 Spring Cloud 核心技術(shù)章節(jié)源碼 Spring Cloud 核心技術(shù)簡書每一篇文章對應(yīng)源碼
005 QueryDSL 核心技術(shù) 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA
006 SpringDataJPA 核心技術(shù) 全面講解SpringDataJPA核心技術(shù)
007 SpringBoot核心技術(shù)學(xué)習(xí)目錄 SpringBoot系統(tǒng)的學(xué)習(xí)目錄优构,敬請關(guān)注點贊K腥!!

構(gòu)建項目

我們先來創(chuàng)建一個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相關(guān)的依賴支持,所以需要添加對應(yīng)的依賴彪腔,修改application.properties配置文件讓JSP生效侥锦,配置內(nèi)容如下所示:

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

相關(guān)JSP配置可以訪問第二章:SpringBoot與JSP間不可描述的秘密查看講解。

SpringMVC的參數(shù)裝載

在講解我們自定義參數(shù)裝載之前德挣,我們先來看看SpringMVC內(nèi)部為我們提供的參數(shù)裝載方式恭垦。

添加測試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/>
        學(xué)生姓名:<input type="text" name="name"/><br/><br/>
        學(xué)生年齡:<input type="text" name="age"/><br/><br/>
        <input type="submit"/>
    </form>
</body>
</html>

index.jsp內(nèi)添加了三個name的文本輸入框番挺,如果我們現(xiàn)在提交到后臺SpringMVC為默認為我們解析成一個數(shù)組,如果根據(jù)描述而言的來處理則是不合理的屯掖,當(dāng)然也可以使用各種手段完成字段參數(shù)的裝載玄柏,比如:為教師的name添加一個數(shù)組或者List集合進行接受,這種方式也是可以實現(xiàn)但不優(yōu)雅贴铜。

如果你們項目組有嚴格的開發(fā)規(guī)范要求粪摘,這種方式是不允許出現(xiàn)在Controller方法內(nèi)的。

那這個問題就讓人頭疼了阀湿,在之前我們使用Struct2的時候是可以根據(jù)指定的前綴赶熟,如:xxx.xxx來進行映射的,而SpringMVC并沒有提供這個支持陷嘴,不過它提供了自定義參數(shù)裝載的實現(xiàn)方法,那就沒有問題了间坐,我們可以手寫灾挨。

自定義的參數(shù)裝載

既然上面的代碼實現(xiàn)滿足不了我們的需求,那么我接下來就來重寫參數(shù)裝載竹宋。

創(chuàng)建ParameterModel注解

對于一直使用SpringMVC的朋友來說劳澄,應(yīng)該對@RequestParam很熟悉,而本章我們自定義的注解跟@RequestParam類似蜈七,主要目的也是標(biāo)識指定參數(shù)完成數(shù)據(jù)的綁定秒拔。下面我們先來看看該注解的源碼,如下所示:

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;

/**
 * 參數(shù)實體映射注解
 * 配置該注解的參數(shù)會使用 com.yuqiyu.chapter36.resovler.CustomerArgumentResolver類完成參數(shù)裝載
 * ========================
 * 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
{
}

該注解目前沒有添加任何一個屬性飒硅,這個也是可以根據(jù)項目的需求已經(jīng)業(yè)務(wù)邏輯進行相應(yīng)添加的砂缩,比如@RequestParam內(nèi)常用的屬性required作谚、defaultValue等屬性,由于我們本章內(nèi)容不需要自定義注解內(nèi)的屬性所以這里就不添加了庵芭。

該注解的作用域是在參數(shù)上@Target(value = ElementType.PARAMETER)妹懒,我們僅可以在方法參數(shù)上使用。

創(chuàng)建參數(shù)接受實體

我們可以回到上面看看index.jsp的內(nèi)容双吆,我們需要教師的基本信息以及學(xué)生的基本信息眨唬,那我們就為教師、以及學(xué)生創(chuàng)建實體(注意:這個實體可以是對應(yīng)數(shù)據(jù)庫內(nèi)的實體)

教師實體
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;
}

教師實體內(nèi)目前為了測試就添加一個跟頁面參數(shù)有關(guān)的字段好乐。

學(xué)生實體
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 {
    //學(xué)生姓名
    private String name;
    //年齡
    private String age;
}

學(xué)生實體添加與頁面參數(shù)對應(yīng)的字段匾竿,名稱、年齡蔚万。

編寫CustomerArgumentResolver參數(shù)裝載

在寫參數(shù)裝載之前岭妖,我們需要先了解下它的接口HandlerMethodArgumentResolver,該接口內(nèi)定義了兩個方法:

supportsParameter
boolean supportsParameter(MethodParameter var1);

supportsParameter方法顧名思義笛坦,是允許裝載的參數(shù)区转,也就是說方法返回true時才會指定裝載方法完成參數(shù)裝載。

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

resolveArgument方法是參數(shù)狀態(tài)的實現(xiàn)邏輯方法版扩,該方法返回的值會直接裝載到指定的參數(shù)上废离,有木有很神奇啊礁芦?下面我們就創(chuàng)建實現(xiàn)類來揭開這位神奇的姑娘的面紗吧蜻韭!

創(chuàng)建CustomerArgumentResolver實現(xiàn)接口HandlerMethodArgumentResolver內(nèi)的兩個方法,具體實現(xiàn)代碼如下所示:

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.*;

/**
 * 自定義參數(shù)裝載
 * ========================
 * 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時調(diào)用resolveArgument方法執(zhí)行邏輯
     * spring家族的架構(gòu)設(shè)計萬變不離其宗啊柿扣,在之前event & listener也是用到了同樣的方式
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(ParameterModel.class);
    }

    /**
     * 裝載參數(shù)
     * @param methodParameter 方法參數(shù)
     * @param modelAndViewContainer 返回視圖容器
     * @param nativeWebRequest 本次請求對象
     * @param webDataBinderFactory 數(shù)據(jù)綁定工廠
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument (
            MethodParameter methodParameter,
            ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest,
            WebDataBinderFactory webDataBinderFactory
    )
            throws Exception
    {
        String parameterName = methodParameter.getParameterName();
        logger.info("參數(shù)名稱:{}",parameterName);
        /**
         * 目標(biāo)返回對象
         * 如果Model存在該Attribute時從module內(nèi)獲取并設(shè)置為返回值
         * 如果Model不存在該Attribute則從request parameterMap內(nèi)獲取并設(shè)置為返回值
         */
        Object target = modelAndViewContainer.containsAttribute(parameterName) ?
                modelAndViewContainer.getModel().get(parameterName) : createAttribute(parameterName, methodParameter, webDataBinderFactory, nativeWebRequest);;

        /**
         * 返回內(nèi)容肖方,這里返回的內(nèi)容才是最終裝載到參數(shù)的值
         */
        return target;
    }

    /**
     * 根據(jù)參數(shù)attributeName獲取請求的值
     * @param attributeName 請求參數(shù)
     * @param parameter method 參數(shù)對象
     * @param binderFactory 數(shù)據(jù)綁定工廠
     * @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) {
            /**
             * 進行類型轉(zhuǎn)換
             * 檢查請求的類型與目標(biāo)參數(shù)類型是否可以進行轉(zhuǎn)換
             */
            Object attribute = convertAttributeToParameterValue(value, attributeName, parameter, binderFactory, request);
            /**
             * 如果存在轉(zhuǎn)換后的值,則返回
             */
            if (attribute != null) {
                return attribute;
            }
        }
        /**
         * 檢查request parameterMap 內(nèi)是否存在以attributeName作為前綴的數(shù)據(jù)
         * 如果存在則根據(jù)字段的類型來進行設(shè)置值未状、集合俯画、數(shù)組等
         */
        else
        {
            Object attribute = putParameters(parameter,request);
            if(attribute!=null)
            {
                return attribute;
            }
        }
        /**
         * 如果以上兩種條件不符合,直接返回初始化參數(shù)類型的空對象
         */
        return BeanUtils.instantiateClass(parameter.getParameterType());
    }

    /**
     * 將attribute的值轉(zhuǎn)換為parameter參數(shù)值類型
     * @param sourceValue 源請求值
     * @param attributeName 參數(shù)名
     * @param parameter 目標(biāo)參數(shù)對象
     * @param binderFactory 數(shù)據(jù)綁定工廠
     * @param request 請求對象
     * @return
     * @throws Exception
     */
    protected Object convertAttributeToParameterValue(String sourceValue,
                                                     String attributeName,
                                                     MethodParameter parameter,
                                                     WebDataBinderFactory binderFactory,
                                                     NativeWebRequest request) throws Exception {
        /**
         * 獲取類型轉(zhuǎn)換業(yè)務(wù)邏輯實現(xiàn)類
         */
        DataBinder binder = binderFactory.createBinder(request, null, attributeName);
        ConversionService conversionService = binder.getConversionService();
        if (conversionService != null) {
            /**
             * 源類型描述
             */
            TypeDescriptor source = TypeDescriptor.valueOf(String.class);
            /**
             * 根據(jù)目標(biāo)參數(shù)對象獲取目標(biāo)參數(shù)類型描述
             */
            TypeDescriptor target = new TypeDescriptor(parameter);
            /**
             * 驗證是否可以進行轉(zhuǎn)換
             */
            if (conversionService.canConvert(source, target)) {
                /**
                 * 返回轉(zhuǎn)換后的值
                 */
                return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
            }
        }
        return null;
    }

    /**
     * 從request parameterMap集合內(nèi)獲取attributeName的值
     * @param attributeName 參數(shù)名稱
     * @param request 請求對象
     * @return
     */
    protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
        /**
         * 獲取PathVariables參數(shù)集合
         */
        Map<String, String> variables = getUriTemplateVariables(request);
        /**
         * 如果PathVariables參數(shù)集合內(nèi)存在該attributeName
         * 直接返回相對應(yīng)的值
         */
        if (StringUtils.hasText(variables.get(attributeName))) {
            return variables.get(attributeName);
        }
        /**
         * 如果request parameterMap內(nèi)存在該attributeName
         * 直接返回相對應(yīng)的值
         */
        else if (StringUtils.hasText(request.getParameter(attributeName))) {
            return request.getParameter(attributeName);
        }
        //不存在時返回null
        else {
            return null;
        }
    }

    /**
     * 獲取指定前綴的參數(shù):包括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內(nèi)獲取該前綴的參數(shù)列表
         */
        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());
                    //如果下一個字符不是 數(shù)字 . _  則不可能是查詢 只是前綴類似
                    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集合內(nèi)獲取該前綴的參數(shù)列表
         */
        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());
                    //如果下一個字符不是 數(shù)字 . _  則不可能是查詢 只是前綴類似
                    if (illegalChar(ch)) {
                        continue;
                    }
                    result.put(name.substring(namePrefixLength + 1), request.getParameterValues(name));
                } else {
                    result.put(name, request.getParameterValues(name));
                }
            }
        }

        return result;
    }

    /**
     * 驗證參數(shù)前綴是否合法
     * @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內(nèi)獲取parameter前綴的所有參數(shù)
     * 并根據(jù)parameter的類型將對應(yīng)字段的值設(shè)置到parmaeter對象內(nèi)并返回
     * @param parameter
     * @param request
     * @return
     */
    protected Object putParameters(MethodParameter parameter,NativeWebRequest request)
    {
        /**
         * 根據(jù)請求參數(shù)類型初始化空對象
         */
        Object object = BeanUtils.instantiateClass(parameter.getParameterType());
        /**
         * 獲取指定前綴的請求參數(shù)集合
         */
        Map<String, String[]> parameters = getPrefixParameterMap(parameter.getParameterName(),request,true);
        Iterator<String> iterator = parameters.keySet().iterator();
        while(iterator.hasNext())
        {
            //字段名稱
            String fieldName = iterator.next();
            //請求參數(shù)值
            String[] parameterValue = parameters.get(fieldName);
            try {
                Field field = object.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

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

                /**
                 * List(ArrayList司草、LinkedList)類型
                 * 將數(shù)組類型的值轉(zhuǎn)換為List集合對象
                 */
                if(List.class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, Arrays.asList(parameterValue));
                }
                /**
                 *Object數(shù)組類型艰垂,直接將數(shù)組值設(shè)置為目標(biāo)字段的值
                 */
                else if(Object[].class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, parameterValue);
                }
                /**
                 * 單值時獲取數(shù)組索引為0的值
                 */
                else {
                    field.set(object, parameterValue[0]);
                }
            }
            catch (Exception e)
            {
                logger.error("Set Field:{} Value Error,In {}",fieldName,object.getClass().getName());
                continue;
            }
        }
        return object;
    }
}

上面我直接貼出了參數(shù)裝載的全部實現(xiàn)方法埋虹,下面我們就開始按照裝載的流程進行講解猜憎。

supportsParameter方法實現(xiàn)
 /**
     * 該方法返回true時調(diào)用resolveArgument方法執(zhí)行邏輯
     * spring家族的架構(gòu)設(shè)計萬變不離其宗啊,在之前event & listener也是用到了同樣的方式
     * @param methodParameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.hasParameterAnnotation(ParameterModel.class);
    }

我們只對配置了ParameterModel注解的參數(shù)進行裝載搔课。

resolveArgument方法實現(xiàn)
 /**
     * 裝載參數(shù)
     * @param methodParameter 方法參數(shù)
     * @param modelAndViewContainer 返回視圖容器
     * @param nativeWebRequest 本次請求對象
     * @param webDataBinderFactory 數(shù)據(jù)綁定工廠
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument (
            MethodParameter methodParameter,
            ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest,
            WebDataBinderFactory webDataBinderFactory
    )
            throws Exception
    {
        String parameterName = methodParameter.getParameterName();
        logger.info("參數(shù)名稱:{}",parameterName);
        /**
         * 目標(biāo)返回對象
         * 如果Model存在該Attribute時從module內(nèi)獲取并設(shè)置為返回值
         * 如果Model不存在該Attribute則從request parameterMap內(nèi)獲取并設(shè)置為返回值
         */
        Object target = modelAndViewContainer.containsAttribute(parameterName) ?
                modelAndViewContainer.getModel().get(parameterName) : createAttribute(parameterName, methodParameter, webDataBinderFactory, nativeWebRequest);;

        /**
         * 返回內(nèi)容胰柑,這里返回的內(nèi)容才是最終裝載到參數(shù)的值
         */
        return target;
    }

該方法作為裝載參數(shù)邏輯的入口,我們從MethodParameter對象內(nèi)獲取了參數(shù)的名稱,根據(jù)該名稱檢查Model內(nèi)是否存在該名稱的值柬讨,如果存在則直接使用并返回崩瓤,反則需要從ParameterMap內(nèi)獲取對應(yīng)該參數(shù)名稱的值返回。
我們下面主要看看從parameterMap獲取的方法實現(xiàn)

createAttribute方法實現(xiàn)
/**
     * 根據(jù)參數(shù)attributeName獲取請求的值
     * @param attributeName 請求參數(shù)
     * @param parameter method 參數(shù)對象
     * @param binderFactory 數(shù)據(jù)綁定工廠
     * @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) {
            /**
             * 進行類型轉(zhuǎn)換
             * 檢查請求的類型與目標(biāo)參數(shù)類型是否可以進行轉(zhuǎn)換
             */
            Object attribute = convertAttributeToParameterValue(value, attributeName, parameter, binderFactory, request);
            /**
             * 如果存在轉(zhuǎn)換后的值姐浮,則返回
             */
            if (attribute != null) {
                return attribute;
            }
        }
        /**
         * 檢查request parameterMap 內(nèi)是否存在以attributeName作為前綴的數(shù)據(jù)
         * 如果存在則根據(jù)字段的類型來進行設(shè)置值谷遂、集合、數(shù)組等
         */
        else
        {
            Object attribute = putParameters(parameter,request);
            if(attribute!=null)
            {
                return attribute;
            }
        }
        /**
         * 如果以上兩種條件不符合卖鲤,直接返回初始化參數(shù)類型的空對象
         */
        return BeanUtils.instantiateClass(parameter.getParameterType());
    }

該方法的邏輯存在兩個分支肾扰,首先通過調(diào)用getRequestValueForAttribute方法從parameterMap內(nèi)獲取指定屬性名的請求值,如果存在值則需要驗證是否可以完成類型轉(zhuǎn)換蛋逾,驗證通過后則直接返回值集晚。

上面的部分其實是SpringMVC原有的參數(shù)裝載的流程,下面我們就來根據(jù)需求個性化定制裝載邏輯区匣。

putParameters方法實現(xiàn)

該方法實現(xiàn)了自定義規(guī)則xxx.xxx方式進行參數(shù)裝載的邏輯偷拔,我們在前臺傳遞參數(shù)的時候只需要將Controller內(nèi)方法參數(shù)名稱作為傳遞的前綴即可,如:teacher.name亏钩、student.name莲绰。

/**
     * 從request內(nèi)獲取parameter前綴的所有參數(shù)
     * 并根據(jù)parameter的類型將對應(yīng)字段的值設(shè)置到parmaeter對象內(nèi)并返回
     * @param parameter
     * @param request
     * @return
     */
    protected Object putParameters(MethodParameter parameter,NativeWebRequest request)
    {
        /**
         * 根據(jù)請求參數(shù)類型初始化空對象
         */
        Object object = BeanUtils.instantiateClass(parameter.getParameterType());
        /**
         * 獲取指定前綴的請求參數(shù)集合
         */
        Map<String, String[]> parameters = getPrefixParameterMap(parameter.getParameterName(),request,true);
        Iterator<String> iterator = parameters.keySet().iterator();
        while(iterator.hasNext())
        {
            //字段名稱
            String fieldName = iterator.next();
            //請求參數(shù)值
            String[] parameterValue = parameters.get(fieldName);
            try {
                Field field = object.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);

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

                /**
                 * List(ArrayList、LinkedList)類型
                 * 將數(shù)組類型的值轉(zhuǎn)換為List集合對象
                 */
                if(List.class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, Arrays.asList(parameterValue));
                }
                /**
                 *Object數(shù)組類型姑丑,直接將數(shù)組值設(shè)置為目標(biāo)字段的值
                 */
                else if(Object[].class.isAssignableFrom(fieldTargetType))
                {
                    field.set(object, parameterValue);
                }
                /**
                 * 單值時獲取數(shù)組索引為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獲取PathVariables栅哀、ParameterMap內(nèi)前綴為MethodParameter名稱的請求參數(shù)列表震肮,遍歷列表對應(yīng)設(shè)置
object內(nèi)的字段,用于完成參數(shù)的裝載留拾,在裝載過程中戳晌,我這里分別根據(jù)CollectionList痴柔、Array沦偎、Single類型進行了處理(注意:這里需要根據(jù)項目需求進行調(diào)整裝載類型)。

配置Spring托管CustomerArgumentResolver

我們將CustomerArgumentResolver托管交付給Spring框架咳蔚,我們來創(chuàng)建一個名叫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
{
    /**
     * 添加參數(shù)裝載
     * @param argumentResolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        /**
         * 將自定義的參數(shù)裝載添加到spring內(nèi)托管
         */
        argumentResolvers.add(new CustomerArgumentResolver());
    }

    /**
     * 配置靜態(tài)請求視圖映射
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("index");
    }
}

我們重寫了WebMvcConfigurerAdapter抽象類內(nèi)的兩個方法addArgumentResolvers屹篓、addViewControllers,其中addArgumentResolvers方法完成了參數(shù)裝載的托管匙奴。

addViewControllers配置了視圖控制器映射堆巧,這樣我們訪問/index地址就可以請求到index.jsp頁面。

創(chuàng)建測試控制器

創(chuàng)建名為IndexController的控制器并添加數(shù)據(jù)提交的方法,具體代碼如下所示:

/**
 * 表單提交控制器
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/16
 * Time:22:26
 * 碼云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class IndexController
{
    /**
     * 裝載參數(shù)測試
     * @return
     */
    @RequestMapping(value = "/submit")
    public String resolver(@ParameterModel TeacherEntity teacher, @ParameterModel StudentEntity student)
    {
        return "教師名稱:"+ JSON.toJSON(teacher.getName()) +"谍肤,學(xué)生名稱:"+student.getName()+"啦租,學(xué)生年齡:"+student.getAge();
    }
}

可以看到我們?yōu)?code>TeacherEntity、StudentEntity分別添加了注解@ParameterModel荒揣,也就證明了這兩個實體需要使用我們的CustomerArgumentResolver完成參數(shù)裝載。

運行測試

在運行測試之前系任,我們需要修改下index.jsp內(nèi)的參數(shù)映射前綴恳蹲,修改后代碼如下所示:

<form method="post" action="/submit">
        教師姓名:<input type="text" name="teacher.name"/><br/><br/>
        學(xué)生姓名:<input type="text" name="student.name"/><br/><br/>
        學(xué)生年齡:<input type="text" name="student.age"/><br/><br/>
        <input type="submit"/>
    </form>

測試單值裝載

我們?yōu)榻處熋Q、學(xué)生名稱崎逃、學(xué)生年齡都分別添加了前綴掷倔,下面我們來啟動項目,訪問項目根下路徑/index婚脱,如下圖1所示:

圖1

在上圖1中輸入了部分請求參數(shù)今魔,點擊“提交”按鈕查看界面輸出的效果,圖下所示:

教師名稱:王老師障贸,學(xué)生名稱:張小跑错森,學(xué)生年齡:23

可以看到參數(shù)已經(jīng)被正確的裝載到了不同的實體類內(nèi)。

上面的例子只是針對實體內(nèi)的單個值的裝載篮洁,下面我們來測試下List類型的值是否可以裝載涩维?

測試List裝載

我們先來修改下教師實體內(nèi)的名稱為List,字段名稱不需要變動袁波,如下所示:

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

再來修改下index.jsp輸入框瓦阐,如下所示:

    <form method="post" action="/submit">
        語文老師姓名:<input type="text" name="teacher.name"/><br/><br/>
        數(shù)學(xué)教師姓名:<input type="text" name="teacher.name"/><br/><br/>
        學(xué)生姓名:<input type="text" name="student.name"/><br/><br/>
        學(xué)生年齡:<input type="text" name="student.age"/><br/><br/>
        <input type="submit"/>
    </form>

在上代碼中我們添加了兩位老師的名稱,接下來重啟項目篷牌,再次提交測試睡蟋,查看是不是我們想要的效果?
修改后的界面如下圖2所示:

圖2

界面輸出內(nèi)容如下所示:

教師名稱:["王老師","李老師"]枷颊,學(xué)生名稱:張小跑戳杀,學(xué)生年齡:24

可以看到我們已經(jīng)拿到了兩位老師的名稱该面,這也證明了我們的CustomerArgumentResolver是可以完成List的映射裝載的。

總結(jié)

以上內(nèi)容就是本章的全部講解內(nèi)容信卡,本章簡單實現(xiàn)了參數(shù)的狀態(tài)隔缀,其中還有很多細節(jié)性質(zhì)的邏輯,如:@Valid注解的生效傍菇、文件的上傳等猾瘸。在下一章我們會降到如果通過參數(shù)裝載實現(xiàn)接口服務(wù)的安全認證。

本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter

作者個人 博客
使用開源框架 ApiBoot 助你成為Api接口服務(wù)架構(gòu)師

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丢习,一起剝皮案震驚了整個濱河市牵触,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛领,老刑警劉巖荒吏,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異渊鞋,居然都是意外死亡绰更,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門锡宋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來儡湾,“玉大人,你說我怎么就攤上這事执俩⌒炷疲” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵役首,是天一觀的道長尝丐。 經(jīng)常有香客問我,道長衡奥,這世上最難降的妖魔是什么爹袁? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮矮固,結(jié)果婚禮上失息,老公的妹妹穿的比我還像新娘。我一直安慰自己档址,他們只是感情好盹兢,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著守伸,像睡著了一般绎秒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尼摹,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天替裆,我揣著相機與錄音校辩,去河邊找鬼。 笑死辆童,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惠赫。 我是一名探鬼主播把鉴,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼儿咱!你這毒婦竟也來了庭砍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤混埠,失蹤者是張志新(化名)和其女友劉穎怠缸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钳宪,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡揭北,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吏颖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搔体。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖半醉,靈堂內(nèi)的尸體忽然破棺而出疚俱,到底是詐尸還是另有隱情,我是刑警寧澤缩多,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布呆奕,位于F島的核電站,受9級特大地震影響衬吆,放射性物質(zhì)發(fā)生泄漏梁钾。R本人自食惡果不足惜瓮顽,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一务漩、第九天 我趴在偏房一處隱蔽的房頂上張望扼脐。 院中可真熱鬧房匆,春花似錦匀们、人聲如沸弟疆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灯谣。三九已至潜秋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胎许,已是汗流浹背峻呛。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工罗售, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钩述。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓寨躁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親牙勘。 傳聞我的和親對象是個殘疾皇子职恳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容