美文网首页
Spring MVC 源码阅读-框架启动过程

Spring MVC 源码阅读-框架启动过程

作者: 听歌闭麦开始自闭 | 来源:发表于2019-02-14 01:01 被阅读0次

Spring Web MVC的文档

本文档基于Spring 5.1.4.RELEASE版本进行编写,高版本的代码中会多出了一些关于WebFlux技术的内容。

Spring Web MVC是基于Servlet API构建的原始Web框架, 从一开始就包含在Spring Framework中。
正式名称"Spring Web MVC"来自其源模块(spring-webmvc)的名称,但它通常被称为"Spring MVC"。

Spring MVC是基于DispatcherServlet展开的(Spring WebFluxDispatcherHandler.)

DispatcherServlet类关系结构图.png

1 Spring MVC的配置

Spring MVC有多种配置方式
1.web.xml方式配置
2.Java方式配置 【多种写法但都与ServletContainerInitializer有关】
关于使用Java代码的配置方式除了下例,还可以通过继承 AbstractContextLoaderInitializer 或者它的两个子类来完成配置,这里不做展示。

这里展示Spring MVC的2种配置方式,下文是根据展示的Xml形式讲解的

<servlet>
    <servlet-name>SpringMVCConfig</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/spring-mvc-config.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>SpringMVCConfig</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("classpath:config/spring-mvc-config.xml");

        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletContext.addServlet("SpringMVCConfig", servlet);
        registration.setLoadOnStartup(1);        
        registration.addMapping("/");
    }
}

2 Spring MVC 框架启动过程分析

最开始的图中可以看出DispatcherServlet继承自GenericServlet,那么它就会遵守Servlet的生命周期进行运作。
由于在web.xml中添加了DispatcherServlet类Servlet节点,所以Servlet容器在启动时会调用DispatcherServlet#init(ServletConfig)来实例化DispatcherServlet对象,这是Servlet技术的逻辑。

2.1 DispatcherServlet类的构造方法

public DispatcherServlet() {
    super();
    setDispatchOptionsRequest(true);
}

public DispatcherServlet(WebApplicationContext webApplicationContext) {
    super(webApplicationContext);
    setDispatchOptionsRequest(true);
}

// FrameworkServlet#setDispatchOptionsRequest(boolean)
public void setDispatchOptionsRequest(boolean dispatchOptionsRequest) {
    this.dispatchOptionsRequest = dispatchOptionsRequest;
}

2.2 DispatcherServlet类的分析

依照类关系图从下向上寻找init()方法,最终会在HttpServletBean类中找到。

注意:
1.init()方法是GenericServlet类提供的,会被init(ServletConfig)主动调用,Servlet接口中并没有init()方法的声明。
2.init()方法内的逻辑代表着Spring MVC的启动过程,init()方法执行完成代表着Spring MVC框架的启动完成

// HttpServletBean
@Override
public final void init() throws ServletException {

    // Set bean properties from init parameters.
    // 获取web.xml中的<init-param>参数
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            /* 
            这里的 this 是 DispatcherServlet的实例对象。
            通过 BeanWrapper 将 web.xml中 DispatcherServlet里的 <init-param>所配置的内容填充到 DispatcherServlet的实例对象内,
            也就是将名为 contextConfigLocation 的属性值填充到 DispatcherServlet对象内 (这个属性定义在FrameworkServlet类中)。

            BeanWrapper 在这可以简单理解为类似通过反射将属性填充到对象内
            */
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);  // 一个空钩子,也就是说内部没任何逻辑
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            if (logger.isErrorEnabled()) {
                logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            }
            throw ex;
        }
    }

    // Let subclasses do whatever initialization they like.
    // (让子类自己定义过程) 我们主要看这里
    initServletBean();
}

// FrameworkServlet
@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 这里是关注点
        // 最终将处理过的ApplicationContext赋给FrameworkServlet类中的属性
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();  // 这是个钩子, 空实现
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }

    // ... 一些日志操作
}

先看一下initWebApplicationContext()方法的实现

这个方法的主要作用有2个
1.获取或创建出Spring MVC的Application,然后整合Spring MVC和Spring Core的ApplicaitonContext
2.调度起来整个Spring MVC框架功能(onRefresh方法)

