SpringMVC的设计与实现
Spring MVC是MVC模式的一种实现 , 在Spring MVC 的使用中 , 除了之前讲过的ContextLoaderListener , 还有一个比较重要的类DispatchServlet , 在web.xml中也对其进行了配置 。从名字可以看出来这是一个Servlet , 这个Servlet实现的是Sun的J2EE核心模式中的前端控制器模式(Front Controller) , 所有的请求都要经过它来处理 , 进行转发、匹配、数据处理后 , 并转由页面进行展现 , 因此可以把它当做Spring MVC视线中最为核心的部分。
DispatchServlet与ContextLoaderListener 有没有什么联系呢 ?
两者都是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()方法最终调用DispatchServlet的initStrategies()方法 , 这部分做的就是上下文的初始化部分。2.对HTTP请求进行响应 , doService()方法会调用doDispatch()方法 , 在这个方法中进行了转发的操作。
1.Dispatchservlet的启动和初始化
作为Servlet , 初始化时init()方法会被调用 , init方法中调用了子类的initServletBean()方法
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 获取Servlet的初始化参数,对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;
}
}
// 调用子类的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) {
// 创建上下文的过程和IoC很相似 , 没有指定就创建默认的上下文
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的具体实现和Spring应用的实现并没有什么太大差别 . 但是MVC在对容器初始化完成之后会调用initStrategies()方法,来实现转发功能的初始化 , 可以从名称看出来 ,这个方法中初始化了支持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属性的值 , 决定后续的转发是由哪个子类执行的
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文件中的内容
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是什么时候转换成Mapping的 , 可以研究一下AbstractHandlerMethodMapping类 , 从名字可以看出来该类是将method作为handler来使用的
// @since 3.1 Spring3.1之后才出现,这个时候注解驱动也出来了
// 实现了initializingBean接口,其实主要的注册操作则是通过afterPropertiesSet()接口方法来调用的
// 它是带有泛型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()
// 唯一实现为:RequestMappingInfoHandlerMethodMappingNamingStrategy
// 策略为:@RequestMapping指定了name属性,那就以指定的为准 否则策略为:取出Controller所有的`大写字母` + # + method.getName()
// 如:AppoloController#match方法 最终的name为:AC#match
// 当然这个你也可以自己实现这个接口,然后set进来即可(只是一般没啥必要这么去干~~)
@Nullable
private HandlerMethodMappingNamingStrategy<T> namingStrategy;
// 内部类:负责注册~
private final MappingRegistry mappingRegistry = new MappingRegistry();
// 此处细节:使用的是读写锁 比如此处使用的是读锁 获得所有的注册进去的Handler的Map
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
}
// 此处是根据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(),观察是如何实现加载HandlerMethod
protected void initHandlerMethods() {
// getCandidateBeanNames:Object.class相当于拿到当前容器(一般都是当前容器) 内所有的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类型,则调用DetectHandlerMethods
// isHandler(beanType):判断这个type是否为Handler类型 它是个抽象方法,由子类去决定到底啥才叫Handler~~~~
// `RequestMappingHandlerMapping`的判断依据为:该类上标注了@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等注解方法时已经遇到过多次
// 此处特别之处在于:getMappingForMethod属于一个抽象方法,由子类去决定它的寻找规则~~~~ 什么才算作一个处理器方法
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) -> {
// 找到这个可调用的方法(AopUtils.selectInvocableMethod)
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
}
大致步骤: 找到容器中的所有bean , 如果bean是handler的话就就对bean进行解析 (是否是handler是交给子类去实现的) , 如果是handler获取到该bean中的所有method , (以RequestMappingHandlerMapping为例 ),如果方法上面有requestMapping,根据注解的信息生成RequestMappingInfo , 并且将方法上的信息与类上面的信息结合 , 将方法与mapping信息的对应关系存到Map中 ,遍历Map为method找到对应的可调用方法invocableMethod , 然后将方法注册到HandlerMapping中 , 注册存储的地方是AbstractHandlerMethodMapping.MappingRegistry:内部类注册中心
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();
... // 提供一些查找方法,都不是线程安全的
// 读锁提供给外部访问,写锁自己放在内部即可~~~
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
// 这里可能会出现常见的一个异常信息: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的启动过程中的重要部分就结束了 ,下面看一下调用过程
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的调用方法入口是doService()方法,在doService()方法中又调用了dodispatch()方法,在这个这个方法中又调用了getHandler()方法 , 根据请求的路径找到对应的handler , 这个方法是请求分发过程中比较重要的部分
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,内部就两个属性:T mapping和HandlerMethod handlerMethod
List<Match> matches = new ArrayList<>();
// 根据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) {
// 依赖于子类实现的抽象方法: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);
}
// 单反只要找到了一个匹配的 就进来这里了~~~
// 请注意:因为到这里 匹配上的可能还不止一个 所以才需要继续处理~~
if (!matches.isEmpty()) {
// getMappingComparator这个方法也是抽象方法由子类去实现的。
// 比如:`RequestMappingInfoHandlerMapping`的实现为先比较Method,patterns、params
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
// 排序后的最佳匹配为get(0)
Match bestMatch = matches.get(0);
// 如果总的匹配个数大于1的话
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
// 次最佳匹配
Match secondBestMatch = matches.get(1);
// 如果发现次最佳匹配和最佳匹配 比较是相等的 那就报错吧~~~~
// Ambiguous handler methods mapped for~~~
// 注意:这个是运行时的检查,在启动的时候是检查不出来的~~~ 所以运行期的这个检查也是很有必要的~~~ 否则就会出现意想不到的效果
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 实现了一句话: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());
}
// 合并后,就开始发挥作用了,该接口来自于顶层接口~~~~
@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之后根据handler找对应的HandlerAdapter, 我们使用的requestMapping对应的adapter是RequestMappingHandlerAdapter
执行所有注册拦截器的preHandler方法, 该方法中调用所有拦截器的preHandle方法,如果preHandle返回true继续执行下一个拦截器 ,否则调用triggerAfterCompletion方法 , triggerAfterCompletion会倒叙调用已经执行过得拦截器的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);
}
}
}
}
可以看出,以上方法就是遍历处理入参,但是可以从这个代码中引出一个概念叫做:HandlerMethodArgumentResolver,HandlerMethod的参数解析器。