SpringMVC源码分析(一)--基本组件

1.SpringMVC的基本组件

要启动SpringMVC程序最基本的组件如下:

  • 内嵌的容器工厂
  • DispatcherServlet
  • DispatcherServlet注册Bean

1)创建一个WebConfig配置类,配置容器工厂、DispatcherServlet及DispatcherServlet注册Bean:

@Configuration // 配置类
@ComponentScan // 扫描当前包及子包下的组件
public class WebConfig {
    @Bean
    public ServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }
}

2)创建一个控制器类,用于测试,放在WebConfig同包下,保证组件能被Spring扫描到:

package com.limin.study.springmvc.A01;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class Controller01 {
    @GetMapping("/test01")
    public void test01(HttpServletResponse response) throws IOException {
        response.getWriter().print("hello");
    }
}

3)编写测试类并启动,web环境下使用AnnotationConfigServletWebServerApplicationContext

package com.limin.study.springmvc.A01;

import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

4)浏览器访问http://127.0.0.1:8080/test01,页面即可响应hello字符串

2.Web容器启动过程

ServletWebServerFactory是一个接口,作用是获取Web容器对象,源码如下:

@FunctionalInterface
public interface ServletWebServerFactory {
    WebServer getWebServer(ServletContextInitializer... initializers);
}

我们希望知道getWebServer是何时调用的,在TomcatServletWebServerFactory的getWebServer方法第一行打一个断点,debug模式启动Test01,可以看到调用栈,如下图所示:

从调用链可以看出,Web容器是在Spring启动过程中的onRresh()中创建的,创建容器的核心方法在ServletWebServerApplicationContext#createWebServer,源码如下:

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        // 1.获取Spring容器中的ServletWebServerFactory组件
        ServletWebServerFactory factory = getWebServerFactory();
        // 2.调用ServletWebServerFactory接口的getWebServer获取具体的Web容器
        this.webServer = factory.getWebServer(getSelfInitializer());
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                           new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                                           new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}

protected ServletWebServerFactory getWebServerFactory() {
    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
    if (beanNames.length == 0) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                                              + "ServletWebServerFactory bean.");
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                                              + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    // 从Spring容器中获取ServletWebServerFactory实现类对象
    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

createWebServer()中从容器中获取了我们在WebConfig配置类中注册的TomcatServletWebServerFactory,并调用了getWebServer方法获取了TomcatWebServer对象

Tomcat的启动正是在创建TomcatWebServer对象时进行的,源码见TomcatWebServer#getWebServer:

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    // 准备好Tomcat容器
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    // 创建TomcatWebServer对象
    return getTomcatWebServer(tomcat);
}

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}

实例化TomcatWebServer的过程中启动了Tomcat容器,源码见TomcatWebServer构造器:

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    // 省略其他代码...

    initialize();
}

private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        // 省略其他代码...
    
        this.tomcat.start();
    
        // 省略其他代码...
    }
}

Tomcat的启动原理会在Tomcat篇中分析,此处不再赘述

3.DispatcherServletRegistrationBean的作用

DispatcherServletRegistrationBean的作用是在Tomcat容器启动时注册DispatcherServlet和路径映射,这样当匹配到请求路径时,DispatcherServlet这个前端控制器就能够处理请求

Tomcat启动Context应用上下文时会注册Servlet,StandardContext#startInternal源码如下:

protected synchronized void startInternal() throws LifecycleException {
    // 省略其他代码...

    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
         initializers.entrySet()) {
        try {
            // 调用ServletContainerInitializer的onStartup
            entry.getKey().onStartup(entry.getValue(), getServletContext());
        } catch (ServletException e) {
            log.error(sm.getString("standardContext.sciFail"), e);
            ok = false;
            break;
        }
    }

    // 省略其他代码...
}

启动时会调用ServletContainerInitializer的onStartup,继而调用ServletContextInitializer的onStartup

public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        for (ServletContextInitializer initializer : this.initializers) {
            // 调用ServletContextInitializer的onStartup
            initializer.onStartup(servletContext);
        }
    }

    // 省略其他代码...
}

调用栈如下图所示:

在执行initializer.onStartup时代码行数为-1,找不到源码,这是因为在创建ServletContextInitializer接口对象时使用了lambda表达式,见ServletWebServerApplicationContext#createWebServer

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        // 创建时调用的getSelfInitializer(),而这是一个lamdba表达式
        this.webServer = factory.getWebServer(getSelfInitializer());
        getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                           new WebServerGracefulShutdownLifecycle(this.webServer));
        getBeanFactory().registerSingleton("webServerStartStop",
                                           new WebServerStartStopLifecycle(this, this.webServer));
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    initPropertySources();
}

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    // 这里调用的是DispatcherServletRegistrationBean.onStartup
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

