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 !