首页 > 其他分享 >国产化:springboot项目TongWeb替换tomcat踩坑实录

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

时间:2024-09-04 16:38:02浏览次数:14  
标签:springboot Tomcat TongWeb Spring boot import tomcat war

前言
全流程记录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依赖,原因如下:

  1. 排除内嵌Tomcat: Spring Boot默认使用内嵌的Tomcat作为Web服务器,这对于通过java -jar方式运行的独立应用非常方便。然而,当你将Spring Boot项目打包为war并部署到外部应用服务器(如外部Tomcat)时,这个内嵌的Tomcat就不需要了。此时,需要排除内嵌Tomcat以避免冲突。

  2. 添加provided范围的Tomcat依赖: 即使排除了内嵌的Tomcat,项目仍然需要一些Tomcat的相关类,比如Servlet API 和 JSP API,这些类在项目编译时是必需的。因此,需要在项目的pom.xml文件中添加provided范围的Tomcat依赖。这意味着在编译项目时,这些依赖是可用的,但在生成的war包中不会包含这些依赖,因为运行时外部的Tomcat服务器已经提供了这些类。

    <!--示例-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除tomcat依赖-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

同时,我们还要在pom文件里修改打包方式,如果有要求,也可以定义打包的war包名称。

<!--打包方式-->
<packaging>war</packaging>

<!--指定war包的名字-->
<build>
    <finalName>demo</finalName>
</build>

2.2 改造启动类

使用外部服务器启动应用时,会跳过原来项目的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包启动流程

  1. 外部 Web 服务器加载应用:
  • 当你将 war 包部署到外部 Web 服务器时,服务器会自动检测到 war 包并加载其中的 Web 应用。
  • 外部服务器不会调用 Spring Boot 应用的 main 方法,而是会按照标准的 Servlet 规范启动应用。
  1. SpringBootServletInitializer 的作用:
  • 为了将 Spring Boot 应用集成到外部 Web 服务器中,通常会扩展 SpringBootServletInitializer 并重写 configure() 方法。
  • SpringBootServletInitializer 是一个 Spring 提供的适配器类,它的作用是初始化 Spring 应用上下文,并在应用服务器的上下文中运行 Spring Boot 应用。
  1. 启动过程:
  • 当外部服务器启动应用时,它会调用 SpringBootServletInitializerconfigure() 方法,Spring Boot 会根据你指定的配置类(如 MySpringBootApplication.class)初始化 Spring 上下文。
  • 这个过程实际上跳过了 main 方法,而是直接由服务器控制整个启动流程。

2.3 无法在Nacos中注册服务问题解决[1]

Nacos其自动注册微服务的类是NacosAutoServiceRegistration

