Springboot web工程通过内置和外置tomcat启动及其原理

  • Post author:
  • Post category:其他


1. 普通的spring web应用(不使用spring boot启动)

需要在web.xml里配置上下文信息。web.xml中通过配置ContextLoaderListener来在tomcat容器启动时创建spring上下文。

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframe.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener实现了ServletContextListener接口,在web.xml配置这个监听器,启动容器时,会执行ContextInitialized方法
@Override
public void contextInitialized(ServletContextEvent event) {
    // 调用父类ContextLoader的方法初始化spring 上下文
    initWebApplicationContext(event.getServletContext());
}

同时ContextLoaderListener继承了ContextLoader,其成员方法initWebApplicationContext()初始化上下文。

如果使用的是spring mvc框架,那么还需要配置前端控制器Servlet,即DispatcherServlet。

<servlet>  
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
    <init-param>  
        <param-name>contextConfigLocation</param-name>            
        <!--多个值用逗号分隔-->  
        <param-value>classpath:spring/dispatcher-servlet.xml</param-value>  
    </init-param>  
    <load-on-startup>1</load-on-startup>
</servlet>  
<servlet-mapping>  
    <servlet-name>DispatcherServlet</servlet-name>  
    <url-pattern>/</url-pattern>
</servlet-mapping>  

DispatcherSevlet中也维护了一个contextConfigLocation参数,context-param中的设置是全局的,而这里的上下文设置服务于DispatcherServlet,事实上,完全可以在dispatcherServlet中设置上下文,而省略掉context-param和ContextLoaderListener配置:DispatcherServlet在初始化时(tomcat启动时初始化servlet,GenericServlet.init()),最终会调用其父类FrameworkServlet的initWebApplicationContext()方法来初始化web上下文。

2. spring boot web 应用

a. 通过内置tomcat启动

依赖配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.13.RELEASE</version>
</dependency>

启动类:

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

运行main方法即可。spring会根据classpath下是否存在web相关类来决定启动普通上下文还是web上下文。

private boolean deduceWebEnvironment() {
    // WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",	            
    //"org.springframework.web.context.ConfigurableWebApplicationContext" };
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}
protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
            // 决定起web上下文还是普通上下文
		    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);
}

b. 通过外置tomcat启动

打包:

<packaging>war</packaging>

依赖配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.13.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>

新增一个继承自SpringBootServletInitializer的类,并重写configure方法。

public class MyServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 参数是之前main方法所在类
        return builder.sources(App.class);
    }
}

根据Servlet3.0规范,web应用启动时会实例化每个jar包中的ServletContainerInitializer提供者(SPI)。实现了ServletContainerInitializer的jar包会在META-INF/services目录下维护一个文件javax.servlet.ServletContainerInitializer,其内容指向ServletContainerInitializer的实现类。如spring-web中:

该文件内容为:

org.springframework.web.SpringServletContainerInitializer

看看SpringServletContainerInitializer做了哪些事情

// @HandlesTypes注解表示SpringServletContainerInitializer能处理的类型
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
    // Set<Class<?>> webAppInitializerClasses是@HandlesTypes标注的类型的所有实例
	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
                // 对webAppInitializerClasses做进一步校验,只有符合条件的才添加到            
                // initializers中
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer) waiClass.newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
        // 执行所有initializers的onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

我们自定义的MyServletInitializer便是可以被SpringServletContainerInitializer 处理的initiaizer,其onStartup方法会被调用

    @Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case a ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
        // 创建web上下文
		WebApplicationContext rootAppContext = createRootApplicationContext(
				servletContext);
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext) {
				@Override
				public void contextInitialized(ServletContextEvent event) {
					// no-op because the application context is already initialized
				}
			});
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as "
					+ "createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

	protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		StandardServletEnvironment environment = new StandardServletEnvironment();
		environment.initPropertySources(servletContext, null);
		builder.environment(environment);
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
        // 配置,我们自定义的initializer重写了该方法,因此会走我们的逻辑
		builder = configure(builder);
        // 创建spring应用
		SpringApplication application = builder.build();
		if (application.getSources().isEmpty() && AnnotationUtils
				.findAnnotation(getClass(), Configuration.class) != null) {
			application.getSources().add(getClass());
		}
		Assert.state(!application.getSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.getSources().add(ErrorPageFilterConfiguration.class);
		}
        // 运行spring应用
		return run(application);
	}

ok,that’s all !



版权声明:本文为yange1025原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。