当前位置: 首页 > news >正文

SpringMVC 6+源码分析(三)DispatcherServlet实例化流程 2--(url 与contrller类如何进行映射)

一、概述

上一章了解了DispatcherServlet初始化的流程,着重讲了一个重点的初始化业务,本章节将重点讲解项目中的 controller 怎么跟url经行关联的。这一关键的业务流程都在 initHandlerMappings(context) 方法中完成的。

二、初始化处理器映射器-initHandlerMappings

initHandlerMappings初始化是SpringMVC中请求映射的核心链路,包括HandlerMapping的注入和HandlerMethod的注册。从Spring启动时加载HandlerMapping到解析请求路径找到相应的Controller方法,深入探讨了SpringMVC处理HTTP请求的内部机制。

	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;}}}

从方法中我们可以看到,在这个过程中会先去Spring容器中查找是否存在自定义的HandlerMapping 的bean,存在则加入handlerMappings。如果没有则加载默认配置的HandlerMapping,默认的HandlerMapping实现类使用资源加载的方式。对应的实现类在配置文件 DispatcherServlet.properties 中,通过资源加载器,获取到默认HandlerMapping的类信息。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\org.springframework.web.servlet.function.support.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

可以看到系统默认加载的HandlerMapping 有 BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping。在日常的业务开发中,业务处理都是在RequestMappingHandlerMapping中的,所以重点看RequestMappingHandlerMapping的实例化过程即可。
请添加图片描述

RequestMappingHandlerMapping继承 AbstractHandlerMethodMapping、AbstractHandlerMapping 而它们还要实现InitializingBean接口,所以RequestMappingHandlerMapping对象被容器创建之后,就会调用afterPropertiesSet()方法进行初始化。

	@Override@SuppressWarnings("deprecation")public void afterPropertiesSet() {this.config = new RequestMappingInfo.BuilderConfiguration();this.config.setTrailingSlashMatch(useTrailingSlashMatch());this.config.setContentNegotiationManager(getContentNegotiationManager());if (getPatternParser() != null && this.defaultPatternParser &&(this.useSuffixPatternMatch || this.useRegisteredSuffixPatternMatch)) {setPatternParser(null);}if (getPatternParser() != null) {this.config.setPatternParser(getPatternParser());Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,"Suffix pattern matching not supported with PathPatternParser.");}else {this.config.setSuffixPatternMatch(useSuffixPatternMatch());this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());this.config.setPathMatcher(getPathMatcher());}super.afterPropertiesSet();}

在容器创建完对象以后,调用RequestMappingHandlerMapping的 afterPropertiesSet方法,这里做了RequestMappingInfo.BuilderConfiguration的初始化,然后调用AbstractHandlerMethodMapping的afterPropertiesSet方法。

	@Overridepublic 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());}

这里开始遍历IOC容器中的bean,并经行下一步的处理。

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);}}if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}@Overrideprotected boolean isHandler(Class<?> beanType) {return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class);}

判断bean是否存在 Controller 注解,如果存在则对该bean进行处理。在此方法中开始遍历bean中的方法。

	protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String beanName ?obtainApplicationContext().getType(beanName) : handler.getClass());if (handlerType != null) {Class<?> userType = ClassUtils.getUserClass(handlerType);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));}else if (mappingsLogger.isDebugEnabled()) {mappingsLogger.debug(formatMappings(userType, methods));}methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);registerHandlerMethod(handler, invocableMethod, mapping);});}}

先判断方法上是否存在 RequestMapping注解如果存在则开始 创建RequestMappingInfo实例,将注解中的 url path 转成视图可识别的格式保存(并将controller 上的 path 值拼接上去),同时保存各类参数。

@Override@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}if (info.isEmptyMapping()) {info = info.mutate().paths("", "/").options(this.config).build();}String prefix = getPathPrefix(handlerType);if (prefix != null) {info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);}}return info;}@Nullableprivate RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);RequestCondition<?> condition = (element instanceof Class<?> clazz ?getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element));return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);}protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {RequestMappingInfo.Builder builder = RequestMappingInfo.paths(resolveEmbeddedValuesInPatterns(requestMapping.path())).methods(requestMapping.method()).params(requestMapping.params()).headers(requestMapping.headers()).consumes(requestMapping.consumes()).produces(requestMapping.produces()).mappingName(requestMapping.name());if (customCondition != null) {builder.customCondition(customCondition);}return builder.options(this.config).build();}

每个方法都会生成对应的 RequestMappingInfo,之后开始注册到MappingRegistry 对象中。

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);}String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {corsConfig.validateAllowCredentials();corsConfig.validateAllowPrivateNetwork();this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping,new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));}finally {this.readWriteLock.writeLock().unlock();}}

