SpringMVC以及Spring中的初始化
tomcat調用Service文件下javax.servlet.ServletContainerInitializer文件中配置的類的全限域名類(org.springframework.web.Spring.SpringServletContainerInitializer),然後通過循環遍歷調用實現@HandlersTypes註解中接口的類中的onStartup方法,從而將Listener對象和DispatcherServlet創建好,在創建DispatchServlet過程中,會創建一個關於DispathServlet上下文環境(WebApplicationContext),由於tomcat啓動的時候需要遵循tomcat規範,所以tomcat啓動的時候會調用到Listener中的contextInitialized方法,在這個方法中就會調用到Spring的核心refresh方法,tomcat啓動的時候會調用DispatcherServlet中的init()方法,在init方法中就會調用到refresh方法。DispatcherServlet調用玩Spring的refresh方法後會調用它本身的onRefresh方法(具體在initWebApplicationContext這個方法中調用到的),onRefresh這個方法就會完成SpringMVC中一系列容器的初始化工作。其中就會觸發實現了RequestMappinghandlerMapping的實例化,由於RequestMappingHandlerMapping擴展至AbstractHandlerMethModMapping這個抽象類,這個抽象類實現了InitializingBean接口,Spring在實例化Bean的過程中就會在調用到這個接口當中的afterPropertiesSet()方法(具體在AbstractAutowiredCapableBeanFactory中的invokerInitMehods()方法中調用到)。在afterPropertieSet方法中就會建立起url和類以及方法的映射關係。在AbstractHandlerMethodMapping中的afterPropertiesSet主要做建立以下的映射關係:
1.將方法上@RequestMapping註解進行封裝,封裝成RequestMappingInfo對象,並把類上的@RequestMapping註解合併到方法上
2.主要是在registerHandlerMethod方法中建立起URL到方法的映射,主要就是對以下重要的map賦值:
(1)對mappingLookup添加值,既建立起RequestMappingInfo對應和HandlerMethod對象的映射關係
(2)對urlLookup集合添加值,既建立起URL(字符串)和RequestMappingInfo的映射關係
(3)對corsLooup集合添加值,既建立起HandlerMethod和corsConfiguration(@CrossOrigin註解對象的封裝,主要是由於跨域問題)對象的映射關係
(4)對registry集合添加值,既建立起RequestMappingInfo對象和MappingRegistration對象(對RequestMappingInfo、HandlerMethod,URL集合(可能有多個URL可以訪問到方法),方法的全限域名的封裝類)的映射關係
至此HandlerMapping初始化完成。
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//在這個方法中就會建立起URL和方法的映射關係
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
//如果類上面有@Controller註解或者@RequestMapping註解
if (beanType != null && isHandler(beanType)) {
//建立uri和method的映射關係
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//獲取方法對象和方法上面的@RequestMapping註解屬性封裝對象的映射關係,這裏面的T對象就是RequestMappingInfo對象
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
//選擇可以反射的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//建立uri和方法的各種映射關係,反正一條,根據uri要能夠找到method對象 ,建立起方法和RequestMappingInfo之間的映射關係
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
//創建HandlerMethod對象,其實就是對方法的封裝,
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//檢驗是否唯一,檢查方法和RequestMappingInfo映射關係的唯一性
assertUniqueMethodMapping(handlerMethod, mapping);
//建立uri對象和handlerMethod的映射關係
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//建立url和RequestMappingInfo映射關係
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
//獲取方法的全限名
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
//判斷method上是否有CrossOrigin註解,把註解裏面的屬性封裝成CorsConfiguration,這個是做跨域訪問控制的
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
//建立映射關係
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
在RequestMappingHandlerMapping中的幾個重要Map關係:
UrlLookup----->是url和RequestMappingInfo的映射集合
mappingLookup---->是RequestMapping和HandlerMethod(方法的封裝類)的映射關係
CorsLookup----->是HandlerMehod和CorsConfiguration(跨域註解@CrossOrigin的封裝類)
Registry------>是RequestMappingInfo和MappingRegistraion(RequestMappingInfo和HandlerMethod,Url,方法的全限名的包裝對象)的映射
瀏覽器發起請求
接着我們發送請求:根據Servlet規範,會請求到Servlet類中的service,然後service中會判斷具體判斷請求類型是是什麼,從而再會去調用doGet、doPost等方法。同樣在DispatcherServlet中也是相同請,當請求過來的時候,首先會調用到service方法,然後通過service方法調用到doservice方法,通過doservice方法繼續調用到doDispatch()方法,這個doDispatcher方法就是我們SpringMVC中的DispatcherServlet的核心流程了。在DispatcherServlet方法中,首先會判斷請求類型,判斷是否是文件傳輸類型,然後會根據請求的URL獲取方法的調用鏈,獲取調用鏈的方法爲getHandler方法,首先會根據URL在urlLookup集合中滿足請求的RequestMappingInfo集合,然後根據RequesMappingInfo對象在mappingLookup集合 中獲取到HandlerMethod對象(由於匹配的時候可能會存在多個,所以代碼內有一系列校驗),然後判斷HandlerMethod如果不爲空則會觸發HandlerMethod當中包裝類的getBean操作,之後就會返回HandlerMethod對象,接着會將HandlerMethod對象包裝成HandlerExecutionChain對象(調用鏈對象),主要是將本次請求所要經歷的攔截器過濾器,已經Controller中的方法封裝成調用鏈對象(在Spring的AOP中也有一個類似的調用鏈對象),如果請求中有跨域屬性,則將跨域的過濾器也加入到調用鏈中來,最後返回調用鏈。
在doDispatcher方法中獲取到了調用鏈後,首先會根據調用鏈中的HandlerMethod對象獲取到一個合適的適配器(這個適配主要作用是根據HandlerMethod中的方法,解析和封裝請求參數,以及封裝返回參數)。接着就是調用調用鏈中的前置攔截方法(如果有一個攔截器失敗,則會反向調用連接器中的後置攔截器方法,主要是由於後置攔截器一般是對於資源的釋放,所有必須執行,在這裏SpringMVC因爲要控制採用一個狀態標誌位來控制攔截器的調用),接着就會調用到Controller中的核心方法(這個方法比較重要,在這裏面它完成了參數的封裝匹配和調用),接着調用到中置攔截器,最後會對視圖ModelAndView的渲染,這視圖渲染結束後會調用到後置攔截器,就完成了請求的所有流程。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//異步管理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//判斷請求類型是否是文件上傳類型
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//這個方法很重要,重點看---》獲取方法的調用鏈(裏面包括了攔截器、過濾器、HandlerMethod對象)
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//獲取跟HandlerMethod匹配的HandlerAdapter對象
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//前置過濾器,如果爲false則直接返回
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//調用到Controller具體方法,核心方法調用,主要是對於請求參數的封裝,以及方法的調用
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//中置過濾器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//視圖渲染,在這個方法中就會調用到後置攔截器
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}