前言
一般的教程都是直接运行main
方法, 看似脱离了Tomcat
运行
实际上是使用的是内嵌的Tomcat
1 2 3 4 5 6
| @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
|
而且打包出来的是jar
包, 虽然可以直接运行, 但是如果想放在外部的Tomcat
下, 就不太好了。
解决方案是
- 继承
SpringBootServletInitializer
并重写 configure
方法1 2 3 4 5 6 7 8 9 10 11 12
| @SpringBootApplication @Controller public class App extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(getClass()); } public static void main(String[] args) { SpringApplication.run(App.class, args); } }
|
- 在项目的
pom.xml
设置 <packaging>war</packaging>
, 注意, 多模块项目只需要在该模块的 pom.xml
设置即可
- 添加spring-boot-starter-tomcat依赖, 并设置
<scope>provided</scope>
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
|
- 打
war包
部署到Tomcat
为什么要 继承SpringBootServletInitializer ?
首先明确一点, Spring Boot
只是许多个Spring
项目和其他项目整合起来, 并不是一个额外的项目, 你可以理解成一个封装了Spring
、SpringMVC
等项目的新项目。
那么, Spring
有入口ContextLoaderListener
、 Spring MVC
有入口DispatcherServlet
。那Spring Boot
的入口当然就是SpringBootServletInitializer
。
(当然如果你不用war包
可以忽略这段话, 也不用看这篇文章)
看到熟悉的ServletInitializer
, 这是Servlet3.0新特性, Tomcat
会自动查找并运行实现了 ServletContainerInitializer
接口的类。
但是能自动加载的是 ServletContainerInitializer
及其子类, 和SpringBootServletInitializer
及其父类WebApplicationInitializer
可是丝毫没有联系的, 能想到的就是有另一个类连接了这两个类。
这个类就是SpringServletContainerInitializer
, 它会去加载所有的WebApplicationInitializer
及其子类SpringBootServletInitializer
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); for (Class<?> waiClass : webAppInitializerClasses) { if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { initializers.add((WebApplicationInitializer) waiClass.newInstance()); } }
AnnotationAwareOrderComparator.sort(initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
|
HandlesTypes
注解中的 WebApplicationInitializer
被注入到 Set集合
中, 然后调用 WebApplicationInitializer
的onStartup
方法。
至此, SpringBootServletInitializer
已经能随着Tomcat
的启动而启动了。
很多网上的文章, 都说要重写 configure方法
, 但是这对我上面那个Hello world
例子是不需要的, 从源码中可以窥视一二。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
public abstract class SpringBootServletInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext rootAppContext = createRootApplicationContext(servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { @Override public void contextInitialized(ServletContextEvent event) { } }); } }
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); 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.listeners(new ServletContextApplicationListener(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder = configure(builder); 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"); if (this.registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } return run(application); }
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder; } }
|
从Configuration
注解的类可以注入 source
发现, 这个 source
就是存储Spring
配置的, 当然, 不是xml
, 而是java
配置。
也就是说
- 只有一个
Java
配置类, 则直接继承 SpringBootServletInitializer
即可。
- 如果有多个
Java
配置类, 则继承 SpringBootServletInitializer
之外,还要重写 configure
方法, 将配置类都注入进去, 包括 SpringBootServletInitializer
的子类。