protected WebApplicationContext initWebApplicationContext() {
    /*
    这行代码是去获取ContextLoaderListener运行过程中存入到ServletContext中的ApplicationContext,存储的过程是Spring Core的工作逻辑。
    存入的值存在3种情况: 
        1.null                                            代表Spring Core没有被使用(没有使用Spring的监听器)
        2.一个可以用的ApplicationContext对象               代表Spring Core逻辑执行正常
        3.一个异常对象(在getWebApplicationContext中)       代表Spring Core逻辑执行失败,getWebApplicationContext方法逻辑中抛出了异常

    将 Spring Core 的 ApplicationContext 作为 RootContext,以使 Spring Core 和 Spring MVC 功能整合在一起。
    */
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // webApplicationContext就是通过DispatcherServlet构造函数传入的,这个ApplicationContext是Spring MVC自己的
    // 如果使用web.xml方式配置,那么这个判断就不会成立。
    if (this.webApplicationContext != null) {
        // 有就直接用
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 这里显式的将Spring Core 的 ApplicationContext设置为父级
                    cwac.setParent(rootContext);
                }
                // 对 Spring MVC 的 ApplicationContext进行一些其他属性设置, 最后执行AbstractApplicationContext#refresh()
                // 包括将 Servlet的相关内容(ServletContext、ServletConfig)交给ApplicationContext
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        /*
        如果在构造DispatcherServlet时没有传入ApplicationContext实例,那么查看在ServletContext中有没有。

        在 web.xml 中以如下形式在DispatcherServlet的配置中设置
        <init-param>
            <param-name>contextAttribute</param-name>
            <param-value>qeqeqe</param-value>
        </init-param>
        这里的qeqeqe是随意命名的,最终会使用这个值前往ServletContext中找属性。
        也就是说你得有个地方写了如下这样的代码而且这个类要比DispatcherServlet先运行,这个findWebApplicationContext()的逻辑才能成功。
        getServletContext().setAttribute("qeqeqe", xmlWebApplicationContext);

        目前尚为体验到过什么时候该用这种形式
        */
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 经过上面步骤如果还没有,那么就自己创建一个,将Spring Core的 ApplicationContext作为 RootContext
        // 默认创建的 ApplicationContext 是 XmlWebApplicationContext 类型的
        // 其中会执行AbstractApplicationContext#refresh()方法
        wac = createWebApplicationContext(rootContext);
    }
    // refreshEventReceived 默认为 false,用于检测onRefresh是否已经被调用的标志。
    if (!this.refreshEventReceived) {
        // 要么上下文不是具有刷新支持的ConfigurableApplicationContext,要么在构建时注入的上下文
        // 已经被刷新 ->手动在这里触发初始onRefresh。
        synchronized (this.onRefreshMonitor) {
            // 这里是Spring MVC 逻辑的一个重点。
            // 该方法被 DispatcherServlet重写,用来加载一些指定的功能逻辑模块,例如: Spring MVC映射路径解析、文件上传、视图解析器等
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // 将Spring MVC的 ApplicationContext 发布为 ServletContext属性。和上面 findWebApplicationContext() 里要找的没有关系。
        // attrName -> FrameworkServlet.class.getName() + ".CONTEXT." + getServletName()
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

上面代码中有两次提到了对 AbstractApplicationContext#refresh() 方法的调用,这个方法在Spring框架的逻辑中是十分重要的,Spring 就是通过这个方法,在IOC容器内完成所有的 bean 的创建以及初始化的。
值得注意的是,refresh()的调用是先于onRefresh(ApplicationContext)的,这就保证了之后Spring MVC初始化策略时逻辑的正确性。
关于该方法的具体逻辑已经脱离本文的主题,这里不进行深究,在Spring的文章中有对它进行说明

ApplicationContext 是一种 IOC 容器,Spring 提供了两种 IOC 容器,另一种就是 BeanFactory,而 ApplicationContext 是 BeanFactory 的子接口,它添加了更多的支持。

我们这里看一下onRefresh(wac)的实现,由于子类重写了该方法,我们跟回到DispatcherServlet中查看

这里是Spring MVC 逻辑的一个重点,负责初始化各种Spring MVC的策略模块。
不同策略的作用在代码注释中进行了简单说明。

// DispatcherServlet
@Override
protected void onRefresh(ApplicationContext context) {
    // 这个上下文携带了父级上下文
    initStrategies(context);
}

// 方法名中带有 Handler 的都与 Controller 有关,可以把 Controller 视为 Handler
protected void initStrategies(ApplicationContext context) {
    // 初始化文件上传处理
    initMultipartResolver(context);
    // 初始化本地化(国际化)解析器,提供国际化支持
    initLocaleResolver(context);
    // 初始化主题处理(与css样式有关系)---我没用过
    initThemeResolver(context);
    // 初始化Handler映射    (用来解析Controller中配置的RequestMapping与方法的映射等相关内容)
    initHandlerMappings(context);
    // 初始化Handler适配器     (用来处理Controller中方法的参数与返回值等相关内容,配合@ControllerAdvice使用)
    initHandlerAdapters(context);
    // 初始化Handler异常处理     (用来处理Controller中方法的异常等相关内容,配合@ControllerAdvice使用)
    initHandlerExceptionResolvers(context);
    // 初始化请求至视图名转换
    initRequestToViewNameTranslator(context);
    // 初始化视图解析器
    initViewResolvers(context);
    // 初始化flash映射管理器(和跳转路由有关)
    initFlashMapManager(context);
}

2.2.1 九类策略

这些策略每种的作用都不一样,但有些策略并不是很常用,所以描述时会跳过一些我不常用的。

DispatcherServlet.properties文件中存放了部分策略的默认值,如果不想使用这些默认实现,可以通过注册同类型的Bean来使用自定义的实现。

2.2.1.1 DispatcherServlet.properties 文件全貌

这个图片是高版本Spring的,里面每一项都可能多了一些默认值,可以自己去旧文件里看看。

DispatcherServlet.properties 文件全貌.png

2.2.1.2 MultipartResolver

MultipartResolver 是用于处理文件上传的解析器,没有默认实现,当使用时需要手动配置相应的 Bean

private void initMultipartResolver(ApplicationContext context) {
        // 首先尝试从IOC容器中确认是否存在对应的Bean,如果没有就设置为null,没有默认值。
    try {
        // MULTIPART_RESOLVER_BEAN_NAME 的值为 multipartResolver
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        // ... 日志代码
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        // ... 日志代码
    }
}
在配置文件中添加如下代码可以启用

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

MultipartResolver 有两个实现,分别为 CommonsMultipartResolverStandardServletMultipartResolver
前者使用时需要引入一个名为commons-fileupload的依赖,而后者不需要。
Spring MVC Multipart Resolver 官方文档

2.2.1.3 LocaleResolver

LocaleResolver是用于处理本地化/国际化解析器,默认实现为AcceptHeaderLocaleResolver
处理时从 request 中解析出来对应的 Locale 对象,并在请求响应式将该对象交给 response,保存到指定的域中。

private void initLocaleResolver(ApplicationContext context) {
    try {
        // LOCALE_RESOLVER_BEAN_NAME 的值为 localeResolver
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        // ... 日志打印
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        // 前往 DispatcherServlet.properties 文件内找到 LocaleResolver 的默认值,并将其实例化,默认为 AcceptHeaderLocaleResolver
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        // ... 日志打印
    }
}
在配置文件中添加如下代码可以更换LocaleResolver

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>

默认的AcceptHeaderLocaleResolver实现对应的是 Http header 中的 accept-language
LocaleResolver一共提供了4种实现,其他3种分别是:
CookieLocaleResolverSessionLocaleResolverFixedLocaleResolver

2.2.1.4 HandlerMappings

关于Spring MVC的主逻辑,更应该较关注initHandlerMappings(context);,所以看下它的操作.

HandlerMappings是用于解析路径映射的处理策略。
在初始化时,会尝试从Controller中解析出来所有合法的@RequestMapping它们映射到的方法,并将它们的关联信息保存到相应的处理策略中。

Handler可视为控制器,也就是以@Controller或者@RequestMapping注解的类。
HandlerMathod是对映射到的方法的包装。
RequestMappingInfo是对@RequestMapping内容的包装。

// DispatcherServlet
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {  // 默认情况下为true
        // 从ApplicationContext容器(包括父容器)中查找所有实现HandlerMapping接口的类。
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 保证HandlerMappings是有序的。
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 从 ApplicationContext 中获取 beanName 为 handlerMapping 的且类型是 HandlerMapping 的单例
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ignore) {
        }
    }

    if (this.handlerMappings == null) {
        // 主要看这里
        /*
        如果没有找到,则从DispatcherServlet.properties 文件中拿出默认值,并将其实例化。
        默认值有3个,分别是
        1.BeanNameUrlHandlerMapping
        2.RequestMappingHandlerMapping
        3.RouterFunctionMapping
        */
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }

        /* 
        怎么说呢,如下代码在早期的Spring中是没有的,这东西使用Spring WebFlux或者Spring Boot时才会用到,
        而使用传统的基于web.xml与xml配置文件使用Spring MVC时是用不到的,这就是在本文中是用不到的。
        */
        for (HandlerMapping mapping : this.handlerMappings) {
            if (mapping.usesPathPatterns()) {
                this.parseRequestPath = true;
                break;
            }
        }
    }
}

