国产化:springboot项目TongWeb替换tomcat踩坑实录

打印 上一主题 下一主题

主题 864|帖子 864|积分 2594

前言
全流程记录Tongweb替换Tomcat过程,最终实现为利用内嵌的Tongweb依赖替换Spring Boot默认的Tomcat,所以可直接从第5节开始看如何利用内嵌TongWeb替换Tomcat。
1 背景

国产化海潮下,项目要求实现web服务器的国产化,利用Tongweb替换Tomcat,商业版的
Tongweb 是单独启动的一个服务,需要将原本的 Spring Boot 项目打成 war 包部署。
2 打war包

因此,第一步需要将 Spring Boot 项目由之前输出的jar包酿成war包,并包管在Tomcat服务器下没有功能正常,再研究利用 Tongweb 部署。
2.1 修改pom依赖

项目利用 Maven 来管理的依赖,首先要修改 pom 文件;在 Spring Boot 项目打包为 war 文件时,需要排除 Tomcat 并添加 provided 范围的 Tomcat 依赖,原因如下:

  • 排除内嵌TomcatSpring Boot 默认利用内嵌的 Tomcat 作为 Web 服务器,这对于通过 java -jar 方式运行的独立应用非常方便。然而,当你将 Spring Boot 项目打包为 war 并部署到外部应用服务器(如外部 Tomcat)时,这个内嵌的 Tomcat 就不需要了。此时,需要排除内嵌 Tomcat 以避免冲突。
  • 添加provided范围的Tomcat依赖: 即使排除了内嵌的 Tomcat ,项目仍然需要一些 Tomcat 的相关类,好比 Servlet APIJSP API,这些类在项目编译时是必需的。因此,需要在项目标 pom.xml 文件中添加 provided 范围的 Tomcat 依赖。这意味着在编译项目时,这些依赖是可用的,但在生成的 war 包中不会包含这些依赖,因为运行时外部的 Tomcat 服务器已经提供了这些类。
  1.    
  2.     <dependencies>
  3.         <dependency>
  4.             <groupId>org.springframework.boot</groupId>
  5.             <artifactId>spring-boot-starter-web</artifactId>
  6.             <exclusions>
  7.                
  8.                 <exclusion>
  9.                     <groupId>org.springframework.boot</groupId>
  10.                     <artifactId>spring-boot-starter-tomcat</artifactId>
  11.                 </exclusion>
  12.             </exclusions>
  13.         </dependency>
  14.         <dependency>
  15.             <groupId>org.springframework.boot</groupId>
  16.             <artifactId>spring-boot-starter-tomcat</artifactId>
  17.             <scope>provided</scope>
  18.         </dependency>
  19.     </dependencies>
复制代码
同时,我们还要在 pom 文件里修改打包方式,如果有要求,也可以定义打包的 war 包名称。
  1. <packaging>war</packaging>
  2. <build>
  3.     <finalName>demo</finalName>
  4. </build>
复制代码
2.2 改造启动类

利用外部服务器启动应用时,会跳过原来项目标main方法,所以我们需要改造原本的启动类以初始化 Spring 上下文。
  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
  4. import org.springframework.context.annotation.ComponentScan;
  5. @SpringBootApplication
  6. @ComponentScan(basePackages = {"com.example"})  // 确保扫描到所有需要的包
  7. public class MySpringBootApplication extends SpringBootServletInitializer {
  8.     @Override
  9.     protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  10.         return application.sources(MySpringBootApplication.class);
  11.     }
  12.     public static void main(String[] args) {
  13.         SpringApplication.run(MySpringBootApplication.class, args);
  14.     }
  15. }
复制代码
war 包启动流程

  • 外部 Web 服务器加载应用:


  • 当你将 war 包部署到外部 Web 服务器时,服务器会主动检测到 war 包并加载其中的 Web 应用。
  • 外部服务器不会调用 Spring Boot 应用的 main 方法,而是会按照尺度的 Servlet 规范启动应用。

  • SpringBootServletInitializer 的作用:


  • 为了将 Spring Boot 应用集成到外部 Web 服务器中,通常会扩展 SpringBootServletInitializer 并重写 configure() 方法。
  • SpringBootServletInitializer 是一个 Spring 提供的适配器类,它的作用是初始化 Spring 应用上下文,并在应用服务器的上下文中运行 Spring Boot 应用。

  • 启动过程:


  • 当外部服务器启动应用时,它会调用 SpringBootServletInitializerconfigure() 方法,Spring Boot 会根据你指定的配置类(如 MySpringBootApplication.class )初始化 Spring 上下文。
  • 这个过程现实上跳过了 main 方法,而是直接由服务器控制整个启动流程。
