Spring Boot 的应用,大都有这样的特别,你在添加了依赖之后,即使是 Web 应用,最终也可以通过 JAR 的形式运行,具体依赖的容器环境,则通过嵌入式的形式隐式的使用。
而像这些环境,Spring 的配置等,更多的隐藏在 Spring Boot 的内部,开发者可以更多的专注于「业务逻辑」的开发。
「解放了双手」的时候,话说回来,某些时候,也是有一些弊端的。比如像之前通过 WAR 文件的形式独立部署时,可以在容器内再额外部署一些「监控」应用,来观察容器的情况,应用的请求情况等,这些内容在嵌入式的时候,就有些办不从心了。
那对于 习惯了 Spring Boot 的 JAR 文件便捷运行的用户,有没有办法,能在保留 JAR 使用习惯的前提下,又能部署其他应用,来满足独立容器部署的形式和使用习惯呢?
答案是有的。鱼和熊掌,也可得兼。 后面我们会以嵌入式的 Tomcat 为例,来说明具体的实现方式。
首先,我们需要认识这一点,对于嵌入式的容器,他本质上依然还是容器,保留了容器的绝大数内容。所以,一些独立部署时的风格,接口也依然可以使用。
不熟悉 Spring Boot 内 Tomcat 工作原理的读者,可以参考这几篇旧文:
Tomcat 是怎样处理 SpringBoot应用的?
Tomcat 中 的可插拔以及 SCI 的实现原理
如何开发自己的 Spring Boot Starter
我们前面说,嵌入式容器,也还是容器,所以我们只要「拿到」这个容器,就可以对其进行操作了。
旧文里我们提过, Spring Boot 内的嵌入式 Tomcat,是自己 new 了一个Tomcat 实例出来,再把应用做为 Context 部署进去。我们要想部署其他的应用,也照着「葫芦」拿到 这个实例,部署应用。
Spring Boot 内,由于要支持各种 Servlet 容器,所以统一进行了抽象了创建容器的Factory,在 Spring Boot 1.x 和 2.x分别由
EmbeddedServletContainerFactory 和 ServletWebServerFactory 这两个接口表示。 而对应的工厂里创建出来的容器对象,在 1.x 和 2.x 中,分别由TomcatEmbeddedServletContainer 和 TomcatWebServer 这两个类来表示。
这个 Factory,也是做为一个 Bean 参与到Spring Boot 的启动流程中。我们需要做的,就是在启动的时候,定义这样一个Bean,并「重写」Factory 中可以拿到 Tomcat 实例的方法,拿到前面创建出来的 Tomcat 实例,即可完成应用的部署。
1.x 的方式如下:
@Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory() {
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
new File(tomcat.getServer().getCatalinaBase(), "webapps").mkdirs();
try {
Context context = tomcat.addWebapp("/test", "/home/test/sample.war"); // 这里是要部署的应用名称和路径
context.setParentClassLoader(getClass().getClassLoader());
} catch (Exception ex) {
throw new IllegalStateException("Failed to add webapp", ex);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}
2.x
@Bean
public ServletWebServerFactory servletContainerFactory() {
return new TomcatServletWebServerFactory() {
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
#p#分页标题#e#
new File(tomcat.getServer().getCatalinaBase(), "hello").mkdirs();
try {
Context context =
tomcat.addWebapp("/foo", "/home/test/sample.war");
context.setParentClassLoader(getClass().getClassLoader());
} catch (Exception ex) {
throw new IllegalStateException("Failed to add webapp", ex);
}
return super.getTomcatWebServer(tomcat);
};
};
}
当然,还有其它的方法也可以实现类似的目的。
比如,几年前的一篇旧文,在分析 IDE里 Tomcat 的工作原理的时候,分析过 IDEA 里, Tomcat 是怎样部署应用的。那个实现思路,是通过 Tomcat 注册的 MBean,其中包含对于应用管理的MBean,对于嵌入式的 Tomcat,也依然放开了 MBean Server, 连接到上面就可以部署应用了。需要注意的一点,是嵌入式的 Tomcat,Host 的ObjectName,和独立运行的并不一样,需要注意,否则会导致部署失败。
总结一下,嵌入式容器,也保留了独立部署容器的管理和使用习惯,在启动创建的过程中,可以获取其容器实例进行操作。也可以通过对外暴露的 MBean Server 进行操作。
【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】