当前位置: 移动技术网 > IT编程>开发语言>Java > Serlvet容器与Web应用

Serlvet容器与Web应用

2020年04月26日  | 移动技术网IT编程  | 我要评论
对启动顺序的错误认识 之前一直有个观点,应用运行在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请求的处理流程。

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网