九 spring整合tomcat核心
9.1 核心思想
我们也许有疑问,不管是Springmvc框架还是Springboot框架都需求嵌入一个Tomcat服务中间件,当然也有可能是Jetty,由于本文主要讲的是tomcat所以我们应该想问的是tomcat启动的时候做了什么呢?
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
万变不离其宗,不管是Springmvc整合tomcat还是Springboot整合tomcat都有一个基本的核心思想,那么我们可以看看这些中间件是怎么跟Spring家族整合在一起的;tomcat是怎么做到在启动的时候完成Spring生命周期过程,加载所有的bean并且对外暴露的端口的;请求是怎么能够根据路径分发到我们后台的业务代码的呢?所以我们就说tomcat启动主要对Spring做了什么这才是最核心的.那么做了什么呢我们可以根据Web.xml的配置来猜测一下.
- ContextLoaderListener在tomcat启动的时候,同时调用 AbstractApplicationContext类的refresh()方法完成Spring生命周期的加载.
- DispatcherServlet tomcat启动的时候会给tomcat的HttpServlet定义好子类用来接受并分发客户端请求到服务器
所以综合上面的分析我们可以得出结论,tomcat启动的时候无非就是给Spring做了两件事,一个是完成了spring的生命周期任务,一个是给ServletContext上下文陪到了一个DispatcherServlet作为请求接收的最小接收单位.
9.2 spi机制
在讲springmvc整合tomcat的时候需要先讲一下spi机制,因为整合的时候tomcat利用了类似spi的方式加载类了.
9.2.1 SPI作用
思考一个场景,我们封装了一套服务,别人通过引入我们写好的包,就可以使用这些接口API,完成相应的操作,这本来没有什么问题,但是会存在使用该服务的实体有不相同的业务需求,需要进一步的扩展,但是由于api是写好的,想要扩展并非那么的简单,如果存在这样子的场景,我们该怎么办?java已经提供了自己的spi机制了.
SPI的全称是Service Provider Interface,是Java提供的可用于第三方实现和扩展的机制,通过该机制,我们可以实现解耦,SPI接口方负责定义和提供默认实现,SPI调用方可以按需扩展.
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。
9.2.2使用场景
概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
比较常见的例子:
- 数据库驱动加载接口实现类的加,JDBC加载不同类型数据库的驱动.
- 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
- Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
- Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口
9.2.3使用介绍
要使用Java SPI,需要遵循如下约定:
1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
9.2.4示例代码
如上工程所示我们可以看到我分工程来定义了接口的两个实现好
Spi-api
public interface HelloService {
public void sayHello();
}
Spi-provider
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("HelloServiceImpl.......");
}
}
如上所示META-INF下面引入services文件指定接口的实现类,然后在类里面是心啊这个接口.
Spi-provider-other
public class HelloServiceImplOther implements HelloService {
@Override
public void sayHello() {
System.out.println("HelloServiceImplOther.......");
}
}
另一个工程结构和实现方式同上一个工程一样.
public static void main(String[] args) {
ServiceLoader<HelloService> load = ServiceLoader.load(HelloService.class);
for (HelloService helloService:load) {
helloService.sayHello();
}
}
可以看到这就是spi的执行结果
十 springmvc整合tomcat
那么我们为什么要介绍spi呢,其实是因为springmvc在整合tomcat的时候就是使用了spi的整合机制的.下面我们来看看具体是怎么处理的.当然spi只是一种设计思想,java有java的spi实现机制,那么肯定spring也是会有spring的实现机制的.那么tomcat是如何在启动的时候拉起Spring,完成Spring的整个生命周期的呢.这里我们要回顾一下tomcat的启动流程了.在tomcat启动流程中有这样一行代码.
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
可以看到tomcat启动的时候会启动,tomcat的engin容器,start()方法里面会启动线程启动所有的容器.那么我们试想一下对于tomcat我们什么情况下代表启动一个应用.那肯定是启动context容器的时候代表启动应用.那这个时候我们启动了context容器的时候如果想不加载xml配置的servlet.那么我们就只能往context容器中添加servlet接受来自客户端的请求分发了.好的接下来我们看context容器做了什么.在context启动的时候的代码.如下代码所示就是tomcat启动的时候的核心代码.
protected synchronized void startInternal() throws LifecycleException {
/**省略部分代码*/
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
/**省略部分代码*/
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
/**省略部分代码*/
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
如上代码分别表示了在ServletContext中添加Servlet和ContextLoaderListener这个启动Spring容器的的监听器类.通过这个启动器就可以完成Spring的生命周期了.
10.1 嵌入Servlet
public static final String CONFIGURE_START_EVENT = "configure_start";
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for (LifecycleListener listener : lifecycleListeners) {
listener.lifecycleEvent(event);
}
}
我们可以看到ContextConfig这个类是实现了 LifecycleListener接口的,所以所有实现了这个接口的类都会被调用lifecycleEvent方法,那我们来看看contextConfig类这个方法主要做了些什么事情.
public void lifecycleEvent(LifecycleEvent event) {
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) { }
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
if (originalDocBase != null) { context.setDocBase(originalDocBase);}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
f (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); 抓取了这段核心代码.行那我们看看在这个观察者里面上下文启动以后这个观察者做了什么.
protected synchronized void configureStart() {
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.start"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.xmlSettings",
context.getName(), Boolean.valueOf(context.getXmlValidation()),
Boolean.valueOf(context.getXmlNamespaceAware())));
}
webConfig();
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
validateSecurityRoles();
}
if (ok) {
authenticatorConfig();
}
if (log.isDebugEnabled()) {
log.debug("Pipeline Configuration:");
Pipeline pipeline = context.getPipeline();
Valve valves[] = null;
if (pipeline != null) { valves = pipeline.getValves();}
if (valves != null) {
for (Valve valve : valves) {
log.debug(" " + valve.getClass().getName());
}
}
}
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
webConfig() 核心代码在这个方法里面,那么这个方法里面做了什么呢?我们可以跟进去看一看.
protected void webConfig() {
/**省略部分代码*/
if (ok) {
processServletContainerInitializers();
}
}
如上所示就是我们的核心代码了.我们看看这个processServletContainerInitializers()方法做了什么呢?
protected void processServletContainerInitializers() {
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString("contextConfig.servletContainerInitializerFail",context.getName()),e);
ok = false;
return;
}
for (ServletContainerInitializer sci : detectedScis) {
initializerClassMap.put(sci, new HashSet<Class<?>>());
HandlesTypes ht;
try {
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info(sm.getString("contextConfig.sci.debug",sci.getClass().getName()),e);
} else {
log.info(sm.getString("contextConfig.sci.info",sci.getClass().getName()));
}
continue;
}
if (ht == null) {continue;}
Class<?>[] types = ht.value();
if (types == null) {continue;}
for (Class<?> type : types) {
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}
detectedScis = loader.load(ServletContainerInitializer.class);核心代码加载context上下文目录里 META-INF/services/下面所有的ServletContainerInitializer实现类.看明白了这里就是类似于java的spi机制,只是tomcat自己做了实现.通过WebappServiceLoader这个类来对整个上下文路径下的所有META-INF/services路径下的的接口文件进行来扫描,然后找到接口的实现类获取这个注解 @HandlesTypes,然后制作一个通过注解的值和实现类的map的映射.好了当制作好的映射以后自然就是执行了.
contextConfig类
if (ok) {
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(entry.getKey(), null);
} else {
context.addServletContainerInitializer( entry.getKey(), entry.getValue());
}
}
}
如上核心代码所示,通过遍历映射类把它添加到的private Map<ServletContainerInitializer,Set<Class<?>>> initializers =new LinkedHashMap<>();中,在context启动的时候,这个就是整个configure_start的事件处理过程.那么整个??构造好以后就是调用了我们再回到Standcontext的启动过程.
/**省略部分代码*/
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
如上代码所示, ServletContainerInitializer调用了所有这个类的子类的onStartup()方法.springmvc的启动就是巧妙的运用了这个类的onStartup()方法.所以我们只要在springmvc下运用spi机制就可以带动Spring启动.好了我们看到了tomcat通过调用接口ServletContainerInitializer的调用,来启动tomcat相关容器的所有这里我们可以想想,假如我们要引入Springmvc的话,在tomcat启动的时候,我们其实应该把配置好的servlet映射就注入到ServletContext中.这样在请求执行的时候才能找到对应的servlet类.好了我们来看看这里Springmvc就巧妙的运用了我们的Spi机制.
如上图所示,是不是感觉似曾相识.ServletContainerInitializer在tomcat中的接口,在spring-web中定义了一个实现类.好的那么就是说tomcat启动的时候,也会调用SpringServletContainerInitializer类的 onStartup()方法了呢.接下来我们看看这个类的源代码做了什么?
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList();
Iterator var4;
if(webAppInitializerClasses != null) {
var4 = webAppInitializerClasses.iterator();
while(var4.hasNext()) {
Class<?> waiClass = (Class)var4.next();
if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)waiClass.newInstance());
} catch (Throwable var7) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
}
}
}
}
if(initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
} else {
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
var4 = initializers.iterator();
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
}
}
}
从上面代码可以看出 SpringServletContainerInitializer 这里我们主要是定义了这个类,迭代的调用了这个类的onStartup()方法.一看这个类是从ServletContainerInitializer 类的onStartup()方法,通过遍历WebApplicationInitializer调用onStartup方法,首先,SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。这个类上还有一个注解@HandlesTypes,它的作用是将感兴趣的一些类注入到ServletContainerInitializer,在tomcat中有获取这个注解把ServletContainerInitializer的spi实现类作为key注解的value值作为值的一个集合可以看到这个时候从SpringServletContainerInitializer 的onStartup()方法传进来的这个集合参数就是注解扫描注入的类, 而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。这样我们就可以通过在启动tomcat的时候通过这个SPI的扩展点来做一些事情了.那么我们看看SpringMVC做了什么.
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
}
如上所示初始化Spring的生命周期,往servletContext中添加对应的DispatcherServlet这样请求过来的时候,就会调用DispatcherServlet 的service()方法了.至此我们完成了两个步里面的其中一步,把DispatcherServlet 添加到servletContext作为Spring的分发器.addServlet()方法就不贴出来了,其实就是创建一个StandardWrapper并调用wrapper.setServlet(servlet)把servlet组合进wapper中,这完成了tomcat的容器结构.这样请求过来的时候就会从wapper中获取这个Servlet进行执行了.接下来我们再回到原来的代码在StandardContext中看到如下所示的代码.
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
public boolean listenerStart() {
if (log.isDebugEnabled()) {
log.debug("Configuring application event listeners");
}
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Configuring event listener class '" +
listeners[i] + "'");
}
try {
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
if (!ok) {
getLogger().error(sm.getString("standardContext.applicationSkipped"));
return false;
}
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
for (Object result : results) {
if ((result instanceof ServletContextAttributeListener)
|| (result instanceof ServletRequestAttributeListener)
|| (result instanceof ServletRequestListener)
|| (result instanceof HttpSessionIdListener)
|| (result instanceof HttpSessionAttributeListener)) {
eventListeners.add(result);
}
if ((result instanceof ServletContextListener) || (result instanceof HttpSessionListener)) {
lifecycleListeners.add(result);
}
}
eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
if (getLogger().isDebugEnabled()) {
getLogger().debug("Sending application start events");
}
getServletContext();
context.setNewServletContextListenerAllowed(false);
Object instances[] = getApplicationLifecycleListeners();
if (instances == null || instances.length == 0) {
return ok;
}
ServletContextEvent event = new ServletContextEvent(getServletContext());
ServletContextEvent tldEvent = null;
if (noPluggabilityListeners.size() > 0) {
noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext());
tldEvent = new ServletContextEvent(noPluggabilityServletContext);
}
for (Object instance : instances) {
if (!(instance instanceof ServletContextListener)) {
continue;
}
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
fireContainerEvent("afterContextInitialized", listener);
getLogger().error(sm.getString("standardContext.listenerStart",
instance.getClass().getName()), t);
ok = false;
}
}
return ok;
}
可以看到如上所示的代码主要是通过获取到ServletContextListener 的所有实现类.然后调用contextInitialized这个方法.好了那么我们可以看看在我们的web.xml中有一个上下文的监听器类.
Web.xml
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
我们不难发现web.xml中有一个ContextLoaderListener类,这个类在tomcat启动的时候会加载web.xml文件,把他设置到ServletContextListener里面具体代码如下.
10.2 Spring生命周期
我们先来看看ContextLoaderListener的工作流程如下图所示
如上所示就是ContextLoaderListener类的工作流程如上时序图所示,可以看到主要调用的方法就是 AbstractApplicationContext这个类的refresh()方法,这个方法可以拉起整个Spring的生命周期过程
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
如上代码所示tomcat通过调用之前tomcat启动的时候加载的web.xml文件下的ServletContextListener 实现类调用ContextLoaderListener 的contextInitialized()方法.那么好的接下来我们看看这个方法具体做了什么.
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
} catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
根据提供的servlet上下文去初始化Spring的web应用上下文,在构造时使用当前应用上下文或者在web.xml中配置参数contextClass和contextConfigLocation去创建新的上下文。先判断是否在ServletContext中存在root上下文,如果有,说明已载入过或配置文件出错,可以从错误信息中看出。通过createWebApplicationContext方法创建web应用上下文,此上下文必定是实现了ConfigurableWebApplicationContext接口,在设置parent for root web application context,在configureAndRefreshWebApplicationContext方法里构造bean工厂和容器里bean的创建,这里就不描述了,下次专门研究这块,最后将跟上下文存入servletContext里,同时根web应用上下文存入到currentContextPerThread,可供后续取出当前上下文,currentContextPerThread = new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);。
ContextLoader中createWebApplicationContext方法创建根上下文
this.context = createWebApplicationContext(servletContext);
我们主要看一下这一行核心代码,看看这个代码主要是做了什么
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
初始化根据上下文,最后返回值需强转成ConfigurableWebApplicationContext。ContextLoader中determineContextClass方法找到根据上下文的Class类型
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);
}
}else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
Web.xml中配置了contextClass就取其值,但必须是实现ConfigurableWebApplicationContext,没有的就取默认值XmlWebApplicationContext。ContextClass默认值和ContextLoader.properties如下:
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
ContextLoader.properties
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
ContextLoader
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
configureAndRefreshWebApplicationContext(cwac, servletContext);核心代码如下所示,可以看到如下代码中 wac.refresh()
就在这个时候拉起了spring.
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
如上代码所示就是整个tomcat整个springmvc的启动过程,那么实际上这里我们也可以思考,springmvc->springboot无非就是去配置了.那么不管去不去配置,启动过程中这两个事情还是要做的.那么接下来我们就一起来看看Springboot和tomcat是怎么整合在一起的.