SpringMVC對(duì)自定義controller入?yún)㈩A(yù)處理方式
Spring Mvc對(duì)自定義controller入?yún)㈩A(yù)處理
在初學(xué)springmvc框架時(shí),我就一直有一個(gè)疑問(wèn),為什么controller方法上竟然可以放這么多的參數(shù),而且都能得到想要的對(duì)象,比如HttpServletRequest或HttpServletResponse,各種注解@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute等。相信很多初學(xué)者都曾經(jīng)感慨過(guò)。
這篇文章就是講解處理這方面內(nèi)容的
我們可以模仿springmvc的源碼,實(shí)現(xiàn)一些我們自己的實(shí)現(xiàn)類(lèi),而方便我們的代碼開(kāi)發(fā)。
HandlerMethodArgumentResolver接口說(shuō)明
package org.springframework.web.method.support; import org.springframework.core.MethodParameter; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; public interface HandlerMethodArgumentResolver { //用于判定是否需要處理該參數(shù)分解,返回true為需要,并會(huì)去調(diào)用下面的方法resolveArgument。 boolean supportsParameter(MethodParameter parameter); //真正用于處理參數(shù)分解的方法,返回的Object就是controller方法上的形參對(duì)象。 Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; }
示例
本示例顯示如何 優(yōu)雅地將傳入的信息轉(zhuǎn)化成自定義的實(shí)體傳入controller方法。
post 數(shù)據(jù):
first_name = Bill
last_name = Gates
初學(xué)者一般喜歡類(lèi)似下面的代碼
package com.demo.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.demo.domain.Person; import com.demo.mvc.annotation.MultiPerson; import lombok.extern.slf4j.Slf4j; @Slf4j @Controller @RequestMapping("demo1") public class HandlerMethodArgumentResolverDemoController { @ResponseBody @RequestMapping(method = RequestMethod.POST) public String addPerson(HttpServletRequest request) { String firstName = request.getParameter("first_name"); String lastName = request.getParameter("last_name"); Person person = new Person(firstName, lastName); log.info(person.toString()); return person.toString(); } }
這樣的代碼強(qiáng)依賴(lài)了javax.servlet-api的HttpServletRequest對(duì)象,并且把初始化Person對(duì)象這“活兒”加塞給了controller。代碼顯得累贅不優(yōu)雅。在controller里我只想使用person而不想組裝person,想要類(lèi)似下面的代碼:
@RequestMapping(method = RequestMethod.POST) public String addPerson(Person person) { log.info(person.toString()); return person.toString(); }
直接在形參列表中獲得person。那么這該如實(shí)現(xiàn)呢?
我們需要定義如下的一個(gè)參數(shù)分解器
package com.demo.mvc.component; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import com.demo.domain.Person; public class PersonArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(Person.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String firstName = webRequest.getParameter("first_name"); String lastName = webRequest.getParameter("last_name"); return new Person(firstName, lastName); } }
在supportsParameter中判斷是否需要啟用分解功能,這里判斷形參類(lèi)型是否為Person類(lèi),也就是說(shuō)當(dāng)形參遇到Person類(lèi)時(shí)始終會(huì)執(zhí)行該分解流程resolveArgument,也可以基于paramter上是否有我們指定的自定義注解判斷是否需要流程分解。在resolveArgument中處理person的初始化工作。
注冊(cè)自定義分解器
傳統(tǒng)XML配置:
<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="com.demo.mvc.component.PersonArgumentResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven>
或
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="customArgumentResolvers"> <bean class="com.demo.mvc.component.PersonArgumentResolver"/> </property> </bean>
spring boot java代碼配置:
public class WebConfig extends WebMvcConfigurerAdapter{ @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(new CustomeArgumentResolver()); } }
SpringMVC技巧之通用Controller
一個(gè)通用Controller。大多數(shù)情況下不再需要編寫(xiě)任何Controller層代碼,將開(kāi)發(fā)人員的關(guān)注點(diǎn)全部集中到Service層。
1. 前言
平時(shí)在進(jìn)行傳統(tǒng)的MVC開(kāi)發(fā)時(shí),為了完成某個(gè)特定的功能,我們通常需要同時(shí)編寫(xiě)Controller,Service,Dao層的代碼。代碼模式大概是這樣的。
這里只貼出Controller層的代碼,Service層也不是本次我們的關(guān)注點(diǎn)。
// ----------------------------------------- Controller層 @RestController @RequestMapping("/a") public class AController { @Resource(name = "aService") private AService aService; @PostMapping(value = "/a") public ResponseBean<String> a(HttpServletRequest request, HttpServletResponse response) { final String name = WebUtils.findParameterValue(request, "name"); return ResponseBean.of(aService.invoke(name)); } } // ----------------------------------------- 前端訪(fǎng)問(wèn)路徑 // {{rootPath}}/a/a.do
2. 問(wèn)題
只要有過(guò)幾個(gè)月Java Web開(kāi)發(fā)經(jīng)驗(yàn)的,應(yīng)該對(duì)這樣的代碼非常熟悉,熟悉到惡心。我們稍微注意下就會(huì)發(fā)現(xiàn):上面的Controller代碼中,大致做了如下事情:
收集前端傳遞過(guò)來(lái)的參數(shù)。
將第一步收集來(lái)的參數(shù)傳遞給相應(yīng)的Service層的某個(gè)方法執(zhí)行。
將Service層執(zhí)行后的結(jié)果使用Controller層特有的ResponseBean進(jìn)行封裝后返回給前臺(tái)。
所以我們?cè)谂懦羯儆械奶厥馇闆r之后,就會(huì)發(fā)現(xiàn)在一般情況下這個(gè)所謂的Controller層的存在感實(shí)在有點(diǎn)稀薄。因此本文嘗試去除掉這部分枯燥的重復(fù)性代碼。
3. 解決方案
直接上代碼。talk is cheap, show me the code。
// 這里之所以是 /lq , 而不是 /* ; 是因?yàn)?AntPathMatcher.combine 方法中進(jìn)行合并時(shí)的處理, 導(dǎo)致 前一個(gè) /* 丟失 /** * <p> 直接以前端傳遞來(lái)的Serivce名+方法名去調(diào)用Service層的同名方法; Controller層不再需要寫(xiě)任何代碼 * <p> 例子 * <pre> * 前端: /lq/thirdService/queryTaskList.do * Service層相應(yīng)的方法簽名: Object queryTaskList(Map<String, Object> parameterMap) * 相應(yīng)的Service注冊(cè)到Spring容器中的id : thirdServiceService * </pre> * @author LQ * */ @RestController @RequestMapping("/lq") public class CommonController { private static final Logger LOG = LoggerFactory.getLogger(ThirdServiceController.class); @PostMapping(value = "/{serviceName}/{serviceMethodName}") public void common(@PathVariable String serviceName, @PathVariable final String serviceMethodName, HttpServletRequest request, HttpServletResponse response) { // 收集前臺(tái)傳遞來(lái)的參數(shù), 并作預(yù)處理 final Map<String, String> parameterMap = HtmlUtils.getParameterMap(request); final Map<String, Object> paramsCopy = preDealOutParam(parameterMap); // 獲取本次的調(diào)度服務(wù)名和相應(yīng)的方法名 //final List<String> serviceAndMethod = parseServiceAndMethod(request); //final String serviceName = serviceAndMethod.get(0) + "Service"; //final String serivceMethodName = serviceAndMethod.get(1); // 直接使用Spring3.x新加入的@PathVariable注解; 代替上面的自定義操作 serviceName = serviceName + "Service"; final String fullServiceMethodName = StringUtil.format("{}.{}", serviceName, serivceMethodName); // 輸出日志, 方便回溯 LOG.debug("### current request method is [ {} ] , parameters is [ {} ]", fullServiceMethodName, parameterMap); // 獲取Spring中注冊(cè)的Service Bean final Object serviceBean = SpringBeanFactory.getBean(serviceName); Object rv; try { // 調(diào)用Service層的方法 rv = ReflectUtil.invoke(serviceBean, serivceMethodName, paramsCopy); // 若用戶(hù)返回一個(gè)主動(dòng)構(gòu)建的FriendlyException if (rv instanceof FriendlyException) { rv = handlerException(fullServiceMethodName, (FriendlyException) rv); } else { rv = returnVal(rv); } } catch (Exception e) { rv = handlerException(fullServiceMethodName, e); } LOG.debug("### current request method [ {} ] has dealed, rv is [ {} ]", fullServiceMethodName, rv); HtmlUtils.writerJson(response, rv); } /** * 解析出Service和相應(yīng)的方法名 * @param request * @return */ private List<String> parseServiceAndMethod(HttpServletRequest request) { // /lq/thirdService/queryTaskList.do 解析出 [ thirdService, queryTaskList ] final String serviceAndMethod = StringUtil.subBefore(request.getServletPath(), ".", false); List<String> split = StringUtil.split(serviceAndMethod, '/', true, true); return split.subList(1, split.size()); } // 將傳遞來(lái)的JSON字符串轉(zhuǎn)換為相應(yīng)的Map, List等 private Map<String, Object> preDealOutParam(final Map<String, String> parameterMap) { final Map<String, Object> outParams = new HashMap<String, Object>(parameterMap.size()); for (Map.Entry<String, String> entry : parameterMap.entrySet()) { outParams.put(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Object> entry : outParams.entrySet()) { final String value = (String) entry.getValue(); if (StringUtil.isEmpty(value)) { entry.setValue(""); continue; } Object parsedObj = JSONUtil.tryParse(value); // 不是JSON字符串格式 if (null == parsedObj) { continue; } entry.setValue(parsedObj); } return outParams; } // 構(gòu)建成功執(zhí)行后的返回值 private Object returnVal(Object data) { return MapUtil.newMapBuilder().put("data", data).put("status", 200).put("msg", "success").build(); } // 構(gòu)建執(zhí)行失敗后的返回值 private Object handlerException(String distributeMethod, Throwable e) { final String logInfo = StringUtil.format("[ {} ] fail", distributeMethod); LOG.error(logInfo, ExceptionUtil.getRootCause(e)); return MapUtil.newMapBuilder().put("data", "").put("status", 500) .put("msg", ExceptionUtil.getRootCause(e).getMessage()).build(); } }
4. 使用
到此為止,Controller層的代碼就算是完成了。之后的開(kāi)發(fā)工作中,在絕大多數(shù)情況下,我們將不再需要編寫(xiě)任何Controller層的代碼。只要遵循如下的約定,前端將會(huì)直接調(diào)取到Service層的相應(yīng)方法,并獲取到約定格式的響應(yīng)值。
- 前端請(qǐng)求路徑 : {{rootPath}}/lq/serviceName/serviceMethodName.do
- {{rootPath}} : 訪(fǎng)問(wèn)地址的根路徑
- lq :自定義的固定名稱(chēng),用于滿(mǎn)足SpringMVC的映射規(guī)則。
- serviceName : 用于獲取Spring容器中的Service Bean。這里的規(guī)則是 該名稱(chēng)后附加上Service字符來(lái)作為Bean Id來(lái)從Spring容器中獲取相應(yīng) Service Bean。
- serviceMethodName : 第三步中找到的Service Bean中的名為serviceMethodName的方法。簽名為Object serviceMethodName(Map<String,Object> param)。
5. 特殊需求
對(duì)于有額外需要的特殊Controller,可以完全按照之前的Controller層寫(xiě)法。沒(méi)有任何額外需要注意的地方。
6. 完善
上面的Service層的方法簽名中,其參數(shù)使用的是固定的Map<String,Object> param。對(duì)Map和Bean的爭(zhēng)論由來(lái)已久,經(jīng)久不衰,這里不攪和這趟渾水。
對(duì)于希望使用Bean作為方法參數(shù)的,可以參考SpringMVC中對(duì)Controller層方法調(diào)用的實(shí)現(xiàn),來(lái)達(dá)到想要的效果。具體的實(shí)現(xiàn)就不在這里獻(xiàn)丑了,有興趣的同學(xué)可以參考下源碼ServletInvocableHandlerMethod.invokeAndHandle。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringMVC實(shí)現(xiàn)Controller的三種方式總結(jié)
- springmvc不進(jìn)入Controller導(dǎo)致404的問(wèn)題
- SpringMVC中@controllerAdvice注解的詳細(xì)解釋
- 關(guān)于SpringMVC在Controller層方法的參數(shù)解析詳解
- Springmvc工程跳轉(zhuǎn)controller無(wú)效的解決
- SpringMVC項(xiàng)目訪(fǎng)問(wèn)controller時(shí)候報(bào)404的解決
- springMVC不掃描controller中的方法問(wèn)題
相關(guān)文章
史上最全Java8日期時(shí)間工具類(lèi)(分享)
這篇文章主要介紹了史上最全Java8日期時(shí)間工具類(lèi)(分享),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例)
這篇文章主要介紹了Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例),需要的朋友可以參考下2017-09-09Java實(shí)現(xiàn)定時(shí)任務(wù)最簡(jiǎn)單的3種方法
幾乎在所有的項(xiàng)目中,定時(shí)任務(wù)的使用都是不可或缺的,如果使用不當(dāng)甚至?xí)斐少Y損,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)定時(shí)任務(wù)最簡(jiǎn)單的3種方法,本文通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06Mybatis中resultMap標(biāo)簽和sql標(biāo)簽的設(shè)置方式
這篇文章主要介紹了Mybatis中resultMap標(biāo)簽和sql標(biāo)簽的設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01