Spring学习总结(2.3)-Spring MVC:handlermapping

上篇博客讲了DispathcerServlet的流转过程以及它是如何工作的,从这篇博客开始就开始深入到DispatcherServlet的内部看看它的几个主要的组件。那么这一篇就从HandlerMapping这个组件开始学习。

HandlerMapping

首先这是一个接口,也就是可扩展。它的作用就是根据不同的请求去匹配对应的Handler,也就是根据请求匹配一个请求处理器。这个过程需要两个步骤:第一步,需要将Handler注册到HandlerMapping中;第二步,分析请求根据规则从已注册的Handler中匹配到对应的Handler,即Controller。默认情况下,SpringMvc为我们提供了几个默认的HandlerMapping的实现,通过优先级的次序决定执行的顺序。

HandlerMapping执行顺序

在基于Spring MVC的Web应用程序中,我们可以为DispatcherServlet提供多个Handler- Mapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。

SimpleUrlHandlerMapping

现在,通过这个实现类,我们来看看handlerMapping是如何注册和获取handler的。

public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {  

    private final Map<String, Object> urlMap = new HashMap<String, Object>();  

    // 通过属性配置URL到Bean名的映射
    public void setMappings(Properties mappings) {
        CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
    }  

    // 配置URL到Bean的映射
    public void setUrlMap(Map<String, ?> urlMap) {
        this.urlMap.putAll(urlMap);
    }  

    public Map<String, ?> getUrlMap() {
        return this.urlMap;
    }  

    @Override
    public void initApplicationContext() throws BeansException {
        super.initApplicationContext();
        // 初始化的时候注册处理器
        registerHandlers(this.urlMap);
    }  

    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
        // 如果配置的处理器映射为空,则警告
        if (urlMap.isEmpty()) {
            logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
        }
        else {
            // 对于没一个配置的URL到处理器的映射,如果URL不是以斜线(/)开头,则追加斜线开头,则注册处理器
            for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
                String url = entry.getKey();
                Object handler = entry.getValue();
                // Prepend with slash if not already present.
                if (!url.startsWith("/")) {
                    url = "/" + url;
                }
                // Remove whitespace from handler bean name.
                if (handler instanceof String) {
                    handler = ((String) handler).trim();
                }
                registerHandler(url, handler);
            }
        }
    }  

}  

首先,它通过一个hashmap来存储请求和controller的对应关系,也就是保存注册信息。类都被Ioc容器管理者,HandlerMapping管理的是对应关系。其中key是http请求的path信息,value可以是一个字符串,或者是一个处理请求的HandlerExecutionChain,如果是String类型,则会将其视为Spring的bean名称。在HandlerMapping对象的创建中,IoC容器执行了一个容器回调方法setApplicationContext,在这个方法中调用initApplicationContext方法进行初始化,各个子类可以根据需求的不同覆写这个方法。关于handlerMap信息的注册就是在initApplicationContext方法中被执行的。

取得Handler的方法在他的父类中,源码如下:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
            //查找符合匹配规则的handler。可能的结果是HandlerExecutionChain对象或者是null
    Object handler = lookupHandler(lookupPath, request);
            //如果没有找到匹配的handler,则需要处理下default handler
    if (handler == null) {
        // We need to care for the default handler directly, since we need to
        // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
        Object rawHandler = null;
        if ("/".equals(lookupPath)) {
            rawHandler = getRootHandler();
        }
        if (rawHandler == null) {
            rawHandler = getDefaultHandler();
        }
                    //在getRootHandler和getDefaultHandler方法中,可能持有的是bean name。
        if (rawHandler != null) {
            // Bean name or resolved handler?
            if (rawHandler instanceof String) {
                String handlerName = (String) rawHandler;
                rawHandler = getApplicationContext().getBean(handlerName);
            }
            validateHandler(rawHandler, request);
            handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
        }
    }
            //如果handler还是为空,则抛出错误。
    if (handler != null && this.mappedInterceptors != null) {
        Set<HandlerInterceptor> mappedInterceptors =
                this.mappedInterceptors.getInterceptors(lookupPath, this.pathMatcher);
        if (!mappedInterceptors.isEmpty()) {
            HandlerExecutionChain chain;
            if (handler instanceof HandlerExecutionChain) {
                chain = (HandlerExecutionChain) handler;
            } else {
                chain = new HandlerExecutionChain(handler);
            }
            chain.addInterceptors(mappedInterceptors.toArray(new HandlerInterceptor[mappedInterceptors.size()]));
        }
    }
    if (handler != null && logger.isDebugEnabled()) {
        logger.debug("Mapping [" + lookupPath + "] to handler '" + handler + "'");
    }
    else if (handler == null && logger.isTraceEnabled()) {
        logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    return handler;
} 

接口定义

看过了这些之后我们再回头看看HandleraMapping接口的定义:

