前言
全流程记录Tongweb替换Tomcat过程,最终实现为使用内嵌的Tongweb依赖替换Spring Boot默认的Tomcat,所以可直接从第5节开始看如何使用内嵌TongWeb替换Tomcat。
国产化浪潮下,项目要求实现web服务器的国产化,使用Tongweb替换Tomcat,商业版的
Tongweb 是单独启动的一个服务,需要将原本的 Spring Boot 项目打成 war 包部署。
因此,第一步需要将 Spring Boot 项目由之前输出的jar
包变成war
包,并保证在Tomcat服务器下没有功能正常,再研究使用 Tongweb 部署。
项目使用 Maven 来管理的依赖,首先要修改 pom 文件;在 Spring Boot 项目打包为 war 文件时,需要排除 Tomcat 并添加 provided 范围的 Tomcat 依赖,原因如下:
排除内嵌Tomcat: Spring Boot 默认使用内嵌的 Tomcat 作为 Web 服务器,这对于通过 java -jar 方式运行的独立应用非常方便。然而,当你将 Spring Boot 项目打包为 war 并部署到外部应用服务器(如外部 Tomcat)时,这个内嵌的 Tomcat 就不需要了。此时,需要排除内嵌 Tomcat 以避免冲突。
添加provided范围的Tomcat依赖: 即使排除了内嵌的 Tomcat ,项目仍然需要一些 Tomcat 的相关类,比如 Servlet API 和 JSP API,这些类在项目编译时是必需的。因此,需要在项目的 pom.xml 文件中添加 provided 范围的 Tomcat 依赖。这意味着在编译项目时,这些依赖是可用的,但在生成的 war 包中不会包含这些依赖,因为运行时外部的 Tomcat 服务器已经提供了这些类。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
org.springframework.boot
spring-boot-starter-tomcat
provided
同时,我们还要在 pom 文件里修改打包方式,如果有要求,也可以定义打包的 war 包名称。
war
demo
使用外部服务器启动应用时,会跳过原来项目的main方法,所以我们需要改造原本的启动类以初始化 Spring 上下文。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.example"}) // 确保扫描到所有需要的包
public class MySpringBootApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MySpringBootApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
war 包启动流程
Nacos 其自动注册微服务的类是 NacosAutoServiceRegistration
public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration {
private NacosRegistration registration;
@Deprecated
public void setPort(int port) {
this.getPort().set(port);
}
protected NacosRegistration getRegistration() {
if (this.registration.getPort() 0) {
this.registration.setPort(this.getPort().get());
}
Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
return this.registration;
}
}
我们看到 NacosAutoServiceRegistration 使用了 this.registration.setPort(this.getPort().get()) 来设置端口号。
而端口号是从其父类 AbstractAutoServiceRegistration 中的方法获取的:
public abstract class AbstractAutoServiceRegistration
implements AutoServiceRegistration, ApplicationContextAware,
ApplicationListener {
private AtomicInteger port = new AtomicInteger(0);
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context)
.getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
}
这段代码监听了内置容器启动完成事件,监听获取到容器端口后,向注册中心注册服务。
因此,当使用外部容器时,如 Tomcat 来部署项目,AbstractAutoServiceRegistration 就不能监听到容器启动事件了,也就不会尝试向服务注册中心注册当前这个微服务,那么注册就失败了,并且也就没有异常信息了。
解决方案
自定义获取获取外部容器端口的方法, 然后监听应用启动事件,当应用被启动时,获取外部容器启动的端口号,然后将这个 port 设置到 NacosAutoServiceReigistration 中。
import java.lang.management.ManagementFactory;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
import com.alibaba.cloud.nacos.registry.NacosRegistration;
import lombok.extern.slf4j.Slf4j;
/**
* 项目打包war情况下部署外部tomcat,需该方式注册nacos
*/
@Component
@Slf4j
public class NacosRegisterOnWar implements ApplicationRunner {
@Autowired
private NacosRegistration registration;
@Autowired
private NacosAutoServiceRegistration nacosAutoServiceRegistration;
@Value("${server.port}")
Integer port;
@Override
public void run(ApplicationArguments args) throws Exception {
if (registration != null && port != null) {
Integer tomcatPort = port;
try {
tomcatPort = new Integer(getTomcatPort());
} catch (Exception e) {
log.warn("获取外部Tomcat端口异常:", e);
}
registration.setPort(tomcatPort);
nacosAutoServiceRegistration.start();
}
}
/**
* 获取外部tomcat端口
*/
public String getTomcatPort() throws Exception {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
Set objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
String port = objectNames.iterator().next().getKeyProperty("port");
return port;
}
}
上面的方法是利用 ApplicationRunner 接口来实现的。ApplicationRunner 是 Spring Boot 提供的一个接口,用于在 Spring 应用启动完成后执行一些特定的代码。它通常用于在应用启动后立即执行一些初始化任务,例如加载配置、初始化资源、预加载数据、注册服务等。
ApplicationRunner 的工作原理
ApplicationRunner 接口只有一个方法:
void run(ApplicationArguments args) throws Exception;
当 Spring Boot 应用启动并完成所有初始化步骤(如创建并初始化所有的 Spring Bean )后,Spring 会自动调用实现了 ApplicationRunner 接口的类的 run 方法。
做完这些之后,在控制台执行 mvn clean package,看到 build success 就表示打包成功。
首先,需要下载Tomcat。
如果需要某个特定的版本,进入 Tomcat 官网首页选择左侧的大版本,
点击快捷导航的Archives
,
便会看到许多旧版本,点击需要的版本
进入bin目录,便可以找到压缩包,根据平台下载即可。
以Windows环境举例,需要在环境变量的系统变量中配置 CATALINA_HOME
同时在Path中添加 %CATALINA_HOME%bin
至于Linux环境的安装及配置就可以参考Tomcat(一):背景知识和安装tomcat[2]。
Windows 环境启动 Tomcat 点击 bin 目录下的 startup.bat ,浏览器访问 127.0.0.1:8080 ,看到下面这个猫头就说明 Tomcat 启动成功了。
我们在 Tomcat 中部署 war 包只需要将 war 包放到 /webapps 目录下,然后启动
/bin/startup.bat ( Windows 环境)就可以了, Tomcat 会在 /webapps 下创建一个与 war 包同名的文件夹,文件夹中就是编译好的代码文件和项目依赖。
这里需要注意的坑是,如果采用上文的方式部署,会产生一个与文件夹名称同名的上下文,也就是访问路径 url 要加上一段文件夹的名称,举例:如原来使用jar包部署的接口为 127.0.0.1:8080/hello,打 war
包名为 demo.war ,那么解压后会产生一个 /demo 文件夹,那么这个接口的访问路径就变为了
127.0.0.1:8080/demo/hello。
如果避免上文的那个状况,需要将 /demo 文件夹的内同替换掉 /ROOT 目录中的内容,这样就可以使用原始的 url 访问了。
前面的我们将项目打成war包并部署在Tomcat成功运行(指服务启动正常,注册nacos正常,一切正常),那么下一步就是将war包放在TongWeb上运行,至于如何安装TongWeb,可以参考国产化-Tomcat替换——TongWeb的安装和使用[3]。其实很简单,解压就能用了。
问题在于TongWeb存在一个虚拟机的概念,和war包部署也有一个类似的上下文概念,叫做应用前缀(这个就更简单易懂一些),同一台虚拟机只能将一个服务的应用前缀设置为 /
那么这样通过网关和服务间的调用就有问题了(这个问题这篇博客[4]中也有提及),一个服务一台虚拟机倒是可以规避这个问题,但是资源占用就太多了,很烦。刚好看到TongWeb有内嵌的版本[5],果断采取内嵌的方式,这样代码改动最小,之前的部署什么也可以沿用。
首先联系厂商提供依赖和 license 文件,接着将压缩包解压,我得到的压缩包目录结构如下:
TongWeb V7.0.E6/
├── TongWeb V7.0.4.8 专用版 中科+兆芯/
├── TongWeb7.0.E.6_P2/
│ ├── 安装工程介质/
│ │ ├── tongweb-embed-7.0.E.6_P2/
│ │ │ ├── lib/
│ │ │ └── installMavenJar.bat
│ │ │ └── ...
│ │ ├── ...
│ ├── 示例工程/
│ ├── 用户手册与配置示例/
忽略那个专用版,/TongWeb7.0.E.6_P2 下3个文件夹依次是依赖安装工具、示例项目代码、文档。
Windows 环境直接执行上面展示的那个 installMavenJar.bat 就可以把嵌入式 TongWeb 的依赖安装到本地 Maven 仓库了,注意路径上不要有中文,否则可能会报错。
使用内嵌式 TongWeb 只需要改造项目的 pom 文件即可,目标就是去除 Tomcat ,替换为 TongWeb 。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
com.tongweb.springboot
tongweb-spring-boot-starter-2.x
com.tongweb
tongweb-embed-dependencies
7.0.E.6_P2
pom
import
这个只是简单的 spring boot 项目的替换示例,至于复杂的建议看看文档再去示例代码中看看。
至于使用 spring-cloud-starter-gateway 的网关,也写一下示例吧
org.springframework.cloud
spring-cloud-starter-gateway
io.projectreactor.ipc
reactor-netty
io.projectreactor.addons
reactor-extra
io.projectreactor.netty
reactor-netty-http
io.projectreactor.netty
reactor-netty
io.projectreactor
reactor-core
org.springframework.boot
spring-boot-starter-webflux
io.projectreactor.netty
reactor-netty-http
io.projectreactor.netty
reactor-netty
io.projectreactor
reactor-core
io.projectreactor.ipc
reactor-netty
com.tongweb
tongweb-spring-boot-reactor-starter
io.netty
netty-resolver-dns-native-macos
com.tongweb
tongweb-embed-dependencies
7.0.E.6_P2
pom
import
将 license.dat 放在项目根目录下即可启动,亲测在IDEA中多个服务只需要在总目录下放一个就行。当然也可以在配置文件中指定 license 的位置[5:1]。
server:
tongweb:
uri-encoding: UTF-8
license:
type: file
path: classpath:tongweb/license.dat
需要将 license.dat 放在和 jar 包一个目录中或者配置文件中的指定位置。
参考:
阿湯哥.Spring Boot 构建war 部署到tomcat下无法在Nacos中注册服务.(2024-04-07) ↩︎
骏马金龙.Tomcat(一):背景知识和安装tomcat.( 2017-10-23) ↩︎
无言行者.国产化-Tomcat替换——TongWeb的安装和使用.(2024-04-14) ↩︎
小尘哥.【信创一】微服务适配TongWeb及遇到的问题.(2023.09.13) ↩︎
Thomas灬Wade.springboot2集成东方通tongweb嵌入式版.(2024-04-17) ↩︎ ↩︎
参与评论
手机查看
返回顶部