Spring Boot以War包启动
1.IDEA Spring Initializer自动构建的war包项目,自动生成的Initializer类,用于外部Tomcat容器启动该项目时调用,如果仍然使用主类main函数方式启动则与此类无关(Debug验证过了)
2.自动构建的war包项目,pom.xml中引入了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!--<scope>provided</scope>-->
</dependency>
注释的scope是我注释的,生成时打开着,这样引入,scope造成main方式运行时没有内嵌Tomcat(只有编译时有),虽然引入了:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
仍然提示错误:
Unregistering JMX-exposed beans on shutdown
所以需要注释掉scope,这样以main方式运行,内嵌的Tomcat可以启动Spring Boot Web项目。参考:https://blog.csdn.net/sun20100912/article/details/52013463
但打war包放在外置Tomcat时就不需要了,要使用exclude干掉内嵌Tomcat,或像这里IDEA自动构建的war包项目一样,使用scope在运行时不使用内嵌Tomcat,这时需要外置Tomcat使用这里生成的Initializer类:
package com.xiaobai.springbootwebdemo; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; public class ServletInitializer extends SpringBootServletInitializer { @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootwebdemoApplication.class);
} }
Initializer类分析
跟踪方法:通过启动IDEA断点Debug,一直按F8,无论哪里有断点。这样走完所有我们打断点的类后可以进入更外层类,即Web容器启动时先行运行的Tomcat的类当中,再一路F8,注意这时是层层向上倒置返回的,不断层层向上打断点,从这一层的子类方法到上一层调用它的父类模板方法(因为进入的一些层的类不过是前一层类的抽象父类,是父类的模板方法调用了子类的方法,实际仍然在子类实例中),从这一层的方法到上一层另一个类调用它的方法。F8走完和打完这次断点后,再从头Debug一遍,F8调试,这样可以先进入上层,哪里F8后程序直接跑完了,就删除这个断点(因为这个断点并不是一个会倒置返回的上层调用,而是一个完整模块调用),再从头Debug,F8,直到从上层最终能进入我们最初打断点的内层,继续,内层已跟踪过的断点适当使用Alt+F9跳过,走到差不多内层出口处再换回F8,进入外层。这样层层向上找,打断点,从头Debug,F8,修改断点,再Debug,F8,最终找到最初启动入口类和方法,它初始化加载的文件,创建的类,传入的参数等
0.Tomcat启动时调用MBeanFactory的下面方法:
public String createStandardContext(String parent, String path, String docBase) throws Exception {
return this.createStandardContext(parent, path, docBase, false, false);
}
该方法调用:
public String createStandardContext(String parent, String path, String docBase, boolean xmlValidation, boolean xmlNamespaceAware) throws Exception {
StandardContext context = new StandardContext();
path = this.getPathStr(path);
context.setPath(path);
context.setDocBase(docBase);
context.setXmlValidation(xmlValidation);
context.setXmlNamespaceAware(xmlNamespaceAware);
ContextConfig contextConfig = new ContextConfig();
context.addLifecycleListener(contextConfig);
ObjectName pname = new ObjectName(parent);
ObjectName deployer = new ObjectName(pname.getDomain() + ":type=Deployer,host=" + pname.getKeyProperty("host"));
if (mserver.isRegistered(deployer)) {
String contextName = context.getName();
mserver.invoke(deployer, "addServiced", new Object[]{contextName}, new String[]{"java.lang.String"});
String configPath = (String)mserver.getAttribute(deployer, "configBaseName");
String baseName = context.getBaseName();
File configFile = new File(new File(configPath), baseName + ".xml");
if (configFile.isFile()) {
context.setConfigFile(configFile.toURI().toURL());
} mserver.invoke(deployer, "manageApp", new Object[]{context}, new String[]{"org.apache.catalina.Context"});
mserver.invoke(deployer, "removeServiced", new Object[]{contextName}, new String[]{"java.lang.String"});
} else {
log.warn("Deployer not found for " + pname.getKeyProperty("host"));
Service service = this.getService(pname);
Engine engine = service.getContainer();
Host host = (Host)engine.findChild(pname.getKeyProperty("host"));
host.addChild(context);
} return context.getObjectName().toString();
}
方法,创建了org.apache.catalina.core.StandardContext
注意里面加粗的反射调用逻辑,与创建的org.apache.catalina.core.StandardContext有关,留意一下这些类中的属性字段尤其是static字段,有些可能事先由其他类加载并初始化了,在一些方法里直接用。
这时使用Alt+F9跳到我们的下一个断点:org.apache.catalina.core.StandardContext的startInternal方法
1.(循环)调用org.apache.catalina.core.StandardContext的startInternal方法,其中一个调用走到我们的Web服务,在这个调用里循环调用该服务的每个ServletContainerInitializer的onStartup方法:
while(i$.hasNext()) {
Entry entry = (Entry)i$.next(); try {
((ServletContainerInitializer)entry.getKey()).onStartup((Set)entry.getValue(), this.getServletContext());
} catch (ServletException var22) {
log.error(sm.getString("standardContext.sciFail"), var22);
ok = false;
break;
}
}
这里传入的ServletContext是使用上面创建的org.apache.catalina.core.StandardContext初始化的:
this.context = new ApplicationContext(this);//this:org.apache.catalina.core.StandardContext
其中一个就是SpringServletContainerInitializer的onStartup方法(也是循环调用,多个SpringServletContainerInitializer),该方法最后通过:
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
这是一个循环调用,其中一个调用SpringBootServletInitializer的onStartup方法(ServletContext已传入):
public void onStartup(ServletContext servletContext) throws ServletException {
this.logger = LogFactory.getLog(this.getClass());
WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
public void contextInitialized(ServletContextEvent event) {
}
});
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
} }
该方法调用方法(继续传入ServletContext):
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
} builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext, null)});
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(this.getClass()));
} Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
} return this.run(application);
}
该方法创建了一个SpringApplicationBuilder,以Spring Boot War项目自动生成的我们的ServletInitializer为mainApplicationClass,使用传入的ServletContext(Tomcat为我们的应用所创建)设置了Spring Boot上下文的Initializer和Listener(将Web环境和Spring Boot互相加入),调用了我们ServletInitializer中重载的:
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootwebdemoApplication.class);
}
将带有@SpringBootApplication注解的我们的SpringbootwebdemoApplication类注入到SpringApplicationBuilder作为source.说明无论是main函数启动方式还是Tomcat以Web项目启动方式,关键的是运行带有@SpringBootApplication注解的类完成Spring Boot配置。
然后SpringApplication由SpringApplicationBuilder来build出来:
SpringApplication application = builder.build();
并运行起来:
return this.run(application);
该方法调用SpringApplication本身的run方法,和main方法启动方式完全一样了。这样就由Tomcat初始化并启动了Spring Boot,并加入到了Web环境(ServletContext)
最后调用SpringApplication本身的run方法是创建了一个AnnotationConfigServletWebServerApplicationContext作为Spring Boot的Web环境上下文对象
2.org.apache.catalina.core.StandardContext的startInternal方法接续1继续执行:
if (ok && !this.listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
进入org.apache.catalina.core.StandardContext的listenerStart方法(也循环多次调用了该方法),该方法方法体内调用:
for(int i = 0; i < instances.length; ++i) {
if (instances[i] instanceof ServletContextListener) {
ServletContextListener listener = (ServletContextListener)instances[i]; try {
this.fireContainerEvent("beforeContextInitialized", listener);
if (this.noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
} this.fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable var12) {
ExceptionUtils.handleThrowable(var12);
this.fireContainerEvent("afterContextInitialized", listener);
this.getLogger().error(sm.getString("standardContext.listenerStart", new Object[]{instances[i].getClass().getName()}), var12);
ok = false;
}
}
}
这又是一个循环,其中有一个就是上面SpringBootServletInitializer的onStartup方法中注册的该服务的Listener:
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
public void contextInitialized(ServletContextEvent event) {
}
});
}
执行其contextInitialized方法。
这里注册的ContextLoaderListener重写的contextInitialize方法里面没有任何实现,所以什么都不做。
至此我们的服务已经启动完成。
进一步研究提示:StandardContext的外层相关类包括
LifecycleBase
ContainerBase
StandardHost
HostConfig
其中LifecycleBase为LifecycleMBeanBase父类,LifecycleMBeanBase为ContainerBase父类,ContainerBase为StandardContext和StandardHost父类,那么调用采用的是模板模式,由父类方法调用子类方法。
更外层的HostConfig使用了StandardHost:
this.host.addChild(context);
因为StandardHost实现了Host接口。
最外面还有一个工具类BaseModelMBean不断执行其invoke方法,反射执行一些方法,反复配合第0步开始的MBeanFactory的createStandardContext方法里面的:
mserver.invoke(deployer, "manageApp", new Object[]{context}, new String[]{"org.apache.catalina.Context"});
mserver.invoke(deployer, "removeServiced", new Object[]{contextName}, new String[]{"java.lang.String"});
最后从这里(MBeanFactory的createStandardContext方法,经过Debug,正向层次调用和逆向层次返回,整个上面都是从这个方法出发的各层次调用流程,最后回到该方法)结束Tomcat所有启动流程,项目可通过浏览器访问了。
整合jsp提示:无法通过main/jar启动方式配置、访问jsp,只能以Tomcat运行,IDEA配置Web模块及其根目录,再在application.yml中配置mvc view相关前后缀属性。
尝试main方式访问jsp的一个错误参考:
https://blog.csdn.net/universsky2015/article/details/77965402
Spring Boot以War包启动的更多相关文章
- spring boot打war包启动Tomcat失败
Tomcat启动失败:最后一个causy by :java.lang.NoSuchMethodError: org.apache.tomcat.util.res.StringManager.getMa ...
- Spring Boot 打war包并利用docBase指定根目录为打包的工程
指定根目录有两种方式 1:直接将打的war包名称定义为ROOT 2:利用docBase 比如笔者war包名为xibu.war,将该war包丢到/Users/archerlj/Library/apach ...
- Spring Boot发布war包流程
1.修改web model的pom.xml <packaging>war</packaging> SpringBoot默认发布的都是jar,因此要修改默认的打包方式jar为wa ...
- Spring Boot 打 war 包的步骤
## Spring Boot 打 war 包的步骤 1. 添加 spring-boot-start-tomcat 的 provided 依赖 ``` <dependency> <gr ...
- spring boot 打 war包
spring boot .spring cloud打 war包,并发布到tomcat中运行 1.pom文件修改 <packaging>war</packaging> 2.< ...
- Spring boot打包war包
1.设置打包的类型(war/jar) 在pom.xml里设置 <packaging>war</packaging> 2.移除嵌入式tomcat插件 //在pom.xml里找到s ...
- spring boot 打war包部署,打jar包
官方文档:http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-create-a-deployable- ...
- spring boot打war包发布
由于公司一贯的方式都是将war包布在中间件tomcat下运行 所以这次springboot项目需要打war包 how to? 第一步:pom.xml 文件中,打包方式需要修改成war <pack ...
- Spring Boot打war包和jar包的目录结构简单讲解
Spring Boot项目可以制作成jar包和war包,其目录结构是不一样的,具体的如下所示: 1.war包目录结构分析WAR(Web Archivefile)网络应用程序文件,是与平台无关的文件格式 ...
随机推荐
- pdf转txt
ubuntu pdf转jpg或txt chenlei posted @ 2009年12月30日 17:22 inLinux , 1818 阅读 呵呵,刚刚在网上定购了一款mp5,后来才发现它不支持PD ...
- word2vector 资料
http://blog.csdn.net/garfielder007/article/details/51345201 https://cs224d.stanford.edu/lecture_note ...
- Apache+Tomcat+Memcached实现会话保持
会话保持的三种方式 Session sticky会话绑定:通过在前端调度器的配置中实现统一session发送至同一后发端服务器 Session cluster会话集群:通过配置Tomcat保持所有To ...
- QString字符串中双引号的梗
[1]QString字符串不支持双引号 最近做项目(本地环境:WIN10 + QT5.9.2 + VS2017).有个需求,需要实现形如 "key="123456"&qu ...
- javascript实现异步编程的4种方法
1.回调函数. 2.事件监听 . 思路:采用事件驱动模式.任务的执行不取决于代码的顺序,而取决于某个事件是否发生 3.观察者模式 (发布/订阅模式) 代码如下: jQuery.subscribe ...
- 微信小程序制作家庭记账本之一
制作的第一天,思索着制作手机端APP还是微信小程序,首先是想到制作APP但是各种收费让我不得不换一条路,所以开始制作小程序,下载了微信小程序开发工具,试着学习制作方法,但是似乎没有成效,但我坚信要一步 ...
- elsearch
1. ElasticSearch是性能优化的分布式全文搜索引擎,存储数据的载体是文档(Document),它的优势在于搜索速度快和支持聚合操作,在更新文档时,基本上能够达到实时搜索.ElasticSe ...
- 计蒜客---N的-2进制表示
对于十进制整数N,试求其-2进制表示. 例如,因为 1*1 + 1*-2 + 1*4 + 0*-8 +1*16 + 1*-32 = -13 ,所以(-13)_10 = ( ...
- Symfony2学习笔记之事件分配器
----EventDispatcher组件使用 简介: 面向对象编程已经在确保代码的可扩展性方面走过了很长一段路.它是通过创建一些责任明确的类,让它们之间变得更加灵活,开发者可以通过继承这 ...
- <转>jmeter(一)基础介绍
本博客转载自:http://www.cnblogs.com/imyalost/category/846346.html 个人感觉不错,对jmeter讲解非常详细,担心以后找不到了,所以转发出来,留着慢 ...