SpringFramework之HandlerMapping的俩个默认实现类的初始化

本文详细解析了Spring MVC中HandlerMapping的工作原理,包括BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping和DefaultAnnotationHandlerMapping的实现机制及用法。通过源码分析帮助理解Spring MVC的请求映射过程。

注:SpringFramework的版本是4.3.x。

1.HandlerMapping的俩个默认实现类

    们由DispatcherServlet的初始化简析得知默认的HandlerMapping是BeanNameUrlHandlerMapping和DefaultAnnotationHandlerMapping,这俩个类的继承图如下图2、图3所示,

                

                                               图2 BeanNameUrlHandlerMapping的类继承图

                 

                                             图3 DefaultAnnotationHandlerMapping的类继承图

    BeanNameUrlHandlerMapping是spring-webmvc模块的,DefaultAnnotationHandlerMapping是spring-webmvc-porlet的。我们主要分析这俩个HandlerMapping。

    BeanNameUrlHandlerMapping的用法如下,

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<bean name="/hello" class="com.mjduan.project.example8_aop.HelloController"/>

    HelloController的源码如下,

public class HelloController extends AbstractController {
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return null;
    }
}

    BeanNameUrlHandlerMapping初始化的时序图,如下图4所示:

                

                                           图4 BeanNameUrlHandlerMapping的初始化时序图

    图3的说明:由于ApplicationObjectSupport实现了ApplicationContextAware接口,所有在执行setApplicationContext的时候会初始化AbstractUrlHandlerMapping的属性handlerMap。

    图4的步骤6中,会从applicationContext中取出所有的MappedInterceptor,放到AbstractHandlerMapping的属性adaptedInterceptors中,这些MappedInterceptor是HandlerInterceptor的子类,在构造HandlerExecutionChain时用到。

    我们再来分析AbstractDetectingUrlHandlerMapping的detectHandlers方法,源码如下List-1所示,

    List-1 AbstractDetectingUrlHandlerMapping的detectHandlers()源码

protected void detectHandlers() throws BeansException {
	if (logger.isDebugEnabled()) {
		logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
	}
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
			getApplicationContext().getBeanNamesForType(Object.class));

	// Take any bean name that we can determine URLs for.
	for (String beanName : beanNames) {
		String[] urls = determineUrlsForHandler(beanName);
		if (!ObjectUtils.isEmpty(urls)) {
			// URL paths found: Let's consider it a handler.
			registerHandler(urls, beanName);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
			}
		}
	}
}

        determineUrlsForHandler方法的实现是在BeanNameUrlHandlerMapping中,源码如下List-2所示,

    List-2 BeanNameUrlHandlerMapping的determineUrlsForHandler方法源码

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<String>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = getApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}
}

    由List-1的代码可知,从applicationContext中取出所有的beanName,之后遍历所有的beanName,如果该beanName以"/"开头,则将这个beanName视为url,记录。

    一般情况下,我们不会使用BeanNameUrlHandlerMapping的,BeanNameUrlHandlerMapping使用起来感觉不是很灵活。

3.SimpleUrlHandlerMapping的用法

    SimpleUrlHandlerMapping的一般使用方式如下,prop中key的值,是spring bean。这种是以前的用法,现在基本都使用注解的方式了,很少用这种了。注:下面这段代码来源: https://blog.youkuaiyun.com/trigl/article/details/50494492

<bean
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/upfile.do">upfile</prop>
            <prop key="/upfiles.do">upfiles</prop>
            <prop key="/extjs.do">SpringMVC</prop>
            <prop key="/show.do">show</prop>
        </props>
    </property>
</bean>

4.DefaultAnnotationHandlerMapping的分析

    List-3 DefaultAnnotationHandlerMapping的initApplicationContext()源码

@Override
public void initApplicationContext() throws BeansException {
	super.initApplicationContext();
	detectHandlers();
}

/**
 * Register all handlers specified in the Portlet mode map for the corresponding modes.
 * @throws org.springframework.beans.BeansException if the handler couldn't be registered
 */
protected void detectHandlers() throws BeansException {
	ApplicationContext context = getApplicationContext();
	String[] beanNames = context.getBeanNamesForType(Object.class);
	for (String beanName : beanNames) {
		Class<?> handlerType = context.getType(beanName);
		RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
		if (mapping != null) {
			// @RequestMapping found at type level
			String[] modeKeys = mapping.value();
			String[] params = mapping.params();
			boolean registerHandlerType = true;
			if (modeKeys.length == 0 || params.length == 0) {
				registerHandlerType = !detectHandlerMethods(handlerType, beanName, mapping);
			}
			if (registerHandlerType) {
				AbstractParameterMappingPredicate predicate = new TypeLevelMappingPredicate(
						params, mapping.headers(), mapping.method());
				for (String modeKey : modeKeys) {
					registerHandler(new PortletMode(modeKey), beanName, predicate);
				}
			}
		}
		else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
			detectHandlerMethods(handlerType, beanName, mapping);
		}
	}
}

    从List-3中的detectHandlers可知,

  •     先获取applicationContext中所有的beanName,而后获取其对应的Class<?>,判断类上是否有RequestMapping注解,有的话,进行解析,之后registerHandler。
  •     若类上没有找到RequestMapping注解,则判断类上是否有Controller注解,如果有,那么执行detectHandlerMethods,这个方法的源码有点多,我只是给出部分,如下List-4所示:

    List-4 DefaultAnnotationHandlerMapping的detectHandlerMethods方法源码