根据上述代码,在默认情况下我们从配置文件中得到3个HandlerMapping的实现
1.BeanNameUrlHandlerMapping
2.RequestMappingHandlerMapping
3.RouterFunctionMapping(这个在早期是没有的,是WebFlux的东西,这里不管)

HandlerMapping类结构关系.png

到这里,initHandlerMappings方法的逻辑实际上已经执行完成了,下面的分析则是更深入的对HandlerMappings的工作逻辑进行说明。

2.2.1.4.1 关于 InitializingBean

InitializingBean是一个接口,内部只声明了一个方法(afterPropertiesSet()),在Spring技术初始化bean的时候,如果该bean实现了InitializingBean接口,那么会自动调用其内部方法的实现。

在Spring技术的xml配置文件中,也有能达到类似效果的方法(init-method),即如下代码:

<bean id="indexController" class="controller.IndexController" init-method="initTest" destroy-method="destroyTest"/>

如果这两种方式以正确的方式同时使用,那么InitializingBean的逻辑先于init-method发生,
原因可查阅AbstractAutowireCapableBeanFactory#invokeInitMethods(String, Object, RootBeanDefinition)

补充: init-method是通过反射调用的,而afterPropertiesSet是方法调用。

2.2.1.4.2 RequestMappingHandlerMapping

HandlerMapping类结构关系图中可以观察到BeanNameUrlHandlerMapping是很普通的继承关系,而RequestMappingHandlerMappingInitializingBean接口有关。
进入RequestMappingHandlerMapping类中可以看看它里面的afterPropertiesSet方法。

// RequestMappingHandlerMapping
@Override
public void afterPropertiesSet() {
    // 创建一个 RequestMappingInfo.BuilderConfiguration() 实例对象,并为其设置一些属性    
    this.config = new RequestMappingInfo.BuilderConfiguration(); // 如果想看如何使用的,可以前往该类的createRequestMappingInfo方法
    this.config.setUrlPathHelper(getUrlPathHelper()); // 默认UrlPathHelper的实例(AbstractHandlerMapping类中声明)
    this.config.setPathMatcher(getPathMatcher()); // 默认AntPathMatcher的实例(AbstractHandlerMapping类中声明)
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); // 默认true
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); // 默认true
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); // 默认false
    this.config.setContentNegotiationManager(getContentNegotiationManager()); // 默认ContentNegotiationManager的实例
    // 主要看这里
    super.afterPropertiesSet();
}

