本文主要梳理下Spring MVC處理http請求的過程,以及配置servlet及業務application需要的常用標簽,及其包含的意義。
spring MVC處理請求過程
首先看一個整體圖
簡單說下各步驟:
handlerMapping
handlerMapping將請求映射到處理器,即圖中的HandlerExecutionChain。依據是請求中的信息:請求URL(value),請求參數(params),請求方法(method),請求頭(headers)。處理器方法從中獲取參數,相關的標注有PathVariable、RequestParam、RequestHeader、CookieValue等。
- 請求方法包括GET、POST、DELETE、HEAD、OPTIONS、PUT、TRACE等。大多數瀏覽器只支持GET和POST,解決方法:客戶端post提交數據,添加“_method”參數來指定特定的方法;服務端配置HiddenHttpMethodFilter。spring會根據_method的值模擬特定的HTTP方法,從而被controller正確獲取。
DataBinder
DataBinder處理servletRequest中的消息,對其進行數據類型轉換(conversionService接口)和數據格式化(Formatter接口)操作,然后填充到入參對象中。再調用validator組件,做數據校驗。把conversion和validator的結果放在BindingResult中。即BindingResult存儲入參對象和校驗錯誤對象,可直接作為controller處理方法的參數。
看一下ConfigurableWebBindingInitializer
(封裝了WebDataBinder
,繼承自DataBinder
)的代碼:
//處理BindingResult
private BindingErrorProcessor bindingErrorProcessor;
//數據校驗
private Validator validator;
//數據類型轉換
private ConversionService conversionService;
- 這里的convert做什么工作呢?比如controller處理方法的參數為User,包含用戶名、密碼、昵稱等,而用戶傳入的參數是userName:password:nick這樣的特定格式,這時就需要一個converter處理String2User。
HandlerExecutionChain
HandlerExecutionChain,執行鏈。包含一個處理器Handler(controller中的處理方法)及若干攔截器HandlerInterceptor。處理過程如下:
- handlerInterceptor如果處理出錯,就會直接返回結果,而不會到達handler。
- 在進入handler之前,執行handlerInterceptor的preHandler方法;handler處理之后,執行handlerInterceptor的postHandler方法;相應被渲染后,執行handlerInterceptor的afterCompletion方法。
viewResolve
視圖對象是一個Bean,視圖對象由視圖解析器負責實例化(感覺有點像handler和handlerAdapter)。可裝配多個視圖解析器,配置優先級。
常用標簽理解
<mvc:annotation-driven/>
- 默認創建並注冊DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter。如果配置自定義的,則替換默認的。
- 默認注冊FormattingConversionServiceFactoryBean(一個默認的ConversionService)。自定義conversionService使用屬性conversion-service來組裝。
- 默認裝配LocalValidatorFactoryBean,支持controller方法的入參標注@Valid。
<mvc:default-servlet-handler />
- 默認配置DefaultServletHttpRequestHandler,檢查URL,若是靜態資源,則將請求轉由web應用服務器默認的servlet處理。否則,由DispatcherServlet處理。
- 如果web應用服務器默認servlet的名字不是“default”,則需要配置
<mvc:default-servlet-handler default-servlet-name="yourServerDefaultServletName"/>
<mvc:resources/>
- 由Spring MVC框架自己處理靜態資源,並添加一些有用的附加功能。可將多個路徑映射為一個邏輯路徑;按照配置路徑順序查找,只要查找到,即返回。
其他
ExceptionHandler
標注在方法上,指定處理特定異常的方法。作用域:類。創建一個BaseController,里面指定各種異常的處理方法;其他controller繼承BaseController。
定義全局處理方法:配置SimpleMappingExceptionResolver 。
示例:
//處理一種異常@ExceptionHandler(RuntimeException.class)
//處理多種異常
@ExceptionHandler({BindException.class,RuntimeException.class})
@ResponseBody
public Map<String, Object> bindExceptionHandler(BindException e, HttpServletResponse response, HttpServletRequest request) {
HashMap body = new HashMap();
body.put("status", Integer.valueOf(1));
response.setStatus(200);
return body;
}
RequestBody
標注RequestBody,將參數按照屬性名匹配的方式,填充入POJO。支持級聯的屬性名。如下,為類結構圖,則傳遞user參數時,應該寫
為userName=tom&dept.deptId=1&dept.address.tel=102
@RequestBody/ResponseBody
@RequestBody/ResponseBody是開發中常用的注解。
- 數據類型轉換主要通過converter來實現,接口:
HttpMessageConverter<T>
,作用:將請求信息轉換為一個對象,將對象輸出為響應信息。相應接口:canRead,read,canWrite,write。相關標注:RequestBody,ResponseBody。 - 當controller處理方法使用到@RequestBody/ResponseBody或者HttpEntity
/ResponseEntity 時,才使用HttpMessageConverter對請求/響應消息進行處理。 - 處理表單數據的FormHttpMessageConverter,處理的數據類型為MultiValueMap,所以如果要模擬post請求,需要將參數封裝成MultiValueMap,看這個示例。
關於HttpMessageConverter
的裝配和使用,看下源代碼:
RequestMappingHandlerAdapter
的屬性包含如下幾個:
private HandlerMethodArgumentResolverComposite argumentResolvers;
//參數解析器
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
//請求報文和對象之間的轉換
private List<HttpMessageConverter<?>> messageConverters;
//封裝DataBinder,用於數據類型轉換、數據格式化、數據校驗
private WebBindingInitializer webBindingInitializer;
其中HandlerMethodArgumentResolverComposite
是包含了一個HandlerMethodArgumentResolver
的List,用於解析參數。 WebBindingInitializer
接口 封裝了WebDataBinder
(繼承自DataBinder
),前面ConfigurableWebBindingInitializer
是它的一個具體實現。 HttpMessageConverter
就是我們配置的數據轉換器。
RequestMappingHandlerAdapter
構造方法中會添加默認的幾個HttpMessageConverter
:
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // See SPR-7316
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<Source>());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
設置之后,將messageConverters
添加到參數解析其中:
@Override
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
......
}
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
......
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
......
return resolvers;
}
可以看到,將messageConverters
放入RequestResponseBodyMethodProcessor
中。然后再在RequestResponseBodyMethodProcessor
中使用具體的messageConverters轉換報文和對象,同時對數據進行校驗。
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//使用MessageConverters讀取報文,並轉為對象
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
//創建WebDataBinder,獲取對象參數
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
//參數校驗,結果存入 binder.getBindingResult()
if (argument != null) {
validate(binder, parameter);
}
//將參數處理結果 binder.getBindingResult()放入mavContainer
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return argument;
}
這里,把HttpMessageConverter和WebDataBinder的順序搞清楚了。其實,既然HttpMessageConverter是用來解析報文為對象的,肯定是放在參數處理第一步的。