我们在上一篇文章 spring boot单元测试 中提到了spring单元测试为SpringApplication指定spring容器实现类,从而达到抛弃tomcat容器的目的,我们这篇文章的目就是反其道而行,让spring boot抛弃tomcat容器。但是有些地方确实需要这么做,我们想利用spring boot为我们提供的便利,但是又不希望引入sevlet容器。

解决方法

网上提供了很多方法,无外乎是去掉 servlet 或者 spring-web jar包依赖,如果我们项目中不需要这些倒也无所谓,如果模块之间有依赖怎么办,这将是个很麻烦的事。在此,我提供了一种更简单有效的办法,不需要去掉额外的依赖,废话不多说,先上代码:

@SpringBootApplication
public class NoneTomcatApplication {
	public static void main(String[] args) throws InterruptedException {
		SpringApplication application = new SpringApplication( NoneTomcatApplication.class );
		
		// 如果是web环境,默认创建AnnotationConfigEmbeddedWebApplicationContext,因此要指定applicationContextClass属性
		application.setApplicationContextClass( AnnotationConfigApplicationContext.class );
		application.run( args );
		
		// 如果不想让spring容器退出,可以使用以下代码
		CountDownLatch latch = new CountDownLatch( 1 );
		latch.await();
	}
}

代码如上所示,我们为SpringApplication指定初始化的spring容器实现类AnnotationConfigApplicationContext,这样spring boot便不会为我们创建servlet容器了。spring boot在创建spring容器时,会判断当前classpath是否存在javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext,如果同时存在,则默认会初始化可以内嵌servlet容器的AnnotationConfigEmbeddedWebApplicationContext。如果我们为spring boot指定spring容器的实现类时,spring boot便老老实实地为我们创建指定的spring容器

下面,我们分析下spring boot相关的源代码

spring boot源码分析

SpringApplication#run()关键代码如下所示,包括了SpringApplicationRunListeners事件通知、创建Environment、打印Banner文字、创建spring容器、准备上下文、刷新spring容器完成初始化,等等

public ConfigurableApplicationContext run(String... args) {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
    		applicationArguments);
    Banner printedBanner = printBanner(environment);
    context = createApplicationContext();
    analyzers = new FailureAnalyzers(context);
    prepareContext(context, environment, listeners, applicationArguments,
    		printedBanner);
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
}

创建spring容器的代码如下所示,其中webEnvironment属性在SpringApplication构造函数中便会进行自动检测,默认情况下是不指定applicationContextClass属性的,因此会根据webEnvironment属性来选择applicationContextClass,非web环境下,创建AnnotationConfigApplicationContext,web环境下创建AnnotationConfigEmbeddedWebApplicationContext,如果我们指定,则会按照我们指定的class创建spring容器。因此,在实际的项目中,如果我们不想使用spring boot提供的内嵌servlet容器,只需要指定applicationContextClass属性就好了,当然排除相关的jar包也是一种方法,不过比较麻烦。具体的代码,见前面的代码

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
				"Unable create a default ApplicationContext, "
						+ "please specify an ApplicationContextClass", ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

下图是ApplicationContextBeanFactory相关的类图,红色框内是上面提到的两个重要实现,spring容器在完成一系列的准备工作之后,会调用onRefresh方法用于ApplicationContext子类初始化额外的bean,这是个模板方法,不同spring容器可以实现自己的逻辑,便于扩展实现

image

由上图可知,AnnotationConfigEmbeddedWebApplicationContext 继承了 EmbeddedWebApplicationContext,其onRefresh方法的主要逻辑如下所示(省略了异常处理):

@Override
protected void onRefresh() {
	super.onRefresh();
	// 创建servlet容器
	createEmbeddedServletContainer();
}

private void createEmbeddedServletContainer() {
	EmbeddedServletContainer localContainer = this.embeddedServletContainer;
	ServletContext localServletContext = getServletContext();
	// 完成servlet容器的创建、启动
	if (localContainer == null && localServletContext == null) {
		EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
		this.embeddedServletContainer = containerFactory
				.getEmbeddedServletContainer(getSelfInitializer());
	}
	else if (localServletContext != null) {
		getSelfInitializer().onStartup(localServletContext);
	}
	initPropertySources();
}

我们再来看看 AnnotationConfigApplicationContext,在 onRefresh 时没有任何处理。两者的对比很明显了,前者会为我们启动servlet容器,后者do nothing

protected void onRefresh() throws BeansException {
	// For subclasses: do nothing by default.
}

值得一提的,spring boot 2.0 移除了 AnnotationConfigEmbeddedWebApplicationContext,使用 AnnotationConfigServletWebServerApplicationContext 替代,将原有的 org.springframework.boot.context.embedded.EmbeddedServletContainer 抽象成 org.springframework.boot.web.server.WebServer,完全脱离了servlet环境的束缚

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