摘要:本章目標(biāo)根據(jù)項(xiàng)目定制參數(shù)狀態(tài)并了解的裝載過(guò)程以及實(shí)現(xiàn)方式。創(chuàng)建測(cè)試控制器創(chuàng)建名為的控制器并添加數(shù)據(jù)提交的方法,具體代碼如下所示表單提交控制器恒宇少年碼云裝載參數(shù)測(cè)試教師名稱(chēng),
在國(guó)內(nèi)企業(yè)開(kāi)發(fā)項(xiàng)目中大多數(shù)都已經(jīng)偏向Spring家族式的開(kāi)發(fā)風(fēng)格,在前幾年國(guó)內(nèi)項(xiàng)目都是以Structs2作為Web開(kāi)發(fā)的主導(dǎo),不過(guò)由于近幾年發(fā)生的事情確實(shí)讓開(kāi)發(fā)者對(duì)它失去了以往的信心。與此同時(shí)Spring家族發(fā)布了SpringMVC,而且完美的整合Spring來(lái)開(kāi)發(fā)企業(yè)級(jí)大型Web項(xiàng)目。它有著比Structs2更強(qiáng)大的技術(shù)支持以及更靈活的自定義配置,接下來(lái)我們就看看本章的內(nèi)容,我們自定義實(shí)現(xiàn)SpringMVC參數(shù)綁定規(guī)則,根據(jù)業(yè)務(wù)定制參數(shù)裝載實(shí)現(xiàn)方式。
本章目標(biāo)根據(jù)項(xiàng)目定制SpringMVC參數(shù)狀態(tài)并了解SpringMVC的裝載過(guò)程以及實(shí)現(xiàn)方式。
構(gòu)建項(xiàng)目我們先來(lái)創(chuàng)建一個(gè)SpringBoot項(xiàng)目,添加本章所需的依賴(lài),pom.xml配置文件如下所示:
...//省略部分配置...//省略部分配置 org.springframework.boot spring-boot-starter-web org.apache.tomcat.embed tomcat-embed-jasper javax.servlet javax.servlet-api javax.servlet jstl org.projectlombok lombok com.alibaba fastjson 1.2.38 org.springframework.boot spring-boot-starter-test test
本章需要JSP相關(guān)的依賴(lài)支持,所以需要添加對(duì)應(yīng)的依賴(lài),修改application.properties配置文件讓JSP生效,配置內(nèi)容如下所示:
spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp
相關(guān)JSP配置可以訪問(wèn)第二章:SpringBoot與JSP間不可描述的秘密查看講解。
SpringMVC的參數(shù)裝載在講解我們自定義參數(shù)裝載之前,我們先來(lái)看看SpringMVC內(nèi)部為我們提供的參數(shù)裝載方式。
我們首先來(lái)添加一個(gè)測(cè)試的jsp頁(yè)面,頁(yè)面上添加一些輸入元素,代碼如下所示:
<%-- 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" %>Title
在index.jsp內(nèi)添加了三個(gè)name的文本輸入框,如果我們現(xiàn)在提交到后臺(tái)SpringMVC為默認(rèn)為我們解析成一個(gè)數(shù)組,如果根據(jù)描述而言的來(lái)處理則是不合理的,當(dāng)然也可以使用各種手段完成字段參數(shù)的裝載,比如:為教師的name添加一個(gè)數(shù)組或者List集合進(jìn)行接受,這種方式也是可以實(shí)現(xiàn)但不優(yōu)雅。
如果你們項(xiàng)目組有嚴(yán)格的開(kāi)發(fā)規(guī)范要求,這種方式是不允許出現(xiàn)在Controller方法內(nèi)的。
那這個(gè)問(wèn)題就讓人頭疼了,在之前我們使用Struct2的時(shí)候是可以根據(jù)指定的前綴,如:xxx.xxx來(lái)進(jìn)行映射的,而SpringMVC并沒(méi)有提供這個(gè)支持,不過(guò)它提供了自定義參數(shù)裝載的實(shí)現(xiàn)方法,那就沒(méi)有問(wèn)題了,我們可以手寫(xiě)。
自定義的參數(shù)裝載既然上面的代碼實(shí)現(xiàn)滿(mǎn)足不了我們的需求,那么我接下來(lái)就來(lái)重寫(xiě)參數(shù)裝載。
對(duì)于一直使用SpringMVC的朋友來(lái)說(shuō),應(yīng)該對(duì)@RequestParam很熟悉,而本章我們自定義的注解跟@RequestParam類(lèi)似,主要目的也是標(biāo)識(shí)指定參數(shù)完成數(shù)據(jù)的綁定。下面我們先來(lái)看看該注解的源碼,如下所示:
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í)體映射注解 * 配置該注解的參數(shù)會(huì)使用 com.yuqiyu.chapter36.resovler.CustomerArgumentResolver類(lèi)完成參數(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 { }
該注解目前沒(méi)有添加任何一個(gè)屬性,這個(gè)也是可以根據(jù)項(xiàng)目的需求已經(jīng)業(yè)務(wù)邏輯進(jìn)行相應(yīng)添加的,比如@RequestParam內(nèi)常用的屬性required、defaultValue等屬性,由于我們本章內(nèi)容不需要自定義注解內(nèi)的屬性所以這里就不添加了。
該注解的作用域是在參數(shù)上@Target(value = ElementType.PARAMETER),我們僅可以在方法參數(shù)上使用。
創(chuàng)建參數(shù)接受實(shí)體我們可以回到上面看看index.jsp的內(nèi)容,我們需要教師的基本信息以及學(xué)生的基本信息,那我們就為教師、以及學(xué)生創(chuàng)建實(shí)體(注意:這個(gè)實(shí)體可以是對(duì)應(yīng)數(shù)據(jù)庫(kù)內(nèi)的實(shí)體)
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; }
教師實(shí)體內(nèi)目前為了測(cè)試就添加一個(gè)跟頁(yè)面參數(shù)有關(guān)的字段。
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è)面參數(shù)對(duì)應(yīng)的字段,名稱(chēng)、年齡。
編寫(xiě)CustomerArgumentResolver參數(shù)裝載在寫(xiě)參數(shù)裝載之前,我們需要先了解下它的接口HandlerMethodArgumentResolver,該接口內(nèi)定義了兩個(gè)方法:
boolean supportsParameter(MethodParameter var1);
supportsParameter方法顧名思義,是允許裝載的參數(shù),也就是說(shuō)方法返回true時(shí)才會(huì)指定裝載方法完成參數(shù)裝載。
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
resolveArgument方法是參數(shù)狀態(tài)的實(shí)現(xiàn)邏輯方法,該方法返回的值會(huì)直接裝載到指定的參數(shù)上,有木有很神奇???下面我們就創(chuàng)建實(shí)現(xiàn)類(lèi)來(lái)揭開(kāi)這位神奇的姑娘的面紗吧!
創(chuàng)建CustomerArgumentResolver實(shí)現(xiàn)接口HandlerMethodArgumentResolver內(nèi)的兩個(gè)方法,具體實(shí)現(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 { /** * 日志對(duì)象 */ private Logger logger = LoggerFactory.getLogger(CustomerArgumentResolver.class); /** * 該方法返回true時(shí)調(diào)用resolveArgument方法執(zhí)行邏輯 * spring家族的架構(gòu)設(shè)計(jì)萬(wàn)變不離其宗啊,在之前event & listener也是用到了同樣的方式 * @param methodParameter * @return */ @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(ParameterModel.class); } /** * 裝載參數(shù) * @param methodParameter 方法參數(shù) * @param modelAndViewContainer 返回視圖容器 * @param nativeWebRequest 本次請(qǐng)求對(duì)象 * @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ù)名稱(chēng):{}",parameterName); /** * 目標(biāo)返回對(duì)象 * 如果Model存在該Attribute時(shí)從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獲取請(qǐng)求的值 * @param attributeName 請(qǐng)求參數(shù) * @param parameter method 參數(shù)對(duì)象 * @param binderFactory 數(shù)據(jù)綁定工廠 * @param request 請(qǐng)求對(duì)象 * @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) { /** * 進(jìn)行類(lèi)型轉(zhuǎn)換 * 檢查請(qǐng)求的類(lèi)型與目標(biāo)參數(shù)類(lèi)型是否可以進(jìn)行轉(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ù)字段的類(lèi)型來(lái)進(jìn)行設(shè)置值、集合、數(shù)組等 */ else { Object attribute = putParameters(parameter,request); if(attribute!=null) { return attribute; } } /** * 如果以上兩種條件不符合,直接返回初始化參數(shù)類(lèi)型的空對(duì)象 */ return BeanUtils.instantiateClass(parameter.getParameterType()); } /** * 將attribute的值轉(zhuǎn)換為parameter參數(shù)值類(lèi)型 * @param sourceValue 源請(qǐng)求值 * @param attributeName 參數(shù)名 * @param parameter 目標(biāo)參數(shù)對(duì)象 * @param binderFactory 數(shù)據(jù)綁定工廠 * @param request 請(qǐng)求對(duì)象 * @return * @throws Exception */ protected Object convertAttributeToParameterValue(String sourceValue, String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { /** * 獲取類(lèi)型轉(zhuǎn)換業(yè)務(wù)邏輯實(shí)現(xiàn)類(lèi) */ DataBinder binder = binderFactory.createBinder(request, null, attributeName); ConversionService conversionService = binder.getConversionService(); if (conversionService != null) { /** * 源類(lèi)型描述 */ TypeDescriptor source = TypeDescriptor.valueOf(String.class); /** * 根據(jù)目標(biāo)參數(shù)對(duì)象獲取目標(biāo)參數(shù)類(lèi)型描述 */ TypeDescriptor target = new TypeDescriptor(parameter); /** * 驗(yàn)證是否可以進(jìn)行轉(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ù)名稱(chēng) * @param request 請(qǐng)求對(duì)象 * @return */ protected String getRequestValueForAttribute(String attributeName, NativeWebRequest request) { /** * 獲取PathVariables參數(shù)集合 */ Mapvariables = getUriTemplateVariables(request); /** * 如果PathVariables參數(shù)集合內(nèi)存在該attributeName * 直接返回相對(duì)應(yīng)的值 */ if (StringUtils.hasText(variables.get(attributeName))) { return variables.get(attributeName); } /** * 如果request parameterMap內(nèi)存在該attributeName * 直接返回相對(duì)應(yīng)的值 */ else if (StringUtils.hasText(request.getParameter(attributeName))) { return request.getParameter(attributeName); } //不存在時(shí)返回null else { return null; } } /** * 獲取指定前綴的參數(shù):包括uri varaibles 和 parameters * * @param namePrefix * @param request * @return * @subPrefix 是否截取掉namePrefix的前綴 */ protected Map getPrefixParameterMap(String namePrefix, NativeWebRequest request, boolean subPrefix) { Map result = new HashMap(); /** * 從PathVariables內(nèi)獲取該前綴的參數(shù)列表 */ Map 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()); //如果下一個(gè)字符不是 數(shù)字 . _ 則不可能是查詢(xún) 只是前綴類(lèi)似 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 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()); //如果下一個(gè)字符不是 數(shù)字 . _ 則不可能是查詢(xún) 只是前綴類(lèi)似 if (illegalChar(ch)) { continue; } result.put(name.substring(namePrefixLength + 1), request.getParameterValues(name)); } else { result.put(name, request.getParameterValues(name)); } } } return result; } /** * 驗(yàn)證參數(shù)前綴是否合法 * @param ch * @return */ private boolean illegalChar(char ch) { return ch != "." && ch != "_" && !(ch >= "0" && ch <= "9"); } /** * 獲取PathVariables集合 * @param request 請(qǐng)求對(duì)象 * @return */ protected final Map getUriTemplateVariables(NativeWebRequest request) { Map variables = (Map ) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (variables != null) ? variables : Collections.emptyMap(); } /** * 從request內(nèi)獲取parameter前綴的所有參數(shù) * 并根據(jù)parameter的類(lèi)型將對(duì)應(yīng)字段的值設(shè)置到parmaeter對(duì)象內(nèi)并返回 * @param parameter * @param request * @return */ protected Object putParameters(MethodParameter parameter,NativeWebRequest request) { /** * 根據(jù)請(qǐng)求參數(shù)類(lèi)型初始化空對(duì)象 */ Object object = BeanUtils.instantiateClass(parameter.getParameterType()); /** * 獲取指定前綴的請(qǐng)求參數(shù)集合 */ Map parameters = getPrefixParameterMap(parameter.getParameterName(),request,true); Iterator iterator = parameters.keySet().iterator(); while(iterator.hasNext()) { //字段名稱(chēng) String fieldName = iterator.next(); //請(qǐng)求參數(shù)值 String[] parameterValue = parameters.get(fieldName); try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true); //字段的類(lèi)型 Class> fieldTargetType = field.getType(); /** * List(ArrayList、LinkedList)類(lèi)型 * 將數(shù)組類(lèi)型的值轉(zhuǎn)換為L(zhǎng)ist集合對(duì)象 */ if(List.class.isAssignableFrom(fieldTargetType)) { field.set(object, Arrays.asList(parameterValue)); } /** *Object數(shù)組類(lèi)型,直接將數(shù)組值設(shè)置為目標(biāo)字段的值 */ else if(Object[].class.isAssignableFrom(fieldTargetType)) { field.set(object, parameterValue); } /** * 單值時(shí)獲取數(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ù)裝載的全部實(shí)現(xiàn)方法,下面我們就開(kāi)始按照裝載的流程進(jìn)行講解。
/** * 該方法返回true時(shí)調(diào)用resolveArgument方法執(zhí)行邏輯 * spring家族的架構(gòu)設(shè)計(jì)萬(wàn)變不離其宗啊,在之前event & listener也是用到了同樣的方式 * @param methodParameter * @return */ @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.hasParameterAnnotation(ParameterModel.class); }
我們只對(duì)配置了ParameterModel注解的參數(shù)進(jìn)行裝載。
/** * 裝載參數(shù) * @param methodParameter 方法參數(shù) * @param modelAndViewContainer 返回視圖容器 * @param nativeWebRequest 本次請(qǐng)求對(duì)象 * @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ù)名稱(chēng):{}",parameterName); /** * 目標(biāo)返回對(duì)象 * 如果Model存在該Attribute時(shí)從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 對(duì)象內(nèi)獲取了參數(shù)的名稱(chēng),根據(jù)該名稱(chēng)檢查Model內(nèi)是否存在該名稱(chēng)的值,如果存在則直接使用并返回,反則需要從ParameterMap內(nèi)獲取對(duì)應(yīng)該參數(shù)名稱(chēng)的值返回。
我們下面主要看看從parameterMap獲取的方法實(shí)現(xiàn)
/** * 根據(jù)參數(shù)attributeName獲取請(qǐng)求的值 * @param attributeName 請(qǐng)求參數(shù) * @param parameter method 參數(shù)對(duì)象 * @param binderFactory 數(shù)據(jù)綁定工廠 * @param request 請(qǐng)求對(duì)象 * @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) { /** * 進(jìn)行類(lèi)型轉(zhuǎn)換 * 檢查請(qǐng)求的類(lèi)型與目標(biāo)參數(shù)類(lèi)型是否可以進(jìn)行轉(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ù)字段的類(lèi)型來(lái)進(jìn)行設(shè)置值、集合、數(shù)組等 */ else { Object attribute = putParameters(parameter,request); if(attribute!=null) { return attribute; } } /** * 如果以上兩種條件不符合,直接返回初始化參數(shù)類(lèi)型的空對(duì)象 */ return BeanUtils.instantiateClass(parameter.getParameterType()); }
該方法的邏輯存在兩個(gè)分支,首先通過(guò)調(diào)用getRequestValueForAttribute方法從parameterMap內(nèi)獲取指定屬性名的請(qǐng)求值,如果存在值則需要驗(yàn)證是否可以完成類(lèi)型轉(zhuǎn)換,驗(yàn)證通過(guò)后則直接返回值。
上面的部分其實(shí)是SpringMVC原有的參數(shù)裝載的流程,下面我們就來(lái)根據(jù)需求個(gè)性化定制裝載邏輯。
該方法實(shí)現(xiàn)了自定義規(guī)則xxx.xxx方式進(jìn)行參數(shù)裝載的邏輯,我們?cè)谇芭_(tái)傳遞參數(shù)的時(shí)候只需要將Controller內(nèi)方法參數(shù)名稱(chēng)作為傳遞的前綴即可,如:teacher.name、student.name。
/** * 從request內(nèi)獲取parameter前綴的所有參數(shù) * 并根據(jù)parameter的類(lèi)型將對(duì)應(yīng)字段的值設(shè)置到parmaeter對(duì)象內(nèi)并返回 * @param parameter * @param request * @return */ protected Object putParameters(MethodParameter parameter,NativeWebRequest request) { /** * 根據(jù)請(qǐng)求參數(shù)類(lèi)型初始化空對(duì)象 */ Object object = BeanUtils.instantiateClass(parameter.getParameterType()); /** * 獲取指定前綴的請(qǐng)求參數(shù)集合 */ Mapparameters = getPrefixParameterMap(parameter.getParameterName(),request,true); Iterator iterator = parameters.keySet().iterator(); while(iterator.hasNext()) { //字段名稱(chēng) String fieldName = iterator.next(); //請(qǐng)求參數(shù)值 String[] parameterValue = parameters.get(fieldName); try { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true); //字段的類(lèi)型 Class> fieldTargetType = field.getType(); /** * List(ArrayList、LinkedList)類(lèi)型 * 將數(shù)組類(lèi)型的值轉(zhuǎn)換為L(zhǎng)ist集合對(duì)象 */ if(List.class.isAssignableFrom(fieldTargetType)) { field.set(object, Arrays.asList(parameterValue)); } /** *Object數(shù)組類(lèi)型,直接將數(shù)組值設(shè)置為目標(biāo)字段的值 */ else if(Object[].class.isAssignableFrom(fieldTargetType)) { field.set(object, parameterValue); } /** * 單值時(shí)獲取數(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í)例化了一個(gè)MethodParameter類(lèi)型的空對(duì)象,然后通過(guò)getPrefixParameterMap獲取PathVariables、ParameterMap內(nèi)前綴為MethodParameter名稱(chēng)的請(qǐng)求參數(shù)列表,遍歷列表對(duì)應(yīng)設(shè)置
object 內(nèi)的字段,用于完成參數(shù)的裝載,在裝載過(guò)程中,我這里分別根據(jù)Collection、List、Array、Single類(lèi)型進(jìn)行了處理(注意:這里需要根據(jù)項(xiàng)目需求進(jìn)行調(diào)整裝載類(lèi)型)。
我們將CustomerArgumentResolver托管交付給Spring框架,我們來(lái)創(chuàng)建一個(gè)名叫WebMvcConfiguration的配置類(lèi),該類(lèi)繼承抽象類(lèi)WebMvcConfigurerAdapter,代碼如下所示:
/** * springmvc 注解式配置類(lèi) * ======================== * 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(ListargumentResolvers) { /** * 將自定義的參數(shù)裝載添加到spring內(nèi)托管 */ argumentResolvers.add(new CustomerArgumentResolver()); } /** * 配置靜態(tài)請(qǐng)求視圖映射 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index").setViewName("index"); } }
我們重寫(xiě)了WebMvcConfigurerAdapter抽象類(lèi)內(nèi)的兩個(gè)方法addArgumentResolvers、addViewControllers,其中addArgumentResolvers方法完成了參數(shù)裝載的托管。
addViewControllers配置了視圖控制器映射,這樣我們?cè)L問(wèn)/index地址就可以請(qǐng)求到index.jsp頁(yè)面。
創(chuàng)建測(cè)試控制器創(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ù)測(cè)試 * @return */ @RequestMapping(value = "/submit") public String resolver(@ParameterModel TeacherEntity teacher, @ParameterModel StudentEntity student) { return "教師名稱(chēng):"+ JSON.toJSON(teacher.getName()) +",學(xué)生名稱(chēng):"+student.getName()+",學(xué)生年齡:"+student.getAge(); } }
可以看到我們?yōu)?b>TeacherEntity 、StudentEntity 分別添加了注解@ParameterModel,也就證明了這兩個(gè)實(shí)體需要使用我們的CustomerArgumentResolver完成參數(shù)裝載。
運(yùn)行測(cè)試在運(yùn)行測(cè)試之前,我們需要修改下index.jsp內(nèi)的參數(shù)映射前綴,修改后代碼如下所示:
測(cè)試單值裝載我們?yōu)榻處熋Q(chēng)、學(xué)生名稱(chēng)、學(xué)生年齡都分別添加了前綴,下面我們來(lái)啟動(dòng)項(xiàng)目,訪問(wèn)項(xiàng)目根下路徑/index,如下圖1所示:
在上圖1中輸入了部分請(qǐng)求參數(shù),點(diǎn)擊“提交”按鈕查看界面輸出的效果,圖下所示:
教師名稱(chēng):王老師,學(xué)生名稱(chēng):張小跑,學(xué)生年齡:23
可以看到參數(shù)已經(jīng)被正確的裝載到了不同的實(shí)體類(lèi)內(nèi)。
測(cè)試List裝載上面的例子只是針對(duì)實(shí)體內(nèi)的單個(gè)值的裝載,下面我們來(lái)測(cè)試下List類(lèi)型的值是否可以裝載?
我們先來(lái)修改下教師實(shí)體內(nèi)的名稱(chēng)為L(zhǎng)ist,字段名稱(chēng)不需要變動(dòng),如下所示:
//教師姓名 private Listname;
再來(lái)修改下index.jsp輸入框,如下所示:
在上代碼中我們添加了兩位老師的名稱(chēng),接下來(lái)重啟項(xiàng)目,再次提交測(cè)試,查看是不是我們想要的效果?
修改后的界面如下圖2所示:
界面輸出內(nèi)容如下所示:
教師名稱(chēng):["王老師","李老師"],學(xué)生名稱(chēng):張小跑,學(xué)生年齡:24
可以看到我們已經(jīng)拿到了兩位老師的名稱(chēng),這也證明了我們的CustomerArgumentResolver是可以完成List的映射裝載的。
總結(jié)以上內(nèi)容就是本章的全部講解內(nèi)容,本章簡(jiǎn)單實(shí)現(xiàn)了參數(shù)的狀態(tài),其中還有很多細(xì)節(jié)性質(zhì)的邏輯,如:@Valid注解的生效、文件的上傳等。在下一章我們會(huì)降到如果通過(guò)參數(shù)裝載實(shí)現(xiàn)接口服務(wù)的安全認(rèn)證。
本章代碼已經(jīng)上傳到碼云:
網(wǎng)頁(yè)地址:http://git.oschina.net/jnyqy/lessons
Git地址:https://git.oschina.net/jnyqy/lessons.git
SpringBoot相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringBoot學(xué)習(xí)目錄
QueryDSL相關(guān)系列文章請(qǐng)?jiān)L問(wèn):QueryDSL通用查詢(xún)框架學(xué)習(xí)目錄
SpringDataJPA相關(guān)系列文章請(qǐng)?jiān)L問(wèn):目錄:SpringDataJPA學(xué)習(xí)目錄
感謝閱讀!
歡迎加入QQ技術(shù)交流群,共同進(jìn)步。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/67618.html
摘要:?jiǎn)栴}來(lái)了,我們到底還在用嗎答案是,不全用。后者是初始化的配置,主要是的配置。啟動(dòng)類(lèi)測(cè)試啟動(dòng)項(xiàng)目后,在瀏覽器里面輸入。通過(guò)查詢(xún)已裝載的,并且支持該而獲取的。按照前面對(duì)的描述,對(duì)于而言,這個(gè)必定是。的核心在的方法中。 之前已經(jīng)分析過(guò)了Spring的IOC(《零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)》)與AOP(《從源碼入手,一文帶你讀懂Spring AOP面向切面編程》)的源碼,本次...
摘要:本章目的基于平臺(tái)整合分別完成客戶(hù)端服務(wù)端的單元測(cè)試。在測(cè)試控制器內(nèi)添加了三個(gè)測(cè)試方法,我們接下來(lái)開(kāi)始編寫(xiě)單元測(cè)試代碼??偨Y(jié)本章主要介紹了基于平臺(tái)的兩種單元測(cè)試方式,一種是在服務(wù)端采用注入方式將需要測(cè)試的或者注入到測(cè)試類(lèi)中,然后調(diào)用方法即可。 單元測(cè)試對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)是非常熟悉的,我們每天的工作也都是圍繞著開(kāi)發(fā)與測(cè)試進(jìn)行的,在最早的時(shí)候測(cè)試都是采用工具Debug模式進(jìn)行調(diào)試程序,后來(lái)Ju...
摘要:對(duì)象是開(kāi)源框架的上下文對(duì)象實(shí)例,在項(xiàng)目運(yùn)行時(shí)自動(dòng)裝載內(nèi)的所有信息到內(nèi)存。總結(jié)本章內(nèi)容較少,主要講解了平臺(tái)下采用的方式完成實(shí)例的獲取,并通過(guò)實(shí)例完成對(duì)管理的實(shí)例手動(dòng)獲取。 ApplicationContext對(duì)象是Spring開(kāi)源框架的上下文對(duì)象實(shí)例,在項(xiàng)目運(yùn)行時(shí)自動(dòng)裝載Handler內(nèi)的所有信息到內(nèi)存。傳統(tǒng)的獲取方式有很多種,不過(guò)隨著Spring版本的不斷迭代,官方也慢慢的不建議使用部...
摘要:本章目標(biāo)繼承采用形式實(shí)現(xiàn)個(gè)性化配置定制。本章代碼已經(jīng)上傳到碼云網(wǎng)頁(yè)地址地址相關(guān)系列文章請(qǐng)?jiān)L問(wèn)目錄學(xué)習(xí)目錄相關(guān)系列文章請(qǐng)?jiān)L問(wèn)通用查詢(xún)框架學(xué)習(xí)目錄相關(guān)系列文章請(qǐng)?jiān)L問(wèn)目錄學(xué)習(xí)目錄感謝閱讀歡迎加入技術(shù)交流群,共同進(jìn)步。 WebMvcConfigurerAdapter配置類(lèi)其實(shí)是Spring內(nèi)部的一種配置方式,采用JavaBean的形式來(lái)代替?zhèn)鹘y(tǒng)的xml配置文件形式進(jìn)行針對(duì)框架個(gè)性化定制,下面我...
摘要:小時(shí)學(xué)會(huì)學(xué)習(xí)總結(jié)時(shí)間年月日星期六說(shuō)明本文部分內(nèi)容均來(lái)自慕課網(wǎng)。慕課網(wǎng)教學(xué)示例源碼暫無(wú)。數(shù)據(jù)庫(kù)操作下第六章事務(wù)管理事務(wù)管理只有查詢(xún)的時(shí)候不加事務(wù),其它任何操作都要加事務(wù)。第七章課程回顧課程回顧總結(jié)介紹安裝配置的使用數(shù)據(jù)庫(kù)操作 《2小時(shí)學(xué)會(huì)SpringBoot》學(xué)習(xí)總結(jié) 時(shí)間:2017年2月18日星期六說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)示...
閱讀 2132·2021-11-11 16:55
閱讀 1558·2019-08-30 15:54
閱讀 857·2019-08-29 15:34
閱讀 2374·2019-08-29 13:11
閱讀 2990·2019-08-26 13:28
閱讀 1977·2019-08-26 10:49
閱讀 1078·2019-08-26 10:40
閱讀 2656·2019-08-23 18:21