RequestMappingInfo.BuilderConfiguration是一个用于 请求映射目的 的配置选项的容器,在创建RequestMappingInfo实例的时候需要这些配置。
在上述代码中设置的属性的含义如下:
1.UrlPathHelper:默认为UrlPathHelper类的实例,用于URL路径匹配,被AbstractUrlHandlerMappingRequestContext用于路径匹配 和/或 URI确定。
2.AntPathMatcher:默认为AntPathMatcher类的实例,用于匹配路径中的通配符,是一种ant-style路径模式的路径匹配器实现。
~| ant-style 路径模式 是Spring参考的Apache Ant项目里面的路径风格,是Spring普遍使用的通配符匹配方式。
3.SuffixPatternMatch:后缀匹配模式,用于能以 .xxx 结尾的方式进行匹配,默认为true。
~| 举例:假设有一个这样的路径映射:@GetMapping("/a")
SuffixPatternMatchtrue/a.do是可以访问到该路径的;如果为SuffixPatternMatchfalse,则访问不到。
~| 这个东西在权限的验证的时候可能产生意想不到的影响,我限制了/a的权限,攻击者使用/a.do进行访问。
~| 在 Spring 5.3+ 中默认为false,源码逻辑中提供了一个除AntPathMatcher之外的PathPatternParser匹配策略,目的是替代AntPathMatcher策略。如果使用高版本Spring Boot,则可以使用 spring.mvc.pathmatch.matching-strategy = path_pattern_parser 来指定。
URL Matching with PathPattern in Spring MVC
4.TrailingSlashMatch:末尾斜杠匹配,用于能以 / 结尾的方式进行匹配,默认为true。
~| 举例:假设有一个这样的路径映射:@GetMapping("/a")
TrailingSlashMatchtrue/a/是可以访问到该路径的;如果为TrailingSlashMatchfalse,则访问不到。
~| 这个东西在权限的验证的时候可能产生意想不到的影响,我限制了/a的权限,攻击者使用/a/进行访问。
5.RegisteredSuffixPatternMatchContentNegotiationManager:这2个东西是配合使用的,如果RegisteredSuffixPatternMatchtrue,则只有在ContentNegotiationManager内设置的后缀可以映射到请求上,但ContentNegotiationManager默认的确是所有后缀都可以。

关于`SuffixPatternMatch`和`TrailingSlashMatch`:使用xml形式配置时,可用如下代码修改它的值。
<mvc:annotation-driven>
    <mvc:path-matching suffix-pattern="false" trailing-slash="false"/>
</mvc:annotation-driven>

关于这两个参数还有个有趣的例子:
@GetMapping("/{keywords}")
public String index(@PathVariable String keywords) {
    return keywords;
}
试试 /a    /a.b    /a.b.c    都返回什么,可以自己测试一下获取到内容。

关于 RequestMappingInfo
我们在编写Spring MVC时常用的@RequestMapping是否与其有关?这个东西在下面的陈述中有展示出它是什么。
它确实与@RequestMapping有关,Spring MVC会将@RequestMapping的相关信息包装到一个 RequestMappingInfo对象中,以供之后的程序逻辑使用。


在继续分析代码之前,我们提前补充点内容:
1.@Controller、@RequestMapping这类注解注解的类就叫做Handler(关于@RequestMapping在类上能起到与@Controller等效作用的示例在之后会给出)。
2.@RequestMapping这类注解注解的方法会被包装成一个HandlerMethod对象。
3.Spring MVC的所有路径映射信息存储在AbstractHandlerMethodMapping.MappingRegistry的实例对象内。

// AbstractHandlerMethodMapping
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

// AbstractHandlerMethodMapping
protected void initHandlerMethods() {
    for (String beanName : getCandidateBeanNames()) {
        // SCOPED_TARGET_NAME_PREFIX = "scopedTarget.",这个跟@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)注解有关。
        // 在这里不需要深入了解该注解,只需要知道常规位于Spring MVC 容器内的 bean都会进入 if代码块就行。
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // 就是在这里,会解析Spring MVC中的映射路径,并注册到 AbstractHandlerMethodMapping.MappingRegistry类实例对象中。
            processCandidateBean(beanName);
        }
    }
    /*
    如果开启日志的Debug模式,那么日志输出一下 Spring MVC解析到的路径映射 的数量。
    getHandlerMethods() 的返回值是一个Map类型数据,key 由 Http 请求方式和请求地址组成,value 则是 HandlerMethod 对象。
    */
    handlerMethodsInitialized(getHandlerMethods());
}

// AbstractHandlerMethodMapping
protected String[] getCandidateBeanNames() {
    // detectHandlerMethodsInAncestorContexts 默认为 false, 也就是说默认只获取Spring MVC 容器内的所有bean
    return (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
            obtainApplicationContext().getBeanNamesForType(Object.class));
}

实际上,如果这个initHandlerMethods()执行完成,代表着整个Spring MVC的映射路径注册完成,也代表第4类策略(initHandlerMappings())的执行完成。
再直白点,执行完这个方法后Spring MVC知晓我们项目内有哪些可供访问的路径,他们又映射到了哪些方法上。

但这里还是继续看完解析路径映射的逻辑,我们将这个逻辑分为2部分来说明。

首先得理解什么是桥接方法合成方法,这样才方便理解下面的代码。
类型擦除的影响 和 桥接方法 文档
Java 中冷门的 synthetic 关键字原理解读
桥接方法是和泛型、泛型擦除相关的内容。
合成方法是和静态内部类相关的内容。

第一部分:

// AbstractHandlerMethodMapping
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        // 根据传入的beanName获取到bean的类型
        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)) {
        // 看方法名和我们上面的约定叫法, 得知这是要检测类中的HandlerMethod
        detectHandlerMethods(beanName);
    }
}

// RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