protected boolean detectHandlerMethods(Class<?> handlerType, final String beanName, final RequestMapping typeMapping) {
	final Set<Boolean> handlersRegistered = new HashSet<Boolean>(1);
	Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
	handlerTypes.add(handlerType);
	handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
	for (Class<?> currentHandlerType : handlerTypes) {
		ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(Method method) {
				PortletRequestMappingPredicate predicate = null;
				String[] modeKeys = new String[0];
				String[] params = new String[0];
				if (typeMapping != null) {
					params = PortletAnnotationMappingUtils.mergeStringArrays(typeMapping.params(), params);
				}
				ActionMapping actionMapping = AnnotationUtils.findAnnotation(method, ActionMapping.class);
				RenderMapping renderMapping = AnnotationUtils.findAnnotation(method, RenderMapping.class);
				ResourceMapping resourceMapping = AnnotationUtils.findAnnotation(method, ResourceMapping.class);
				EventMapping eventMapping = AnnotationUtils.findAnnotation(method, EventMapping.class);
				RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
				if (actionMapping != null) {
					params = PortletAnnotationMappingUtils.mergeStringArrays(params, actionMapping.params());
					predicate = new ActionMappingPredicate(actionMapping.name(), params);
				}
   ......
         

    从List-4可知,detectHandlerMethods方法,对类的方法进行遍历,之后逐个处理每个方法。

    DefaultAnnotationHandlerMapping处理的就是我们平时所用的基于注解的方式。

转载于:https://my.oschina.net/u/2518341/blog/1827389

### Spring MVC 初始化流程详解 Spring MVC 的初始化流程是一个复杂的过程,涉及多个核心组件的加载和配置。以下是对其初始化流程的具体说明: #### 1. DispatcherServlet 加载 `DispatcherServlet` 是 Spring MVC 中的核心前端控制器,在应用程序启动时会自动被加载。它的加载依赖于 `web.xml` 文件中的 `<servlet>` 和 `<servlet-mapping>` 配置[^1]。 当 Web 容器(如 Tomcat)启动时,`DispatcherServlet` 会被实例化并触发其初始化逻辑。这个过程主要包括以下几个阶段: - **读取配置文件**:默认情况下,`DispatcherServlet` 会在类路径下寻找名为 `[servlet-name]-servlet.xml` 的配置文件。如果未找到该文件,则抛出异常。 - **创建上下文环境**:`DispatcherServlet` 创建一个独立的 `WebApplicationContext` 实例,并将其与根上下文(由 `ContextLoaderListener` 或者 `AnnotationConfigWebApplicationContext` 提供)关联起来[^3]。 #### 2. 注册 BeanDefinition 并扫描组件 在 `DispatcherServlet` 初始化完成后,Spring 开始注册各种 BeanDefinition 到 IoC 容器中。这一部分工作主要分为两步: - **Bean 扫描**:通过指定的基础包路径(通常是通过 `@ComponentScan` 注解或者 XML 配置实现),Spring 自动发现所有标注了特定注解(如 `@Controller`, `@Service` 等)的类并将它们注册为 Bean。 - **自定义 Bean 定义**:开发者可以在 XML 配置文件或 Java Config 类中手动声明额外的 Bean,这些也会在此阶段加入到容器中[^5]。 #### 3. 构建 URL 映射表 (HandlerMapping) URL 请求与具体处理器之间的映射关系是由一系列实现了 `HandlerMapping` 接口的对象维护的。在初始化期间,Spring 会按照优先级顺序依次尝试匹配不同的 `HandlerMapping` 实现,直到成功为止。常见的 `HandlerMapping` 包括但不限于: - **RequestMappingHandlerMapping**:用于处理基于 `@RequestMapping` 及其衍生注解的方法级别映射。 - **SimpleUrlHandlerMapping**:支持简单的字符串形式的 URL 路径映射[^2]。 每种类型的 `HandlerMapping` 对象都会构建自己的内部缓存结构——即存储着从 URL 模式到目标 handler 方法之间的一一对应关系的数据结构。 #### 4. 处理器适配器 (HandlerAdapter) 的准备 即使有了清晰的 URL-to-handler 映射规则,还需要一种机制能够实际调用对应的 controller 方法。这就是为什么需要引入 `HandlerAdapter` 这样的概念的原因所在。简单来说,每一个可能成为 handler 的对象都需要有一个专门为其定制的服务代理来完成真正的业务操作执行任务。 对于标准的 spring mvc 应用而言,默认已经内置了几种常用的 adapter ,比如针对 annotation driven controllers 设计出来的 RequestMappingHandlerAdapter 。当然也允许扩展新的 Adapter 来满足特殊需求场景下的功能开发诉求。 --- ```java // 示例代码展示如何配置 DispatcherServlet public class MyWebAppInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(AppConfig.class); ServletRegistration.Dynamic servlet = container.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); } } ``` --- ### 总结 综上所述,Spring MVC 的初始化流程涵盖了从基础设置到高级特性的全面覆盖,包括但不限于 dispatcher servlet 启动、bean factory 填充以及 url-pattern 解析等多个重要环节。只有充分理解上述各个组成部分及其相互作用原理才能更好地利用 framework 提供的强大特性去构建高效稳定的应用程序体系架构[^1].
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值