SpringMVC的設計與實現(xiàn)
? Spring MVC是MVC模式的一種實現(xiàn) , 在Spring MVC 的使用中 , 除了之前講過的ContextLoaderListener , 還有一個比較重要的類DispatchServlet , 在web.xml中也對其進行了配置 。從名字可以看出來這是一個Servlet , 這個Servlet實現(xiàn)的是Sun的J2EE核心模式中的前端控制器模式(Front Controller) , 所有的請求都要經(jīng)過它來處理 , 進行轉(zhuǎn)發(fā)、匹配、數(shù)據(jù)處理后 , 并轉(zhuǎn)由頁面進行展現(xiàn) , 因此可以把它當做Spring MVC視線中最為核心的部分。
? DispatchServlet與ContextLoaderListener 有沒有什么聯(lián)系呢 ?
? 兩者都是Spring上下文體系中非常重要的部分 , 在完成ContextLoaderListener的初始化后 , Web容器開始初始化DispatchServlet 。DispatchServlet會建立自己的上下文來持有Spring MVC的Bean對象 , 它會將ServletContext中的根上下文作為它的雙親上下文 , 并把自己持有的上下文也進行初始化并保存到ServletContext中。
? DispatchServlet是怎么對上下文進行初始化的呢?先看看他的類繼承關系圖

DispatchServlet通過繼承FramewServlet和HttpServletBean而繼承了HttpServlet , 通過使用Servlet API來對HTTP請求進行響應 , 成為Spring MVC的前端處理器 , 同時成為MVC模塊與Web容器集成的處理前端。