public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<Registration> {

    private NacosRegistration registration;

    @Deprecated
    public void setPort(int port) {
        this.getPort().set(port);
    }

    protected NacosRegistration getRegistration() {
        if (this.registration.getPort() < 0 && this.getPort().get() > 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<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware,
		ApplicationListener<WebServerInitializedEvent> {

	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<ObjectName> 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 方法。

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文件,接着将压缩包解压,我得到的压缩包目录结构如下:

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仓库了,注意路径上不要有中文,否则可能会报错。

5.2 项目改造

使用内嵌式TongWeb只需要改造项目的pom文件即可,目标就是去除Tomcat,替换为TongWeb。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!--排除tomcat依赖-->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.tongweb.springboot</groupId>
            <artifactId>tongweb-spring-boot-starter-2.x</artifactId>
        </dependency>
    <dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.tongweb</groupId>
                <artifactId>tongweb-embed-dependencies</artifactId>
                <version>7.0.E.6_P2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

这个只是简单的spring boot项目的替换示例,至于复杂的建议看看文档再去示例代码中看看。
至于使用spring-cloud-starter-gateway的网关,也写一下示例吧

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.projectreactor.ipc</groupId>
                    <artifactId>reactor-netty</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor.addons</groupId>
                    <artifactId>reactor-extra</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor.netty</groupId>
                    <artifactId>reactor-netty-http</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor.netty</groupId>
                    <artifactId>reactor-netty</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor</groupId>
                    <artifactId>reactor-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.projectreactor.netty</groupId>
                    <artifactId>reactor-netty-http</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor.netty</groupId>
                    <artifactId>reactor-netty</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor</groupId>
                    <artifactId>reactor-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.projectreactor.ipc</groupId>
                    <artifactId>reactor-netty</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.tongweb</groupId>
            <artifactId>tongweb-spring-boot-reactor-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-resolver-dns-native-macos</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    <dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.tongweb</groupId>
                <artifactId>tongweb-embed-dependencies</artifactId>
                <version>7.0.E.6_P2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

5.3 添加license

license.dat放在项目根目录下即可启动,亲测在IDEA中多个服务只需要在总目录下放一个就行。当然也可以在配置文件中指定license的位置[5:1]

server:
  tongweb:
    uri-encoding: UTF-8
    license:
      type: file
      path: classpath:tongweb/license.dat

5.4 jar包启动

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

参考:


  1. 阿湯哥.Spring Boot 构建war 部署到tomcat下无法在Nacos中注册服务.(2024-04-07) ↩︎

  2. 骏马金龙.Tomcat(一):背景知识和安装tomcat.( 2017-10-23) ↩︎

  3. 无言行者.国产化-Tomcat替换——TongWeb的安装和使用.(2024-04-14) ↩︎

  4. 小尘哥.【信创一】微服务适配TongWeb及遇到的问题.(2023.09.13) ↩︎

  5. Thomas灬Wade.springboot2集成东方通tongweb嵌入式版.(2024-04-17) ↩︎ ↩︎

标签:springboot,Tomcat,TongWeb,Spring,boot,import,tomcat,war
From: https://www.cnblogs.com/datangguanjunhou/p/18388120

相关文章

  • 基于SpringBoot美食分享管理系统的设计和实现(源码+LW+部署讲解)
     目录:完整视频演示:系统架构:程序运行截图:核心代码参考:   数据库sql:项目技术介绍:java介绍:Mysql数据库介绍:为什么选择我:获取源码:......
  • 为什么SpringBoot的 jar 可以直接运行?
     SpringBoot应用程序被打包成的jar包之所以可以直接通过 java-jar 命令运行,是因为SpringBoot在构建过程中做了一些特殊的设计和配置。具体原因:Fat/UberJAR:SpringBoot使用maven插件spring-boot-maven-plugin(或Gradle对应的插件)将项目及其所有依赖项打包成一个单一......
  • Tomcat介绍及安装部署
    一、Tomcat概述程控制允许你根据条件执行不同的代码块,或者重复执行某段代码直到满足特定条件。1、Tomcat概念Tomcat是Java语言开发的,服务器是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP......
  • springboot+vue校园物品私人订制平台【程序+论文+开题】计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着校园文化的日益丰富与学生个性化需求的不断增长,传统校园市场已难以满足学生对独特、个性化物品的追求。当前,市场上虽不乏各类电商平台,但针对校园特定环境与学生群体的私人订制服务尚显不足。学生群体对于学习用品、生活用品乃至纪......
  • springboot+vue在线教育平台系统【程序+论文+开题】计算机毕业设计.mp4.baiduyun.p
    系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,教育行业正经历着前所未有的变革。在线教育平台作为这一变革的重要产物,凭借其跨越时空限制、资源丰富多样、学习灵活便捷等优势,逐渐成为人们获取知识、提升技能的重要途径。特别是在全球疫情的影响下,在线教育......
  • springboot+vue疫情访客审批管理系统的设计于与实现【程序+论文+开题】计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着全球疫情的持续影响,各类公共场所尤其是学校、企事业单位等对于访客管理的要求日益严格。传统的访客管理模式往往依赖于纸质登记、人工审核,不仅效率低下,且难以有效追踪与管理,增加了疫情传播的风险。因此,开发一套高效、智能、安全的......
  • springboot+vue网上社区团购平台的设计与实现【程序+论文+开题】计算机毕业设计
    系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,电子商务已成为现代经济不可或缺的一部分,深刻改变着人们的消费习惯。近年来,网上社区团购作为一种新兴的电商模式迅速崛起,以其价格优惠、便捷高效的特点赢得了广大消费者的青睐。社区团购通过整合社区资源,以团......
  • 基于SpringBoot大学失物招领系统的设计与实现(源码+LW+调试和讲解)
     目录:博主介绍:  完整视频演示:系统技术介绍:后端Java介绍前端框架Vue介绍具体功能截图:部分代码参考:  Mysql表设计参考:项目测试:项目论文:​为什么选择我:源码获取:博主介绍:  ......
  • 简单可靠的SpringBoot Jar包启动和更新的Shell脚本
    能用脚本执行,就不用手动nohupjava-jar啦。1.参数说明最终代码实现如下,支持四个参数:●operation:start-启动新包/update-更新包●env_para:指定使用哪个配置文件,如test/prod/dev等●new_jar_package_name:新包的名称,这里必须要求脚本的位置在新,旧包的同一目......
  • 基于Java+SpringBoot+Mysql在线众筹系统功能设计与实现五
    一、前言介绍:1.1项目摘要随着互联网的普及和人们消费观念的转变,众筹作为一种创新的融资方式,逐渐受到社会各界的关注和青睐。它打破了传统融资模式的限制,为初创企业、艺术家、公益项目等提供了更为灵活和便捷的融资渠道。因此,开发众筹系统旨在满足这一市场需求,促进创新项......