2.3 无法在Nacos中注册服务问题解决[1]

Nacos 其主动注册微服务的类是 NacosAutoServiceRegistration
  1. public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<Registration> {
  2.     private NacosRegistration registration;
  3.     @Deprecated
  4.     public void setPort(int port) {
  5.         this.getPort().set(port);
  6.     }
  7.     protected NacosRegistration getRegistration() {
  8.         if (this.registration.getPort() < 0 && this.getPort().get() > 0) {
  9.             this.registration.setPort(this.getPort().get());
  10.         }
  11.         Assert.isTrue(this.registration.getPort() > 0, "service.port has not been set");
  12.         return this.registration;
  13.     }
  14. }
复制代码
我们看到 NacosAutoServiceRegistration 利用了 this.registration.setPort(this.getPort().get()) 来设置端口号。
而端口号是从其父类 AbstractAutoServiceRegistration 中的方法获取的:
  1. public abstract class AbstractAutoServiceRegistration<R extends Registration>
  2.                 implements AutoServiceRegistration, ApplicationContextAware,
  3.                 ApplicationListener<WebServerInitializedEvent> {
  4.         private AtomicInteger port = new AtomicInteger(0);
  5.         @Deprecated
  6.         public void bind(WebServerInitializedEvent event) {
  7.                 ApplicationContext context = event.getApplicationContext();
  8.                 if (context instanceof ConfigurableWebServerApplicationContext) {
  9.                         if ("management".equals(((ConfigurableWebServerApplicationContext) context)
  10.                                         .getServerNamespace())) {
  11.                                 return;
  12.                         }
  13.                 }
  14.                 this.port.compareAndSet(0, event.getWebServer().getPort());
  15.                 this.start();
  16.         }
  17. }
