idea springmvc_SpringMVC 源码解析

03625c58797f2bc18535aec4da9a9d06.png

前言

本文是我粗略看了 SpringMVC 源码之后的一篇总结,对之前解析的一个提炼,为以后再刷源码提供一个参考思路,可能有错漏的地方,请大家指正交流。

我试着从源码一条 request 的请求执行路径去解析 springmvc 的原理,并且稍微涉及一点点设计模式的相关概念。

前提

观看本文最好具备一定的 spring 源码基础,因为我可能会略过一些在我看来大家都知道的细节部分,因为追踪源码太复杂,会导致篇幅过长。

知识点

  • springmvc 有 2 个spring容器,我们在学习 springioc 的时候可能稍微接触过父子容器的概念。

这里祭出 springmvc 的架构图,root 根容器包含了services、repository;而子容器才是视图和控制器

a3947493050e4f4a8e5afda55c614efb.png

分析

Demo

还是从 demo 开始,我想大家比较早接触 spring 差不多应该是从 springmvc 开始的,那个时候我们搭建一个 springmvc 项目需要一些配置文件,配置 beans 和扫包,事务,等等组件。然后会修改一下web.xml这个文件,我们一般会配置下ContextLoaderListenerDispatcherServlet部分。

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

我们总是听说什么 SpringMVC 是基于 DispatcherServlet 的,DispatcherServlet是核心巴拉巴拉的,但是为什么是核心呢?

还有为什么要在 web.xml 这里配置这些玩意?有什么作用呢?

本篇我会解答这些疑问

演示代码

我这里先不写传统的 springmvc 的项目配置,而是采用零配置文件的方式,配置一个 springmvc 的环境,然后用 tomcat 跑起来。我就依据这个项目来做源码分析,并解答上面的疑问。可能有些人觉得不可思议,springmvc 也可以做到零配置吗?不需要配置文件的不就是 springboot 了吗?

利用 servlet3.0 和 spi 机制,我们在 jdk6 和 tomcat7 之后,在 web 项目中不需要加入 web.xml 配置文件了。至于 spi 机制是什么,这里不过多细节讲解,我们在 demo 中捎带说下。我们看起来高端的 springboot 其实就是零配置的 springmvc 的更高级组合,它把一些常用的配置全部做了默认实施,所以我们可以拿来即用,但是底层其实还是同一套东西的。

废话不多说,上代码

项目结构(我是直接在 spring 源码项目中创建的测试工程,为了方便对源码进行注释):

e2d2adc3f2a14e1ade6e9f33e1055570.png

jar包:

plugins {
    id 'java'
}
apply plugin: 'war'
apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    optional("javax.servlet:javax.servlet-api:4.0.1")
    compile(project(":spring-webmvc"))
    compile(project(":spring-context"))
}

我们的启动类,继承AbstractAnnotationConfigDispatcherServletInitializer

public class StarterClass extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        /**
         * 获取 root 容器配置
         */
        return new Class<?>[]{SpringRootApplicationContext.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        /**
         * 获取 springmvc 容器配置
         */
        return new Class<?>[]{SpringMvcApplicationContext.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

2 个配置文件代码,没有特别内容,主要是扫包,注意我这里标注的扫包位置和类的名称的区别

@Configuration
@ComponentScan(value = {"top.ybq87.controller"})
public class SpringMvcApplicationContext {
}
@Configuration
@ComponentScan(value = {"top.ybq87.service"})
public class SpringRootApplicationContext {
}

3 个 controller 作用的类

@Controller
public class HelloController {

    @Autowired
    private UserService userService;

    @ResponseBody
    @RequestMapping("/hello")
    public Object hello(String name) {
        return userService.say(name);
    }
}
@Component("/myBeanNameController")
public class MyBeanNameController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        System.out.println(">>>>>MyBeanNameController");
        return null;
    }
}
@Component("/myHttpRequest")
public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        System.out.println(">>>>>> MyHttpRequestHandler >>>>>>");
    }
}

然后是接口和实现类,很简单

public interface UserService {
    String say(String name);
}
@Service
public class UserServiceImpl implements UserService {
    @Override
    public String say(String name) {
        return "UserServiceImpl >> " + name;
    }
}

好了,测试代码已经完毕,我们使用 tomcat7+跑起项目来,注意我这里可是一个配置文件都没有写的。

