SpringMVC對自定義controller入?yún)㈩A(yù)處理方式
Spring Mvc對自定義controller入?yún)㈩A(yù)處理
在初學(xué)springmvc框架時,我就一直有一個疑問,為什么controller方法上竟然可以放這么多的參數(shù),而且都能得到想要的對象,比如HttpServletRequest或HttpServletResponse,各種注解@RequestParam、@RequestHeader、@RequestBody、@PathVariable、@ModelAttribute等。相信很多初學(xué)者都曾經(jīng)感慨過。
這篇文章就是講解處理這方面內(nèi)容的
我們可以模仿springmvc的源碼,實(shí)現(xiàn)一些我們自己的實(shí)現(xiàn)類,而方便我們的代碼開發(fā)。
HandlerMethodArgumentResolver接口說明
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為需要,并會去調(diào)用下面的方法resolveArgument。
boolean supportsParameter(MethodParameter parameter);
//真正用于處理參數(shù)分解的方法,返回的Object就是controller方法上的形參對象。
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é)者一般喜歡類似下面的代碼
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)依賴了javax.servlet-api的HttpServletRequest對象,并且把初始化Person對象這“活兒”加塞給了controller。代碼顯得累贅不優(yōu)雅。在controller里我只想使用person而不想組裝person,想要類似下面的代碼:
@RequestMapping(method = RequestMethod.POST)
public String addPerson(Person person) {
log.info(person.toString());
return person.toString();
}
直接在形參列表中獲得person。那么這該如實(shí)現(xiàn)呢?
我們需要定義如下的一個參數(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中判斷是否需要啟用分解功能,這里判斷形參類型是否為Person類,也就是說當(dāng)形參遇到Person類時始終會執(zhí)行該分解流程resolveArgument,也可以基于paramter上是否有我們指定的自定義注解判斷是否需要流程分解。在resolveArgument中處理person的初始化工作。
注冊自定義分解器
傳統(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
一個通用Controller。大多數(shù)情況下不再需要編寫任何Controller層代碼,將開發(fā)人員的關(guān)注點(diǎn)全部集中到Service層。
1. 前言
平時在進(jìn)行傳統(tǒng)的MVC開發(fā)時,為了完成某個特定的功能,我們通常需要同時編寫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));
}
}
// ----------------------------------------- 前端訪問路徑
// {{rootPath}}/a/a.do
2. 問題
只要有過幾個月Java Web開發(fā)經(jīng)驗(yàn)的,應(yīng)該對這樣的代碼非常熟悉,熟悉到惡心。我們稍微注意下就會發(fā)現(xiàn):上面的Controller代碼中,大致做了如下事情:
收集前端傳遞過來的參數(shù)。
將第一步收集來的參數(shù)傳遞給相應(yīng)的Service層的某個方法執(zhí)行。
將Service層執(zhí)行后的結(jié)果使用Controller層特有的ResponseBean進(jìn)行封裝后返回給前臺。
所以我們在排除掉少有的特殊情況之后,就會發(fā)現(xiàn)在一般情況下這個所謂的Controller層的存在感實(shí)在有點(diǎn)稀薄。因此本文嘗試去除掉這部分枯燥的重復(fù)性代碼。
3. 解決方案
直接上代碼。talk is cheap, show me the code。
// 這里之所以是 /lq , 而不是 /* ; 是因?yàn)?AntPathMatcher.combine 方法中進(jìn)行合并時的處理, 導(dǎo)致 前一個 /* 丟失
/**
* <p> 直接以前端傳遞來的Serivce名+方法名去調(diào)用Service層的同名方法; Controller層不再需要寫任何代碼
* <p> 例子
* <pre>
* 前端: /lq/thirdService/queryTaskList.do
* Service層相應(yīng)的方法簽名: Object queryTaskList(Map<String, Object> parameterMap)
* 相應(yīng)的Service注冊到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) {
// 收集前臺傳遞來的參數(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中注冊的Service Bean
final Object serviceBean = SpringBeanFactory.getBean(serviceName);
Object rv;
try {
// 調(diào)用Service層的方法
rv = ReflectUtil.invoke(serviceBean, serivceMethodName, paramsCopy);
// 若用戶返回一個主動構(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());
}
// 將傳遞來的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層的代碼就算是完成了。之后的開發(fā)工作中,在絕大多數(shù)情況下,我們將不再需要編寫任何Controller層的代碼。只要遵循如下的約定,前端將會直接調(diào)取到Service層的相應(yīng)方法,并獲取到約定格式的響應(yīng)值。
- 前端請求路徑 : {{rootPath}}/lq/serviceName/serviceMethodName.do
- {{rootPath}} : 訪問地址的根路徑
- lq :自定義的固定名稱,用于滿足SpringMVC的映射規(guī)則。
- serviceName : 用于獲取Spring容器中的Service Bean。這里的規(guī)則是 該名稱后附加上Service字符來作為Bean Id來從Spring容器中獲取相應(yīng) Service Bean。
- serviceMethodName : 第三步中找到的Service Bean中的名為serviceMethodName的方法。簽名為Object serviceMethodName(Map<String,Object> param)。
5. 特殊需求
對于有額外需要的特殊Controller,可以完全按照之前的Controller層寫法。沒有任何額外需要注意的地方。
6. 完善
上面的Service層的方法簽名中,其參數(shù)使用的是固定的Map<String,Object> param。對Map和Bean的爭論由來已久,經(jīng)久不衰,這里不攪和這趟渾水。
對于希望使用Bean作為方法參數(shù)的,可以參考SpringMVC中對Controller層方法調(diào)用的實(shí)現(xiàn),來達(dá)到想要的效果。具體的實(shí)現(xiàn)就不在這里獻(xiàn)丑了,有興趣的同學(xué)可以參考下源碼ServletInvocableHandlerMethod.invokeAndHandle。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例)
這篇文章主要介紹了Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例),需要的朋友可以參考下2017-09-09
Java實(shí)現(xiàn)定時任務(wù)最簡單的3種方法
幾乎在所有的項(xiàng)目中,定時任務(wù)的使用都是不可或缺的,如果使用不當(dāng)甚至?xí)斐少Y損,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)定時任務(wù)最簡單的3種方法,本文通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Mybatis中resultMap標(biāo)簽和sql標(biāo)簽的設(shè)置方式
這篇文章主要介紹了Mybatis中resultMap標(biāo)簽和sql標(biāo)簽的設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

