走向 Native 化:SpringDubbo AOT 技术示例与原理讲解

作者:刘军

Java 应用在云计算时代面临“冷启动”慢、内存占用高、预热时间长等问题,无法很好的适应 Serverless 等云上部署模式,GraalVM 通过静态编译、打包等技术在很大程度上解决了这些问题,同时针对 GraalVM 的一些使用限制,Spring 和 Dubbo 等主流框架也都提供了相应的 AOT 解决方案。

本文我们将详细分析 Java 应用在云时代面临的挑战,GraalVM Native Image 是如何解决这些问题,GraalVM 的基本概念与工作原理,最后我们通过一个 Spring6 + Dubbo3 的微服务应用示例演示了如何将一个普通微服务应用进行静态化打包。

本文主要分为以下四个部分展开:

  1. 首先我们会先看一下在云计算快速发展的当下,云上应用应该具备的特点,Java 应用在云上所面临的挑战有哪些。
  2. 其次,我会介绍一下 GraalVM,什么是 Native Image,如何通过 GraalVM 对 Java 应用进行静态化打出 Native Image 可执行的二进制程序。
  3. 第三部分,我们知道 GraalVM 的使用是有一定限制的,比如 Java 的反射等动态特性是不被支持的,因此我们需要提供特殊的 Metadata 配置来绕过这些限制,在这一部分我们会讲解如何加入引入 AOT Processing 来实现自动化的 Metadata 配置,包括 Spring6 框架中 AOT 处理、Dubbo3 框架的 AOT 处理等。
  4. 最后,我们将通过一个 Spring6+Dubbo3 的应用示例,来演示如何将这么一个 Java 应用进行静态化打包。

Java 应用在云时代所面临的挑战

首先,我们先看一下云计算时代的应用特点,以及 Java 在云时代所面临的挑战。从各个统计机构给出的数据来看,Java 语言仍然是当今最受开发者欢迎的编程语言之一,仅次于一些脚本开发语言。使用 Java 语言可以非常高效的开发业务应用,丰富的生态使得 Java 具有非常高的开发和运行效率,有无数的应用基于 Java 语言开发。

在这里插入图片描述

但在来到云计算时代,Java 应用的部署和运行开始面临非常多的问题。我们以Serverless为例,Serverless是云上的一种越来越主流的部署模式,它让开发者更专注业务逻辑、通过快速弹性等帮助解决资源问题,根据最新的一些数据,Java在所有云计算厂商的 Serverless 运行时中所占比例并不高,远远不能和它在传统应用开发中所占的比例相匹配。

在这里插入图片描述

出现这样的原因,主要是 Java 应用不能很好的满足 Serverless 场景的几个关键要求。

  • 首先是启动速度问题,Java 冷启动的耗时是比较长的。 这对于 Serverless 需要快速弹起的场景是一个非常大的挑战,因为 Java 应用的拉起时间可能是秒、数十秒级别的;
  • 第二点,Java 应用往往都需要一定的预热时间, 才能达到最佳的性能状态,刚刚拉起的应用如果分配比较大的流量是不合适的,往往会出现请求超时、资源占用过高等问题,这就进一步拉长了 Java 应用的有效拉起时间;
  • 第三点是 Java 应用对运行环境的要求, 它往往需要较大的内存、计算资源,而这些真正分配给业务自身的并不多,都消耗在一些 JVM 运行时上,这与用云降本提效的目标并补匹配;
  • 最后,Java 应用打出来的包或者镜像也是非常大, 从总体上也影响存储、拉取的效率。

接下来,我们具体看一下针对 Java 应用所面临的这些问题, GraalVM 这样一种打包和运行时技术是如何解决的。

GraaIVM 简介

GraalVM compiles your Java applications ahead of time into standalone binaries that start instantly, provide peak performance with no warmup, and use fewer resources.