fdd4cc692ab15660e08164eef2efebc5.png

访问成功。开工

容器初始化

我们从 StarterClass 这个文件开始分析,我这里继承了AbstractAnnotationConfigDispatcherServletInitializer,可能大家再看其他的源码或者零配置教程文章的时候都是继承的WebApplicationInitializer,包括官网都给的也是这个例子。因为前者经过了一些封装,我们不需要写那么复杂的配置,所以用起来更加简单,其实底层一样,不深究。

稍微涉及一点 tomcat 的启动原理,tomcat 容器启动的时候的加载逻辑:

org.apache.catalina.startup.ContextConfig#lifecycleEvent
    org.apache.catalina.startup.ContextConfig#configureStart
        org.apache.catalina.startup.ContextConfig#webConfig
            org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
                org.apache.catalina.startup.WebappServiceLoader#load
查看 tomcat 源码:在 IDEA 编辑器双击 shift,贴入org.apache.catalina.startup.WebappServiceLoader,会自动找到这个类

这个方法我列几个比较重要的代码,不展开

// serviceType = ServletContainerInitializer,此参数由上级方法传入
// SERVICES = META-INF/services/
String configFile = SERVICES + serviceType.getName();
// 找到指定文件
WebResource[] resources = context.getResources().getClassLoaderResources("/" + configFile);

这个代码的意思就是从目录 META-INF/services/javax.servlet.ServletContainerInitializer 找到文件然后加载里面的 class。

不过我们回看自己的 demo 没有发现这个文件,但是使用 IDEA 的快捷搜索双击 shift,我们发现这个文件在spring-web项目下。哦,原来我们在引入包的时候已经加载进来了。

这个文件里面只有一条数据org.springframework.web.SpringServletContainerInitializer,看到了全限定类名,自然想到了反射去实例化,我们看看这个类的具体实现

SpringServletContainerInitializer

tomcat 启动时,会依据 SPI 机制找到项目下的这个类,然后调用它的 onStartup()方法,但是 spring-web帮我们实现了这个机制,并且做了一个拓展。

/**
 * HandlesTypes 注解含义:会扫描所有 实现了 value=WebApplicationInitializer 的接口实现类,组装为 set 参数,
 * 传递给 SpringServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)
 */
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    // webAppInitializerClasses 这个参数我们知道什么意思了,不展开了。
    @Override
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList<>();

        if (webAppInitializerClasses != null) {
            /**
             * 循环 WebApplicationInitializer 的实现类,然后调用这个类的 onStartup 方法
             */
            for (Class<?> waiClass : webAppInitializerClasses) {
                /**
                 * 不是接口,不是抽象类,而且实现了 WebApplicationInitializer 接口的
                 */
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        /**
                         * 将通过判定的加入到 initializers,为了在后面依次调用它的 onStartup 方法
                         */
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        /**
         * 排序,因为可以实现多个 WebApplicationInitializer,实现了 order 接口或者使用了 @Order 注解的
         */
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            /**
             * 调用它的 onStartup 方法,指向我们的 top.ybq87.StarterClass
             * 但是我们自己没有实现 onStartup 方法,所以去看他的父类找
             */
            initializer.onStartup(servletContext);
        }
    }
}

看到这里我们知道了,回去找我们的启动类的父级,我们找到了AbstractDispatcherServletInitializer#onStartup

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    // 调用的父类,实例化我们的 spring root 上下文,只是创建,没有初始化
    super.onStartup(servletContext);
    // 注册我们的 DispatcherServlet 创建我们 spring web 上下文对象
    registerDispatcherServlet(servletContext);
}

