Spring MVC HandlerMapping : BeanNameUrlHandlerMapping 源代码解析

本文解析了Spring MVC中BeanNameUrlHandlerMapping的工作原理,详细介绍了其如何通过Bean名称及其别名来映射URL路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源代码版本 : spring-webmvc-5.1.4.RELEASE

概述

BeanNameUrlHandlerMapping实现了接口HandlerMapping用于映射URL到请求处理器handler,它独特的地方在于它扫描容器中所有名称以斜杠/开头的bean,然后将这些bean名称作为URL/URL pattern和该bean形成的映射对管理起来。也就是说,它所管理的映射对是这样的 :

  • 映射对内容为 : <bean name, bean> 或者 <bean alias name, bean>
    • 因为bean可能有别名,所以会出现多个映射项映射到同一个bean的情况
  • 映射对中 bean name/bean alias name总是以斜杠/开头
    • 可以是URL,也可以是URL pattern(含通配符*)

BeanNameUrlHandlerMappingDispatcherServlet缺省使用的HandlerMapping实现,配合RequestMappingHandlerMapping使用。除此之外,开发人员可以使用SimpleUrlHandlerMapping声明式地定义请求处理器的映射。

源代码解析

package org.springframework.web.servlet.handler;

import java.util.ArrayList;
import java.util.List;

import org.springframework.util.StringUtils;

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 * 找到指定bean名称为beanName的所映射的URL或者URL pattern,要遍历该beanName以及
	 * 该 bean 的所有别名
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		if (beanName.startsWith("/")) {
			// 如果 beanName 本身就以斜杠开头,那么它自身就是需要映射的URL或者URL pattern
			urls.add(beanName);
		}
		// 获取该 bean 的所有别名
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				// 如果 该别名 以斜杠开头,那么它也是需要映射的URL或者URL pattern
				urls.add(alias);
			}
		}
		// 将 beanName 对应的 bean 的 bean 名称和别名中以`斜杠开头的那些组成一个字符串数组返回
		return StringUtils.toStringArray(urls);
	}

}

BeanNameUrlHandlerMapping其实就实现了一个方法determineUrlsForHandler,用于找到某个bean名称或者别名中所有以斜杠开头的那些然后作为一个数组返回。主要的逻辑,通过基类AbstractDetectingUrlHandlerMapping实现。

基类 AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMappingHandlerMapping接口的抽象实现,它会从应用上下文中检查所有bean定义用于检测URLbean之间的映射。这里的URL其实也可以是URL pattern,具体可以是什么,如何由bean定义计算得到,完全由实现子类决定。

package org.springframework.web.servlet.handler;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.ObjectUtils;

public abstract class AbstractDetectingUrlHandlerMapping extends AbstractUrlHandlerMapping {

	// 是否从祖先应用上下文中检测请求处理器bean,缺省为 false,也就是缺省总是只从
	// 当前HandlerMapping被定义的应用上下文中检测请求处理器bean
	private boolean detectHandlersInAncestorContexts = false;


	public void setDetectHandlersInAncestorContexts(boolean detectHandlersInAncestorContexts) {
		this.detectHandlersInAncestorContexts = detectHandlersInAncestorContexts;
	}


	/**
	 * initApplicationContext()是基类定义的一个方法,在当前 HandlerMapping 被设置 ApplicationContext 属性
	 * 时调用该方法,该方法又调用了 detectHandlers() 从应用上下文中检测URL到请求处理器bean的映射
	 * Calls the #detectHandlers() method in addition to the
	 * superclass's initialization.
	 */
	@Override
	public void initApplicationContext() throws ApplicationContextException {
		super.initApplicationContext();
		detectHandlers();
	}

