转载

我的Spring Boot学习记录(二):Tomcat Server以及Spring MVC的问题

Spring Boot版本: 2.0.0.RELEASE

这里需要引入依赖 spring-boot-starter-web

这里有可能有个人的误解,请抱着怀疑态度看。

建议: 感觉自己也会被绕晕,所以有兴趣的使用IDE工具看下源码

1、Tomcat在什么时候被初始化了?

ServletWebServerApplicationContext 中有段代码,如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext

    @Override
    protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }
    
    /**
     * 创建Web服务容器,如:Tomcat,Jetty等;具体创建的容器根据#getWebServerFactory得到
     * 而WebServerFactory在BeanFactory获取,也就是在加载Bean时确定的,
     * 这里通过Spring Boot自动配置了Tomcat,如果想要深入可以追着#getWebServerFactory看
     * 下面有对应的不太详细的解释
     */
    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        // .....
    }
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedTomcat#tomcatServletWebServerFactory
    /**
     * 这里就通过自动加载Bean时加载了关于Tomcat的WebServerFactory
     *  至于为什么加载Tomcat而不是Jetty,就要多谢Bean加载时@ConditionalOnClass注解
     * 因为我们引入依赖spring-boot-starter-web 其次,它又引入了 spring-boot-starter-tomcat依赖
     * 因为存在{ Servlet.class, Tomcat.class, UpgradeProtocol.class }这些class,所以加载Tomcat
     */
    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

    }
    // .......

上述中说明了 Tomcat 的创建,而什么时候调用 onRefresh 并创建 Tomcat 呢?

其实 onRefreshorg.springframework.context.support.AbstractApplicationContext 一个未具体实现方法,交给子类实现,它在调用 refresh() 方法中调用。

org.springframework.boot.SpringApplication#run(java.lang.String...) 调用了 refreshContext ,又间接调用上述 refresh() 方法

onRefresh 调用后在 refresh() 中还调用了 finishRefresh() ,而重写了其方法 finishRefreshServletWebServerApplicationContext 就启动了Web服务,代码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh
    @Override
    protected void finishRefresh() {
        super.finishRefresh();
        WebServer webServer = startWebServer();
        if (webServer != null) {
            publishEvent(new ServletWebServerInitializedEvent(webServer, this));
        }
    }
    
    private WebServer startWebServer() {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            // 这里如何启动具体可看org.springframework.boot.web.embedded.tomcat.TomcatWebServer
            // 这里之所以我们启动了Spring Boot后程序还在运行是因为Tomcat启动线程在后台运行
            webServer.start();
        }
        return webServer;
    }
org.springframework.boot.web.embedded.tomcat.TomcatWebServer
    
    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();
    }
    
    private void initialize() throws WebServerException {
        //.....
        // Unlike Jetty, all Tomcat threads are daemon threads. We create a
        // blocking non-daemon to stop immediate shutdown
        // 大概意思是创建一个非守护线程来运行吧
        startDaemonAwaitThread();
        //....
    }

    private void startDaemonAwaitThread() {
        Thread awaitThread = new Thread("container-" + (containerCounter.get())) {

            @Override
            public void run() {
                TomcatWebServer.this.tomcat.getServer().await();
            }

        };
        awaitThread.setContextClassLoader(getClass().getClassLoader());
        awaitThread.setDaemon(false);
        awaitThread.start();
    }

上面就大概说了Tomcat怎么在Spring Boot启动后加载创建的

2、Spring MVC在这里怎么工作的?

平时使用SSM开发时,使用Spring MVC 我们知道需要配置 DispatcherServlet ,而这里的是通过自动配置的,还对 DispatcherServlet 通过 ServletRegistrationBean 类进行封装,这里自动装配的代码在 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 中可见,代码如下:

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletConfiguration
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(
                this.webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(
                this.webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
        return dispatcherServlet;
    }


org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration
    /**
     * 这里通过构造器获取已经注册的 DispatcherServlet Bean 
     * 然后封装在ServletRegistrationBean
     */
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
            DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
                dispatcherServlet,
                this.serverProperties.getServlet().getServletMapping());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(
                this.webMvcProperties.getServlet().getLoadOnStartup());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }

封装了Servlet的 ServletRegistrationBean 各个Bean又会被 ServletContextInitializerBeans 进行管理。

在上述 第一点中Tomcat创建加载 中有个方法没有详述,就是 ServletWebServerApplicationContext 中的 createWebServer 方法,里面调用了一个 getSelfInitializer 方法,使用返回值作为参数,代码如下:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
    private void createWebServer() {
        //...
        this.webServer = factory.getWebServer(getSelfInitializer());
        //...
    }

    /**
     * Returns the {@link ServletContextInitializer} that will be used to complete the
     * setup of this {@link WebApplicationContext}.
     * @return the self initializer
     * @see #prepareWebApplicationContext(ServletContext)
     */
    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
        // 又封装成了ServletContextInitializer
        return this::selfInitialize;
    }
    
    private void selfInitialize(ServletContext servletContext) throws ServletException {
        //....
        //getServletContextInitializerBeans这里就能获取到封装了Servlet或Filter等的ServletContextInitializer
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            // 这里通过回调Servlet或Filter能够获取到servletContext,并且将自己(Servlet或Filter)注册到servletContext,这里可能要去了解下Tomcat了
            beans.onStartup(servletContext);
        }
    }

    /**
     * Returns {@link ServletContextInitializer}s that should be used with the embedded
     * web server. By default this method will first attempt to find
     * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
     * {@link EventListener} beans.
     * @return the servlet initializer beans
     */
    protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
        // 这里在new ServletContextInitializerBeans 时会将BeanFactory给它
        // 它在构造器中将封装了Servlet或Filter等的ServletContextInitializer子类获取
        // 并放入一个成员变量sortedList中
        return new ServletContextInitializerBeans(getBeanFactory());
    }

到此,大概就知道了Spring MVC中 DispatcherServlet 是怎么进入Tomcat的了,如果还想细究 DispatcherServlet 是怎么被一步步置入Tomcat容器中的,可以看下 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer 中的代码,这里不展开了。

3、随便唠嗑

入职后好久没运动,被同事拉去打羽毛球,结果,气喘不上来,脑晕,感觉不好,第一天全身酸痛,第二天更痛。我认为,入职后第一条建议就是找时间运动。

这篇其实接着第一篇文章 我的Spring Boot学习记录(一):自动配置的大致调用过程 就想要写的了,拖了好久。期间问自己,为什么干这种无趣的东西。不论如何,还是抽了时间写了,就这样吧。

这里有可能有个人的误解,请抱着怀疑态度看。

建议: 感觉自己也会被绕晕,所以有兴趣的使用IDE工具看下源码

原文  http://www.cnblogs.com/lger/p/11681367.html
正文到此结束
Loading...