本文旨在詳細分析SpringMVC工做原理以及做爲開發者如何基於SpringMVC作擴展。由於SpringMVC分析的文章比較多,因此本文重點講解如何利用SpringMVC的擴展點實現咱們的需求。前端
SpringMVC的做用是什麼呢?須要解決什麼問題呢?java
下圖是一個客戶端與服務端的交互web
在以前的 詳解http報文(2)-web容器是如何解析http報文的一文中我也提到過。 此次再更細緻的分析一遍。一個請求如何中客戶端發到服務端,再從服務端返回內容。乾的這件事在web中叫請求動態內容,區別於靜態內容。在java語言中,爲了解決這件事定義了一個規範就是servlet。具體的實現由各大廠商本身定義。大致部分分爲兩部分一塊是創建鏈接、一塊是傳輸內容。因此servlet規範包括兩大部分,,一部分是servlet接口,定義處理請求的規範。一部分是servlet容器的,去管理加載servlet實例。spring
輕量級的servlet容器有tomcat/jetty/undertow,servlet框架有SpringMVC/Struts/Webx這些,本篇重點講解SpringMVCapi
基於以上,Spring MVC 提供了不一樣層面的擴展,方便開發者實現定製化的功能,而不須要底層代碼的修改tomcat
Filter其實不算是SpringMVC,是servlet的,這時候請求尚未到DispatchServlet
。Filter容許對請求和響應作一些統一的定製化處理,好比你限流、日誌、trace。bash
實現javax.servlet.Filter
接口便可restful
HandleMapping屬於Controll層面,咱們能夠編寫任意的HandlerMapping實現類,而後定義策略來決定一個web請求到HandlerExecutionChain對象的生成。mvc
繼承RequestMappingHandlerMapping
類便可。 這個具體案例能夠看下fredal的博客-使用基於 SpringMVC 的透明 RPC 開發微服務app
簡要來講,他的rpc通訊協議是基於http的。因此rpc調用就是基於服務端仍是原來的restful api。寫法給普通的前端去掉無異,而後包一層rpc client。方便客戶端調用。可是這樣太麻煩了,對於不須要暴露給前端的API,單純是服務間的rpc調用。再走一遍servlet-SpringMVC不必。
因此他基於RequestMappingHandlerMapping
作了一個改造。再也不基於SpringMVC,而是本身定義了一套rpc的範式,而後轉換爲springmvc。
Interceptor屬於Controll層面,咱們能夠自定義各類攔截器,在一個請求被真正處理以前、請求被處理但還沒輸出到響應中、請求已經被輸出到響應中以後這三個時間點去作任何咱們想要作的事情。普遍應用於Log,Session,鑑權等場景。
實現HandlerInterceptor
接口便可
解析方法參數的,能夠很方便的擴展http請求參數。 實現HandlerMethodArgumentResolver
接口便可
好比須要從http header中處理設備信息
@Component
public class DeviceResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(final MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(DeviceInfo.class);
}
@Override
public Object resolveArgument(final MethodParameter methodParameter,
final ModelAndViewContainer modelAndViewContainer,
final NativeWebRequest nativeWebRequest,
final WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request =
(HttpServletRequest) nativeWebRequest.getNativeRequest(HttpServletRequest.class);
// 從head頭中獲取設備信息
String id = request.getHeader("x-device-id");
if (id != null) {
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setId("id");
return deviceInfo;
}
return null;
}
}
複製代碼
類型轉換器,主要和序列化相關,參數綁定時springmvc會對將前端傳來的參數經過某種規則轉化成方法定義的參數的類型,默認實現的有StringHttpMessageConverter
、ByteArrayHttpMessageConverter
等等,默認的不能知足需求時咱們可本身定義此接口來實現本身的類型的轉換。
繼承AbstractHttpMessageConverter
便可。
異常處理,一般須要定義的全局異常。
@ControllerAdvice
註解便可 在一次和前端的相互甩鍋的問題記錄中有總結過這種
當咱們須要對RequestBody
的內容進行統一處理時,由於HandlerMethodArgumentResolver
只能處理特定類型的,作不到這點要求。
實現RequestBodyAdvice
接口便可。好比我須要處理requestbody中的內容,將emoji輸入轉換掉
@RestControllerAdvice
public class EmojiReplaceAdvice implements RequestBodyAdvice {
@Override
public boolean supports(final MethodParameter methodParameter, final Type targetType,
final Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasParameterAnnotation(EmojiReplace.class);
}
@Override
public Object handleEmptyBody(final Object body, final HttpInputMessage inputMessage,
final MethodParameter parameter, final Type targetType,
final Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage,
final MethodParameter parameter,
final Type targetType, final Class<? extends HttpMessageConverter<?>> converterType)
throws IOException {
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
final String content = IOUtils.toString(inputMessage.getBody());
final String emojiUnicodeToAlias = StringUtil.parseEmojiUnicodeToAlias(content);
return new ByteArrayInputStream(
emojiUnicodeToAlias.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
@Override
public Object afterBodyRead(final Object body, final HttpInputMessage inputMessage,
final MethodParameter parameter, final Type targetType,
final Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
複製代碼
這篇文章主要是系統的歸納了SpringMVC的工做原理和各類擴展機制,屬於高度歸納,細節不足。具體的每一個擴展點的實現、坑、應用場景須要在以後的文章繼續闡述。