复制代码
这段代码监听了内置容器启动完成事件,监听获取到容器端口后,向注册中心注册服务。
因此,当利用外部容器时,如 Tomcat 来部署项目,AbstractAutoServiceRegistration 就不能监听到容器启动事件了,也就不会尝试向服务注册中心注册当前这个微服务,那么注册就失败了,并且也就没有异常信息了。
解决方案
自定义获取获取外部容器端口的方法, 然后监听应用启动事件,当应用被启动时,获取外部容器启动的端口号,然后将这个 port 设置到 NacosAutoServiceReigistration 中。
  1. import java.lang.management.ManagementFactory;
  2. import java.util.Set;
  3. import javax.management.MBeanServer;
  4. import javax.management.ObjectName;
  5. import javax.management.Query;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.boot.ApplicationArguments;
  9. import org.springframework.boot.ApplicationRunner;
  10. import org.springframework.stereotype.Component;
  11. import com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration;
  12. import com.alibaba.cloud.nacos.registry.NacosRegistration;
  13. import lombok.extern.slf4j.Slf4j;
  14. /**
  15. * 项目打包war情况下部署外部tomcat,需该方式注册nacos
  16. */
  17. @Component
  18. @Slf4j
  19. public class NacosRegisterOnWar implements ApplicationRunner {
  20.     @Autowired
  21.     private NacosRegistration registration;
  22.     @Autowired
  23.     private NacosAutoServiceRegistration nacosAutoServiceRegistration;
  24.     @Value("${server.port}")
  25.     Integer port;
  26.     @Override
  27.     public void run(ApplicationArguments args) throws Exception {
  28.         if (registration != null && port != null) {
  29.             Integer tomcatPort = port;
  30.             try {
  31.                 tomcatPort = new Integer(getTomcatPort());
  32.             } catch (Exception e) {
  33.                 log.warn("获取外部Tomcat端口异常:", e);
  34.             }
  35.             registration.setPort(tomcatPort);
  36.             nacosAutoServiceRegistration.start();
  37.         }
  38.     }
  39.     /**
  40.      * 获取外部tomcat端口
  41.      */
  42.     public String getTomcatPort() throws Exception {
  43.         MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
  44.         Set<ObjectName> objectNames = beanServer.queryNames(new ObjectName("*:type=Connector,*"), Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
  45.         String port = objectNames.iterator().next().getKeyProperty("port");
  46.         return port;
  47.     }
  48. }
复制代码
上面的方法是利用 ApplicationRunner 接口来实现的。ApplicationRunnerSpring Boot 提供的一个接口,用于在 Spring 应用启动完成后执行一些特定的代码。它通常用于在应用启动后立刻执行一些初始化任务,例如加载配置、初始化资源、预加载数据、注册服务等。
ApplicationRunner 的工作原理
ApplicationRunner 接口只有一个方法:
  1. void run(ApplicationArguments args) throws Exception;
复制代码
Spring Boot 应用启动并完成全部初始化步骤(如创建并初始化全部的 Spring Bean )后,Spring 会主动调用实现了 ApplicationRunner 接口的类的 run 方法。
2.4 mvn打包

做完这些之后,在控制台执行 mvn clean package,看到 build success 就表示打包成功。
3 部署war包到Tomcat

3.1 安装Tomcat

3.1.1 下载Tomcat

首先,需要下载Tomcat
如果需要某个特定的版本,进入 Tomcat 官网首页选择左侧的大版本,

点击快捷导航的Archives,

便会看到很多旧版本,点击需要的版本

进入bin目录,便可以找到压缩包,根据平台下载即可。

3.1.2 配置环境变量

以Windows环境举例,需要在环境变量的体系变量中配置 CATALINA_HOME

同时在Path中添加 %CATALINA_HOME%\bin

至于Linux环境的安装及配置就可以参考Tomcat(一):背景知识和安装tomcat[2]
3.1.3 启动Tomcat

Windows 环境启动 Tomcat 点击 bin 目录下的 startup.bat ,欣赏器访问 127.0.0.1:8080 ,看到下面这个猫头就阐明 Tomcat 启动成功了。

3.2 部署 war

我们在 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 访问了。
4 放弃利用war包部署TongWeb

前面的我们将项目打成war包并部署在Tomcat成功运行(指服务启动正常,注册nacos正常,一切正常),那么下一步就是将war包放在TongWeb上运行,至于如何安装TongWeb,可以参考国产化-Tomcat替换——TongWeb的安装和利用[3]。其实很简单,解压就能用了。
问题在于TongWeb存在一个虚拟机的概念,和war包部署也有一个类似的上下文概念,叫做应用前缀(这个就更简单易懂一些),同一台虚拟机只能将一个服务的应用前缀设置为 /

那么如许通过网关和服务间的调用就有问题了(这个问题这篇博客[4]中也有提及),一个服务一台虚拟机倒是可以规避这个问题,但是资源占用就太多了,很烦。刚好看到TongWeb有内嵌的版本[5],果断采取内嵌的方式,如许代码改动最小,之前的部署什么也可以沿用。
5 内嵌 TongWeb 替换 Tomcat

5.1 准备

首先联系厂商提供依赖和 license 文件,接着将压缩包解压,我得到的压缩包目录结构如下:
  1. TongWeb V7.0.E6/
  2. ├── TongWeb V7.0.4.8 专用版 中科+兆芯/
  3. ├── TongWeb7.0.E.6_P2/
  4. │   ├── 安装工程介质/
  5. │   │   ├── tongweb-embed-7.0.E.6_P2/
  6. │   │   │   ├── lib/
  7. │   │   │   └── installMavenJar.bat
  8. │   │   │   └── ...
  9. │   │   ├── ...
  10. │   ├── 示例工程/
  11. │   ├── 用户手册与配置示例/
复制代码
忽略谁人专用版,/TongWeb7.0.E.6_P2 下3个文件夹依次是依赖安装工具、示例项目代码、文档。
Windows 环境直接执行上面展示的谁人 installMavenJar.bat 就可以把嵌入式 TongWeb 的依赖安装到当地 Maven 仓库了,留意路径上不要有中文,否则可能会报错。
5.2 项目改造

利用内嵌式 TongWeb 只需要改造项目标 pom 文件即可,目标就是去除 Tomcat ,替换为 TongWeb
  1.     <dependencies>
  2.         <dependency>
  3.             <groupId>org.springframework.boot</groupId>
  4.             <artifactId>spring-boot-starter-web</artifactId>
  5.             <exclusions>
  6.                
  7.                 <exclusion>
  8.                     <groupId>org.springframework.boot</groupId>
  9.                     <artifactId>spring-boot-starter-tomcat</artifactId>
  10.                 </exclusion>
  11.             </exclusions>
  12.         </dependency>
  13.         <dependency>
  14.             <groupId>com.tongweb.springboot</groupId>
  15.             <artifactId>tongweb-spring-boot-starter-2.x</artifactId>
  16.         </dependency>
  17.     <dependencies>
  18.     <dependencyManagement>
  19.         <dependencies>
  20.             <dependency>
  21.                 <groupId>com.tongweb</groupId>
  22.                 <artifactId>tongweb-embed-dependencies</artifactId>
  23.                 <version>7.0.E.6_P2</version>
  24.                 <type>pom</type>
  25.                 <scope>import</scope>
  26.             </dependency>
  27.         </dependencies>
  28.     </dependencyManagement>
复制代码
这个只是简单的 spring boot 项目标替换示例,至于复杂的建议看看文档再去示例代码中看看。
至于利用 spring-cloud-starter-gateway 的网关,也写一下示例吧
  1.     <dependencies>
  2.         <dependency>
  3.             <groupId>org.springframework.cloud</groupId>
  4.             <artifactId>spring-cloud-starter-gateway</artifactId>
  5.             <exclusions>
  6.                 <exclusion>
  7.                     <groupId>io.projectreactor.ipc</groupId>
  8.                     <artifactId>reactor-netty</artifactId>
  9.                 </exclusion>
  10.                 <exclusion>
  11.                     <groupId>io.projectreactor.addons</groupId>
  12.                     <artifactId>reactor-extra</artifactId>
  13.                 </exclusion>
  14.                 <exclusion>
  15.                     <groupId>io.projectreactor.netty</groupId>
  16.                     <artifactId>reactor-netty-http</artifactId>
  17.                 </exclusion>
  18.                 <exclusion>
  19.                     <groupId>io.projectreactor.netty</groupId>
  20.                     <artifactId>reactor-netty</artifactId>
  21.                 </exclusion>
  22.                 <exclusion>
  23.                     <groupId>io.projectreactor</groupId>
  24.                     <artifactId>reactor-core</artifactId>
  25.                 </exclusion>
  26.             </exclusions>
  27.         </dependency>
  28.         <dependency>
  29.             <groupId>org.springframework.boot</groupId>
  30.             <artifactId>spring-boot-starter-webflux</artifactId>
  31.             <exclusions>
  32.                 <exclusion>
  33.                     <groupId>io.projectreactor.netty</groupId>
  34.                     <artifactId>reactor-netty-http</artifactId>
  35.                 </exclusion>
  36.                 <exclusion>
  37.                     <groupId>io.projectreactor.netty</groupId>
  38.                     <artifactId>reactor-netty</artifactId>
  39.                 </exclusion>
  40.                 <exclusion>
  41.                     <groupId>io.projectreactor</groupId>
  42.                     <artifactId>reactor-core</artifactId>
  43.                 </exclusion>
  44.                 <exclusion>
  45.                     <groupId>io.projectreactor.ipc</groupId>
  46.                     <artifactId>reactor-netty</artifactId>
  47.                 </exclusion>
  48.             </exclusions>
  49.         </dependency>
  50.         <dependency>
  51.             <groupId>com.tongweb</groupId>
  52.             <artifactId>tongweb-spring-boot-reactor-starter</artifactId>
  53.             <exclusions>
  54.                 <exclusion>
  55.                     <groupId>io.netty</groupId>
  56.                     <artifactId>netty-resolver-dns-native-macos</artifactId>
  57.                 </exclusion>
  58.             </exclusions>
  59.         </dependency>
  60.     <dependencies>
  61.     <dependencyManagement>
  62.         <dependencies>
  63.             <dependency>
  64.                 <groupId>com.tongweb</groupId>
  65.                 <artifactId>tongweb-embed-dependencies</artifactId>
  66.                 <version>7.0.E.6_P2</version>
  67.                 <type>pom</type>
  68.                 <scope>import</scope>
  69.             </dependency>
  70.         </dependencies>
  71.     </dependencyManagement>
复制代码
5.3 添加 license

license.dat 放在项目根目录下即可启动,亲测在IDEA中多个服务只需要在总目录下放一个就行。当然也可以在配置文件中指定 license 的位置[5:1]
  1. server:
  2.   tongweb:
  3.     uri-encoding: UTF-8
  4.     license:
  5.       type: file
  6.       path: classpath:tongweb/license.dat
复制代码
5.4 jar 包启动

需要将 license.dat 放在和 jar 包一个目录中或者配置文件中的指定位置。
参考:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用多少眼泪才能让你相信

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表