相当于在ServletWebServerApplicationContext内部创建一个ServletContextInitializer的实现类,即

new ServletContextInitializer() {
    public void onStartup(ServletContext servletContext) throws ServletException {
        selfInitialize(servletContext)
    }
}

因此在执行时initializer.onStartup(servletContext)就会直接调用到selfInitialize方法中

而DispatcherServletRegistrationBean正是ServletContextInitializer的实现类,在selfInitialize方法中调用的就是DispatcherServletRegistrationBean的onStartup方法,最终会调用到DynamicRegistrationBean#register

protected final void register(String description, ServletContext servletContext) {
    // 1.注册Servlet,创建Wrapper
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    // 2.配置映射路径
    configure(registration);
}

protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
    String name = getServletName();
    return servletContext.addServlet(name, this.servlet);
}

register方法中做了2件事:

1)调用addServlet方法将DispatcherServlet添加到Wrapper中,ApplicationContext#addServlet源码如下:

private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
                                               Servlet servlet, Map<String,String> initParams) throws IllegalStateException {
    // 省略其他代码...

    // 从Context应用上下文中根据名称查找Wrapper 
    Wrapper wrapper = (Wrapper) context.findChild(servletName);
    if (wrapper == null) {
        // 如果没有找到,则创建Wrapper
        wrapper = context.createWrapper();
        wrapper.setName(servletName);
        // 添加到应用上下文中
        context.addChild(wrapper);
    } else {
        if (wrapper.getName() != null &&
            wrapper.getServletClass() != null) {
            if (wrapper.isOverridable()) {
                wrapper.setOverridable(false);
            } else {
                return null;
            }
        }
    }

    ServletSecurity annotation = null;
    if (servlet == null) {
        wrapper.setServletClass(servletClass);
        Class<?> clazz = Introspection.loadClass(context, servletClass);
        if (clazz != null) {
            annotation = clazz.getAnnotation(ServletSecurity.class);
        }
    } else {
        // 设置DispatcherServlet到Wrapper中
        wrapper.setServletClass(servlet.getClass().getName());
        wrapper.setServlet(servlet);
        if (context.wasCreatedDynamicServlet(servlet)) {
            annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
        }
    }

    // 省略其他代码...

    return registration;
}

在Tomcat中Context表示应用,Wrapper表示应用中的Servlet容器,如果根据Wrapper名称在Context中没有找到Wrapper,则会创建Wrapper,设置Servlet

2)设置相关属性,例如映射路径、初始化时机等,源码见ServletRegistrationBean#configure

protected void configure(ServletRegistration.Dynamic registration) {
    super.configure(registration);
    String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
    if (urlMapping.length == 0 && this.alwaysMapUrl) {
        urlMapping = DEFAULT_MAPPINGS;
    }
    if (!ObjectUtils.isEmpty(urlMapping)) {
        // 添加路径映射
        registration.addMapping(urlMapping);
    }
    registration.setLoadOnStartup(this.loadOnStartup);
    if (this.multipartConfig != null) {
        registration.setMultipartConfig(this.multipartConfig);
    }
}

4.通过配置类修改容器启动参数

Tomcat启动端口、上下文路径等参数都可以通过配置文件修改

DispatcherServlet默认是在接收到第一个请求时初始化的,可以通过配置修改

1)在resources下创建application.properties

server.port=8888
server.servlet.context-path=/test

spring.mvc.servlet.load-on-startup=1

2)这些配置在SpringBoot都有对应的配置类,可以进行配置绑定,修改WebConfig

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties") // 指定配置文件
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class}) // 绑定配置类
public class WebConfig {
    @Bean
    public ServletWebServerFactory servletWebServerFactory(ServerProperties serverProperties) {
        Integer port = serverProperties.getPort();
        String contextPath = serverProperties.getServlet().getContextPath();
        return new TomcatServletWebServerFactory(contextPath, port);
    }

    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }
}

DispatcherServletRegistrationBean中的配置会在上文configure方法中将这些属性设置到servlet中

应用启动时调用finishRefresh()会根据loadOnStartup的值判断是否初始化,如下图所示:

3)再启动程序,配置修改完成

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lm_ylj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值