引用官方介绍来看,GraalVM 为 Java 应用提供 AOT 编译和二进制打包能力,基于 GraalVM 打出的二进制包可以实现快速启动、具有超高性能、无需预热时间、同时需要非常少的资源消耗。这里所说的 AOT 是发生在编译期间的一个技术简称,即 Ahead-of-time,这一点我们后续会讲到。总的来说 GraalVM 可以分为两部分内容来看

  • 首先,GraalVM 是一个完整的 JDK 发行版本, 从这一点它是与 OpenJDK 对等的,可以运行任何面向 jvm 的语言开发的应用;
  • 其次,GraalVM提供了 Native Image 打包技术, 这可以将应用打包为可以独立运行的二进制包,这个包是自包含的、可脱离 JVM 运行的应用程序。

在这里插入图片描述

如上图所示,GraalVM 编译器提供了 JIT 和 AOT 两种模式。

  • 对于 JIT 而言,我们都知道 Java 类会被编译为 .class 格式的文件,这里编译后就是 jvm 识别的字节码,在 Java 应用运行的过程中,而 JIT 编译器又将一些热点路径上的字节码编译为机器码,已实现更快的执行速度;
  • 对于 AOT 模式来说,它直接在编译期间就将字节码转换为机器码,直接省去了运行时对 jvm 的依赖,由于省去了 jvm 加载和字节码运行期预热的时间,AOT 编译和打包的程序具有非常高的运行时效率。

在这里插入图片描述

总的来说,JIT 使得应用可以具备更高的极限处理能力,可以降低请求的最大延迟这一关键指标;而 AOT 则可以进一步的提升应用的冷启动速度、具有更小的二进制包提及、在运行态需要更少的内存等资源。

什么是 Native Image?

我们上面多次提到 GraalVM 中 Native Image 概念,Native Image 是一项将 Java 代码编译打包为可执行二进制程序的技术,打出的包中仅包含运行期所需要的代码,包括应用自身代码、标准依赖包、 语言运行时、JDK 库关联的静态代码。这个包的运行不再需要 jvm 环境,当然它是和具体的机器环境相绑定的,需要为不同的机器环境单独打包。Native Image 有这里列出来的一系列特点:

  • 仅包含 JVM 运行所需的一部分资源,运行成本更低

  • 毫秒级的启动时间

  • 启动后即进入最佳状态,无需预热

  • 可打包为更轻量的二进制包,让部署速度更快更高效

  • 安全程度更高

在这里插入图片描述

总结起来就是这里的关键几项:更快的启动个速度、更少的资源占用、更小的安全漏洞风险、更紧凑的二进制包体积。解决 Java 应用在 Sererless 等云计算应用场景中面临的突出问题。

GraaIVM Native Image 的基本原理与使用

接下来,我们看一下 GraalVM 的基本使用方式,首先,需要安装 native-image 需要的相关基础依赖,根据不同的操作系统环境会有所差异,接下来可以使用 GraalVM JDK 下载器下载 native-image。都安装好之后,第二步,就可以使用 native-image 命令编译和打包 Java 应用了,输入可以是 class 文件、jar文件、Java 模块等,最终打包为一个可独立运行的可执行文件,比如这里的 HelloWorld。另外,GraalVM 也提供了对应的 Maven和Gradle 构建工具插件,让打包过程更容易。

在这里插入图片描述

GraalVM 基于叫做 “closed world assumption” 即封闭世界假设的概念,要求在编译期间程序的所有运行时资源和行为即能被完全确定下来。图中是具体的 AOT 编译和打包过程,左侧应用代码、仓库、jdk 等全部作为输入,GraalVM 以 main 为入口,扫描所有可触达的代码与执行路径,在处理过程中可能会涉及到一些前置初始化动作,最终 AOT 编译的机器码和一些初始化资源等状态数据,被打包为可执行的 Native 包。

相比于传统的 JVM 部署模式,GraalVM Native Image 模式带来的非常大的不同。

  • GraalVM 在编译构建期间就会以 main 函数为入口,完成对应用代码的静态分析
  • 在静态分析期间无法被触达的代码,将会被移除,不会包含在最终的二进制包中
  • GraalVM 无法识别代码中的一些动态调用行为,如反射、resource 资源加载、序列化、动态代理等都动态行为都将受限
  • Classpath 在构建阶段就固化下来,无法修改
  • 不再支持延迟的类加载,所有可用类和代码在程序启动阶段就确定了
  • 还有一些其他的 Java 应用能力是受限使用的(比如类初始化提前等)

