九 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生命周期
10.2.1 web.xml配置方式
我们先来看看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是怎么整合在一起的.
10.1.2 注解的方式
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上文,添加DispatherServlet,然后在tomcat实例化的时候会调用生命周期方法.然后调用父类的 HttpServletBean的init()方法来进行实例化
this.initServletBean();
然后调用 FrameworkServlet类的initServletBean() 方法完成spring的12大步
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var4) {
this.logger.error("Context initialization failed", var4);
throw var4;
}
if (this.logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
}
if (this.logger.isInfoEnabled()) {
this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = this.findWebApplicationContext();
}
if (wac == null) {
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized(this.onRefreshMonitor) {
this.onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
}
return wac;
}
十一 springboot整合tomcat
11.1 Spring生命周期
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
如上代码所示,当我们引入如上所示的starter-web包以后,我们就默认引入了tomcat了.那么springboot在启动的时候是怎么整合tomcat,并启动tomcat的呢.这里是main函数入口,两句代码最耀眼,分别是SpringBootApplication注解和SpringApplication.run()方法。
@SpringBootApplication
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
如上代码所示springboot启动的核心代码.那么具体这个代码是怎么执行的呢我们接着往下看.
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
如上进入run方法以后.我们看一下如下图所示的核心代码.
public ConfigurableApplicationContext run(String... args) {
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
//打印banner,这里你可以自己涂鸦一下,换成自己项目的logo
Banner printedBanner = this.printBanner(environment);
//创建应用上下文
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//预处理上下文
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
this.refreshContext(context);
//再刷新上下文
this.afterRefresh(context, applicationArguments);
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {}
}
既然我们想知道tomcat在SpringBoot中是怎么启动的,那么run方法中,重点关注创建应用上下文(createApplicationContext)和刷新上下文(refreshContext)。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
这里会创建一个AnnotationConfigServletWebServerApplicationContext可以知道的是spring上下文接口的子类ConfigurableApplicationContext,那么我们看看初始化了一个Spring上下文以后会做什么,接着看后面的代码.
private void refreshContext( ConfigurableApplicationContext context) {
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {}
}
}
//这里直接调用最终父类AbstractApplicationContext.refresh()方法
protected void refresh(ApplicationContext applicationContext) {
((AbstractApplicationContext)applicationContext).refresh();
}
父类AbstractApplicationContext.refresh()方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
//调用各个子类的onRefresh()方法,也就说这里要回到子类:AnnotationConfigServletWebServerApplicationContext,调用该类的onRefresh()方法
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
如上代码片段所示就是我们所熟悉的spring的生命周期初始化的代码了.可见springboot的启动做的第一件核心的事情也是一样的初始化spring的上下文,那么AnnotationConfigServletWebServerApplicationContext在执行到 this.onRefresh();的时候如果子类有重写这个方法的化就会调用子类的onRefresh()方法.然而我们回到AnnotationConfigServletWebServerApplicationContext类中并没有发现onRefresh();倒是发现了在其继承的父类里面有这样一个类ServletWebServerApplicationContext继承ConfigurableWebServerApplicationContext类.所以我们可以知道实际上是调用类ServletWebServerApplicationContext类的 onRefresh()方法.好了这个时候我们可以看一下这个方法做了什么.
11.2 嵌入Servlet
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
如上所示我们看到了一个扩展方法createWebServer(),好的接下来我们看看这个方法做了什么呢.
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {}}
this.initPropertySources();
}
这里我们解读一下代码,如果没有服务就创建服务,如果有服务就把ServletContextInitializer的实现类都onstartup()一下.
private ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
this.prepareWebApplicationContext(servletContext);
this.registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
Iterator var2 = this.getServletContextInitializerBeans().iterator();
while(var2.hasNext()) {
ServletContextInitializer beans = (ServletContextInitializer)var2.next();
beans.onStartup(servletContext);
}
}
这里我们可以看到和Springmvc一样ServletContextInitializer它从spring中获取所有的ServletContextInitializer接口的实现类依次调用了onStartup()方法.总之就是把所ServletContextInitializer实现类都执行一下onStartup()方法,并且放到tomcat的上线文中.那么这里我们会有一个疑问,Springboot的另一个核心要点DispatcherServlet这个类是什么时候放入到tomcat()的上下文中的.这里我们先不管先看看什么地方启动了tomcat.
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
这里ServletWebServerFactory接口有4个实现类
可以看到服务器的创建工厂有四个实现类.而其中我们常用的有两个:TomcatServletWebServerFactory和JettyServletWebServerFactory。TomcatServletWebServerFactory.java 这里我们使用的tomcat,所以我们查看TomcatServletWebServerFactory。到这里总算是看到了tomcat的踪迹。
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//创建Connector对象
Connector connector = new Connector(this.protocol);
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);
}
这里我们重点看一下 prepareContext(tomcat.getHost(), initializers);这个函数做了什么,这里可以看出是为tomcat启动准备上下文环境,好了那么必安然我们知道的是ServletContextInitializer一定会塞到tomcat的context容器中的我们接着往下看.
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = this.getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new TomcatServletWebServerFactory.LoaderHidingResourceRoot(context));
}
context.setName(this.getContextPath());
context.setDisplayName(this.getDisplayName());
context.setPath(this.getContextPath());
File docBase = documentRoot != null ? documentRoot : this.createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader());
this.resetDefaultLocaleMapping(context);
this.addLocaleMappings(context);
context.setUseRelativeRedirects(false);
try {
context.setCreateUploadTargets(true);
} catch (NoSuchMethodError var8) {
}
this.configureTldSkipPatterns(context);
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (this.isRegisterDefaultServlet()) {
this.addDefaultServlet(context);
}
if (this.shouldRegisterJspServlet()) {
this.addJspServlet(context);
this.addJasperInitializer(context);
}
context.addLifecycleListener(new TomcatServletWebServerFactory.StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers);
host.addChild(context);
this.configureContext(context, initializersToUse);
this.postProcessContext(context);
}
如上所示是把所有的 ServletContextInitializer组合了一下.好的那么我们看一下this.configureContext(context, initializersToUse);的核心代码做了什么.
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext)context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
Iterator var7 = this.contextLifecycleListeners.iterator();
while(var7.hasNext()) {
LifecycleListener lifecycleListener = (LifecycleListener)var7.next();
context.addLifecycleListener(lifecycleListener);
}
var7 = this.contextValves.iterator();
while(var7.hasNext()) {
Valve valve = (Valve)var7.next();
context.getPipeline().addValve(valve);
}
var7 = this.getErrorPages().iterator();
while(var7.hasNext()) {
ErrorPage errorPage = (ErrorPage)var7.next();
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
var7 = this.getMimeMappings().iterator();
while(var7.hasNext()) {
Mapping mapping = (Mapping)var7.next();
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
this.configureSession(context);
(new DisableReferenceClearingContextCustomizer()).customize(context);
var7 = this.tomcatContextCustomizers.iterator();
while(var7.hasNext()) {
TomcatContextCustomizer customizer = (TomcatContextCustomizer)var7.next();
customizer.customize(context);
}
}
如上代码所示核心代码 TomcatStarter starter = new TomcatStarter(initializers);首先是把从spring中获取到的ServletContextInitializer这个类与TomcatStarter 进行组合.其次就是 context.addServletContainerInitializer(starter, NO_CLASSES);这个把ServletContextInitializer类添加到tomcat的context()上下文中.好了那我们看看TomcatStarter的实现.
class TomcatStarter implements ServletContainerInitializer {
private static final Log logger = LogFactory.getLog(TomcatStarter.class);
private final ServletContextInitializer[] initializers;
private volatile Exception startUpException;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
try {
ServletContextInitializer[] var3 = this.initializers;
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
ServletContextInitializer initializer = var3[var5];
initializer.onStartup(servletContext);
}
} catch (Exception var7) {
this.startUpException = var7;
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: " + var7.getClass().getName() + ". Message: " + var7.getMessage());
} }
}
}
如上代码所示 TomcatStarter 实现了ServletContainerInitializer 这个接口,那么在Tomcat启动的时候就可以调用onStartup()方法遍历spring中取出来的ServletContainerInitializer集合,执行它的onStartup()方法.这里也许会有一个疑问不是应该执行DispatherServlet么,结果有些意外.那我们找找注入spring的ServletContainerInitializer实现类是什么,怎么实现的onStartup()这个方法.接下来我们看一个类.
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private int order = 2147483647;
private boolean enabled = true;
public RegistrationBean() {
}
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = this.getDescription();
if (!this.isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
} else {
this.register(description, servletContext);
}
}
protected abstract String getDescription();
protected abstract void register(String description, ServletContext servletContext);
}
如上代码就是ServletContainerInitializer的其中一个实现类,可以看到最后是调用了register()方法,实际上是调用了子类的register()方法了,那么我们看看这个方法的实现类具体是做了什么呢.
public abstract class DynamicRegistrationBean<D extends Dynamic> extends RegistrationBean {
private static final Log logger = LogFactory.getLog(RegistrationBean.class);
private String name;
private boolean asyncSupported = true;
private Map<String, String> initParameters = new LinkedHashMap();
public DynamicRegistrationBean() {
}
/**省略部分代码*/
protected final void register(String description, ServletContext servletContext) {
D registration = this.addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
} else {
this.configure(registration);
}
}
protected abstract D addRegistration(String description, ServletContext servletContext);
protected void configure(D registration) {
registration.setAsyncSupported(this.asyncSupported);
if (!this.initParameters.isEmpty()) {
registration.setInitParameters(this.initParameters);
}
}
}
如上面类所示addRegistration这个方法我们再深入的看看这个方法主要是做了什么.
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<Dynamic> {
private static final String[] DEFAULT_MAPPINGS = new String[]{"/*"};
private T servlet;
private Set<String> urlMappings;
private boolean alwaysMapUrl;
private int loadOnStartup;
private MultipartConfigElement multipartConfig;
public ServletRegistrationBean() {
this.urlMappings = new LinkedHashSet();
this.alwaysMapUrl = true;
this.loadOnStartup = -1;
}
public ServletRegistrationBean(T servlet, String... urlMappings) {
this(servlet, true, urlMappings);
}
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
this.urlMappings = new LinkedHashSet();
this.alwaysMapUrl = true;
this.loadOnStartup = -1;
Assert.notNull(servlet, "Servlet must not be null");
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.servlet = servlet;
this.alwaysMapUrl = alwaysMapUrl;
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
public void setServlet(T servlet) {
Assert.notNull(servlet, "Servlet must not be null");
this.servlet = servlet;
}
public T getServlet() {
return this.servlet;
}
public void setUrlMappings(Collection<String> urlMappings) {
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.urlMappings = new LinkedHashSet(urlMappings);
}
public Collection<String> getUrlMappings() {
return this.urlMappings;
}
public void addUrlMappings(String... urlMappings) {
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
public void setLoadOnStartup(int loadOnStartup) {
this.loadOnStartup = loadOnStartup;
}
public void setMultipartConfig(MultipartConfigElement multipartConfig) {
this.multipartConfig = multipartConfig;
}
public MultipartConfigElement getMultipartConfig() {
return this.multipartConfig;
}
protected String getDescription() {
Assert.notNull(this.servlet, "Servlet must not be null");
return "servlet " + this.getServletName();
}
protected Dynamic addRegistration(String description, ServletContext servletContext) {
String name = this.getServletName();
return servletContext.addServlet(name, this.servlet);
}
protected void configure(Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
if (urlMapping.length == 0 && this.alwaysMapUrl) {
urlMapping = DEFAULT_MAPPINGS;
}
if (!ObjectUtils.isEmpty(urlMapping)) {
registration.addMapping(urlMapping);
}
registration.setLoadOnStartup(this.loadOnStartup);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
public String getServletName() {
return this.getOrDeduceName(this.servlet);
}
public String toString() {
return this.getServletName() + " urls=" + this.getUrlMappings();
}
}
如上代码所示servletContext.addServlet(name, this.servlet);核心代码就是往servletContext中放入对应的Serverlet,好的那么我们在看一段代码.
@Conditional({DispatcherServletAutoConfiguration.DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletAutoConfiguration.DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {
protected DispatcherServletRegistrationConfiguration() {
}
@Bean( name = {"dispatcherServletRegistration"})
@ConditionalOnBean(
value = {DispatcherServlet.class},
name = {"dispatcherServlet"}
)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({HttpProperties.class, WebMvcProperties.class})
protected static class DispatcherServletConfiguration {
protected DispatcherServletConfiguration() {
}
@Bean(name = {"dispatcherServlet"})
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
如上代码所示就是Springboot启动装配的时候执行的代码.会在Springboot启动的时候.加载META-INF下的spring.factories文件下所有的自动装配类.把需要注入到spring中的bean对象添加到spring中.好了这整个过程就串起来了.这里主要是往Tomcat中添加 DispatcherServlet类.
11.3 启动tomcat
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
getWebServer这个方法创建了Tomcat对象,并且做了两件重要的事情:把Connector对象添加到tomcat中,configureEngine(tomcat.getEngine());getWebServer方法返回的是TomcatWebServer。
//TomcatWebServer.java //这里调用构造函数实例化TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
this.tomcat.start();
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {}
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
this.tomcat.start(); 如上代码所示核型代码逻辑,这里的话我们就看到了springboot的run()函数其实是调用了服务的创建工厂,创建了tomcat服务,然后调用了tomcat的start()方法启动,然后调用了Server()服务启动了tomcat的.
public void start() throws LifecycleException {
this.getServer();
this.server.start();
}
public void stop() throws LifecycleException {
this.getServer();
this.server.stop();
}
public Server getServer() {
if (this.server != null) {
return this.server;
} else {
System.setProperty("catalina.useNaming", "false");
this.server = new StandardServer();
this.initBaseDir();
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(this.basedir), (String)null));
this.server.setPort(-1);
Service service = new StandardService();
service.setName("Tomcat");
this.server.addService(service);
return this.server;
}
}
以上就是整个Springboot跟tomcat的集成过程.
11.4 手写springboot
11.4.1 导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
</dependency>
11.4.2 主函数
public class WebApplication {
public static void main(String[] args) throws LifecycleException, ServletException {
SpringApplication.run();
}
}
11.4.3 主函数实现类
public class SpringApplication {
public static void run() throws LifecycleException, ServletException {
Tomcat tomcat=new Tomcat();
Connector connector = new Connector();
connector.setPort(8081);
connector.setURIEncoding("utf-8");
tomcat.getService().addConnector(connector);
tomcat.addWebapp("/app","/Users/worn/code/github/manul-project/src/main/resources/webapp");
tomcat.start();
tomcat.getServer().await();
}
}
11.4.4 spi
11.4.5 加载springmvc
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
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("/");
}
}
https://blog.csdn.net/worn_xiao/article/details/122261313