zoukankan      html  css  js  c++  java
  • Serlvet容器与Web应用

    对启动顺序的错误认识

    之前一直有个观点,应用运行在Servlet容器中,因为从Servlet容器与Web应用的使用方式来看,确实很有这种感觉。

    我们每次都是启动Servlet容器,然后再启动我们的应用程序,比如如果Web应用使用Spring框架的话,先启动Servlet容器,然后才是Spring容器的初始化。

    这样就会产生一种错觉,我们写的程序代码,是运行时Servlet容器的,而容器这个词,更是加深了这种误会。

    然后遇到了SpringBoot的内嵌Servlet容器,这种情况下,是先初始化我们的Spring容器,在初始化SpringContext的过程中,去启动我们的Servlet容器。

    这就尴尬了,颠覆了之前的认知,于是稍微看了下Spring启动Servlet容器的过程,重新理解下Servlet容器。

    先Servlet容器后Spring容器

    以前我们使用Servlet容器来部署Java的Web应用时,需要在web.xml中做如下配置

    <!-- 配置ServletContext 参数 -->
    <context-param>
       <!-- 参数名,这个是固定的,不能变 -->
       <param-name>contextConfigLocation</param-name>
       <param-value>
         	 <!-- Spring 配置文件路径 -->
           classpath:applicationContext.xml
       </param-value>
    </context-param>
    <!-- 上下文加载器的监听器 -->
    <listener>
       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

    在web.xml中作如上的配置,在Servlet容器启动成功后,就可以初始化我们的Spring ApplicationContext了

    怎么做的呢? 稍微记录下

    首先,配置的监听器,会在Servlet容器启动后,由Servlet容器进行一个事件发布,将此事件发布给配置的所有的监听器,以及Servlet容器内部的一些监听器。

    org.springframework.web.context.ContextLoaderListener implements ServletContextListener
    
    public class ContextLoaderListener implements ServletContextListener {
        private ContextLoader contextLoader;
    
        public ContextLoaderListener() {
        }
    
        public void contextInitialized(ServletContextEvent event) {
            this.contextLoader = this.createContextLoader();
            // 从ServletContextEvent事件中,获取ServletContext对象
            this.contextLoader.initWebApplicationContext(event.getServletContext());
        }
    
        protected ContextLoader createContextLoader() {
            return new ContextLoader();
        }
    
        public ContextLoader getContextLoader() {
            return this.contextLoader;
        }
    
        public void contextDestroyed(ServletContextEvent event) {
            if (this.contextLoader != null) {
                this.contextLoader.closeWebApplicationContext(event.getServletContext());
            }
    
        }
    }
    
    

    然后看下初始化WebApplicationContext

    org.springframework.web.context.ContextLoader#createWebApplicationContext方法中我们可以看到如下的一段内容

    protected WebApplicationContext createWebApplicationContext(ServletContext servletContext, ApplicationContext parent) throws BeansException {
            Class contextClass = this.determineContextClass(servletContext);
            if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
                throw new ApplicationContextException("xxxx");
            } else {
                ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
                wac.setParent(parent);
                wac.setServletContext(servletContext);
                // 这里是从ServletContext对象中,获取我们配置的Spring上下文配置文件路径
                wac.setConfigLocation(servletContext.getInitParameter("contextConfigLocation"));
                this.customizeContext(servletContext, wac);
                wac.refresh();
                return wac;
            }
        }
    

    通过上面的两个类,一个配置,我们对之前使用Servlet容器来启动Spring容器,就有了一个比较直观的认识。

    先Spring容器后Servlet容器

    接下来我们看下SpringBoot是如何启动Servlet容器的

    我们启动SpringBoot一般都是如此

    SpringApplication.run(Application.class, args);
    

    static run方法中,实例化SpringApplication对象

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    		this.resourceLoader = resourceLoader;
    		Assert.notNull(primarySources, "PrimarySources must not be null");
    		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                    // 决定WebApplication类型
    		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));    
    		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    		this.mainApplicationClass = deduceMainApplicationClass();
    	}
    
    static WebApplicationType deduceFromClasspath() {
                     // 如果存在类 org.springframework.web.reactive.DispatcherHandler  
                     // 并且没有 org.springframework.web.servlet.DispatcherServlet 
                     // 和 org.glassfish.jersey.servlet.ServletContainer 
                     // 则认为是REACTIVE类型的WEB应用
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
                    // 存在 javax.servlet.Servlet 
                    // 和 org.springframework.web.context.ConfigurableWebApplicationContext 
                    // 则认为是SERVLET类型的WEB应用
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
                    // 都没有出现就不是WEB应用
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    

    然后在方法org.springframework.boot.SpringApplication#createApplicationContext

    protected ConfigurableApplicationContext createApplicationContext() {
    		Class<?> contextClass = this.applicationContextClass;
    		if (contextClass == null) {
                              // 如果还没有决定好使用哪个ApplicationContext的子类,根据WebApplicationType来决定
    			try {
    				switch (this.webApplicationType) {
    				case SERVLET:
                                              // 加载 AnnotationConfigServletWebServerApplicationContext类
    					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
    					break;
    				case REACTIVE:
    					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    					break;
    				default:
    					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    				}
    			}
    			catch (ClassNotFoundException ex) {
    				throw new IllegalStateException(
    						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
    			}
    		}
      	        // 实例化ApplicationContext对象 
    		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    	}
    
    

    之后在org.springframework.boot.SpringApplication#run(java.lang.String...)方法中,调用org.springframework.boot.SpringApplication#refreshContext,然后调用下面的方法

    protected void refresh(ApplicationContext applicationContext) {
                    // 类型判断
    		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
                    // 调用refresh方法 这里利用多态,调用实际对象的refresh方法 
    		((AbstractApplicationContext) applicationContext).refresh();
    }
    

    最终会调用到org.springframework.context.support.AbstractApplicationContext#refresh

    refresh方法中有一个onRefresh()

    public void refresh() throws BeansException, IllegalStateException {
    		synchronized (this.startupShutdownMonitor) {
    			// ... 省略
    			try {
    				// ... 省略
    				// Initialize other special beans in specific context subclasses.
                                     // 在特定的上下文子类中,初始化一些特殊的Bean
    				onRefresh();
    			        // ... 省略
    			}
    			catch (BeansException ex) {
    				// ... 省略
    			}
    			finally {
    				// ... 省略
    			}
    		}
    	}
    
    

    这个onRefresh方法由子类实现,这里是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

    protected void onRefresh() {
    		super.onRefresh();
    		try {
                             // 关键时刻来了,创建WebServer
    			createWebServer();
    		}
    		catch (Throwable ex) {
    			throw new ApplicationContextException("Unable to start web server", ex);
    		}
    	}
    

    暂时看到这里就可以了,后面就是根据具体引入了哪个Servlet容器的jar包,来进行启动操作,以Tomcat为例

    org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

    public WebServer getWebServer(ServletContextInitializer... initializers) {
    		if (this.disableMBeanRegistry) {
    			Registry.disableRegistry();
    		}
    		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);
    		return getTomcatWebServer(tomcat);
    	}
    

    这一步走完后,Servlet容器基本就被启动了,不过Spring容器还没有初始化完成。

    总结

    无论是Servlet容器先启动,还是Spring容器先启动,其实都没有关系,区别就是先后。

    这两个构成了一个整体,并不是你中有我,或者我中有你的关系。

    在Servlet容器启动时,或者Spring容器启动时,都会开启一个虚拟机实例进程,后面加载的代码,全部都是位于这一个虚拟机进程中,Servlet容器会负责监听一个端口,处理HTTP请求,再与我们Spring容器对接。

    两种方式的启动先后顺序,并没有改变对HTTP请求的处理流程。

    也可以看出,这俩的相互独立性。

  • 相关阅读:
    微信开发者工具http申请图片变成https
    vue 中v-for img src 路径加载问题
    nodejs内置模块querystring中parse使用问题
    用git上传项目到github遇到的问题和解决方法
    页面刷新——微信小程序生命周期探索
    小程序项目复盘(三) 用全局变量传参的问题
    小程序项目复盘(二) wx.request异步请求处理
    小程序项目复盘(一)字符串处理问题
    微信小程序中我常用到的CSS3弹性盒子布局(flex)总结
    wx.request中POST方法传参问题,用到JSON.stringify()
  • 原文地址:https://www.cnblogs.com/heartlake/p/12779521.html
Copyright © 2011-2022 走看看