欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Serlvet容器与Web应用

程序员文章站 2022-07-01 15:21:51
对启动顺序的错误认识 之前一直有个观点,应用运行在Servlet容器中,因为从Servlet容器与Web应用的使用方式来看,确实很有这种感觉。 我们每次都是启动Servlet容器,然后再启动我们的应用程序,比如如果Web应用使用Spring框架的话,先启动Servlet容器,然后才是Spring容器 ......

对启动顺序的错误认识

之前一直有个观点,应用运行在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请求的处理流程。

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