内容简介:在Spring Boot 初体验一文中我们学习了以 JAR 形式快速启动一个利用Spring Initializr 工具下载基本的打开下载的工程后,对启动类
在Spring Boot 初体验一文中我们学习了以 JAR 形式快速启动一个 Spring Boot
程序,而 Spring Boot
也支持传统的部署方式: 将项目打包成 WAR
,然后由 Web
服务器进行加载启动,这次以 Tomcat
为例,我们就快速学习下如何以 WAR
方式部署一个 Spring Boot
项目,代码托管于 Github , 并做一些简单的源码分析.
正文
利用Spring Initializr 工具下载基本的 Spring Boot
工程,选择 Maven
方式构建, 版本为正式版1.5.16, 只选择一个 Web
依赖.
继承 SpringBootServletInitializer
加载
打开下载的工程后,对启动类 SpringbootTomcatApplication
进行修改, 继承 SpringBootServletInitializer
这个抽象类,并且重写父类方法 SpringApplicationBuilder configure(SpringApplicationBuilder builder)
.
@SpringBootApplication public class SpringbootTomcatApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SpringbootTomcatApplication.class); } public static void main(String[] args) { SpringApplication.run(SpringbootTomcatApplication.class, args); } } 复制代码
SpringBootServletInitializer
类将在 Servlet
容器启动程序时允许我们对程序自定义配置,而这里我们将需要让 Servlet
容器启动程序时加载这个类.
修改打包方式为 WAR
接下来在 pom.xml
文件中,修改打包方式为 WAR
,让 Maven
构建时以 WAR
方式生成.
<groupId>com.one</groupId> <artifactId>springboot-tomcat</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> 复制代码
另外要注意的是:为了确保嵌入式 servlet
容器不会影响部署war文件的servlet容器,此处为 Tomcat
。我们还需要将嵌入式 servlet
容器的依赖项标记为 provided
。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> 复制代码
实现 Rest 请求处理
为了验证 WAR
部署是否成功,我们实现一个最基础的处理 Web
请求的功能,在启动类添加一些 Spring MVC
的代码
@SpringBootApplication @RestController public class SpringbootTomcatApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(SpringbootTomcatApplication.class); } public static void main(String[] args) { SpringApplication.run(SpringbootTomcatApplication.class, args); } @RequestMapping(value = "/") public String hello() { return "hello tomcat"; } } 复制代码
项目打包
现在就可以打包 Spring Boot
程序成 WAR
, 然后让 Tomcat
服务器加载了,在当前项目路径下使用构建命令
mvn clean package 复制代码
出现 BUILD SUCCESS
就说明打包成功了
然后就可以项目的 target
目录下看到生成的 WAR
.
部署 Tomcat
将 springboot-tomcat-0.0.1-SNAPSHOT.war
放在 Tomcat程序的文件夹 **webapps**
下,然后运行 Tomcat
, 启动成功就可以在浏览器输入 http://localhost:8080/springboot-tomcat-0.0.1-SNAPSHOT/ ,请求这个简单 Web
程序了.
到这里, WAR
方式部署的 Spring Boot
程序就完成了. :tada::tada::tada:
源码分析
完成到这里, 不禁有个疑问: 为何继承了 SpringBootServletInitializer
类,并覆写其 configure 方法就能以 war 方式去部署了呢 ? 带着问题,我们从源码的角度上去寻找答案.
在启动类 SpringbootTomcatApplication 覆写的方法进行断点,看下 Tomcat 运行项目时这个方法调用过程.
通过 Debug 方式运行项目,当运行到这行代码时,可以看到两个重要的类 SpringBootServletInitializer
和 SpringServletContainerInitializer
.
从图可以看到 configure 方法调用是在父类的 createRootApplicationContext
,具体代码如下,非关键部分已省略,重要的已注释出来.
protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); // 新建用于构建SpringApplication 实例的 builder builder.main(getClass()); // .... builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder = configure(builder); // 调用子类方法,配置当前 builder builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); // 构建 SpringApplication 实例 if (application.getSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.getSources().add(getClass()); } //... return run(application); // 运行 SpringApplication 实例 } 复制代码
SpringApplicationBuilder
实例, 应该是遵循建造者设计模式,来完成 SpringApplication
的构建组装.
而 createRootApplicationContext
方法的调用还是在这个类内完成的,这个就比较熟悉, 因为传统的 Spring Web
项目启动也会创建一个 WebApplicationContext
实例.
@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()); WebApplicationContext rootAppContext = createRootApplicationContext( servletContext); // 创建一个 WebApplicationContext 实例. // ... } 复制代码
问题又来了,这里的 onStartup
方法又是如何执行到的呢? SpringServletContainerInitializer
类就登场了.
SpringServletContainerInitializer
类实现 Servlet 3.0
规范的 ServletContainerInitializer
接口, 也就意味着当 Servlet
容器启动时,就以调用 ServletContainerInitializer
接口的 onStartup
方法通知实现了这个接口的类.
public interface ServletContainerInitializer { void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; } 复制代码
现在我们来看下 SpringServletContainerInitializer
的 onStarup
方法的具体实现如下, 关键代码23~24行里 initializers
是一个 LinkedList
集合,有着所有实现 WebApplicationInitializer
接口的实例,这里进行循环遍历将调用各自的 onStartup
方法传递 ServletContext
实例,以此来完成 Web
服务器的启动通知.
@Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { // 提取webAppInitializerClasses集合中 实现 WebApplicationInitializer 接口的实例 initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } // ... for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); // 调用所有实现 WebApplicationInitializer 实例的onStartup 方法 } } 复制代码
追踪执行到 SpringServletContainerInitializer
类的22行, 我们可以看到集合里就包含了我们的启动类,因此最后调用了其父类的 onStartup
方法完成了 WebApplicationContext
实例的创建.
看到这里,我们总结下这几个类调用流程,梳理下 Spring Boot
程序 WAR
方式启动过程:
SpringServletContainerInitializer#onStartup
=> SpringBootServletInitializer#onStartup
=> ``SpringBootServletInitializer#createRootApplicationContext =>
SpringbootTomcatApplication#configure`
另外,我还收获了一点就是: 当执行 SpringBootServletInitializer
的 createRootApplicationContext
方法最后,调用了 run(application)
.
这也说明了当 WAR
方式部署 Spring Boot
项目时, 固定生成的 Main
方法不会再被执行到,是可以去掉.
//当项目以WAR方式部署时,这个方法就是无用代码 public static void main(String[] args) { SpringApplication.run(SpringbootTomcatApplication.class, args); } 复制代码
结语
本文主要实战学习如何让 Spring Boot
以 WAR
方式启动,并且进行简单的源码分析,帮助我们更好地理解 Spring Boot
.希望有所帮助,后续仍会更多的实战和分析,敬请期待哈. :grin::grin::grin:.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- SpringBoot实战分析-MongoDB操作
- 二进制各种漏洞原理实战分析总结
- ARM汇编之堆栈溢出实战分析(GDB)
- ARM汇编之堆栈溢出实战分析二(GDB)
- ARM汇编之堆栈溢出实战分析三(GDB)
- ARM汇编之堆栈溢出实战分析四(GDB)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。