在國內(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ù)Collection
、List
痴柔、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中輸入了部分請求參數(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所示:
界面輸出內(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