首页 > 其他分享 >将多个SpringBoot / 微服务应用合并成一个SpringBoot应用

将多个SpringBoot / 微服务应用合并成一个SpringBoot应用

时间:2024-09-25 14:24:21浏览次数:1  
标签:SpringBoot xxx 合并 private public 应用 provider com class

前言

当下在设计大型系统或网站时,为了满足系统的灵活性、扩展性、模块化、松耦合、高可用等特性,在技术架构选择时往往会选用微服务架构。独立服务的拆分会增加部署时机器资源的消耗。在轻量化部署场景的催化下,需要考虑中间件的缩减以及微服务应用的合并部署,已达到降低对服务器资源的依赖。

项目结构

我们的项目工程结构如下所示,其中xxx代表一个独立的微服务 ,整个工程由多个独立的微服务模块组成,这里只举例说明,没有列举完整的项目结构,api-xxx模块表示某个独立的微服务的后台管理能力,provider-xxx模块表示某个独立微服务对其它服务提供能力的模块。

- project-parent
    - api-xxx
    - provider-xxx

应用合并需要考虑的问题

因为系统整体基于微服务构建,在进行应用合并实现资源减配时,主要考虑将apiprovider应用进行合并,遇到的主要问题如下:

  1. apiprovider从业务角度属于同一个,所以重名的类较多,因此会导致Spring容器中的beanName重复
  2. ORM框架用的是JPAHibernate中的实体只有类名没有包路径,类名重复会导致JPA中的实体重复
  3. SpringMVC中注册的接口请求路径重复的问题
  4. apiprovider合并为一个服务后,其它应用通过RPC调用provider服务的服务名需要调整
  5. 其它一些由业务和技术特性决定的不具备普遍性的问题,这里不加赘述
    面临上面的问题,如果在一个SpringBoot模块中,直接通过Mavenapi-xxx模块provider-xxx模块引入后启动肯定会报错的。

应用合并合并

基于以上问题,理想状态是在一个JVM里面启动两个Spring容器,分别对应apiprovider,减少对服务器资源需求的同时最大程度保留原有的技术架构。
支持多个应用同时启动的容器类,这是一个抽象类,需要由具体启动的应用继承后设置应用名称和SpringBoot的Application类:

public abstract class MultipleServiceRunner {

    private ConfigurableApplicationContext applicationContext;

    private final String applicationName;

    private final Class<?>[] applicationClasses;

    private String[] args;

    private final static Object lock = new Object();

    private Boolean wait = Boolean.FALSE;

    public MultipleServiceRunner(String applicationName, Class<?>... applicationClasses) {
        this.applicationName = applicationName;
        this.applicationClasses = applicationClasses;
    }

    public void setArgs(String[] args) {
        this.args = args;
    }

    public void run() {
        if(applicationContext != null) {
            throw new IllegalStateException("AppContext must be null to run this backend");
        }
        runBackendInThread();
        waitUntilBackendIsStarted();
    }