// AbstractHandlerMethodMapping
protected void detectHandlerMethods(Object handler) {
    // beanName一般都是字符串, 则通常都是获取给定的bean的类
    Class<?> handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        // 通常是返回给定的类,如果是CGLIB生成的子类则返回原始类
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 这里使用了lambda, 我们先看下lambda内做了什么
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        // 整个lambda函数内,只是尝试调用该方法而已, 后面在第二部分会具体分析这个方法。
                        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));
        }
        // 遍历map 
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            // 这个也是核心, 后面在第二部分进行分析
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

// MethodIntrospector
/*
这个方法不止会被 AbstractHandlerMethodMapping#detectHandlerMethods(Object) 调用,也会被其它地方调用。

这个方法的目的:找出类中所有自己写的(非JVM生成、非Object内的)方法,挨个调用传递的metadataLookup函数。
*/
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
    final Map<Method, T> methodMap = new LinkedHashMap<>();
    Set<Class<?>> handlerTypes = new LinkedHashSet<>();
    Class<?> specificHandlerType = null; // 记录的是当前原始类本身,也就是自己实打实写的代码类,而不是cglib代理生成的。

    // -------------------------------- 获取到当前类的类型(开始) --------------------------------------------
    // 一个类的类型不仅包括它自身,还有它的父类

    if (!Proxy.isProxyClass(targetType)) { // 如果不是代理类
        specificHandlerType = ClassUtils.getUserClass(targetType);
        handlerTypes.add(specificHandlerType);
    }
    handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType)); // 获取当前类的父类型

    // -------------------------------- 获取到当前类的类型(结束) --------------------------------------------

    for (Class<?> currentHandlerType : handlerTypes) {
        final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

        /*
        由于篇幅问题,这里不展示 doWithMethods 具体的内部代码,在这里只简单说明下该方法是干什么的。
        作用如下:
        针对当前类,包含其所有父类和所有上层接口内的  所有 非桥接的并且非合成的并且不是Object内的方法,
        调用这里的第二个参数(也就是lambda函数)

        说简单点就是  对类中你自己写的方法(包含重写的方法) 进行第二个参数(lambda函数)的运算。
        */
        ReflectionUtils.doWithMethods(currentHandlerType, method -> {
            /* 
            在 targetClass类 中找到 指定的method 并返回。
            例如,方法可以是IFoo.bar(),目标类可以是DefaultFoo。在这种情况下,方法可能是DefaultFoo.bar()。
            如果目标类中没有bar(),那返回结果将保持为传入的IFoo.bar() 不变。
            */
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
            // metadataLookup是由上层传递过来的 lambda函数,代码应该退回上层 AbstractHandlerMethodMapping#detectHandlerMethods 查看
            T result = metadataLookup.inspect(specificMethod);
            if (result != null) {
                // 判断是否为桥接方法,不是就直接返回,是的话则找到被桥接的方法返回回来,按照这里上下文的逻辑,传入是什么返回的就是什么。
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                    methodMap.put(specificMethod, result);
                }
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
        /*  
        第三个参数 MethodFilter USER_DECLARED_METHODS = 
        (method -> (!method.isBridge() && !method.isSynthetic() && method.getDeclaringClass() != Object.class));
        这是一个对方法的过滤条件,只有符合这个条件的方法才会进行第二个参数的运算。
        */
    }
    // 将解析到的方法和@RequestMapping对应关系返回
    return methodMap;
}

到这里,解析路径映射的逻辑的第一部分就展示并描述完了。
第一部分实际上做了这么点事:
1.获取到所有可以称作Handler的bean。
2.对当前 bean 以及其上层类/接口中的所有自己写的方法,挨个执行getMappingForMethod调用。

getMappingForMethod方法则成为了第一部分和第二部分的分界点。

第二部分:

// RequestMappingHandlerMapping
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 根据 Method 解析出 RequestMappingInfo 对象,同时在此过滤掉没有 @RequestMapping 注解的方法(方式就是通过下面对 null的判断)
    // 再次重申 RequestMappingInfo 对象是 @RequestMapping 注解信息的封装
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // 这里是对类上面的 @RequestMapping 注解进行扫描
        // 如果类上面也有 @RequestMapping,那么会把类上配置的映射和方法上的映射结合在一起
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info); // 结合的方法
        }
        // 这个我不知道是什么,没找到怎么配置的,目前看起来也不是很重要,但逻辑挺简单的
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            info = RequestMappingInfo.paths(prefix).build().combine(info);
        }
    }
    return info;
}

// RequestMappingHandlerMapping
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    // 在给予的元素(Class 或者 Method)上找 @RequestMapping, 如果没有就返回null
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    // 不管是 getCustomTypeCondition 还是 getCustomMethodCondition 都是空实现
    RequestCondition<?> condition = (element instanceof Class ?
            getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

protected RequestMappingInfo createRequestMappingInfo(
            RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

    // paths() 会返回一个 Build,默认实现为RequestMappingInfo.DefaultBuilder
    // 属性通过构造器模式来设置
    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);
    }
    // 最后调用构造器的 build() 完成 RequestMappingInfo 对象的创建,具体内部逻辑就不再展示。
    // 这里的 this.config 就是之前 在 afterPropertiesSet() 里创建的 RequestMappingInfo.BuilderConfiguration()。
    return builder.options(this.config).build();
}

RequestMappingHandlerMapping#getMappingForMethod(Method, Class)的逻辑到这里就完成了。
我们还需要回到AbstractHandlerMethodMapping#detectHandlerMethods(Object)中,这个方法里面最先面有个forEach的代码块没进行说明。