	/**
	 * Register all handlers found in the current ApplicationContext.
	 * The actual URL determination for a handler is up to the concrete
	 *  #determineUrlsForHandler(String) implementation. A bean for
	 * which no such URLs could be determined is simply not considered a handler.
	 * @throws org.springframework.beans.BeansException if the handler couldn't be registered
	 * @see #determineUrlsForHandler(String)
	 */
	protected void detectHandlers() throws BeansException {
		// 获取应用上下文 , 如果应用上下文尚未设置,则抛出异常
		ApplicationContext applicationContext = obtainApplicationContext();
		// 获取应用上下文(或者 应用上下文及其祖先应用上下文)中所有的bean名称
		String[] beanNames = (this.detectHandlersInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
				applicationContext.getBeanNamesForType(Object.class));

		// Take any bean name that we can determine URLs for.
		// 遍历每一个bean名称 
		for (String beanName : beanNames) {
			// 找到每个bean名称自己以及相应的别名中的URL/URL pattern
			String[] urls = determineUrlsForHandler(beanName);
			// 如果 urls 不为空,也就是检测到该bean从定义来看应该是一个请求处理器bean,那么将它注册为一个
			// 请求处理器bean , 每个 urls 元素 和当前 bean 作为形成一个映射对被管理
			if (!ObjectUtils.isEmpty(urls)) {
				// URL paths found: Let's consider it a handler.              
				registerHandler(urls, beanName);
			}
		}

		if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
			logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
		}
	}


	/**
	 * Determine the URLs for the given handler bean.
	 * @param beanName the name of the candidate bean
	 * @return the URLs determined for the bean, or an empty array if none
	 */
	protected abstract String[] determineUrlsForHandler(String beanName);

}

AbstractUrlHandlerMapping

接下来我们看AbstractUrlHandlerMapping中跟理解BeanNameUrlHandlerMapping紧密相关的一些方法。

registerHandler

	/**
	 * Register the specified handler for the given URL paths.
	 * @param urlPaths the URLs that the bean should be mapped to
	 * @param beanName the name of the handler bean
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected void registerHandler(String[] urlPaths, String beanName) 
		throws BeansException, IllegalStateException {
		Assert.notNull(urlPaths, "URL path array must not be null");
		for (String urlPath : urlPaths) {
			// 将每个 url ,也可能是 url pattern 跟当前 bean 作为一个映射项登记
			registerHandler(urlPath, beanName);
		}
	}

	/**
	 * Register the specified handler for the given URL path.
	 * @param urlPath the URL the bean should be mapped to
	 * @param handler the handler instance or handler bean name String
	 * (a bean name will automatically be resolved into the corresponding handler bean)
	 * @throws BeansException if the handler couldn't be registered
	 * @throws IllegalStateException if there is a conflicting handler registered
	 */
	protected void registerHandler(String urlPath, Object handler) 
		throws BeansException, IllegalStateException {
		Assert.notNull(urlPath, "URL path must not be null");
		Assert.notNull(handler, "Handler object must not be null");
		Object resolvedHandler = handler;

		// Eagerly resolve handler if referencing singleton via name.
		// 需要尽早初始化请求处理器bean的情况
		if (!this.lazyInitHandlers && handler instanceof String) {
			String handlerName = (String) handler;
			ApplicationContext applicationContext = obtainApplicationContext();
			if (applicationContext.isSingleton(handlerName)) {
				resolvedHandler = applicationContext.getBean(handlerName);
			}
		}

		Object mappedHandler = this.handlerMap.get(urlPath);
		if (mappedHandler != null) {
			if (mappedHandler != resolvedHandler) {
				// 将同一个 url/url pattern 映射到多个请求处理器bean的情况是不允许的,抛出异常
				throw new IllegalStateException(
						"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
						"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
			}
		}
		else {
			if (urlPath.equals("/")) {
				// 登记根请求处理器的特殊处理
				if (logger.isTraceEnabled()) {
					logger.trace("Root mapping to " + getHandlerDescription(handler));
				}
				setRootHandler(resolvedHandler);
			}
			else if (urlPath.equals("/*")) {
				// 登记缺省请求处理器的特殊处理
				if (logger.isTraceEnabled()) {
					logger.trace("Default mapping to " + getHandlerDescription(handler));
				}
				setDefaultHandler(resolvedHandler);
			}
			else {
				// 这是登记动作
				this.handlerMap.put(urlPath, resolvedHandler);
				if (logger.isTraceEnabled()) {
					logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
				}
			}
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值