    private void waitUntilBackendIsStarted() {
        try {
            synchronized (lock) {
                if(wait) {
                    lock.wait();
                }
            }
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    private void runBackendInThread() {
        final Thread runnerThread = new ApplicationRunner(applicationName);
        wait = Boolean.TRUE;
        runnerThread.setContextClassLoader(applicationClasses[0].getClassLoader());
        runnerThread.start();
    }

    public void stop() {
        if (Optional.ofNullable(applicationContext).isPresent()) {
            SpringApplication.exit(applicationContext);
            applicationContext = null;
        }
    }

    protected class ApplicationRunner extends Thread {

        public ApplicationRunner(String name) {
            super(name);
        }

        @Override
        public void run() {
            applicationContext = SpringApplication.run(applicationClasses, args);
            synchronized (lock) {
                wait = Boolean.FALSE;
                lock.notify();
            }
        }
    }

}

扫描MultipleServiceRunner的子类,启动SpringBoot容器:

public class MultipleServiceStarter {

    private final static List<Container> containers = new ArrayList<>(4);

    private final static String RUNNER_PACKAGE = "com.xxx";

    protected static Set<Class<?>> scan() throws IOException, ClassNotFoundException {
        Set<Class<?>> classes = new HashSet<>();

        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(RUNNER_PACKAGE) + "/**/*.class";
        Resource[] resources = resourcePatternResolver.getResources(pattern);
        //MetadataReader 的工厂类
        MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
        for (Resource resource : resources) {
            //用于读取类信息
            MetadataReader reader = readerfactory.getMetadataReader(resource);
            //扫描到的class
            String classname = reader.getClassMetadata().getClassName();
            Class<?> clazz = Class.forName(classname);
            if (MultipleServiceRunner.class.isAssignableFrom(clazz) && !Objects.equals(MultipleServiceRunner.class, clazz)) {
                classes.add(clazz);
            }
        }

        return classes;
    }

    public static void start(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Set<Class<?>> runnerClasses = scan();

        for (Class<?> runnerClass : runnerClasses) {
            MultipleServiceRunner runnerInstance = (MultipleServiceRunner) runnerClass.newInstance();
            containers.add(new Container(runnerClass, runnerInstance));

            runnerInstance.setArgs(args);
            runnerInstance.run();
        }
    }

    public static void stop() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        for (Container container : containers) {
            container.runnerInstance.stop();
        }
    }

    protected static class Container {
        private Class<?> runnerClass;

        private MultipleServiceRunner runnerInstance;

        public Container(Class<?> runnerClass, MultipleServiceRunner runnerInstance) {
            this.runnerClass = runnerClass;
            this.runnerInstance = runnerInstance;
        }
    }

}

主程序启动类:

public class LiteLauncherApplication {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        MultipleServiceStarter.start(args);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            try {
                MultipleServiceStarter.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }));
    }

}

微服务改造

新增lite-xxx模块,Maven引入apiprovider模块,修改打包插件,指定程序入口,由于公司安全政策原因已对敏感信息进行脱敏:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.xxx</groupId>
        <artifactId>parent</artifactId>
        <version>4.8.0-SNAPSHOT</version>
    </parent>

    <groupId>com.xxx</groupId>
    <artifactId>lite-xxx</artifactId>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.xxx</groupId>
            <artifactId>service-xxx</artifactId>
            <version>${xxx.version}</version>
        </dependency>

        <dependency>
            <groupId>com.xxx</groupId>
            <artifactId>provider-xxx</artifactId>
            <version>${xxx.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>unpack-some-artifact</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>com.xxx</groupId>
                                    <artifactId>service-xxx</artifactId>
                                    <type>jar</type>
                                    <overWrite>true</overWrite>
                                    <outputDirectory>${project.build.directory}/classes</outputDirectory>
                                    <includes>**/*</includes>
                                    <excludes>*.properties,logback-spring.xml</excludes>
                                </artifactItem>
                                <artifactItem>
                                    <groupId>com.xxx</groupId>
                                    <artifactId>provider-xxx</artifactId>
                                    <type>jar</type>
                                    <overWrite>true</overWrite>
                                    <outputDirectory>${project.build.directory}/classes</outputDirectory>
                                    <includes>**/*</includes>
                                    <excludes>*.properties,logback-spring.xml</excludes>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.xxx.LiteLauncherApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中-->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

api模块容器类:

public class ApiContainerRunner extends MultipleServiceRunner {
    public ApiContainerRunner() {
        super("api-xxx", ApiApplication.class);
        System.setProperty("spring.profiles.active", "release");
        System.setProperty("spring.application.name", "xxx");
        System.setProperty("spring.cloud.nacos.config.group", "xxx");
        System.setProperty("spring.datasource.master.jpa.packageToScan", "com.xxx.servicexxx.bean,com.xxx.servicexxx.bean");
    }
}

api模块Application类,保留关键注解,一是引入配置文件,二是Spring扫描bean时排除掉provider模块下的类否则还是会出现beanName重复:

@SpringBootApplication
@PropertySource(value = {"classpath:bootstrap-release.properties"})
@ComponentScan(nameGenerator = VersionAnnotationBeanNameGenerator.class, basePackages="com.xxx.*",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = {
                "com.xxx.providerxxx.*",
                "com.xxx.servicedxxx.*"
        })}
)
public class ApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiApplication.class, args);
    }

}

provider模块的Run和Application参考实现即可。
通过com.xxx.LiteLauncherApplication类启动服务,会看到apiprovider模块依次启动成功,至此应用合并完成

注意事项

