Spring MVC 第三篇 - 关于处理器映射器 RequestMappingHandlerMapping

目录

前言

RequestMappingHandlerMapping 什么时候被加载进容器?

        Spring MVC 创建容器和实例化 bean 的流程               ​ 

        命名空间处理器 NamespaceHandler

RequestMappingHandlerMapping 什么时候保存的请求和处理器的映射关系?

再谈请求达到服务端时如何找到对应的 HandlerMethod


前言

        处理器映射器保存了请求和处理器的映射关系,在 Spring MVC 项目中,每当一个请求到达的时候,都需要根据请求找到与之匹配的处理器。处理器映射器就是来做这个中介的,交由 HandlerMapping 一个 Request 请求,它就能返给给你一个处理器,然后就可以执行目标方法了。而我们目前最常用的就是使用 @Controller 配合 @RequestMapping 来编写给外部提供的接口方法,这种方式对应的处理器映射器就是 RequestMappingHandlerMapping,所以本文就来探讨一下 RequestMappingHandlerMapping 到底是怎么工作的。

RequestMappingHandlerMapping 什么时候被加载进容器?

        我们知道,在 Spring 项目中,要想使用一个对象的功能,一般都是要先将其实例化保存进容器,然后当我们需要用的时候再从容器中取出来,那么 RequestMappingHandlerMapping 什么时候被实例化并装进容器里的呢?

        首先我们知道需要在 Spring MVC 的配置文件中加上下面的一行配置才能使 Spring MVC 容器加载到 RequestMappingHandlerMapping

<mvc:annotation-driven></mvc:annotation-driven>

        前面的文章讲了 Tomcat 如何加载 Spring 容器和 Spring MVC 的容器,链接如下Tomcat 什么时候加载 Spring 的容器?但是该文章只是讲到把 Spring 上下文创建出来这一步,很重要的 wac.refresh() 方法却没有讲,因为这个方法是 Spring 的核心方法,涉及的功能点很多,所以本文只列出跟 Spring MVC 流程相关的一些内容。

        Spring MVC 创建容器和实例化 bean 的流程                

                从上面流程可以看到,Spring MVC 创建好容器之后就会读取 DispatcherServelt 配置的配置文件的内容,然后通过不同的 bean定义解析器来解析 bean 定义,最终通过获取到的 bean 定义再调用 getBean 方法来实例化 bean 并放入容器中。

        命名空间处理器 NamespaceHandler

                上面流程图画到 bean 定义解析器就没有往下走了,不同的节点类型对应不同的解析器,比如<context:component-scan>对应 ComponentScanBeanDefinitionParser 类型的解析器,<mvc:annotation-driven> 对应 AnnotationDrivenBeanDefinitionParser 类型的解析器,怎么知道这一点的呢?就在于上图中的 namespaceHandler.init(), 不同的命名空间处理器有对应不同的初始化方法,我们举例看一下

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
		registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
		registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
		registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
		registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
	}

}
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}

}

        既然我们是要本文是要研究 RequestMappingHandlerMapping 是怎么实例化的,那么就得看一下 AnnotationDrivenBeanDefinitionParser 的 parse 方法。