在register方法中,先根据 对象及方法 创建HandlerMethod对象,再判断mapping是否已经存在,如果存在则抛出对应的错误。后续开始对mapping进行处理。
首先将路径映射到快速索引中,this.pathLookup,主要的作用是 通过path 快速找到对应的 RequestMappingInfo从而找到 对应的方法,
如:
“/api/getUser/info” -> getUserInfo() 方法。
同时支持 支持路径模式匹配
例如:
“/api/getUserByid/7234” 它可以匹配到
“/api/getUserByid/{id }” 或者 “/api/getUserByid/*” 等url。
pathLookup由于使用 MultiValueMap 结构,同一个路径模式可关联多个 RequestMappingInfo 对象
这对路径的动态参数匹配至关重要。

接下来是将注定nameLookup,nameLookup是一个映射,键(key)是处理器方法的名字(通常是方法名或用户指定的名称),值(value)是该名称对应的HandlerMethod列表。在RequestMapping 中设置类name 属性,则将name 作为key,如果没有设置则使用自定义策略生成对应的key,框架在获取不到指定策略时,将使用默认的策略生成 name 作为key,默认策略是 使用类名+“#” +方法名的 方式。

protected RequestMappingInfoHandlerMapping() {setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());}@Overridepublic String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {if (mapping.getName() != null) {return mapping.getName();}StringBuilder sb = new StringBuilder();String simpleTypeName = handlerMethod.getBeanType().getSimpleName();for (int i = 0; i < simpleTypeName.length(); i++) {if (Character.isUpperCase(simpleTypeName.charAt(i))) {sb.append(simpleTypeName.charAt(i));}}sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());return sb.toString();}

nameLookup 其主要作用是通过处理器方法名称快速查找对应的 HandlerMethod 对象。

接下来就是将跨域配置注册到this.corsLookup属性类中。

		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {corsConfig.validateAllowCredentials();corsConfig.validateAllowPrivateNetwork();this.corsLookup.put(handlerMethod, corsConfig);}

最后将 映射关系 添加在registry 中,将 RequestMappingInfo作为值,MappingRegistration对象作为value,用于存储所有控制器方法(HandlerMethod)与请求映射条件(如 URL 模式、HTTP 方法等)的绑定关系。为后续的执行流程提供映射表。

三、结尾

至此 url 与contrller类的映射主流程就基本完成,下一章节将介绍 adapter的初始化,及Springmvc的执行流程。在此过程中如存在纰漏或者错误,麻烦请支持,相互探讨相互学习。

http://www.lryc.cn/news/609935.html

相关文章:

  • 【Spring AI快速上手 (一)】ChatModel与ChatCilent构建对话
  • 小鹏汽车前端面经
  • Python+QT开发环境搭建
  • 数据从mysql迁移到postgresql
  • 纯前端导出Excel
  • MCP安全机制深度剖析:权限控制与数据保护最佳实践
  • 体验Java接入langchain4j运用大模型OpenAi
  • 学习游戏制作记录(角色属性和状态脚本)8.4
  • 多源异构信号同步采集与赛道数据融合技术解析
  • 迅为RK3568开发板OpeHarmony学习开发手册-修改调试串口波特率
  • codeBuddy IDE 使用教程
  • 零售行业线上线下融合趋势,华为云智能零售解决方案,在门店运营与电商业务中的技术应用与场景实践
  • Qt 自动无法加载数据库为空
  • SP20D120CTR碳化硅二极管详解:高性能与广泛应用
  • 最小二乘法MSE
  • 嵌入式开发学习———Linux环境下IO进程线程学习(三)
  • AtCoder Beginner Contest 416 C 题
  • 同质无向加权图:理论基础、算法演进与应用前沿
  • 张宇高数基础30讲与1000题学习笔记(第4-6章)
  • Node.js高并发接口下的事件循环卡顿问题与异步解耦优化方案
  • Lego-Loam TransformToStartIMU TransformToStart TransformToEnd的区别
  • 时序数据库如何高效处理海量数据
  • Node.js(四)之数据库与身份认证
  • Python 数据科学与可视化工具箱 - 数组形状操作:reshape(), flatten()
  • SpringBoot3.0+Vue3.0开源版考试系统
  • 高防服务器租用的作用都有哪些?
  • 【慕伏白】Android Studio 配置国内镜像源
  • 机器学习——基本算法
  • 理解 JavaScript 中的“ / ”:路径、资源与目录、nginx配置、请求、转义的那些事
  • 北斗变形监测技术在基础设施安全中的应用