第一行代码调用了 super,又去找了它的父级AbstractContextLoaderInitializer#onStartup

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
    /**
     * 1、新建我们的 父容器 RootApplicationContext
     * 当前类没有发现实现方法,说明交给了子类 : AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()
     * 注意到这个方法之后,只是 new 了一个根容器,但是没有初始化【即没有调用 refresh()】
     */
    WebApplicationContext rootAppContext = createRootApplicationContext();
    if (rootAppContext != null) {
        /**
         * 2、创建一个 ContextLoaderListener ,这个是不是超级熟悉,我们以前在使用配置文件启动 springmvc 的时候
         * 经常在 web.xml 中配置的 ContextLoaderListener
              <listener>
                    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
               </listener>

               <context-param>
                   <param-name>contextConfigLocation</param-name>
                   <param-value>/WEB-INF/app-context.xml</param-value>
               </context-param>
         *
         * 创建一个监听器对象, 然后将监听器注册到 servlet 上下文,也就是注册到 tomcat
         * 然后他持有一个 web 容器的引用【目前是空的】
         * 我们回到 super.onStartup(servletContext)
         */
        ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
        listener.setContextInitializers(getRootApplicationContextInitializers());
        servletContext.addListener(listener);
    }
    else {
        logger.debug("No ContextLoaderListener registered, as " +
                     "createRootApplicationContext() did not return an application context");
    }
}

原来这里是用来创建 springmvc 中 2 个容器的根容器的,只不过我们看到的创建都是交给子类实现,目前还没有具体方法。我们先记下来。

分析第二行代码

/**
 * 我们一般的配置信息
     <servlet>
         <servlet-name>app</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value></param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
     <servlet-mapping>
         <servlet-name>app</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
 */
protected void registerDispatcherServlet(ServletContext servletContext) {
    // 获取 dispatcherservlet 的名称
    String servletName = getServletName();
    Assert.hasLength(servletName, "getServletName() must not return null or empty");

    /**
     * 创建 WebApplicationContext 对象,交给子类实现
     * AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()
     * 实例化 子容器
     */
    WebApplicationContext servletAppContext = createServletApplicationContext();
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

    /**
     * 创建 DispatcherServlet 对象,所以 tomcat 会对 DispatcherServlet 进行生命周期管理
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     * 我们进去发现只是设置关联子容器,没有更多操作。
     */
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    /**
     * 获取 ServletApplicationContextInitializers 对象,注册到 dispatcherServlet
     */
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

    /**
     * 将 DispatcherServlet 注册到 tomcat
     */
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    if (registration == null) {
        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
                                        "Check if there is another servlet registered under the same name.");
    }

    /**
     * 眼熟吧,设置 dispatcherServlet 属性
     */
    registration.setLoadOnStartup(1);
    registration.addMapping(getServletMappings());
    registration.setAsyncSupported(isAsyncSupported());

    Filter[] filters = getServletFilters();
    if (!ObjectUtils.isEmpty(filters)) {
        for (Filter filter : filters) {
            registerServletFilter(servletContext, filter);
        }
    }

    customizeRegistration(registration);
}

这里就是创建子容器的地方了,但是也是交给子类实现。

看了这些源码我想你可能还是有点懵,我这里提醒大家关注 2 个方法,也就是流出来给子类拓展的createRootApplicationContext()createServletApplicationContext()

AbstractAnnotationConfigDispatcherServletInitializer

上面 2 个方法都是 AbstractAnnotationConfigDispatcherServletInitializer类的,我们看看实现。

createRootApplicationContext()

protected WebApplicationContext createRootApplicationContext() {
    // 获取父容器的配置类,抽象方法,所以是交给子类实现,也就是 top.ybq87.StarterClass.getRootConfigClasses
    Class<?>[] configClasses = getRootConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        // 创建我们的根容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        // 把配置类加载到根容器中
        context.register(configClasses);
        return context;
    }
    else {
        return null;
    }
}

看到这里终于和我们的 demo 代码有关联了,我们的配置是这么写的,还记得开篇的那张图么。

@Configuration
@ComponentScan(value = {"top.ybq87.service"})
public class SpringRootApplicationContext {   
}

但是我们看完整个代码,发现这里就真的只是实例化了一个AnnotationConfigWebApplicationContext类,并初始化了一些参数(获取到根容器的配置文件位置)没有别的操作了。

慌!!

createServletApplicationContext()

再看看另外一个类,子容器的创建如何

@Override
protected WebApplicationContext createServletApplicationContext() {
    // 和我们实例化父容器一样的操作
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    Class<?>[] configClasses = getServletConfigClasses();
    if (!ObjectUtils.isEmpty(configClasses)) {
        context.register(configClasses);
    }
    return context;
}

摔!居然和父容器一样的创建过程。

ContextLoaderListener

遇到困难不要怕,我们灵光一闪,记得有 2 个重要的方法被忽略了,

ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);

这一步在 tomcat 的上下文环境注册了一个 listener!