应用合并后,大家要理解本质是在同一个JVM中启动了两个Spring容器/Spring Context,如果有些代码实现是JVM全局的,可能会涉及到部分代码调整。

标签:SpringBoot,xxx,合并,private,public,应用,provider,com,class
From: https://www.cnblogs.com/changxy-codest/p/18431056

相关文章

  • SimpleAIAgent:使用免费的glm-4-flash即可开始构建简单的AI Agent应用FI
    合集-C#(80)1.使用C#将几个Excel文件合并去重分类2023-11-152.C#使用SqlSugar操作MySQL数据库实现简单的增删改查2023-11-163.C#中的类和继承2023-11-174.C#中的virtual和override关键字2023-11-175.C#中的属性2023-11-206.C#winform中使用SQLite数据库2023-11-237.C#简化工作之......
  • 【万字文档+PPT+源码】基于springboot+vue二手交易平台-可用于毕设-课程设计-练手学习
    博主简介:......
  • MATLAB在无线通信系统故障诊断中的应用
    在无线通信系统的开发和维护过程中,故障诊断是一个至关重要的环节。MATLAB提供了一系列的工具和功能,可以帮助工程师和研究人员进行有效的故障诊断。本文将详细介绍如何在MATLAB中进行无线通信系统的故障诊断,包括基本的故障诊断方法、代码示例和应用场景。一、无线通信系统故......
  • Redisearch 入门指南构建高性能搜索应用
    1.概述Redisearch是一个强大的全文搜索引擎,基于流行的Redis数据库构建,专为高效的数据检索而设计。它结合了Redis的快速存储能力和搜索引擎的复杂查询功能,使得开发者能够在海量数据中实现实时搜索体验。Redisearch支持丰富的特性,包括模糊匹配、布尔搜索、聚合、地理......
  • ElasticSearch安装分词器与整合SpringBoot
    ElasticSearch安装分词器与整合SpringBoot如果还没安装的点击安装ElasticSearch查看怎么安装分词器1.分词器在Elasticsearch中,分词器(Tokenizer)是分析器(Analyzer)的一部分,它的主要职责是将文本输入(如字符串)分割成一系列的词元(tokens)。这些词元是搜索和索引的基础单元。......
  • 基于SpringBoot的酒店在线预定系统的设计与实现(源码+LW+讲解和调试)
     目录:博主介绍:  完整视频演示:系统技术介绍:后端Java介绍前端框架Vue介绍具体功能截图:部分代码参考:  Mysql表设计参考:项目测试:项目论文:​为什么选择我:源码获取:博主介绍:  ......
  • 基于SpringBoot上门做菜预定管理系统的设计与实现(源码+LW+讲解和调试)
     目录:博主介绍:  完整视频演示:系统技术介绍:后端Java介绍前端框架Vue介绍具体功能截图:部分代码参考:  Mysql表设计参考:项目测试:项目论文:​为什么选择我:源码获取:博主介绍:  ......
  • SimpleAIAgent:使用免费的glm-4-flash即可开始构建简单的AI Agent应用
    SimpleAIAgent是基于C#SemanticKernel与WPF构建的一款AIAgent探索应用。主要用于使用国产大语言模型或开源大语言模型构建AIAgent应用的探索学习,希望能够帮助到感兴趣的朋友。接下来我想分享一下我的AIAgent应用实践。翻译文本并将文本存入文件第一个例子是翻译文本,并将......
  • 用C#写个PDF批量合并工具简化日常工作
    用C#写个PDF批量合并工具简化日常工作一.前言由于项目需要编写大量的材料,以及各种签字表格、文书等,最后以PDF作为材料交付的文档格式,过程文档时有变化或补充,故此处理PDF文档已经成为日常工作的一部分。网上有各种PDF处理工具,总是感觉用得不跟手。最后回顾自己的需求总结为以下......
  • [含文档+PPT+源码等]精品基于springboot实现的原生Andriod移动学习APP软件
    软件开发环境及开发工具:数据库管理工具:phpstudy/Navicat或者phpstudy/sqlyog开发工具:AndroidStudio后台管理系统涉及技术:后台使用框架:Springboot前端使用技术:Vue,HTML5,CSS3、JavaScript等数据库:Mysql数据库本系统功能完整,适合作为计算机项目设计参考以及学习、就业......