public interface HandlerMapping {

    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    public abstract HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

眼尖的同学可能就要说了,这个接口下取得handler的方法怎么返回值是HandlerExecutionChain而不是一个handler呢?HandlerExecutionChain这个类,从名字可以直观的看得出,这个对象是一个执行链的封装。熟悉Struts2的都知道,Action对象也是被层层拦截器包装,这里可以做个类比,说明SpringMVC确实是吸收了Struts2的部分设计思想。大家可以再看看源码,太长了我就不贴了。从源码中可以看出,一个实质执行对象,还有一堆拦截器。这不就是Struts2中的实现么,SpringMVC没有避嫌,还是采用了这种封装。得到HandlerExecutionChain这个执行链(execution
chain)之后,下一步的处理将围绕其展开。至此,HandlerExecutionChain整个执行脉络也就清楚了:在真正调用其handler对象前,HandlerInterceptor接口实现类组成的数组将会被遍历,其preHandle方法会被依次调用,然后真正的handler对象将被调用。

小结:这里主要的内容就是注册和获取的两个过程,以及SpringMvc对handler的一些封装。认真的读读源码,就能很清晰的看到这个类的整个执行过程。加深对SpringMvc的执行过程的了解,感觉还是相当不错的。

时间: 2024-11-13 07:56:06

Spring学习总结(2.3)-Spring MVC:handlermapping的相关文章

《Spring学习笔记》:Spring、Hibernate、struts2的整合(以例子来慢慢讲解,篇幅较长)

<Spring学习笔记>:Spring.Hibernate.struts2的整合(以例子来慢慢讲解,篇幅较长) 最近在看马士兵老师的关于Spring方面的视频,讲解的挺好的,到了Spring.Hibernate.struts2整合这里,由于是以例子的形式来对Spring+Hibernate+struts2这3大框架进行整合,因此,自己还跟着写代码的过程中,发现还是遇到了很多问题,因此,就记录下. 特此说明:本篇博文完全参考于马士兵老师的<Spring视频教程>. 本篇博文均以如下这

Spring学习笔记一(Spring核心思想)

通过学习<Spring in action (Third edition)>的第一章,我大概了解了Spring的基本思想: 1,依赖注入(Dependnecy Injection): 在不使用Spring框架的情况下,一个类要跟另一个类建立联系,可能会使用如下的模式: class A{...} class B{ private A a; ...       } 这样的话,每次实例化一个B的对象,如b1,必定实例化一个A的对象,如a1,并且b1和a1是紧耦合的,b1牢牢地和a1绑定在一起了.他们

spring学习二:根据spring原理自己写个spring

请先看我另一篇文章:"Spring学习一:IOC(控制反转)和AOP(面向切面)的xml配置和注解方式"中大概知道他的简单用法 那我自己想写一个简单sping,注解的方式以后再写 方式:1.解析xml配置 2.使用java的反射机制生产动态代理对象 3.扫描类上的注解,再用反射(没写) 代码如下(简单实现,重原理轻代码,不喜勿喷) xml配置我就直接用我上一篇spring-test的配置了,代码也用上一篇的,但解析的时候是用自己写的,没有用调用任何spring API代码 <?x

Spring学习系列笔记之Spring心得

1 解释一下Dependency injection(DI依赖注入)和IOC(Inversion of control,控制反转)? 答:1.1:Dependency injection(DI依赖注入): 就是说将一个目标与目标对象之间的依赖通过Spring容器来实现注入,这样使得与传统的通过自己写代码来创建对象的方式完全颠倒过来了,这就是控制反转的由来吧. 我觉得用口头举例子来说明DI更方便,更清晰.通过DI机制,降低了替换业务对象的复杂性. 依赖注入主要有两种方式来实现: a:设值注入.  

spring学习(一)spring简介

Spring简介: Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程). 什么是框架: 框架:是能完成一定功能的半成品. 框架能够帮助我们完成的是:项目的整体框架.一些基础功能.规定了类和对象如何创建,如何协作等,当我们开发一个项目时,框架帮助我们完成了一部分功能,我们自己再完成一部分,那这个项目就完成了

Spring学习(八)spring整合struts2

一.spring框架对struts等表现层框架的整合原理 : 使用spring的ioc容器管理struts中用于处理请求的Action 将Action配置成ioc容器中的bean 延伸:spring对持久层框架/技术的整合原理 (封装) : 提供模板类封装对应技术/框架的开发流程 通过对模板类的使用,实现对传统开发流程的"代替". 二.整合方式: 插件方式 struts2为了实现对spring框架整合,也提供了一个插件的配置文件struts-plugin.xml struts2-spr

Spring学习(2)Spring 常用注解

Spring 常用注解 使用注解来构造IoC容器 用注解来向Spring容器注册Bean.需要在applicationContext.xml中注册<context:component-scanbase-package="pagkage1[,pagkage2,-,pagkageN]"/>. 如:在base-package指明一个包 1 <context:component-scan base-package="cn.gacl.java"/> 表

Spring学习(九)--Spring的AOP

1.配置ProxyFactoryBean Spring IOC容器中创建Spring AOP的方法. (1)配置ProxyFactoryBean的Advisor通知器 通知器实现定义了对目标对象进行增强的切面行为,即Advice通知. (2)定义ProxyFactoryBean类 设定实现AOP的重要属性,如proxyInterface.interceptorNames.target等. interceptorNames:需要定义的通知器,通知器在proxyFactoryBean的配置下,通过代

Spring学习总结(2.3)-Spring MVC:handlerAdapter

前面一篇博客介绍了HandlerMapping这个组件,它负责的是定位请求处理器Handler.这是SpringMvc处理流程的第二步.那么,当定位到Handler之后,DispatcherServlet会将得到的Handler告知HandlerAdapter,HandlerAdapter再根据请求去定位请求的具体处理方法是哪一个. 职责 在HandlerMapping返回处理请求的Controller实例后,需要一个帮助定位具体请求方法的处理类,这个类就是HandlerAdapter,Hand

spring学习(一)—— spring mvc

目的 了解Spring mvc 学习对象 https://github.com/spring-projects/spring-mvc-showcase 学习收获 1. 自定义注解 // 以下是注解接口Target(value={ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MaskFor