还记得JavaWeb 三大组件中 Listener 的相关特性么?

在 servlet 容器的创建时调用监听器的 void contextInitialized(ServletContextEvent sce)

我们去看看ContextLoaderListener的这个方法

@Override
public void contextInitialized(ServletContextEvent event) {
    /**
     * 初始化容器
     */
    initWebApplicationContext(event.getServletContext());
}

原来如此,在 servlet 容器启动的时候,因为之前注册了一个根容器的监听器,在配置信息都加载完成之后,servlet 容器启动了调用监听的 init 方法,此时去真正初始化根容器。

跟踪到ContextLoader#initWebApplicationContext,毫无疑问,这里进行了spring 容器的初始化。

6e8957ceedab19339cf0a4ee9b1b2384.png

DispatcherServlet

同理分析子容器的初始化,

FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
/**
 * 获取 ServletApplicationContextInitializers 对象,注册到 dispatcherServlet
 */
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

/**
 * 将 DispatcherServlet 注册到 tomcat
 */
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);

这段代码构建了一个FrameworkServlet实例,然后注册到了 tomcat 的上下文。

servlet 组件的生命周期:构建时调用 init 方法,接收消息(一个 request调用一次)调用 service 方法,销毁调用 destory 方法

我们直接看FrameworkServlet,发现它是个抽象类,看父类HttpServletBean#init但是还是回到了子类的FrameworkServlet#initServletBean

try {
    /**
     * 初始化 web 容器
     */
    this.webApplicationContext = initWebApplicationContext();
    // 拓展点,交给开发人员在初始化之后做点事情
    initFrameworkServlet();
}

FrameworkServlet#initWebApplicationContext方法

protected WebApplicationContext initWebApplicationContext() {
    /**
     * 获取 AnnotationConfigServletWebServerApplicationContext 类型的web容器
     * 即得到父容器 rootContext 是 WebApplicationContext 类型的,
     * 记得之前在父容器的初始化时,将父容器注册到了 servlet应用的上下文么,所以这里可以得到父容器
     */
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    // 设置父子容器关联
                    cwac.setParent(rootContext);
                }
                /**
                 * 1、刷新 springmvc 的上下文,即初始化子容器,
                 * 2、触发一个 spring 的监听事件
                 */
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    /**
     * 在子容器初始化之后,触发了 spring 的 ApplicationListener 事件,调用
     * FrameworkServlet.ContextRefreshListener#onApplicationEvent
     * 回去修改 refreshEventReceived = true,这里再次判定一下,避免事件没有触发。
     */
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            /**
             * 刷新容器,交给了子类实现,进入 DispatcherServlet
             */
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // 事件推送
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

我们看注释,发现最关键的部分还是交给了子类实现onRefresh

/**
 * 用于初始化我们 springmvc 的九大组件
 */
protected void initStrategies(ApplicationContext context) {
    /**
     * 初始化 web 上下文对象的 用于文件上传下载的解析器对象
     * 处理 multipart-form-data 类型请求
     */
    initMultipartResolver(context);
    // 初始化 国际化资源
    initLocaleResolver(context);
    // 初始化 主题解析器
    initThemeResolver(context);
    /**
     * 初始化 handlermappings,handlerMapper 存储的是 uri 对应的 Controller 处理类(或者具体方法)。
     * 当 DispatcherServlet 接受到客户端的请求后,SpringMVC 通过 uri 在 handlermappings 定位到 Controller 处理类(或者具体方法)
     */
    initHandlerMappings(context);
    /**
     * 初始化 handleradapters,我们拿到了 controller 就要调用它的对应的方法。adapter 负责方法调用。
     */
    initHandlerAdapters(context);
    // 初始化 异常处理器
    initHandlerExceptionResolvers(context);
    //
    initRequestToViewNameTranslator(context);
    /**
     * view 解析器
     */
    initViewResolvers(context);
    initFlashMapManager(context);
}

到这里我们才真正的接触到了 springmvc 的核心部分。

DispatcherServlet

篇幅有限,只分析我们重点看的 2 个方法。

initHandlerMappings