// 这里的 methods 实际是 MethodIntrospector.selectMethods 的返回值,是一个Map
// key 是 Method,value 是 RequestMappingInfo 对象
methods.forEach((method, mapping) -> {
    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    // 这个也是核心, 后面在第二部分进行分析
    registerHandlerMethod(handler, invocableMethod, mapping);
});
// AbstractHandlerMethodMapping
private final MappingRegistry mappingRegistry = new MappingRegistry();
// MappingRegistry 是 AbstractHandlerMethodMapping 的内部类

// AbstractHandlerMethodMapping
/*
根据上下文来看,
handler 是 当前的 beanName
method 是 要执行的方法
mapping 是 @RequestMapping 内的信息
*/
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

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

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        // 在这里  beanName、IOC容器、要被调用的方法 一同组成一个 HandlerMethod
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        // 根据 mapping 判断 mappingLookup 中是否存在重复的 HandlerMethod,
        // 简而言之就是,确保一个路径映射只能存在一个对应的 HandlerMethod
        assertUniqueMethodMapping(handlerMethod, mapping);
        this.mappingLookup.put(mapping, handlerMethod);

        /* 
        获取到 @RequestMapping 中配置的路径
        例如: @PostMapping(value = {"/test", "/foo", "/bar"})
        那么 getDirectUrls(mapping) 得到的结果就是 ["/test", "/foo", "/bar"]
        */
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }

        // 这里逻辑是:对映射到的方法根据特定的命名策略进行命名,然后将这个名词作为key,HandlerMethod作为value中的一员存入nameLookup 
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

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

        /*
        将 这些数据放入到 registry中,registry是 AbstractHandlerMethodMapping.MappingRegistry 中持有的一个属性

        MappingRegistration 是一个持有RequestMappingInfo、HandlerMethod和directUrl的类,信息很全。
        */
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

protected HandlerMethod createHandlerMethod(Object handler, Method method) {
    HandlerMethod handlerMethod;
    if (handler instanceof String) {
        String beanName = (String) handler;
        // 这里可以看出来 HandlerMethod 是什么成分,beanName、容器、要被调用的方法
        handlerMethod = new HandlerMethod(beanName,
                obtainApplicationContext().getAutowireCapableBeanFactory(), method);
    }
    else {
        handlerMethod = new HandlerMethod(handler, method);
    }
    return handlerMethod;
}

无论是readWriteLockmappingLookupurlLookupnameLookup还是registry
这些属性都是AbstractHandlerMethodMapping.MappingRegistry类内部的,而AbstractHandlerMethodMapping持有这个类的对象实例。
其中AbstractHandlerMethodMapping内部又提供了诸多对于这些属性操作的方法,可以供它的子类(RequestMappingHandlerMappingRequestMappingInfoHandlerMapping)等其他地方使用。

到这里 第4类策略就算是处理完成了,我们最终得到了Spring MVC中关于方法的映射信息。
这个部分的内容有点多,可以慢慢看,多看几遍。
至于BeanNameUrlHandlerMapping这个东西,它主要是用来处理bean的id作为url的情况,在Spring MVC到本节的启动逻辑中没有发挥作用,所以不做分析。

2.2.1.5 HandlerAdapters

HandlerAdapters是对Handler进行的功能扩展,以使在请求进入HanlderMethod之前或者之后提供一些额外的服务,虽然不是拦截器,但却发挥拦截器相似的效果。

再次说明 Handler 可以理解为 Controller
HandlerMethod 可以理解为被映射到的方法

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) { // 默认为true
        // 从当前ApplicationContext(包括祖先的ApplicationContext)中找到所有HandlerAdapters。
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // 保证HandlerAdapters的顺序。
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            // HANDLER_ADAPTER_BEAN_NAME 的值为 handlerAdapter
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    if (this.handlerAdapters == null) {
        /*
        从 DispatcherServlet.properties 文件中拿出默认值,并将其实例化。
        默认值有3个,分别是
        1.HttpRequestHandlerAdapter
        2.SimpleControllerHandlerAdapter
        3.RequestMappingHandlerAdapter
        */
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        // ... 记录日志
    }
}

根据上述代码,在默认情况下我们从配置文件中得到3个HandlerAdapter的实现
1.HttpRequestHandlerAdapter,如果Handler实现自HttpRequestHandler类,该适配器在请求到来时工作。
2.SimpleControllerHandlerAdapter,如果Handler实现自Controller类,该适配器在请求到来时工作。
3.RequestMappingHandlerAdapter

HandlerAdapter类结构关系图.png

有了HandlerMappings的经验,我们就可以直接进入RequestMappingHandlerAdapter#afterPropertiesSet()

2.2.1.5.1 RequestMappingHandlerAdapter
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

大体进行了这样4个步骤
1.initControllerAdviceCache()
2.处理argumentResolvers有关内容
3.处理initBinderArgumentResolvers有关内容
4.处理returnValueHandlers有关内容
这4个步骤的加载过程是很简单的

2.2.1.5.1.1 initControllerAdviceCache()
private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    // 在给定的容器内查找带@ControllerAdvice注解的bean,并且将其包装为ControllerAdviceBean实例对象
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        // 查找当前bean内,被@ModelAttribute注解的方法
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        // 查找当前bean内,被@InitBinder注解的方法
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        // 看看当前bean是否是 RequestBodyAdvice 或者 RequestBodyAdvice 的派生类
        if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }

    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }

    // ... 日志打印
}