GraalVM 不支持反射等动态特性,而我们的很多应用和框架中却大量使用了反射、动态代理等特性,如何才能将这些应用打包为 Native Image 实现静态化那?GraalVM 提供了元数据配置入口,通过为所有动态特性提供配置文件,“closed world assumption” 模式还是成立的,可以让 GraalVM 在编译期知道所有的预期行为。这里给了两个例子:

  1. 编码方式上,比如这里反射的编码方式,可以让 GraalVM 通过代码分析计算 Metadata。

在这里插入图片描述

在这里插入图片描述

  1. 另一个示例是提供额外的 json 配置文件并放在指定的目录 META-INF/native-image// 下。

在这里插入图片描述

AOT Processing

Java 应用或框架中的反射等动态特性的使用是影响 GraalVM 使用的障碍,而大量的框架都存在这个限制,如果都要求应用或者开发者提供 Metadata 配置的话将会是一项非常有挑战的任务,因此,Spring 和 Dubbo 等框架都在 AOT Compilation 即 AOT 编译之前引入了 AOT Processing 即 AOT 预处理的过程,AOT Processing 用来完成自动化的 Metadata 采集,并将 Metadata 提供给 AOT 编译器使用。

在这里插入图片描述

AOT 编译机制是对所有 Java 应用通用的,但相比于 AOT 编译,AOT Processing 采集 Metadata 的过程是每个框架都不同的,因为每个框架对于反射、动态代理等都有自己的用法。

我们以一个典型的 Spring + Dubbo 的微服务应用为例,要实现这个应用的静态化打包,这里涉及到 Spring、Dubbo 以及一众第三方依赖的 Metadata 处理过程。

  • Spring - Spring AOT processing
  • Dubbo - Dubbo AOT processing
  • Third-party libraries - Reachability Metadata

对于 Spring 来说,Spring6 中发布了 Spring AOT 机制,用来支持 Spring 应用的静态化预处理;Dubbo 最近也在 3.2 版本中发布了 Dubbo AOT 机制,让 Dubbo 相关组件可以自动化实现 Native 预处理;除了这两个与业务开发密切相关的框架,一个应用中往往还有大量的第三方依赖,这些依赖的 Metadata 也是影响静态化的关键,如果它们中有反射、类加载等行为,那么需要为它们提供 Metadata 配置,对于这些第三方应用目前有两个渠道,一个是 GraalVM 官方提供的共享空间,这里有相当一部分依赖的 Metadata 配置可供使用,另一种方式则是要求组件官方发布的发布中包含 Metadata 配置,对于这两种情况 GraalVM 都可以做到对于 Metadata 的自动读取。

Metadata 配置:https://github.com/oracle/graalvm-reachability-metadata

Spring AOT

接下来我们看一下 Spring AOT 做了哪些编译之前的预处理工作,Spring 框架中有非常多的动态特性,比如自动配置、条件 Bean 等特性。Spring AOT 就是针对针对这些动态特性,在构建阶段进行预处理,生成可供 GraalVM 使用的一系列 Metadata 输入,这里生成的内容包括:

  • Spring Bean 定义相关的代码预生成,如下图展示代码段
  • 在构建阶段生成动态代理相关代码
  • 关于一些反射等使用的 JSON 元数据文件

在这里插入图片描述

Dubbo AOT

Dubbo AOT 做的事情与 Spring AOT 非常类似,只不过 Dubbo AOT 是专门针对 Dubbo 框架特有的使用方式进行预处理,这包括:

  • SPI 扩展相关的源代码生成
  • 一些反射使用的 JSON 配置文件生成
  • RPC 代理类代码生成

在这里插入图片描述

在这里插入图片描述

Spring6+Dubbo3 示例演示

接下来,我们通过一个 Spring6 + Dubbo3 的示例微服务应用,演示如何使用 Spring AOT、Dubbo AOT 等,来实现应用的 Native Image 打包。

完整的代码示例可在这里下载:https://github.com/apache/dubbo-samples/tree/master/1-basic/dubbo-samples-native-image