先看注册 HandlerMapping 的方法,从容器找到 HeandlerMapping 的实现类,并初始化这些类。

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    /**
     * 是否允许查找所有的 handler。默认 true
     */
    if (this.detectAllHandlerMappings) {
        // 从容器查询所有的 HandlerMapping 的实现类
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        // 找到了
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            // 排序,spring 处理请求就是依据这个排序的结果进行,如果当前handlerMapping不可以处理则抛给下一个
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    /**
     * 确保有至少有一个 HandlerMapping,如果前面没有找到就使用默认的 HandlerMapping
     */
    if (this.handlerMappings == null) {
        /**
         * 没有使用 @EnableWebMvc 注解,进入默认方法,得到了 3 个默认的 HandlerMapping
         * BeanNameUrlHandlerMapping(实现接口的 controller)
         * RequestMappingHandlerMapping(基于注解的 controller)
         * RouterFunctionMapping(Spring5.0 的 WebFlux 加入的,先忽略)
         */
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                         "': using default strategies from DispatcherServlet.properties");
        }
    }
}

我们的项目没有使用到 @EnableWebMvc 注解,所以是加载的默认 HandlerMapping,我们关注的是其中的 2 个BeanNameUrlHandlerMappingRequestMappingHandlerMapping

在上面的代码中我们看到DispatcherServlet#getDefaultStrategies

这个方法的一些关键代码

Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);

createDefaultStrategy这个方法的实现

protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
    return context.getAutowireCapableBeanFactory().createBean(clazz);
}

这里我们似乎看到了在学习 springioc 源码的时候经常见到的 createBean,初始化 bean 的工作。也就是会去初始化RequestMappingHandlerMappingBeanNameUrlHandlerMapping

RequestMappingHandlerMapping

先看RequestMappingHandlerMapping这个类,找一些我们熟悉的东西。

// 这个方法在 springioc 源码分析的时候应该看到过,在 bean 属性赋值之后调用。
public void afterPropertiesSet() {
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(useSuffixPatternMatch());
    this.config.setTrailingSlashMatch(useTrailingSlashMatch());
    this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    /**
     * 查看父类的方法
     */
    super.afterPropertiesSet();
}

追踪到父类,眼熟啊

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

/**
 * 该方法就是去把我们的 Controller 中的 RequestMapping 注解的路径 URI 和方法进行一一映射保存
 * 比如我们的 demo 中的 HelloController 的 uri 映射就是:
 * key = /hello, value = public Object hello(String name) 这个方法的包装类 HandlerMethod
 */