public BeanDefinition parse(Element element, ParserContext context) {
		Object source = context.extractSource(element);
		XmlReaderContext readerContext = context.getReaderContext();

		CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
		context.pushContainingComponent(compDefinition);

		RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
        // 这里,这里,看这里呀~~~~~~
		RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
		handlerMappingDef.setSource(source);
		handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		handlerMappingDef.getPropertyValues().add("order", 0);
		handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

		if (element.hasAttribute("enable-matrix-variables")) {
			Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
			handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
		}

		configurePathMatchingProperties(handlerMappingDef, element, context);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

		RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
		handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

		RuntimeBeanReference conversionService = getConversionService(element, source, context);
		RuntimeBeanReference validator = getValidator(element, source, context);
		RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

		RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
		bindingDef.setSource(source);
		bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		bindingDef.getPropertyValues().add("conversionService", conversionService);
		bindingDef.getPropertyValues().add("validator", validator);
		bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

		ManagedList<?> messageConverters = getMessageConverters(element, source, context);
		ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
		ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
		String asyncTimeout = getAsyncTimeout(element);
		RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
		ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
		ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");

		RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
		handlerAdapterDef.setSource(source);
		handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
		handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
		addRequestBodyAdvice(handlerAdapterDef);
		addResponseBodyAdvice(handlerAdapterDef);

		if (element.hasAttribute("ignore-default-model-on-redirect")) {
			Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
			handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
		}
		if (argumentResolvers != null) {
			handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		if (asyncTimeout != null) {
			handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
		}
		if (asyncExecutor != null) {
			handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
		}

		handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
		handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);

		RootBeanDefinition uriContributorDef =
				new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
		uriContributorDef.setSource(source);
		uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
		uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
		String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
		readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);

		RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
		csInterceptorDef.setSource(source);
		csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
		RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
		mappedInterceptorDef.setSource(source);
		mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
		String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);

		RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
		methodExceptionResolver.setSource(source);
		methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
		methodExceptionResolver.getPropertyValues().add("order", 0);
		addResponseBodyAdvice(methodExceptionResolver);
		if (argumentResolvers != null) {
			methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);

		RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
		statusExceptionResolver.setSource(source);
		statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		statusExceptionResolver.getPropertyValues().add("order", 1);
		String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);

		RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
		defaultExceptionResolver.setSource(source);
		defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		defaultExceptionResolver.getPropertyValues().add("order", 2);
		String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

		context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
		context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
		context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
		context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
		context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));

		// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
		MvcNamespaceUtils.registerDefaultComponents(context, source);

		context.popAndRegisterContainingComponent();

		return null;
	}

        上面的代码比较简单,主要就是实例化bean定义,然后注册 bean 定义,这些之前的文章就讲过了。而且我们会发现,加上 <mvc:annotation-driven> 配置之后不止会注册 RequestMappingHandlerMapping 的 bean 定义,还会有 RequestMappingHandlerAdapter,DefaultHandlerExceptionResolver 等。如此一来 bean 定义就有了,那么根据 bean 定义再去实例化 bean 就轻而易举了。

RequestMappingHandlerMapping 什么时候保存的请求和处理器的映射关系?

        相信看过前面讲 Controller 方法执行流程的同学已经知道 RequestMappingHandlerMapping 的父类 AbstractHandlerMethodMapping 有一个 MappingRegistry 类型的私有变量,它保存了请求信息和处理器的映射关系,当请求过来的时候,通过这些映射关系来找到匹配的 handlerMethod。

class MappingRegistry {

		private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

		private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

		private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

		private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

		private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

		private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        //.... 省略下面的代码
}

        我们目前知道怎么用这些映射关系,可是他们是什么时候赋值的呢?怎么就把对应的映射关系给填到对应的 Map 里去了呢?

        其实还是要从 RequestMappingHandlerMapping 入手,我们知道 bean 创建之后如果实现了InitializingBean 接口,会调用重写的 afterPropertiesSet 初始化方法,那么就从这个初始化方法来跟踪一下执行流程。

        上述过程大概可以分为以下几步

  1. 获取到 Spring MVC 容器中所有的 bean 的名称。
  2. 遍历所有在第一步中找到的 beanName, 过滤出类信息上包含 @Controller 或者 @RequestMapping 的 beanName。
  3. 通过 beanName 获取对应的类信息,然后通过反射获取到类的所有方法
  4. 如果方法上标有 @RequestMapping 注解,则根据注解配置的信息构造 RequestMappingInfo 对象。
  5. 以 Method 和 RequestMappingInfo 对应关系为依据,解析生成AbstractHandlerMethodMapping 的 MappingRegistry 对象所需要的映射关系。

        上述的5步除了最后一步之外都已经在流程图里体现了,现在再看一下最后一步执行的 register 方法。