第一步:安装 GraalVM

  1. 在 Graalvm 官网根据自己的系统选取对应 Graalvm 版本:https://www.graalvm.org/downloads/

  2. 根据官方文档安装 native-image:https://www.graalvm.org/latest/reference-manual/native-image/#install-native-image

第二步:创建项目

这个示例应用就是普通的、常见的微服务应用,我们使用 SpringBoot3 进行应用配置开发,使用 Dubbo3 定义并发布 RPC 服务;应用构建工具使用 Maven。

在这里插入图片描述

在这里插入图片描述

第三步:配置 Maven 插件

重点是增加 spring-boot-maven-plugin、native-maven-plugin、dubbo-maven-plugin 三个插件配置,开启 AOT 处理过程,修改 dubbo-maven-plugin 中的 mainClass 为所需的启动类全路径。(其中 API 使用方式无需添加 spring-boot-maven-plugin 依赖。)

<profiles>
        <profile>
            <id>native</id>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <configuration>
                            <release>17</release>
                            <fork>true</fork>
                            <verbose>true</verbose>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>process-aot</id>
                                <goals>
                                    <goal>process-aot</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.graalvm.buildtools</groupId>
                        <artifactId>native-maven-plugin</artifactId>
                        <version>0.9.20</version>
                        <configuration>
                            <classesDirectory>${project.build.outputDirectory}</classesDirectory>
                            <metadataRepository>
                                <enabled>true</enabled>
                            </metadataRepository>
                            <requiredVersion>22.3</requiredVersion>
                        </configuration>
                        <executions>
                            <execution>
                                <id>add-reachability-metadata</id>
                                <goals>
                                    <goal>add-reachability-metadata</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-maven-plugin</artifactId>
                        <version>${dubbo.version}</version>
                        <configuration>
                            <mainClass>com.example.nativedemo.NativeDemoApplication</mainClass>
                        </configuration>
                        <executions>
                            <execution>
                                <phase>process-sources</phase>
                                <goals>
                                    <goal>dubbo-process-aot</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

第四步:在 Pom 依赖中添加 native 相关的依赖