protected void initHandlerMethods() {
    /**
     * 去 web 容器中获取出所有组件的 beanNames 获取出来
     */
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // 关键方法
            processCandidateBean(beanName);
        }
    }
    //
    handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        /**
         * 通过 beanName 去我们的web容器中获取 beanType(class对象)
         */
        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);
        }
    }
    // isHandler 判定条件:是否有 Controller 或者 RequestMapping 注解
    if (beanType != null && isHandler(beanType)) {
        /**
         * 通过 Class 对象判断是不是一个 controller 对象
         * 判断类上面有没有 @Controller || @RequestMapping 注解
         */
        detectHandlerMethods(beanName);
    }
}
protected void detectHandlerMethods(Object handler) {
    /**
     * 判定 传入的 handler 是否是 beanName
     * 1、是:通过 beanName 从web 容器中获取 beanName 对应的 bean 的 class 对象
     * 2、不是:直接获取 handler 的 class 对象
     * 从我们之前的代码看这里是 string 类型的 beanName
     */
    Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        /**
         * 获取目标的class对象,防止class对象被cglib增强的
         */
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        /**
         * 作用:把我们的Controller 中标注的 @RequestMapping 的方法对象做为key,配置的路径作为value
         * 设置到Map对象中
         *
         * 在这里使用的拉姆达表示式,把getMappingForMethod(method,userType)的方法注入到了
         * MethodIntrospector.MetadataLookup接口中的inspect方法中
         * 那么真正的调用inspect()方法的时候就会调用getMappingForMethod方法
         */
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            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,把method---path 的映射关系保存到
         * MappingRegistry 对象中.
         */
        methods.forEach((method, mapping) -> {
            /**
             * 解析map中的key(method)对象
             * 获取method对象是不是一个可执行的 method 对象
             */
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            /**
             * 把我们的映射关系保存到 MappingRegistry 中,一路追踪这个方法,
             * AbstractHandlerMethodMapping.MappingRegistry#register
             * 一个写锁方法
             */
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

简单看下registerHandlerMethod方法调用AbstractHandlerMethodMapping.MappingRegistry#register的代码

this.readWriteLock.writeLock().lock();
try {
    /**
     * 根据 controller 对象和被调用的 method 对象 来创建我们的 HandlerMethod
     */
    HandlerMethod handlerMethod = createHandlerMethod(handler, method);

    /**
     * 把我们的 url,和 handlerMethod 保存到 mappingLookup map 中
     * mappingLookup<RequestMappingInfo,HandlerMethod>
     */
    this.mappingLookup.put(mapping, handlerMethod);

    // 从 RequestMapping 注解得到 value 的值,因为是数组
    List<String> directUrls = getDirectUrls(mapping);
    for (String url : directUrls) {
        this.urlLookup.add(url, mapping);
    }

    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);
    }
    // 映射表注册 MappingRegistration 对象
    this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
} finally {
    this.readWriteLock.writeLock().unlock();
}

这里我们就知道了,原来是将我们的 controller 的具体方法和 uri 进行了映射。

822ee77df8bc225f5ce7a48fb929203f.png

BeanNameUrlHandlerMapping

再来看 BeanNameUrlHandlerMapping 这个类的初始化,直接告诉你追踪这个AbstractDetectingUrlHandlerMapping#detectHandlers

protected void detectHandlers() throws BeansException {
    ApplicationContext applicationContext = obtainApplicationContext();
    String[] beanNames = (this.detectHandlersInAncestorContexts ?
                          BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
                          applicationContext.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.
            // 注册 handler
            registerHandler(urls, beanName);
        }
    }

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

我们追踪到这里只是简单的将 uri 和对应的 BeanName 映射,存储到了AbstractUrlHandlerMapping#handlerMap这个 map 中。

好了经过一些列的判定,终于我们得到了 3 个 HandlerMapping 类型的实例,并且分别存储了 uri 到 method(类)的映射关系。

到目前为止我们大概知道了一个 uri 请求过来之后,应该可以找到对应的处理类(方法),但是怎么调用方法还没看到。

initHandlerAdapters

再看 HandlerAdapter 的实现。我简化下,补贴代码了,分析的方法和上面的差不多,

this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);

这个方法找到了 4 个适配器,我们忽略 webflux 的,应该是关心其中的 3 个:

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • RequestMappingHandlerAdapter

我们知道在 spring 中实现一个 controller 的方式有 2 大类(实现接口或者注解),3 种方式(实现 Controller 接口、实现 HttpRequestHandler 接口、使用@Controller 注解)与这里的适配器对应。

老铁们看到这里应该想起了适配器模式了,先不展开,留个扣子。

这里注册了 3 个适配器。

总结下 DispatcherServlet 的 init

分析到这里我们掌握的信息看,dispatcherservlet 在初始化的时候,分解得到了 uri 和方法的对应关系,然后还有注册了几个适配器。实际的应用是怎么生效的看下面。

一条请求的执行路径

我们以http://localhost:8080/hello这个请求为例分析,Servlet 处理请求一般走 service,我们这里就对应的是DispatcherServlet#doDispatch这个方法。

摘取一些重要逻辑代码

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
    /**
     * 1、从我们当前的请求中推断出我们的 HandlerExecuteChain 处理器执行链对象
     * 重点!!
     * 经过这一步,通过 uri 获取到一个对应的 controller 的 method 方法
     * (或者是一个类对象,因为注册 controller 的方式有 3 种,但是我们最常用的是用 @RequestMapping 注册 uri)
     * 同时在 处理链加入了 适配的拦截器。还记得我们在分析 HandlerMapping 的时候的结果么按照 demo 中 HandlerMapping 的结构应该是:
     * key = /hello, value = top.ybq87.controller.HelloController#hello
     * key = /myBeanNameController, value = myBeanNameController
     * key = /myHttpRequest, value = myHttpRequestHandler
     * 后面 2 个的值是类,第一个是方法
     */
    mappedHandler = getHandler(processedRequest);
    ...
    /**
     * 2、获取对应的 handlerAdapter 适配器,最终目的是要调用被适配的 handler 方法
     */
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    /**
     * 3、调用 handle,执行请求
     * 通过我们的适配器真正的调用我们的目标方法
     * 因为 controller 的定义方式有多种,所以对应的方法调用方式需要做适配。
     */
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}