public void register(T mapping, Object handler, Method method) {
			// Assert that the handler method is not a suspending one.
			if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
				Class<?>[] parameterTypes = method.getParameterTypes();
				if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
					throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
				}
			}
			this.readWriteLock.writeLock().lock();
			try {
                // 创建 handlerMethod 对象
				HandlerMethod handlerMethod = createHandlerMethod(handler, method);
				validateMethodMapping(handlerMethod, mapping);
                // 保存 requestMappingInfo 和 handlerMethod 的映射关系
				this.mappingLookup.put(mapping, handlerMethod);
                // 获取配置的路径信息,@RequestMapping 可以配置多个
				List<String> directUrls = getDirectUrls(mapping);
				for (String url : directUrls) {
                    // 构造请求路径和 requestMappingInfo 的映射关系
					this.urlLookup.add(url, mapping);
				}

				String name = null;
				if (getNamingStrategy() != null) {
                    // 根据类名称和方法名称按照特定规则生成一个名字
					name = getNamingStrategy().getName(handlerMethod, mapping);
                    // 构造名字和 handlerMethod 的映射关系
					addMappingName(name, handlerMethod);
				}

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

        上面代码其实很简单,也加了一些注释,相信大家看到这里就知道各个处理器与方法的映射关系是怎么来的了。

再谈请求达到服务端时如何找到对应的 HandlerMethod

        我们从 AbstractHandlerMapping.getHandler(HttpServletRequest request) 方法开始再分析一下。

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		Object handler = getHandlerInternal(request);
		// 后面的都省略
	}

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		try {
            // 调用父类的方法
			return super.getHandlerInternal(request);
		}
		finally {
			ProducesRequestCondition.clearMediaTypesAttribute(request);
		}
	}

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // 从 request 对象中获取到请求路径
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		request.setAttribute(LOOKUP_PATH, lookupPath);
		this.mappingRegistry.acquireReadLock();
		try {
            // 由请求路径获取 handlerMethod
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}
    
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
        // 先通过请求路径把 requestMappingInfo 拿出来,底层就是用的 this.urlLookup.get(urlPath);
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
            // 通过 requestMappingInfo 信息来获取 Match 对象
			addMatchingMappings(directPathMatches, matches, request);
		}
		// 省略后面的代码
	}

    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
		for (T mapping : mappings) {
            // 将刚刚获取到的 requestMappingInfo 跟 request 对比,看看参数,请求头之类的是否能匹配,如果匹配的话,则继续往下走,否则返回空
			T match = getMatchingMapping(mapping, request);
			if (match != null) {
                // 通过 requestMappingInfo 在 mappingLookup 这个 Map 里寻找对应的 HandlerMethod
				matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
			}
		}
	}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC中,处理器映射器Handler Mapping)是一个重要的组件,它负责将请求映射到对应的控制器方法上。处理器映射器的作用是根据请求的URL路径或其他条件,确定应该由哪个控制器方法进行处理。 在Spring MVC中,有多种类型的处理器映射器可供选择,包括默认的RequestMappingHandlerMappingHandlerMapping。默认的RequestMappingHandlerMapping是最常用的处理器映射器,它通过注解(如@RequestMapping)来进行请求映射。而HandlerMapping是一个接口,可以自定义实现。 处理器映射器的主要功能是根据请求的URL路径和请求方法等信息,将请求映射到对应的控制器方法上。它会根据配置的映射规则,匹配请求的URL路径,并找到合适的控制器方法来处理请求。处理器映射器可以支持通配符和正则表达式等方式进行路径匹配,以提供更灵活的映射规则。 处理器映射器的配置通常是在Spring MVC的配置文件中进行的,可以通过注解或XML配置的方式来定义映射规则。在配置中,可以指定请求路径、请求方法、请求参数等条件,以及对应的控制器方法。这样,当有请求到达时,处理器映射器就会根据配置的规则找到合适的控制器方法来处理请求。 总结一下,处理器映射器Spring MVC中起到了关键的作用,它负责将请求映射到对应的控制器方法上。通过配置不同的映射规则,可以实现灵活的请求映射方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [SpringMVC](https://blog.csdn.net/xmcxmc___/article/details/125322377)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [Spring MVC+MyBatis开发从入门到项目实战](https://download.csdn.net/download/shuishanshu30/11133663)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值