另外,对于 Dubbo 而言,由于当前一些 Native 机制依赖 JDK17 等版本,Dubbo 没有将一些包默认打包到发行版本中,因此需要增加两个额外的依赖 dubbo-spring6 适配和 dubbo-native 组件。

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-config-spring6</artifactId>
    <version>${dubbo.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-native</artifactId>
    <version>${dubbo.version}</version>
</dependency>

第五步:调整 compiler、proxy、serialization 和 logger

同时,这个示例对于第三方组件的支持目前也是受限的,主要是第三方组件的 Reachability Metadata 。比如目前支持的网络通信或编码组件有 Netty 和 Fastjson2;支持的日志等组件为 Logback;微服务组件有 Nacos、Zookeeper 等。

  • 序列化方式目前支持的比较好的是 Fastjson2
  • compiler、proxy 目前只能选择 jdk
  • logger 目前需要配置 slf4j,目前仅支持 logback

示例配置如下:

dubbo:
  application:
    name: ${spring.application.name}
    logger: slf4j
    compiler: jdk
  protocol:
    name: dubbo
    port: -1
    serialization: fastjson2
  registry:
    id: zk-registry
    address: zookeeper://127.0.0.1:2181
  config-center:
    address: zookeeper://127.0.0.1:2181
  metadata-report:
    address: zookeeper://127.0.0.1:2181
  provider:
    proxy: jdk
    serialization: fastjson2
  consumer:
    proxy: jdk
    serialization: fastjson2

第六步:编译

在项目根路径下执行以下编译命令:

  • API 方式直接执行
mvn clean install -P native -Dmaven.test.skip=true
  • 注解和 xml 方式(Springboot3 集成的方式)
mvn clean install -P native native:compile -Dmaven.test.skip=true

第七步:执行二进制文件即可

二进制文件在 target/ 目录下,一般以工程名称为二进制包的名称,比如 target/native-demo。

总结

GraalVM 技术为 Java 在云计算时代的应用带来了新的变革,帮助解决了 Java 应用启动慢、资源占用,但同时我们也看到了 GraalVM 的使用也存在一些限制,因此 Spring6、SpringBoot3、Dubbo3 都提供了相应的 Native 解决方案。除了 Dubbo 之外,Spring Cloud Alibaba 也正在推进静态化打包方案,接下来,我们将围绕两款框架的周边生态组件如 nacos、sentinel、seata 等推进整体的 Native 静态化验证。

相关链接:

[1] Apache Dubbo 博客

https://dubbo.apache.org/zh-cn/blog/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/36045.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes service服务的发布 - kube-proxy(负载均衡器)-IPVS

目录 Service Service将内部的pod暴露到外面&#xff0c;让用户可以访问 负载均衡策略&#xff1a; Service 的类型&#xff1a; 案例&#xff1a;Service服务发布案例 扩展&#xff1a;我们在案例再加入一个探针的使用 更改后的my_nginx.yaml文件&#xff1a; 创建Pod&…

jar-protect Jar 包加壳工具

jar-protect 是 java 的 jar 加密加壳工具&#xff0c;对 class 文件进行加密防护&#xff0c;避免反编译破解。 java 本身是开放性极强的语言&#xff0c;代码也容易被反编译&#xff0c;没有语言层面的一些常规保护机制&#xff0c;jar 包很容易被反编译和破解。 受 classfi…

OpenCV 入门教程:自适应阈值处理

OpenCV 入门教程&#xff1a;自适应阈值处理 导语一、自适应阈值处理二、示例应用2.1 图像二值化2.2 图像去噪 总结 导语 自适应阈值处理是图像处理中常用的技术之一&#xff0c;它能够根据图像的局部特征自动调整阈值&#xff0c;从而提高图像的处理效果。在 OpenCV 中&#…

vue element select下拉框树形多选

components 文件下新建 SelectTree文件 index.vue SelectTree index.vue <!--* 下拉树形选择 组件--> <template><el-select ref"select" style"min-width: 260px" :value"value" v-model"valueName" collapse-tags :…

【LeetCode热题100】打卡第33天:环形链表LRU缓存

文章目录 【LeetCode热题100】打卡第33天&#xff1a;环形链表&LRU缓存⛅前言 环形链表&#x1f512;题目&#x1f511;题解 LRU缓存&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第33天&#xff1a;环形链表&LRU缓存 ⛅前言 大家好&#xff0c;我是知…

Chapter 3: Conditional | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介Chapter 3: Conditional executionBoolean expressionsLogical operatorsConditional executionAlternative executionChained conditionalsNested conditionalsCatching exceptions using try and exceptShort-circuit evaluation of lo…

运维开发面试题第一期

1.tail -f和tail -F的区别是什么? tail -f 根据文件描述符进行追踪&#xff0c;当文件改名或被删除&#xff0c;追踪停止。 tail -F 根据文件名进行追踪&#xff0c;并保持重试&#xff0c;即该文件被删除或改名后&#xff0c;如果再次创建相同的文件名&#xff0c;会继续…

Tomcat相关

1. 运行项目 将java项目打包为war或者war所对应的文件夹&#xff0c;放置于tomcat的webapps目录下。其实tomcat运行时会解压war到项目中并运行class文件&#xff0c;延伸开来&#xff0c;为啥不能用jar包&#xff0c;因为jar可能可以表示项目但也能表示依赖&#xff0c;tomcat…

国产4 通道模拟复合视频解码芯片MIPI CSI 接口,XS9922B

XS9922B 是一款 4 通道模拟复合视频解码芯片&#xff0c;支持 HDCCTV 高清协议和 CVBS 标 清协议&#xff0c;视频制式支持 720P/1080P 高清制式和 960H/D1 标清制式。芯片将接收到的高清 模拟复合视频信号经过模数转化&#xff0c;视频解码以及 2D 图像处理之后…

git在工作中如何搭建和运用(巨详细!!)

最近有点闲&#xff0c;出一版git在实际公司上的一些运用 1&#xff0c;下载git&#xff0c; 下载git就不多说了&#xff0c;官方上下载安装就好了。 2&#xff0c;初始化 下载安装完成后&#xff0c;找个项目的空文件夹进去&#xff0c;右键点击git bash here &#xff0c;…

Android 视频直播提拉流 嵌入式硬件 流媒体开发详细内容

1 Linux 系统编程网络编程基础 2 Linux 网络编程流媒体服务器&#xff0c;客户端开发实践 3 Android流媒体客户端 FFmpeg OpenGL ES 开发实践 4 Android H.264 AAC 封装mp4开发实战 5 流媒体开发实战之Rtmp推流 6 流媒体开发实战之RTSP推流 7 流媒体开发实战之UDP 8 P2P点对点项…

培训报名小程序报名列表页开发

目录 1 创建页面2 组件搭建3 设置URL参数4 设置筛选条件5 首页跳转6 最终的效果总结 这节我们来开发报名列表功能&#xff0c;先看原型 1 创建页面 功能要在页面上呈现&#xff0c;需要先创建页面。打开我们的培训报名小程序&#xff0c;在页面区&#xff0c;点击创建页面的…

多元回归预测 | Matlab主成分分析PCA降维,PLS偏小二乘回归预测。PCA-PLS回归预测模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab主成分分析PCA降维,PLS偏小二乘回归预测。PCA-PLS回归预测模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量 warn…

CUDA+CUDNN+torch+torchvision安装

弄了好久&#xff0c;终于弄好了&#xff01;&#xff01;&#xff01; 原因&#xff1a;其实之前我是已经配置好pytorch的相关环境的了。但是这段时间&#xff0c;在跑GNN相关论文中的代码时&#xff0c;发现代码中的某个函数要求torch必须得是1.8 而我之前安装的是torch1.1…

《MySQL技术内幕》读书总结(一):MySQL体系结构和存储引擎

文章目录 前言&#xff1a;1、定义数据库和实例2、MySQL体系结构3、MySQL存储引擎InnoDBMyISAM 4、连接MySQL 前言&#xff1a; 该技术文章是我阅读《MySQL技术内幕 InnoDB存储引擎》第2版的总结梳理 我写这里文章的目的&#xff1a;书中的内容过于系统和繁琐&#xff0c;并不是…

C++学习 数组

目录 数组 一维数组 数组名 案例&#xff1a;冒泡排序 二维数组 数组名 数组 数组就是一个集合&#xff0c;里面存放了相同类型的数据元素。 下面的数字对应为数组的下标(索引)&#xff0c;可以看到索引范围为0~数组长度-1 特点&#xff1a; 数组中数据元素的数据类型相同。…

Unity3D 场景添加obj模型

有一个立方体的obj模型&#xff1b;将其拖到Assets文件夹节点上&#xff0c;在此节点放手&#xff0c;资源被加入项目&#xff1b; 在右侧显示出对象概览&#xff1b; 点击箭头&#xff0c;显示此模型下的子对象&#xff1b; 然后按住Assets面板中的cube1对象&#xff0c;拖动…

36.RocketMQ之Broker如何实现磁盘文件高性能读写

highlight: arduino-light Broker读写磁盘文件的核心技术:mmap Broker中大量的使用mmap技术去实现CommitLog这种大磁盘文件的高性能读写优化的。 通过之前的学习&#xff0c;我们知道了一点&#xff0c;就是Broker对磁盘文件的写入主要是借助直接写入os cache来实现性能优化的&…

【Java项目】Vue+ElementUI+Ceph实现多类型文件上传功能并实现文件预览功能

文章目录 效果演示前端后端Java 效果演示 先说一下我们的需求&#xff0c;我们的需求就是文件上传&#xff0c;之前的接口是只支持上传图片的&#xff0c;之后需求是需要支持上传pdf&#xff0c;所以我就得换接口&#xff0c;把原先图片上传的接口换为后端ceph&#xff0c;但是…

诚迈科技董事长、统信软件董事长王继平出席全球数字经济大会

7月5日&#xff0c;2023全球数字经济大会“数字未来新一代软件产业高质量发展论坛”在北京大兴隆重举行。论坛以“数字新高地&#xff0c;数创兴未来”为主题&#xff0c;共同探讨产业升级新路径&#xff0c;凝聚数字经济合作新共识&#xff0c;构建数字产业集聚发展新高地。诚…