标注的方法我们一个一个的分析

getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        /**
         * 遍历所有的 mapping,得到对应的处理类,
         */
        for (HandlerMapping mapping : this.handlerMappings) {
            // 重点,我们系统一共有 3 个 HandlerMappings,其中一个是 webflux 忽略。
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

找到AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    /**
     * 1、调用具体的实现去获取 handler(可以看成是 controller),具体类是 HandlerMethod
     * AbstractUrlHandlerMapping#getHandlerInternal
     * uri 得到 method 处理类
     */
    Object handler = getHandlerInternal(request);
    ...

    /**
     * 通过 uri,去配置的拦截器 interceptor 看看是否有匹配的拦截器,加入处理链
     */
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    ...
    return executionChain;
}

getHandlerInternal

这里会调用子类的实现,具体对应的有 2 个:AbstractUrlHandlerMapping#getHandlerInternalRequestMappingInfoHandlerMapping#getHandlerInternal

因为我们分析的是/hello这个请求,所以我们知道它会去找到RequestMappingInfoHandlerMapping#getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    /**
     * 获取UrlPathHelper对象,用于来解析从们的request中解析出请求映射路径
     */
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        /**
         * 通过我们从Request对象中解析出来的lookupPath 然后通过lookupPath获取HandlerMethod对象
         */
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    } finally {
        this.mappingRegistry.releaseReadLock();
    }
}

原来这里就通过 uri 得到了我们之前包装的 HandlerMethod 对象。

getHandler这个方法返回了 HandlerMethod 和一些默认的过滤器,(当然如果我们分析的是/myBeanNameController这个请求,那么这里就不是 HandlerMethod 了,而是MyBeanNameController这个类,老铁们能转过弯来么)

getHandlerAdapter方法

上一步得到了 HandlerMethod,我们知道了具体的方法,但是怎么调用?这里就用到了适配器模式,因为 Handler可能是一个 method 对象,也可能是一个 class,而且他们之间没有任何关联。作为 client 的 DispatcherServlet 想要调用这些 Target 端,就必须通过适配器。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            /**
             * 依据 Handler 的类型【
             * HandlerMethod(AbstractHandlerMethodAdapter)、
             * 自定义 MyBeanNameController 的类型(SimpleControllerHandlerAdapter)、
             * 自定义 MyHttpRequestHandler 类(HttpRequestHandlerAdapter)
             * 】 判断找到合适的 adapter
             * 1、AbstractHandlerMethodAdapter 中会去判定是否是 HandlerMethod 对象,
             * 【那么什么情况下这个 handler 是一个 HandlerMethod?就是使用注解注册我们的 uri 的时候,会追踪到具体的 method,
             * 所以在 HandlerMapping 中存储的是 HandlerMethod 对象】
             * 2、HttpRequestHandler 实现了HttpRequestHandler接口的类,那么就判定是否是HttpRequestHandler这个类型
             * 因为这个 uri 对应的是一个类
             * 3、SimpleControllerHandlerAdapter,实现了 Controller 接口的
             */
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

这里我们就找到了AbstractHandlerMethodAdapter这个适配器。

ha.handle方法

client 调用适配器的方法,实际由适配器调用具体的 target 的方法。这里就是AbstractHandlerMethodAdapter#handle指向RequestMappingHandlerAdapter#handleInternal

追踪下去就是利用反射机制调用了对应的方法。

当然如果是SimpleControllerHandlerAdapter这种就简单很多,直接调用类的方法。

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    // 转为接口
    return ((Controller) handler).handleRequest(request, response);
}

后面返回处理的视图部分代码就不贴了。

总结

分析下来,其实 springmvc 的逻辑并不算太复杂,有几个比较有意思的点

  • 它充分利用了 listener 和 servlet 的特性进行根容器和子容器的初始化工作
  • 在处理请求时使用了策略模式和适配器模式,很好的兼容旧版的方法,也便于拓展

最后来个流程图

6657e1b3b1600939db67c7679e362a9b.png

c388d13696cbfdf095d839aacf9f749e.png

e616c15e8847aea904b485464871945a.png
欢迎转发和关注
公众号:林子曰
开源项目:git@github.com:Lingouzi/blog.git
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值