这些代码的是围绕 @ControllerAdvice 注解进行工作的。
initControllerAdviceCache()内有其他2个注解和2个接口配合该注解工作,分别是:
1.@ModelAttribute
2.@InitBinder
3.RequestBodyAdvice:只支持 @RequestBody 注解的 Controller 方法
4.ResponseBodyAdvice:只支持 @ResponseBody 注解的 Controller 方法
至于这些东西的用法,已经脱离了本文档的叙述范围,可以看看别人的文章,常用的全局异常处理就是这些内容使用的一个示例
SpringMVC 中 @ControllerAdvice 注解的三种使用场景!
RequestBodyAdvice 和 RequestBodyAdvice 的使用示例

主要做的工作就是解析到相应内容后存放起来。

2.2.1.5.1.2 argumentResolvers
if (this.argumentResolvers == null) {
    // 里面加载了很多很多的解析器,有基于注解的,也有基于类型的
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}

在Spring MVC框架加载过程中,这里会通过getDefaultArgumentResolvers()来注册特别多的参数解析器
这些解析器有基于注解的、基于类型、还可以有自定义的,它们核心功能就是用来处理解析参数。
只有当请求到来时,这些解析器才会被调用起来进行各自的工作内容。

2.2.1.5.1.3 initBinderArgumentResolvers
if (this.initBinderArgumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
    this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}

在Spring MVC框架加载过程中,这里会通过getDefaultInitBinderArgumentResolvers()来注册一些和@InitBinder相关的参数解析器
这些解析器有基于注解的、基于类型、还可以有自定义的,它们核心功能就是用来处理解析参数。
只有当请求到来时,这些解析器才会被调用起来进行各自的工作内容。

2.2.1.5.1.4 returnValueHandlers
if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}

在Spring MVC框架加载过程中,这里会通过getDefaultReturnValueHandlers()来注册一些返回值处理器
这些处理器有基于注解的、基于类型、还可以有自定义的,它们核心功能就是用来处理解析参数。
只有当请求到来时,这些处理器才会被调用起来进行各自的工作内容。

2.2.1.6 HandlerExceptionResolvers

HandlerExceptionResolvers是用于处理 请求执行时出现异常之后应该如何做 的问题,会实例化多个默认实现。

ExceptionHandlerExceptionResolver是其默认实现之一,也是排在第一位执行的,内部包含了对@ControllerAdvice@ExceptionHandler注解的支持,使得我们可以通过全局异常的形式来捕获并处理请求执行时遇到的问题。

private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        // 从当前ApplicationContext(包括祖先的ApplicationContext)中找到所有 HandlerExceptionResolvers。
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            // 保证HandlerExceptionResolvers的顺序。
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            // HANDLER_ADAPTER_BEAN_NAME 的值为 handlerExceptionResolver
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    if (this.handlerExceptionResolvers == null) {
        /*
        从 DispatcherServlet.properties 文件中拿出默认值,并将其实例化。
        默认值有3个,分别是
        1.ExceptionHandlerExceptionResolver
        2.ResponseStatusExceptionResolver
        3.DefaultHandlerExceptionResolver
        */
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        // ... 日志打印
    }
}

根据上述代码,在默认情况下我们从配置文件中得到3个HandlerExceptionResolver的实现
1.ExceptionHandlerExceptionResolver
2.ResponseStatusExceptionResolver
3.DefaultHandlerExceptionResolver

HandlerExceptionResolver类文件结构.png
2.2.1.6.1 ExceptionHandlerExceptionResolver
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();

    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

大体进行了这样3个步骤
1.initExceptionHandlerAdviceCache()
2.处理argumentResolvers有关内容
4.处理returnValueHandlers有关内容
这3个步骤的加载过程是很简单的,和 HandlerAdapters 中的做法差不多一样

2.2.1.6.1.1 initExceptionHandlerAdviceCache
private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }
    // 在给定的容器内查找带@ControllerAdvice注解的bean,并且将其包装为ControllerAdviceBean实例对象
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
        if (resolver.hasExceptionMappings()) {
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
        }
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            this.responseBodyAdvice.add(adviceBean);
        }
    }

    // ... 日志打印
}


public ExceptionHandlerMethodResolver(Class<?> handlerType) {
    // 查找当前bean内,被@ExceptionHandler注解的方法
    for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
        // 这里进行的这些行为,目的是获取到定义的异常类型 并且和 异常的处理方法 以key-value形式保存起来。
        // 如果已经看过HandlerMappings的处理过程,那这个就更容易看懂了
        for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
            addExceptionMapping(exceptionType, method);
        }
    }
}

这些代码的是围绕 @ControllerAdvice 注解进行工作的。
initExceptionHandlerAdviceCache()内有其他1个注解和1个接口配合该注解工作,分别是:
1.@ExceptionHandler
2.ResponseBodyAdvice:只支持 @ResponseBody 注解的 Controller 方法至于这些东西的用法,已经脱离了本文档的叙述范围,可以看看别人的文章,常用的全局异常处理就是这些内容使用的一个示例
Spring异常处理@ExceptionHandler

2.2.1.6.1.2 argumentResolvers 和 returnValueHandlers

与之前的RequestMappingHandlerAdapter的过程差不多,自己去ExceptionHandlerExceptionResolver类中查看。

2.2.1.7 RequestToViewNameTranslator

RequestToViewNameTranslator是用于处理Controller中方法返回值没有指定具体ViewName的问题,默认实现是DefaultRequestToViewNameTranslator
注意: 现在陈述的是Spring MVC的框架启动过程,它内部的实现逻辑不做说明。

