交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
springmvc第3 章:HandlerMapping
分享
未结
0
898
李延
LV6
2021-05-24
悬赏:20积分
# 作用 在DispatcherServlet中,我们解析到所有请求都是通过HandlerMapping获取Handler,而一个Handler就是我们自己写的一个controller方法。 # 初始化 在解析DispatcherServlet时,我们看到在initStrategies中调用了initHandlerMappings方法。  我们看一下这个方法: ```java 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. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } for (HandlerMapping mapping : this.handlerMappings) { if (mapping.usesPathPatterns()) { this.parseRequestPath = true; break; } } } ``` 我们看到初始化就是从Beanfacotry中读取所有类型为HandlerMapping的bean。  通过debug,我们看到一共加载了5个类。 其中RequestMappingHandlerMapping是我们使用的方法,所以我们以这个类为切入点进行解析。 # 继承关系  继承比较复杂,在忽略掉各种Aware接口后,其实基本上就是一条关系继续下来。我们逐一分析。 ## HandlerMapping 顶级接口,主要有一个方法定义: ```java HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; ``` ## ApplicationObjectSupport 基础自ApplicationObjectSupport,说明spring在创建这个bean的时候会调用它的setApplicationContext方法。 ```java @Override public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } } ``` 忽略掉其他负值代码后,我们发现其中有一行调用了 initApplicationContext(context);方法。 ```java protected void initApplicationContext(ApplicationContext context) throws BeansException { initApplicationContext(); } protected void initApplicationContext() throws BeansException { } ``` 我们发现这个方法是一个空的实现,交给子类自己扩展 ## WebApplicationObjectSupport 通过上面类的分析,我们知道这个类在初始化的时候调用了initApplicationContext方法。我们继续看一下 ```java @Override protected void initApplicationContext(ApplicationContext context) { super.initApplicationContext(context); if (this.servletContext == null && context instanceof WebApplicationContext) { this.servletContext = ((WebApplicationContext) context).getServletContext(); if (this.servletContext != null) { initServletContext(this.servletContext); } } } protected void initServletContext(ServletContext servletContext) { } ``` 这个类在父类的基础上扩展了参数为ServletContext的方法,而本身也是空的交给子类实现。 ## AbstractHandlerMapping 我们之间看它的getHandler方法 ```java @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //获取handler Object handler = getHandlerInternal(request); //如果是空的就使用默认的handler if (handler == null) { handler = getDefaultHandler(); } //如果还是null,就之间返回 if (handler == null) { return null; } // Bean name or resolved handler? //如果handler是String尝试从beanFactory里获取handler if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } //将Handler分装到HandlerExecutionChain中 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; } ``` 我们看到其核心的获取getHandlerInternal方法是由子类去实现的 ## AbstractHandlerMethodMapping 我们看到它继承了InitializingBean接口,所以对象在初始化的时候将会调用afterPropertiesSet方法 ### afterPropertiesSet ```java @Override public void afterPropertiesSet() { initHandlerMethods(); } /** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #getCandidateBeanNames() * @see #processCandidateBean * @see #handlerMethodsInitialized */ protected void initHandlerMethods() { for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } protected String[] getCandidateBeanNames() { return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); } ``` 在initHandlerMethods方法中我们看到他是通过getCandidateBeanNames方法获取到bean的名称,而getCandidateBeanNames方法是获取BeanFactory里所有的bean对象 在遍历时调用processCandidateBean方法,我们具体看一下 ```java 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); } } 判断是否为handler if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } ``` 这个方法中,我们看到通过isHandler,方法来判断这个类是否为handler类,具体是交给子类实现。 如果是,就说明这个类里面有方法是handler。我们继续查看detectHandlerMethods ```java protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); //遍历所以method,找到符合要求的method,并封装为Handler对象 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)); } //调用registerHandlerMethod注册handler对象 methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } } ``` 通过上面我们看到主要有2个重要步骤,一个是遍历所有方法,找到需要的method,并进行封装。对于封装结果使用registerHandlerMethod进行注册。 其中getMappingForMethod需要资料具体实现,我们继续看registerHandlerMethod方法 ```java protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { //封装对象 HandlerMethod handlerMethod = createHandlerMethod(handler, method); validateMethodMapping(handlerMethod, mapping); //获取之间路径,带有 {} 就不是之间路径 Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); for (String path : directPaths) { this.pathLookup.add(path, mapping); } //将handlerMethod封装到nameLookup中 String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } CorsConfiguration config = initCorsConfiguration(handler, method, mapping); if (config != null) { config.validateAllowCredentials(); this.corsLookup.put(handlerMethod, config); } //注册到registry中 this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name)); } finally { this.readWriteLock.writeLock().unlock(); } } ``` #### afterPropertiesSet总结 我们看到在bean对象初始化的时候,便利所有类类,通过isHandler方法,判断是否为Handler。如果是,遍历所以方法,通过getMappingForMethod封装为Handler对象,最好将其注册。 其中isHandler 和 getMappingForMethod 方法是抽象方法,交给子类实现。 而注册是分别保存到pathLookup、nameLookup、registry 3个地方 ### getHandlerInternal 父类中,我们分析到请求获取Handler就是交给这个方法的,这个方法在当前类有实现 ```java @Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //获取请求路径 String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { //匹配 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } ``` 我们再看一下lookupHandlerMethod方法: ```java @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); //在pathLookup 通过路径之间匹配 List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } //匹配不到时便利所有handler处理请求 if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (logger.isTraceEnabled()) { logger.trace(matches.size() + " matching mappings: " + matches); } if (CorsUtils.isPreFlightRequest(request)) { return PREFLIGHT_AMBIGUOUS_MATCH; } Match secondBestMatch = matches.get(1); 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.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.handlerMethod; } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } } ``` #### getHandlerInternal 总结 我们看到其实获取handler就是通过请求路径与之前初始化好的handlerMap去匹配。 ## RequestMappingHandlerMapping 在父类中,我们看到了在初始化是加载了所有的handler,以及请求与对应handler的匹配过程。 但如何判断哪些类里面又handler方法,同时又是怎样分装为handler,这是交给了子类实现。 ### isHandler ```java @Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); } ``` 其实很简单,就是我们平常使用的注解,如果类上有Controller或RequestMapping注解,我们就认为它是。 ### getMappingForMethod ```java @Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; } ``` 我们看到这块的主要逻辑是处理RequestMapping注解,同时,对于class上也有RequestMapping注解时,需要将两个进行合并。
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号