? 從上圖中可以看到Dispatchservlet的工作可以分為兩部分: 1.初始化部分 , 由initServletBean()啟動 , 通過initWebApplicationContext()方法最終調(diào)用DispatchServlet的initStrategies()方法 , 這部分做的就是上下文的初始化部分。2.對HTTP請求進行響應 , doService()方法會調(diào)用doDispatch()方法 , 在這個方法中進行了轉(zhuǎn)發(fā)的操作。
1.Dispatchservlet的啟動和初始化
? 作為Servlet , 初始化時init()方法會被調(diào)用 , init方法中調(diào)用了子類的initServletBean()方法
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 獲取Servlet的初始化參數(shù),對Bean屬性進行配置
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 調(diào)用子類的initServletBean進行具體的初始化
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//初始化上下文
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// 創(chuàng)建上下文的過程和IoC很相似 , 沒有指定就創(chuàng)建默認的上下文
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
DispatchServlet持有自己的一個Servlet名稱命名的IoC容器 , 這個IoC容器是一個WebApplicationContext對象 , SpringMVC的具體實現(xiàn)和Spring應用的實現(xiàn)并沒有什么太大差別 . 但是MVC在對容器初始化完成之后會調(diào)用initStrategies()方法,來實現(xiàn)轉(zhuǎn)發(fā)功能的初始化 , 可以從名稱看出來 ,這個方法中初始化了支持request映射的HandlerMapping、視圖、國際化的localResolver等 .
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
initHandlerMappings方法并不是干初始化Mapping的工作的 , 它只是初始化handlerMappings屬性的值 , 決定后續(xù)的轉(zhuǎn)發(fā)是由哪個子類執(zhí)行的
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// 默認加載的是DispatcherServlet.properties文件中的內(nèi)容
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
? DispatcherServlet.properties
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
至于Controller是什么時候轉(zhuǎn)換成Mapping的 , 可以研究一下AbstractHandlerMethodMapping類 , 從名字可以看出來該類是將method作為handler來使用的
// @since 3.1 Spring3.1之后才出現(xiàn),這個時候注解驅(qū)動也出來了
// 實現(xiàn)了initializingBean接口,其實主要的注冊操作則是通過afterPropertiesSet()接口方法來調(diào)用的
// 它是帶有泛型T的。
// T:包含HandlerMethod與傳入請求匹配所需條件的handlerMethod的映射~
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
// SCOPED_TARGET的BeanName的前綴
private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget.";
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
// 跨域相關
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
static {
ALLOW_CORS_CONFIG.addAllowedOrigin("*");
ALLOW_CORS_CONFIG.addAllowedMethod("*");
ALLOW_CORS_CONFIG.addAllowedHeader("*");
ALLOW_CORS_CONFIG.setAllowCredentials(true);
}
// 默認不會去祖先容器里面找Handlers
private boolean detectHandlerMethodsInAncestorContexts = false;
// @since 4.1提供的新接口
// 為處HandlerMetho的映射分配名稱的策略接口 只有一個方法getName()
// 唯一實現(xiàn)為:RequestMappingInfoHandlerMethodMappingNamingStrategy
// 策略為:@RequestMapping指定了name屬性,那就以指定的為準 否則策略為:取出Controller所有的`大寫字母` + # + method.getName()
// 如:AppoloController#match方法 最終的name為:AC#match
// 當然這個你也可以自己實現(xiàn)這個接口,然后set進來即可(只是一般沒啥必要這么去干~~)
@Nullable
private HandlerMethodMappingNamingStrategy<T> namingStrategy;
// 內(nèi)部類:負責注冊~
private final MappingRegistry mappingRegistry = new MappingRegistry();
// 此處細節(jié):使用的是讀寫鎖 比如此處使用的是讀鎖 獲得所有的注冊進去的Handler的Map
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
}
// 此處是根據(jù)mappingName來獲取一個Handler 此處需要注意哦~~~
@Nullable
public List<HandlerMethod> getHandlerMethodsForMappingName(String mappingName) {
return this.mappingRegistry.getHandlerMethodsByMappingName(mappingName);
}
// 最終都是委托給mappingRegistry去做了注冊的工作 此處日志級別為trace級別
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
public void unregisterMapping(T mapping) {
if (logger.isTraceEnabled()) {
logger.trace("Unregister mapping \"" + mapping + "\"");
}
this.mappingRegistry.unregister(mapping);
}
// 這個很重要,是初始化HandlerMethods的入口~~~~~
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
// 看initHandlerMethods(),觀察是如何實現(xiàn)加載HandlerMethod
protected void initHandlerMethods() {
// getCandidateBeanNames:Object.class相當于拿到當前容器(一般都是當前容器) 內(nèi)所有的Bean定義信息
// 如果閣下容器隔離到到的話,這里一般只會拿到@Controller標注的web組件 以及其它相關web組件的 不會非常的多的~~~~
for (String beanName : getCandidateBeanNames()) {
// BeanName不是以這個打頭得 這里才會process這個BeanName~~~~
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 會在每個Bean里面找處理方法,HandlerMethod,然后注冊進去
processCandidateBean(beanName);
}
}
// 略:它就是輸出一句日志:debug日志或者trace日志 `7 mappings in 'requestMappingHandlerMapping'`
handlerMethodsInitialized(getHandlerMethods());
}
// 確定指定的候選bean的類型,如果標識為Handler類型,則調(diào)用DetectHandlerMethods
// isHandler(beanType):判斷這個type是否為Handler類型 它是個抽象方法,由子類去決定到底啥才叫Handler~~~~
// `RequestMappingHandlerMapping`的判斷依據(jù)為:該類上標注了@Controller注解或者@Controller注解 就算作是一個Handler
// 所以此處:@Controller起到了一個特殊的作用,不能等價于@Component的喲~~~~
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
// 即使拋出異常 程序也不會終止~
}
if (beanType != null && isHandler(beanType)) {
// 這個和我們上篇博文講述的類似,都屬于detect探測系列~~~~
detectHandlerMethods(beanName);
}
}
// 在指定的Handler的bean中查找處理程序方法Methods 找打就注冊進去:mappingRegistry
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 又是非常熟悉的方法:MethodIntrospector.selectMethods
// 它在我們招@EventListener、@Scheduled等注解方法時已經(jīng)遇到過多次
// 此處特別之處在于:getMappingForMethod屬于一個抽象方法,由子類去決定它的尋找規(guī)則~~~~ 什么才算作一個處理器方法
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);
}
});
// 把找到的Method 一個個遍歷,注冊進去~~~~
methods.forEach((method, mapping) -> {
// 找到這個可調(diào)用的方法(AopUtils.selectInvocableMethod)
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
}
大致步驟: 找到容器中的所有bean , 如果bean是handler的話就就對bean進行解析 (是否是handler是交給子類去實現(xiàn)的) , 如果是handler獲取到該bean中的所有method , (以RequestMappingHandlerMapping為例 ),如果方法上面有requestMapping,根據(jù)注解的信息生成RequestMappingInfo , 并且將方法上的信息與類上面的信息結合 , 將方法與mapping信息的對應關系存到Map中 ,遍歷Map為method找到對應的可調(diào)用方法invocableMethod , 然后將方法注冊到HandlerMapping中 , 注冊存儲的地方是AbstractHandlerMethodMapping.MappingRegistry:內(nèi)部類注冊中心
class MappingRegistry {
// mapping對應的其MappingRegistration對象~~~
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
// 保存著mapping和HandlerMethod的對應關系~
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
// 保存著URL與匹配條件(mapping)的對應關系 當然這里的URL是pattern式的,可以使用通配符
// 這里的Map不是普通的Map,而是MultiValueMap,它是個多值Map。其實它的value是一個list類型的值
// 至于為何是多值?有這么一種情況 URL都是/api/v1/hello 但是有的是get post delete等方法 所以有可能是會匹配到多個MappingInfo的
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
// 這個Map是Spring MVC4.1新增的(畢竟這個策略接口HandlerMethodMappingNamingStrategy在Spring4.1后才有,這里的name是它生成出來的)
// 保存著name和HandlerMethod的對應關系(一個name可以有多個HandlerMethod)
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// 這兩個就不用解釋了
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
// 讀寫鎖~~~ 讀寫分離 提高啟動效率
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
... // 提供一些查找方法,都不是線程安全的
// 讀鎖提供給外部訪問,寫鎖自己放在內(nèi)部即可~~~
public void acquireReadLock() {
this.readWriteLock.readLock().lock();
}
public void releaseReadLock() {
this.readWriteLock.readLock().unlock();
}
// 注冊Mapping和handler 以及Method 此處上寫鎖保證線程安全~
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 此處注意:都是new HandlerMethod()了一個新的出來~~~~
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 同樣的:一個URL Mapping只能對應一個Handler
// 這里可能會出現(xiàn)常見的一個異常信息:Ambiguous mapping. Cannot map XXX
assertUniqueMethodMapping(handlerMethod, mapping);
// 緩存Mapping和handlerMethod的關系
this.mappingLookup.put(mapping, handlerMethod);
// 保存url和RequestMappingInfo(mapping)對應關系
// 這里注意:多個url可能對應著同一個mappingInfo呢~ 畢竟@RequestMapping的url是可以寫多個的~~~~
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
// 保存name和handlerMethod的關系 同樣也是一對多
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
// 注冊mapping和MappingRegistration的關系
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
// 釋放鎖
finally {
this.readWriteLock.writeLock().unlock();
}
}
// 相當于進行一次逆向操作~
public void unregister(T mapping) { ... }
...
}
參考:https://cloud.tencent.com/developer/article/1497621
注冊完成之后DispatchServlet的啟動過程中的重要部分就結束了 ,下面看一下調(diào)用過程
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);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 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 (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 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);
}
}
}
}
DispatchServlet也是一個servlet ,我們知道servlet的調(diào)用方法入口是doService()方法,在doService()方法中又調(diào)用了dodispatch()方法,在這個這個方法中又調(diào)用了getHandler()方法 , 根據(jù)請求的路徑找到對應的handler , 這個方法是請求分發(fā)過程中比較重要的部分
DispatchServlet類:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
AbstractHandlerMapping類:
獲取到handle并且把攔截器封裝到HandlerExecutionChain中 , 以便后面使用
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
...
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 要進行匹配的 請求的URI path
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
//委托給方法lookupHandlerMethod() 去找到一個HandlerMethod去最終處理~
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
// Match是一個private class,內(nèi)部就兩個屬性:T mapping和HandlerMethod handlerMethod
List<Match> matches = new ArrayList<>();
// 根據(jù)lookupPath去注冊中心里查找mappingInfos,因為一個具體的url可能匹配上多個MappingInfo的
// 至于為何是多值?有這么一種情況 URL都是/api/v1/hello 但是有的是get post delete等方法 當然還有可能是headers/consumes等等不一樣,都算多個的 所以有可能是會匹配到多個MappingInfo的
// 所有這個里可以匹配出多個出來。比如/hello 匹配出GET、POST、PUT都成,所以size可以為3
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 依賴于子類實現(xiàn)的抽象方法:getMatchingMapping() 看看到底匹不匹配,而不僅僅是URL匹配就行
// 比如還有method、headers、consumes等等這些不同都代表著不同的MappingInfo的
// 最終匹配上的,會new Match()放進matches里面去
addMatchingMappings(directPathMatches, matches, request);
}
// 當還沒有匹配上的時候,別無選擇,只能瀏覽所有映射
// 這里為何要瀏覽所有的mappings呢?而不是報錯404呢?這里我有點迷糊,愿有知道的指明這個設計意圖~~~
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 單反只要找到了一個匹配的 就進來這里了~~~
// 請注意:因為到這里 匹配上的可能還不止一個 所以才需要繼續(xù)處理~~
if (!matches.isEmpty()) {
// getMappingComparator這個方法也是抽象方法由子類去實現(xiàn)的。
// 比如:`RequestMappingInfoHandlerMapping`的實現(xiàn)為先比較Method,patterns、params
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
// 排序后的最佳匹配為get(0)
Match bestMatch = matches.get(0);
// 如果總的匹配個數(shù)大于1的話
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
// 次最佳匹配
Match secondBestMatch = matches.get(1);
// 如果發(fā)現(xiàn)次最佳匹配和最佳匹配 比較是相等的 那就報錯吧~~~~
// Ambiguous handler methods mapped for~~~
// 注意:這個是運行時的檢查,在啟動的時候是檢查不出來的~~~ 所以運行期的這個檢查也是很有必要的~~~ 否則就會出現(xiàn)意想不到的效果
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
// 把最最佳匹配的方法 放進request的屬性里面~~~
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
// 它也是做了一件事:request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath)
handleMatch(bestMatch.mapping, lookupPath, request);
// 最終返回的是HandlerMethod~~~
return bestMatch.handlerMethod;
}
// 一個都沒匹配上,handleNoMatch這個方法雖然不是抽象方法,protected方法子類復寫
// RequestMappingInfoHandlerMapping有復寫此方法~~~~
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
...
// 因為上面說了mappings可能會有多個,比如get post put的都算~~~這里就是要進行篩選出所有match上的
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
// 只有RequestMappingInfoHandlerMapping 實現(xiàn)了一句話:return info.getMatchingCondition(request);
// 因此RequestMappingInfo#getMatchingCondition() 方法里大有文章可為~~~
// 它會對所有的methods、params、headers... 都進行匹配 但凡匹配不上的就返回null
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
}
// ===============RequestMappingInfo 的源碼部分講解================
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
// 這些個匹配器都繼承自AbstractRequestCondition,會進行各自的匹配工作
// 下面會以PatternsRequestCondition為例進行示例講解~~~~~
// 他們頂級抽象接口為:RequestCondition @since 3.1 :Contract for request mapping conditions
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
private final ParamsRequestCondition paramsCondition;
private final HeadersRequestCondition headersCondition;
private final ConsumesRequestCondition consumesCondition;
private final ProducesRequestCondition producesCondition;
private final RequestConditionHolder customConditionHolder;
// 因為類上和方法上都可能會有@RequestMapping注解,所以這里是把語意思合并 該方法來自頂層接口
@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(other);
PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);
return new RequestMappingInfo(name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
// 合并后,就開始發(fā)揮作用了,該接口來自于頂層接口~~~~
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}
}
? 找到對應的handler之后根據(jù)handler找對應的HandlerAdapter, 我們使用的requestMapping對應的adapter是RequestMappingHandlerAdapter
? 執(zhí)行所有注冊攔截器的preHandler方法, 該方法中調(diào)用所有攔截器的preHandle方法,如果preHandle返回true繼續(xù)執(zhí)行下一個攔截器 ,否則調(diào)用triggerAfterCompletion方法 , triggerAfterCompletion會倒敘調(diào)用已經(jīng)執(zhí)行過得攔截器的afterCompletion的方法。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
可以看出,以上方法就是遍歷處理入?yún)?,但是可以從這個代碼中引出一個概念叫做:HandlerMethodArgumentResolver,HandlerMethod的參數(shù)解析器。