当Controller中映射方法的返回值内没有指定具体的ViewName时,RequestToViewNameTranslator将负责 把请求地址翻译为ViewName,以使之后的逻辑进行工作。
如果你的方法返回的是Json、Xml等类型的数据,那跟这个就没关系了。

这里列举个示例来解释 RequestToViewNameTranslator 到底是干什么的

@GetMapping("/page/goods")
public String goodsHtml() {
    return null;
}
// @GetMapping("/page/goods") ModelAndView goodsHtml() { return new ModelAndView() }
// @GetMapping("/page/goods") void goodsHtml() {}

请求:"Ge localhost:8080/page/goods"
当遇到这种情况时,RequestToViewNameTranslator 才会开始工作。
它会获取到路径中的 "/page/goods",然后将经过一些规则处理后的值当作默认的ViewName,在默认情况下 "/page/goods" 会被处理成 "page/goods"。
也就是说虽然你方法内返回值为 "null",但实际上 Spring MVC 却认为你返回了 "page/goods"。
可以使用下面的方式进行配置,这个配置内虽然也有对前缀和后缀的配置,但它和之后ViewResolvers中展示的那种不是一个概念(但效果一样。。)。

<bean class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator">
    <property name="prefix" value="classpath:/templates"/>
    <property name="suffix" value=".html"/>
    <property name="stripLeadingSlash" value="false"/>
    <property name="stripTrailingSlash" value="true"/>
</bean>

2.2.1.8 ViewResolvers

ViewResolvers是用于处理使用模板引擎技术时模板上数据的填充问题,默认实现是InternalResourceViewResolver
注意: 现在陈述的是Spring MVC的框架启动过程,它内部的实现逻辑不做说明。

在一个请求处理过程中,ViewResolvers 会将 Spring MVC 的 Controller 中指定返回的 viewName 以及解析到的 Locale 处理成一个 View 对象,View 对象是用来渲染页面的,不同的解析器会解析成不同类型的 View 对象。
在默认的InternalResourceViewResolver实现中,默认生成的是 JstlView 类型的 View 对象,是 Jsp 技术的对应类型。

可以使用下面的方式进行配置,这个配置在以前还是挺常见的

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <!-- 默认就是,可以不写 -->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

当使用Thymeleaf技术时,则会更换解析器,这里没有展示Thymeleaf的完整配置,如有需要自行百度。
<!-- ThymeleafViewResolver也是ViewResolver的实现,不过是由Thymeleaf技术提供的实现,会生成ThymeleafView类型的View对象 -->
<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> 
    <property name="templateEngine" ref="templateEngine"/>
    <property name="characterEncoding" value="UTF-8"/>
</bean>

Spring 5.0+ 已经放弃对Velocity的支持了,至于FreeMarker的配置和上面的差不了多少

2.2.1.9 FlashMapManager

FlashMapManager是用于处理重定向时的参数的传递问题,默认实现是SessionFlashMapManager
注意: 现在陈述的是Spring MVC的框架启动过程,它内部的实现逻辑不做说明。

常规情况下,重定向时想要把参数也传递过去,则需要在目标 path 上进行参数拼接,使用这种方式不是很理想。
FlashMapManager策略提供了一种新的重定向参数的传递方式,这种方式,可以不用再在路径上拼接参数了,而是依赖Spring框架完成传递,下面展示简单使用示例。

// 我常用这种写法,对于其他人博客里面的用法我没用过。
@PostMapping("/a")
public void a(HttpServletRequest request, HttpServletResponse response) throws IOException {
    FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
    flashMap.put("a", "1");
    flashMap.put("b", "2");

    String redirect = "/b";
    // 将 FlashMap 交由 FlashMapManager 管理
    RequestContextUtils.saveOutputFlashMap(redirect, request, response);
    response.sendRedirect(redirect);
}

@GetMapping("/b")
public String b(HttpServletRequest request) {
    Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
    Object returnValue = null;
    if (inputFlashMap != null) {
        returnValue = inputFlashMap.get("a");
    }
    return (String) returnValue;
}
// 在配置文件中添加如下代码可以对细节内容进行调整

<!-- 使用 SessionFlashMapManager 时,保存的属性会以 HttpSession 作为底层进行存取 -->
<bean id="flashMapManager" class="org.springframework.web.servlet.support.SessionFlashMapManager">
    <property name="flashMapTimeout" value="180" />
    <property name="urlPathHelper" ref="xxx"/> <!-- 这个属性的值必须是 UrlPathHelper 对象实例,不建议修改 -->
</bean>

注意:FlashMap是有时效性的,默认为180秒,在AbstractFlashMapManager类中有定义

2.2.1.10 ThemeResolver

用于解析主题,跟样式有关。没用过,不了解。

3 4种创建Handler的方式

3.1 @Controller

@Controller // @RestController
public class IndexController {
}

3.2 @RequestMapping

@Component
@RequestMapping
public class IndexController {
}

3.3 Controller 接口

@Component
public class IndexController implements Controller {
}

3.4 HttpRequestHandler 接口

@Component
public class IndexController implements HttpRequestHandler {
}

结束

到这里,Spring MVC 的框架配置与启动过程大体已经陈述,其中的一些细节需要时自己查看就行。
下一篇Spring MVC 源码阅读-请求的处理流程

相关文章

网友评论

      本文标题:Spring MVC 源码阅读-框架启动过程

      本文链接:https://www.haomeiwen.com/subject/bvwdeqtx.html