注意:
- 本教程需要访问 Oracle Cloud。要注册免费账户,请参阅开始使用 Oracle Cloud Infrastructure 免费套餐。
- 它对 Oracle Cloud Infrastructure 身份证明、租户和区间使用示例值。完成实验室后,请使用特定于云环境的那些值替换这些值。
在 OKE 上使用 Spring Boot 创建基于 Oracle GraalVM 的 Java 应用,以在 ATP 中存储 SOAP 消息并发送到 OCI 队列
简介
我们的许多客户都依靠传统的简单对象访问协议 (SOAP) 消息来实现应用程序之间的通信。他们通常需要存储事务数据、确保服务分离、实现传入请求负载平衡以及对这些消息启用异步通信。
在本教程中,我们将了解如何使用部署在 Oracle Cloud Infrastructure Kubernetes Engine (OKE) 基础设施上的 Spring Boot 来构建基于 Oracle GraalVM 的 Java 应用,该基础设施将用作与 Oracle Autonomous Transaction Processing (ATP) 和 OCI Queue 等不同 Oracle Cloud Infrastructure (OCI) 服务的事务集成商。此设置允许系统在没有直接连接的情况下进行交互,从而促进不同处理速度的应用程序之间的通信。此外,一旦信息存储在数据库中,就可以对其进行查询或分析。
我们将使用以下技术:
Oracle Cloud Infrastructure 服务 (OCI): OCI 是一个安全、高性能的云平台,提供超过 150 种云服务。它专为实现可扩展性、安全性和性能而设计。
-
Oracle Cloud Infrastructure Kubernetes Engine (OKE): OKE 是一种托管式服务,用于在 OCI 上部署、管理和扩展容器化应用。它可以自动执行供应、扩展和监视等任务,同时确保强大的安全性和 OCI 集成。OKE 支持无状态和有状态的工作负载,为现代应用开发提供了灵活的平台。
-
经典 OCI 容器注册表:它是 OCI 中与 Docker 兼容的托管注册表,支持用户安全地存储、管理和部署容器映像。它与 OKE 和其他 OCI 服务无缝集成,支持高效的容器化应用部署。
-
Oracle Autonomous Transaction Processing (ATP): ATP 是一种云数据库服务,可自动执行预配、打补丁、扩展和备份等任务,从而确保高可用性和安全性。它利用机器学习实现自治操作和性能优化,为面向事务处理的工作负载提供强大的性能。借助应用开发等简单易用的生成式 AI、完整的数据保护和安全性功能,ATP 可确保稳定的高性能和最高可用性。
-
OCI 队列:它是完全托管的无服务器消息队列服务,支持在分布式应用之间进行异步、分离的通信。它通过自动缩放、消息持久性和公平处理等功能确保可靠的消息传送。OCI Queue 可与其他 OCI 服务集成,支持行业标准协议,非常适合事件驱动的可扩展架构。
Oracle 技术:
- Oracle GraalVM 它是支持多种编程语言(例如 Java、JavaScript、Python 等)的高性能多语言虚拟机 (VM)。它通过即时 (JIT) 和提前 (AOT) 编译来提高性能,从而加快启动速度并降低资源使用率。Oracle GraalVM 还提供了优化和分析工具,非常适合微服务和云原生应用。
其他技术:
- Spring Boot:它是一个 Java 框架,通过自动配置和嵌入式 Web 服务器简化创建独立的生产就绪应用的过程。它提供健康检查和指标等内置功能,非常适合快速构建微服务和可扩展应用。
OCI 高级别体系结构:
注:
- 需要明确的是,JMeter 将模拟客户应用程序生成的 SOAP 消息。
- 本教程旨在用于教育目的,为学生提供可控的环境,以探索和获得实践经验。请注意,演示的安全配置和实践专为学习而设计,可能不适用于生产环境或实际应用。
目标
-
使用 Spring Boot 框架构建基于 Oracle GraalVM 的 Java 应用,并预配将事务性消息存储在 ATP 中并将其发送到 OCI 队列所需的整个 OCI 服务。
-
预配和配置 OKE 集群。
-
预配、配置和访问 OCI Container Registry Classic。
-
预配和配置 ATP 无服务器数据库。
-
在 ATP 中连接和创建项目表。
-
预配和配置 OCI 队列。
-
使用 Spring Boot 构建基于 Oracle GraalVM 的 Java 应用,并在 OKE 中部署。
-
使用 JMeter 测试 Spring Boot Oracle GraalVM 应用程序。
-
先决条件
-
OCI 环境:本教程中提供了必要的云基础设施、服务和安全配置,以便高效地部署、管理和扩展应用。
-
访问 OCI 租户。要创建免费 Oracle Cloud 账户,请参阅创建免费 Oracle Cloud 账户。
-
创建可用于对 OCI 服务进行分组的区间。有关详细信息,请参阅创建区间。
-
-
管理主机:具有管理主机(在本教程中,我们使用了 Oracle Linux 8)非常重要,如高级体系结构中所示。管理主机必须配置有 OCI CLI、kubectl 和 Docker 才能监视、查看和控制 OKE 集群和 OCI Container Registry Classic。
-
预配管理主机。有关详细信息,请参阅创建实例。
-
安装 Oracle Cloud Infrastructure 命令行界面 (OCI CLI)。有关更多信息,请参见 Installing OCI CLI 。
-
在 Oracle Linux 8/7 上安装 Docker。有关更多信息,请参见 Installing Docker 。
-
在 Linux 上安装和设置 kubectl。有关更多信息,请参见 Installing kubectl 。
-
-
开发环境:我们需要一个开发环境来编写、测试和调试代码。安装 Apache Maven 和 Oracle GraalVM。
-
安装 Oracle GraalVM。有关详细信息,请参阅 Oracle GraalVM 入门。
-
下载并安装 Apache Maven。有关更多信息,请参见 Download Maven 和 Install Maven 。
-
开发 Spring Boot 应用程序。有关更多信息,请参见 Developing Your First Spring Boot Application 和 Spring Quickstart Guide 。
-
任务 1:预配和配置 OKE 集群
在此任务中,我们将预配 Kubernetes 平台,在其中应用程序将支持要存储在 ATP 中的所有 SOAP 高事务性消息,并实时将每个消息发送到 OCI 队列。
-
登录到 OCI 控制台,导航到 Developer Services(开发人员服务)、Kubernetes 集群 (OKE) ,然后选择首选的区间。
创建 OKE 群集有两种方法:
- 快速创建。
- 定制创建。
-
选择 Quick create(快速创建),因为此方法更简单、更快,并自动部署 OKE 所需的所有元素以执行其操作,例如:
- 虚拟云网络 (VCN)。
- Internet 网关。
- 网络地址转换 (Network Address Translation,NAT) 网关。
- 服务网关。
- Kubernetes 集群。
- Kubernetes worker 节点和节点池。
注:对于已经拥有服务、网络、基础设施的企业环境,必须自定义 OKE 部署,使其符合合规性,并与客户机体系结构、资源保持一致并遵循最佳做法。
-
单击创建集群并输入以下信息。
- 名称:输入 OKE 集群的名称。
- 区间:选择为此项目创建的区间。
- Kubernetes 版本:选择可用的最新 Kubernetes 版本。
- Kubernetes API 端点:在本教程中,选择公共端点,但也可以选择专用端点。
- 节点类型:选择托管节点。
- Kubernetes worker 节点:选择专用 worker 。
- 形状和图像:选择 VM.Standard.E5。灵活,定制 OCPU 数 (2) 和内存 (16GB) 并保留默认 Oracle Linux 8 映像。
- 节点计数:输入要与 OKE 节点池一起部署的 2 个 worker 节点。
查看 OKE 群集是否正常工作。
任务 2:对经典 OCI 容器注册表进行预配、配置和访问
我们需要在存储库中管理项目映像。为此,我们将预配经典 OCI 容器注册表。映像存储在 OCI 经典容器注册表中后,我们即可在 OKE 中部署这些映像。
-
转到 OCI 控制台,导航到 Developer Services(开发人员服务)、 Containers & Artifacts(容器和对象)、 Container Registry(容器注册表)并单击 Create Repository(创建资料档案库)。
-
输入以下信息,然后单击创建。
- 在区间中创建:选择为此项目创建的区间。
- 访问权限:选择公共。
- 资料档案库名称:输入
springboot/tutorialapp
。
-
创建系统信息库后,从 Oracle 管理主机使用以下命令访问该系统信息库。
docker login -u 'tenancy_namespace/domain/username' regionID.ocir.io
Password: xxxxxx
任务 3:预配和配置 Oracle Autonomous Transaction Processing (ATP) 无服务器数据库
在 ATP 数据库中,我们将存储每个事务处理接收的每条 SOAP 消息的数据,大约每条消息的数据将以毫秒为单位,以并行和顺序插入为单位。
-
转到 OCI 控制台,导航到 Oracle Database ,然后单击 Autonomous Transaction Processing(自治事务处理)。
-
单击创建 Autonomous Database 并输入以下信息。
- 选择区间:选择为此项目创建的区间。
- 显示名称:输入显示名称。
- 显示数据库名:输入数据库名。
- 选择工作量类型:选择事务处理。
- 选择部署类型:选择无服务器。
- 配置数据库:
- 开发人员:取消选择它。
- 选择数据库版本:选择 23ai 。
- ECPU 计数:输入 2 。
- 计算自动缩放:选择它。
- 存储:输入 1024GB 。
- 自动备份保留期(天):保留默认选项 60 天。
- 创建管理员身份证明:
- 用户名:默认情况下为
ADMIN
,无法编辑。 - 密码:输入您的首选密码。
- 确认密码:再次输入密码。
- 用户名:默认情况下为
- 选择网络访问:选择仅专用端点访问,然后选择为此项目创建的 VCN 和子网。此设置仅限制与指定专用网络 (VCN) 的连接。但是,您可以选择其他选项,这取决于公司的需求。
检查 ATP 数据库是否正在运行。
任务 4:在 Oracle Autonomous Transaction Processing (ATP) 中连接和创建项目表
现在,我们需要在任务 3 中生成的 ATP 数据库中配置、连接和创建项目表。
-
转到 OCI 控制台,导航到 Oracle Database 、 Autonomous Transaction Processing ,然后单击 Database Connection 。选择 TLS 验证、 TLS ,然后单击 Download Wallet(下载 Wallet)。
-
解压缩 wallet
.zip
文件,在tnsnames.ora
中,您可以获取数据源 URL 以获取到此数据库的连接。保存此数据源 URL。例如:
tutorialoracleatp_medium = (description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=xxxxxxxx.adb.sa-saopaulo-1.oraclecloud.com))(connect_data=(service_name=xxxxxxxxxxxxxxx_tutorialoracleatp_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))
-
现在需要访问数据库。预配 ATP 时,已启用 Oracle REST Data Services (ORDS) 访问。有关更多信息,请参见 Oracle REST Data Services 。
转到 Autonomous Database 详细信息页面,单击数据库操作。请注意,只能从在同一虚拟云网络 (VCN) 中运行的计算实例进行访问。
-
将 URL 粘贴到浏览器中,并使用之前在 ATP 数据库中输入的用户和密码访问 ORDS,然后访问 Oracle SQL Developer Web 模块。
-
使用以下查询创建与我们将接收的 SOAP 消息相关的表
USERS
、CARS
和HOUSES
。CREATE TABLE USERS ( username varchar(50) NOT NULL, userlastname varchar(50) NOT NULL, id int NOT NULL, email varchar(50) NOT NULL, dateuser varchar(50) NOT NULL, attributeuser varchar(50) NOT NULL ); CREATE TABLE CARS ( userid int NOT NULL, brand varchar(50) NOT NULL, color varchar(50) NOT NULL, plate varchar(50) NOT NULL ); CREATE TABLE HOUSES ( userid int NOT NULL, floors int NOT NULL, locationhouse varchar(50) NOT NULL, rooms int NOT NULL, bathrooms int NOT NULL );
任务 5:预配和配置 OCI 队列
在 OCI 队列中,我们将通过 RESTful HTTP API 在特定时间段内存储高度事务性消息。然后,消费者可以立即或方便地读取和删除消息,从而确保分离和防止数据丢失。
-
转到 OCI 控制台,导航到开发人员服务、应用程序集成,然后单击队列。
-
单击创建队列,输入以下信息,然后单击创建队列。
- 名称:输入适合队列的名称。
- 区间:选择您的工作区间。
- 队列设置:在本教程中,我们将选择默认配置,但如果您愿意,您可以根据业务需求自定义多个选项,例如:可见性超时、最大保留期、最大通道消耗量和死信队列设置。
- 配置加密设置:选择 Oracle 管理的密钥,但客户管理的密钥也是一个选项。
检查 OCI 队列是否正在运行。
任务 6:使用 Spring Boot 构建基于 Oracle GraalVM 的 Java 应用程序并在 OKE 中部署该应用程序
现在,我们将在 Spring Boot 上开发和部署基于 Oracle GraalVM 的 Java 应用,该应用将执行以下任务:
-
从每个 HTTP Post 请求中接收的 XML SOAP 消息中获取数据。
-
在 ATP 中插入从每个事务处理的 XML SOAP 消息中提取的数据。
-
从 SOAP XML 格式转换为 JSON 格式。
-
将转换的每个消息放入 OCI 队列。
注:在开始之前,务必创建管理主机和开发环境,如先决条件 - 管理主机和先决条件 - 开发环境部分所示。
配置管理员主机和开发环境并准备就绪后,即可开始开发 Spring Boot 项目。
-
转到 Spring initializr 并创建第一个项目,为 Spring Boot 项目提供文件夹结构和基本文件,以便稍后根据我们的要求对其进行修改。输入以下信息并单击生成,这将自动下载 Spring Boot 项目,将其保存并解压缩到开发主机中。
- 项目:选择 Maven 。
- 语言:选择 Java 。
- Spring boot:选择 3.3.6 。
- 项目元数据:
- 组:输入
com.tutorial_springboot
。 - 对象:输入教程。
- 名称:输入教程。
- 说明:输入 Spring Boot Application (read SOAP,transform to JSON,insert ATP and Put in OCI Queue) 。
- 组:输入
- 打包:选择 Jar 。
- Java:选择 17 。
- 依赖项:选择 Oracle 驱动程序、 Spring Web 服务和 Spring Web 。
注:我们可以根据需要,在项目中添加一些依赖项,然后直接在
pom.xml
文件中添加更多依赖项。 -
现在我们有弹簧启动结构项目。
查看
pom.xml
文件,我们将开始对其进行处理。根据本教程中建议的范围更新 pom.xml 文件。
-
添加
oci sdk
版本和以下依赖项。oci-java-sdk-common
.oci-java-sdk-queue
.oci-java-sdk-addons-oke-workload-identity
.oci-java-sdk-common-httpclient-jersey3
.
应用需要使用 OCI 进行身份验证,连接和管理 OCI 服务,例如 OCI Queue。
-
我们的
pom.xml
文件已经具有spring-boot-starter-web-services
相关项,我们必须添加wsdl4j
相关项。主要目的是获取从 SOAP 消息接收的数据并将其放入 Java 对象中,创建处理 XML 有效负载的 Spring Web 服务,旨在促进合同优先的 SOAP 服务开发。还允许配置从 XML 方案定义 (XSD) 文件加载的端口、URI 和设置 XML 方案。 -
添加 JSON 相关性。此库将用于生成包含从 SOAP 消息提取的数据的 JSON 格式。
-
在“构建”部分中添加
spring-boot-maven-plugin
插件。这个插件将允许我们生成 jar 可执行 Spring Boot 项目文件。 -
在“构建”部分中添加
jaxb2-maven-plugin
插件。此插件将使用 Java API 进行 XML 绑定 (JAXB),从 XML 方案生成 Java 类,通过这种方式,我们可以将数据从 SOAP 消息传递到由我们创建的 Java 类对象。在此插件部分中,务必将指示路径的配置放在 Spring Boot 项目中包含 XSD 文件的位置。
<configuration> <sources> <source>${project.basedir}/src/main/resources/messages.xsd<source> </sources> </configuration>
-
在 "Dependencies"(依赖关系)部分中添加
jasypt-spring-boot-starter
相关项,在 "Build"(构建)部分中添加jasypt-maven-plugin
插件,这将允许我们对application.properties
文件中的敏感参数进行加密,从而确保在我们的应用程序中安全使用。
查看在
pom.xml
文件中添加的以下依赖项。 -
-
下载库并运行以下命令。
-
在开发环境中,运行以下命令以访问项目。
cd tutorial
-
清除项目并删除以前构建生成的所有文件。
mvn clean
-
从本地 maven 资料档案库中清除(删除和(可选)重新解析)构件。
mvn dependency:purge-local-repository
-
-
我们已经在项目中配置了依赖项和
pom.xml
文件,我们将继续检查 SOAP XML 文件,因为它表示来自客户端的请求,以及 XSD 文件,它解释了 Spring Boot 项目端的请求。-
此 SOAP XML 文件包含两条消息,其中包含来自两个不同客户机的个人信息和其他类型的属性,我们将按请求发送这些信息,如下图所示。
-
现在,在我们的 Spring Boot 项目端,必须使用 XML 模式来定义 Spring Web 服务自动导出为 WSDL 的 Web 服务域,下图显示了为此教程定义的
messages.xsd
文件。messages.xsd:
-
将
messages.xsd
文件保存在 Spring Boot 项目的资源文件夹中。
-
-
在 jar 文件中构建和安装项目文件。运行以下命令并确保您位于 Spring Boot 项目文件夹中。
mvn install
注:执行 maven install 命令后,将自动生成目标文件夹,并以同样的方式根据之前创建的 XSD 文件和项目的可执行
.jar
文件生成 Java 类。 -
现在,我们可以将所需的 Java 类添加到 Spring Boot 项目中。
WebServiceConfig.java Class:
开发此 Java 类是为了创建 SOAP Web 服务:- 设置 servlet 以处理 SOAP 请求。
- 基于 XML 方案生成 WSDL 定义。
- 定义 SOAP Web 服务的访问端点。
- 使用类路径中的
messages.xsd
方案文件。
//Imports import org.springframework.boot.web.servlet.ServletRegistrationBean; //import the ServletRegistrationBean class import org.springframework.context.ApplicationContext; //import the ApplicationContext class import org.springframework.context.annotation.Bean; //import the Bean class import org.springframework.context.annotation.Configuration; //import the Configuration class import org.springframework.core.io.ClassPathResource; //import the ClassPathResource class import org.springframework.ws.config.annotation.EnableWs; //import the EnableWs class import org.springframework.ws.config.annotation.WsConfigurerAdapter; //import the WsConfigurerAdapter class import org.springframework.ws.transport.http.MessageDispatcherServlet; //import the MessageDispatcherServlet class import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; //import the DefaultWsdl11Definition class import org.springframework.xml.xsd.SimpleXsdSchema; //import the SimpleXsdSchema class import org.springframework.xml.xsd.XsdSchema; //import the XsdSchema class //Configuration class for the Web Service configuration @EnableWs //Enable the Web Service @Configuration //Define the class as a Configuration class public class WebServiceConfig extends WsConfigurerAdapter { //Create a ServletRegistrationBean object to register the MessageDispatcherServlet object with the application context @Bean public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) { MessageDispatcherServlet servlet = new MessageDispatcherServlet(); //Create a MessageDispatcherServlet object servlet.setApplicationContext(applicationContext); //Set the application context for the MessageDispatcherServlet object servlet.setTransformWsdlLocations(true); //Set the transformWsdlLocations property to true return new ServletRegistrationBean<>(servlet, "/ws/*"); //Return a new ServletRegistrationBean object with the MessageDispatcherServlet object and the URL pattern } //Create a DefaultWsdl11Definition object to define the WSDL @Bean(name = "messages") public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema messagesSchema) { DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition(); //Create a DefaultWsdl11Definition object wsdl11Definition.setPortTypeName("MessagesPort"); //Set the port type name wsdl11Definition.setLocationUri("/ws"); //Set the location URI wsdl11Definition.setTargetNamespace("http://tutorial_example.com/ns0"); //Set the target namespace wsdl11Definition.setSchema(messagesSchema); //Set the schema return wsdl11Definition; //Return the DefaultWsdl11Definition object } //Create a XsdSchema object to define the schema @Bean public XsdSchema messagesSchema() { return new SimpleXsdSchema(new ClassPathResource("messages.xsd")); //Return a new SimpleXsdSchema object with the messages.xsd file } }
注:如果要测试 Web 服务,可以在同一本地开发环境桌面中运行 Spring Boot 项目并使用
curl
发送 HTTP 请求,如下所示:mvn spring-boot:run
项目运行且 Web 服务启动后,使用
curl
运行本地 SOAP HTTP 请求,如下所示:curl --location 'http://localhost:8080/ws/'
您将从我们的 Spring Boot 项目中公开的 Web 服务获得响应。
-
创建一个名为
model
的文件夹,在此文件夹中,我们将添加以下 Java 类。注:这些 Java 类
Car
、House
和User
将根据从 HTTP SOAP 请求中提取的每个 SOAP 消息中的数据检索信息。-
Car.java class:
此 Java 类表示一个 Car 对象,其属性链接到每个用户。``` //Imports import org.springframework.stereotype.Component; // For component scanning import org.springframework.context.annotation.Scope; // For defining bean scope @Component // Marks a class as a Spring-managed component @Scope("prototype") //A new instance is created every time the bean is requested public class Car { //Attributes private String brand; private String color; private String plate; //"getter" and "setter" methods to get and set the information in each object public String getBrand(){ return brand; } public void setBrand(String brand){ this.brand = brand; } public String getColor(){ return color; } public void setColor(String color){ this.color = color; } public String getPlate(){ return plate; } public void setPlate(String plate){ this.plate = plate; } } ```
-
House.java class:
此 Java 类表示 House 对象,其属性链接到每个用户。//Imports import org.springframework.stereotype.Component; // For component scanning import org.springframework.context.annotation.Scope; // For defining bean scope @Component // Marks a class as a Spring-managed component @Scope("prototype") //A new instance is created every time the bean is requested public class House { //Attributes private int floors; private String location; private int rooms; private int bathrooms; //"getter" and "setter" methods to get and set the information in each object public int getFloors(){ return floors; } public void setFloors(int floors){ this.floors = floors; } public String getLocation(){ return location; } public void setLocation(String location){ this.location = location; } public int getRooms(){ return rooms; } public void setRooms(int rooms){ this.rooms = rooms; } public int getBathRooms(){ return bathrooms; } public void setBathRooms(int bathrooms){ this.bathrooms = bathrooms; } }
-
User.java class:
此 Java 类表示一个用户对象及其属性,其中包含 Car 和 House 对象。//Imports import org.springframework.stereotype.Component; // For component scanning import org.springframework.context.annotation.Scope; // For defining bean scope @Component // Marks a class as a Spring-managed component @Scope("prototype") //A new instance is created every time the bean is requested public class User { //Attributes private String username; private String userlastname; private int id; private String email; private String date; private String attribute; private Car car; private House house; //"getter" and "setter" methods to get and set the information in each object public String getUserName(){ return username; } public void setUserName(String username){ this.username = username; } public String getUserLastName(){ return userlastname; } public void setUserLastName(String userlastname){ this.userlastname = userlastname; } public int getID(){ return id; } public void setID(int id){ this.id = id; } public String getEmail(){ return email; } public void setEmail(String email){ this.email = email; } public String getDate(){ return date; } public void setDate(String date){ this.date = date; } public String getAttribute(){ return attribute; } public void setAttribute(String attribute){ this.attribute = attribute; } public Car getCar(){ return car; } public void setCar(Car car){ this.car = car; } public House getHouse(){ return house; } public void setHouse(House house){ this.house = house; } }
-
-
-
现在,我们将在 Spring Boot 项目中配置必要的参数,以将获取的数据存储在 OCI ATP 数据库中。在
resources
文件夹中,必须找到用于添加应用程序所需的参数的application.properties
文件。它会自动生成,并在应用程序启动时由 Spring Boot 加载。注意:管理加密和安全方法非常重要,以确保黑客无法提取或查看敏感信息(例如密码或相关数据)。在本教程中,我们使用在
pom.xml
文件中配置的jasypt
库。有关更多信息,请参见 How to encrypt passwords in a Spring Boot project using Jasypt 。此外,在 Spring Boot 上的 Java 类中,还记录了在何处添加与此库相关的注释和源代码以解密application.properties
参数。在
application.properties
文件中添加 ATP 数据库所需的相应参数,如下图中所示。创建 Spring Boot Java 类以将每条消息存储在数据库中,它将位于
database
文件夹中,如下图中所示。-
SoapObjectRepository.java:
此 Spring Boot Java 类允许使用 JDBC 驱动程序以实时事务处理形式插入 ATP 中的每条消息。//Java Classes USER, CAR, HOUSE Imports import com.oracle_springboot_tutorial.tutorial.model.*; //Spring Boot Imports import org.springframework.stereotype.Repository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.beans.factory.annotation.Autowired; //Repository Class to save SOAP Messages in the Database @Repository public class SoapObjectRepository { private JdbcTemplate template; private User user; //Getters and Setters for JdbcTemplate template object public JdbcTemplate getTemplate(){ return template; } //Autowired annotation to inject JdbcTemplate object into the template object @Autowired public void setTemplate(JdbcTemplate template){ this.template = template; } //Method to save User SOAP Message in the Database public void saveUserSOAPMessage(User user){ this.user = user; String sql = "INSERT INTO USERS (username, userlastname, id, email, dateuser, attributeuser) VALUES(?, ?, ?, ?, ?, ?)"; template.update(sql, user.getUserName(), user.getUserLastName(), user.getID(), user.getEmail(), user.getDate(), user.getAttribute()); } //Method to save Car SOAP Message in the Database public void saveCarSOAPMessage(Car car){ String sql = "INSERT INTO CARS (userid, brand, color, plate) VALUES(?, ?, ?, ?)"; template.update(sql, user.getID(), car.getBrand(), car.getColor(), car.getPlate()); } //Method to save House SOAP Message in the Database public void saveHouseSOAPMessage(House house){ String sql = "INSERT INTO HOUSES (userid, floors, locationhouse, rooms, bathrooms) VALUES(?, ?, ?, ?, ?)"; template.update(sql, user.getID(), house.getFloors(), house.getLocation(), house.getRooms(), house.getBathRooms()); } }
现在,添加 JSON 软件代码,首先创建
json_message
文件夹及其 Java Spring Boot 类,如下图所示。 -
JsonBuilder.java:
此 Spring Boot Java 类从 SOAP XML 格式转换为 JSON 格式。//Imports to be used for JSON import org.json.JSONArray; import org.json.JSONObject; //Imports to be used for the User class import com.oracle_springboot_tutorial.tutorial.model.*; //Imports to be used for the ArrayList class import java.util.ArrayList; public class JsonBuilder { //The buildJsonMessage method creates a JSON object from the ArrayList of User objects public JSONObject buildJsonMessage(ArrayList<User> usersMessageArray) { JSONObject rootJson = new JSONObject(); //Create a new JSON object called rootJson JSONObject messagesJson = new JSONObject(); //Create a new JSON object called messagesJson JSONArray messageArray = new JSONArray(); //Create a new JSON array called messageArray //Iterate through the ArrayList of User objects and create a JSON object for each User object in the ArrayList for (User user : usersMessageArray) { JSONObject messageJson = new JSONObject(); messageJson.put("username", user.getUserName()); //Add the username of the user to the messageJson object messageJson.put("userlastname", user.getUserLastName()); //Add the userlastname of the user to the messageJson object messageJson.put("id", user.getID()); //Add the id of the user to the messageJson object messageJson.put("email", user.getEmail()); //Add the email of the user to the messageJson object messageJson.put("date", user.getDate()); //Add the date of the user to the messageJson object messageJson.put("attribute", user.getAttribute()); //Add the attribute of the user to the messageJson object // JSONObject bodyJson = new JSONObject(); //Create a new JSON object called bodyJson JSONObject envelopeJson = new JSONObject(); //Create a new JSON object called envelopeJson //Switch statement to check the attribute of the User object switch (user.getAttribute()) { case "CAR": Car car = user.getCar(); envelopeJson.put("brand", car.getBrand()); //Add the brand of the car to the envelopeJson object envelopeJson.put("color", car.getColor()); //Add the color of the car to the envelopeJson object envelopeJson.put("plate", car.getPlate()); //Add the plate of the car to the envelopeJson object break; case "HOUSE": House house = user.getHouse(); envelopeJson.put("floors", house.getFloors()); //Add the floors of the house to the envelopeJson object envelopeJson.put("location", house.getLocation()); //Add the location of the house to the envelopeJson object envelopeJson.put("rooms", house.getRooms()); //Add the rooms of the house to the envelopeJson object envelopeJson.put("bathrooms", house.getBathRooms()); //Add the bathrooms of the house to the envelopeJson object break; default: System.out.println("Unknown subject: " + user.getAttribute()); } bodyJson.put("envelope", envelopeJson); //Add the envelopeJson object to the bodyJson object messageJson.put("body", bodyJson); //Add the bodyJson object to the messageJson object messageArray.put(messageJson); //Add the messageJson object to the messageArray array } messagesJson.put("message", messageArray); //Add the messageArray array to the messagesJson object rootJson.put("messages", messagesJson); //Add the messagesJson object to the rootJson object return rootJson; } }
-
-
现在,我们可以将 JSON 格式的消息发送到 OCI 队列。创建
oci_queue
文件夹及其 Java Spring Boot 类,如下图中所示。注:在
OCIQueue.java
类中,我们需要定义从 OKE 到 OCI 队列的访问权限。在本教程中,我们将使用负载访问授予对 OCI 资源的访问权限,而无需处理与租户关联的敏感信息,例如用户、密码、OCID。有关详细信息,请参阅授予负载对 OCI 资源的访问权限。在开始开发
OCIQueue.java
类之前,我们将在租户中配置工作负载访问。首先,我们需要创建一个名称空间来与基于 Oracle GraalVM 的 Java 应用相关联。确保您位于管理主机中。kubectl create ns-tutorial
然后,为应用程序创建 Kubernetes 服务账户。
kubectl create serviceaccount tutorialserviceaccount --namespace ns-tutorial
现在,定义 OCI IAM 策略以允许工作负载访问所需的 OCI 资源。在本教程中,OCI 队列。
转到 OCI 控制台,导航到身份与安全、策略,然后单击创建策略。输入以下信息,然后单击创建。
- 名称:输入首选策略名称。
- 说明:输入从 oke 到 oci 队列的访问权限。
-
策略构建器:
Allow any-user to use queues in compartment id ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx where all {request.principal.type = 'workload', request.principal.namespace = 'ns-tutorial', request.principal.service_account = 'tutorialserviceaccount', request.principal.cluster_id = 'ocid1.cluster.oc1.sa-saopaulo-1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
配置工作负载访问策略、名称空间和服务账户后,我们可以继续操作。
在
application.properties
文件中,添加连接和管理在任务 5 中创建的特定 OCI 队列所需的队列参数。-
OCIQueue.java:
此 Spring Boot Java 类允许访问消息并将其放在 OCI 队列中。//Imports import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; //Import OkeWorkloadIdentityAuthenticationDetailsProvider to enable access and use OCI Workload Identity import com.oracle.bmc.queue.QueueClient; //Import QueueClient to have access and manage of OCI Queue import com.oracle.bmc.queue.model.PutMessagesDetails; //Import PutMessagesDetails to send messages to the OCI Queue import com.oracle.bmc.queue.model.PutMessagesDetailsEntry; //Import PutMessagesDetailsEntry to send messages to the OCI Queue import com.oracle.bmc.queue.requests.PutMessagesRequest; //Import PutMessagesRequest to send messages to the OCI Queue //Imports for the ArrayList and List import java.util.ArrayList; import java.util.List; public class OCIQueue { //Set the required parameters to access to OCI and the Queue //Variables private String queueId; private String endPoint; private String region; //Constructor to initialize the OCI Queue object with the required parameters public OCIQueue(String queueId, String endPoint, String region){ this.queueId = queueId; this.endPoint = endPoint; this.region = region; } //The sendMessages method sends a message to the OCI Queue public void sendMessages(String jsonMessage){ try{ //Create an OkeWorkloadIdentityAuthenticationDetailsProvider object to authenticate the OCI Queue OkeWorkloadIdentityAuthenticationDetailsProvider provider = new OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder().build(); //Create a QueueClient object to send the message to the OCI Queue QueueClient queueClient = QueueClient.builder().build(provider); queueClient.setRegion(region); queueClient.setEndpoint(endPoint); //Create a PutMessagesDetailsEntry object to send the message PutMessagesDetailsEntry message = PutMessagesDetailsEntry.builder() .content(jsonMessage) .build(); //Create a List of PutMessagesDetailsEntry objects to send the message List<PutMessagesDetailsEntry> messages = new ArrayList<>(); messages.add(message); //Create a PutMessagesDetails object to send the message PutMessagesDetails putMessagesDetails = PutMessagesDetails.builder() .messages(messages) .build(); // Create a PutMessagesRequest object to send the message PutMessagesRequest putMessagesRequest = PutMessagesRequest.builder() .queueId(queueId) .putMessagesDetails(putMessagesDetails) .build(); // Send the request and get the response queueClient.putMessages(putMessagesRequest); }catch(Exception e){ System.out.println("Exception sending message to OCI Queue: "+e); } } }
-
为数据库、JSON 和 OCI 队列提供 Spring Boot Java 类后,我们可以继续执行
MessagesEndpoint.java
类。为此,我们将创建一个名为
endpoint
的文件夹及其 Spring Boot Java 类。注:在
MessagesEndpoint.java
中,我们需要导入一些自动生成的类。为此,请在pom.xml
文件的 "Configuration"(配置)部分中添加以下源:<configuration> <sources> <source>${project.build.directory}/generated-sources</source> </sources> </configuration>
pom.xml
文件应该类似。-
MessagesEndpoint.java:
此 Spring Boot Java 类的用途是提取 SOAP HTTP 请求并将其值映射到每条消息的 User、Car 和 House Java 对象。然后,它将提取的数据存储在每个 SOAP XML 事务处理的 ATP 数据库中,将数据从 XML 转换为 JSON 格式,并将消息放在 OCI 队列中。以后,使用者可以从队列中检索和删除这些消息。//Imports import com.oracle_springboot_tutorial.tutorial.model.*; //Import all the classes from the model package import com.oracle_springboot_tutorial.tutorial.oci_queue.OCIQueue; //Import the OCIQueue class from the oci_queue package import com.tutorial_example.ns0.Messages;//Import the Messages class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL) import com.tutorial_example.ns0.MessageType; //Import the MessageType class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL) //Import the ArrayList class from the java.util package import java.util.ArrayList; //Spring Boot imports to be used for the SOAP Web Service import org.springframework.beans.factory.annotation.Autowired; //Import the @Autowired annotation to inject the SoapObjectRepository object import org.springframework.beans.factory.annotation.Value; //Import the @Value annotation to inject the values from the application.properties file import org.springframework.stereotype.Component; //Import the @Component annotation to register the class with Spring //Spring Boot imports import org.springframework.ws.server.endpoint.annotation.Endpoint; //Import the @Endpoint annotation to register the class with Spring WS import org.springframework.ws.server.endpoint.annotation.PayloadRoot; //Import the @PayloadRoot annotation to specify the namespace URI and local part of the request payload import org.springframework.ws.server.endpoint.annotation.RequestPayload; //Import the @RequestPayload annotation to map the request payload to the method parameter import org.springframework.ws.server.endpoint.annotation.ResponsePayload; //Import the @ResponsePayload annotation to map the returned value to the response payload //Imports to be used storing SOAP information in the database import com.oracle_springboot_tutorial.tutorial.database.SoapObjectRepository; //Import the SoapObjectRepository class from the database package //Imports to be used for JSON import com.oracle_springboot_tutorial.tutorial.json_message.JsonBuilder; //Import the JsonBuilder class from the json_message package import org.json.JSONObject; //Import the JSONObject class from the org.json package //The @Endpoint annotation registers the class with Spring WS. //The @Component annotation registers the class with Spring to be used as a Spring Bean. @Endpoint @Component public class MessagesEndpoint { //Inject not encrypted and decrypted values using jasypt library from the application.properties file @Value("${oci.queue.queueId}") private String queueId; @Value("${oci.queue.endPoint}") private String endPoint; @Value("${oci.queue.region}") private String region; @Value("${spring.datasource.password}") private String datasourcePassword; //The @Autowired loads JDBC template in SoapObjectRepository. @Autowired private SoapObjectRepository soapObjectRepository = new SoapObjectRepository(); //Create a new instance of the JsonBuilder class JsonBuilder jsonBuilder = new JsonBuilder(); //The namespace URI private static final String NAMESPACE_URI = "http://tutorial_example.com/ns0"; //The handleMessagesRequest method is annotated with @PayloadRoot, which means that it is invoked when a request with the specified namespace URI and local part is received. @PayloadRoot(namespace = NAMESPACE_URI, localPart = "messages") //The @ResponsePayload annotation makes Spring WS map the returned value to the response payload. @ResponsePayload //The handleMessagesRequest method processes the request and sends the message to the OCI Queue. public void handleMessagesRequest(@RequestPayload Messages request) { OCIQueue ociQueue = new OCIQueue(queueId, endPoint, region); //Create an ArrayList to store the users ArrayList<User> usersMessageArray = new ArrayList<User>(); //Iterate over the messages, extracting the SOAP Messages and storing in the Java Objects (Car, House, User) for (MessageType message : request.getMessage()) { User user = new User(); user.setUserName(message.getUsername()); user.setUserLastName(message.getUserlastname()); user.setID(message.getId()); user.setEmail(message.getEmail()); user.setDate(message.getDate()); user.setAttribute(message.getAttribute()); //Insert User in Oracle ATP soapObjectRepository.saveUserSOAPMessage(user); //Process the attributes Car or House depending of the kind of User processMessage(user, message); //Add the user to the ArrayList usersMessageArray.add(user); } //Convert to JSON format JSONObject jsonObject = jsonBuilder.buildJsonMessage(usersMessageArray); //Send the JSON message to OCI Queue ociQueue.sendMessages(jsonObject.toString()); } //The processMessage method processes the message based on the user's attribute. private void processMessage(User user, MessageType message) { String subject = user.getAttribute(); switch (subject) { case "CAR": handleCAR(user, message); break; case "HOUSE": handleHouse(user, message); break; default: System.out.println("Unknown subject: " + subject); } } //The handleCAR method processes the CAR message. private void handleCAR(User user, MessageType message) { Car car = new Car(); car.setBrand(message.getBody().getEnvelope().getBrand()); car.setColor(message.getBody().getEnvelope().getColor()); car.setPlate(message.getBody().getEnvelope().getPlate()); user.setCar(car); //Insert Car in Oracle ATP soapObjectRepository.saveCarSOAPMessage(user.getCar()); } //The handleHouse method processes the HOUSE message. private void handleHouse(User user, MessageType message) { House house = new House(); house.setFloors(message.getBody().getEnvelope().getFloors()); house.setLocation(message.getBody().getEnvelope().getLocation()); house.setRooms(message.getBody().getEnvelope().getRooms()); house.setBathRooms(message.getBody().getEnvelope().getBathrooms()); user.setHouse(house); //Insert Houses in Oracle ATP soapObjectRepository.saveHouseSOAPMessage(user.getHouse()); } }
-
-
现在我们已经完成了 Spring Boot 项目的整个构建,我们将在 project 文件夹中创建
Dockerfile
。
-
Dockerfile
:FROM container-registry.oracle.com/graalvm/jdk:17 WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"]
注:Docker 文件的第一行会从 Oracle 容器注册表中提取 GraalVM JDK 映像并将其设置为基本映像。我们从一开始就提供高性能 JDK,利用即时 (JIT) 编译来优化执行。
-
运行以下命令以在本地 Docker 系统信息库中构建和推送项目映像。
docker build . -t springbootapp:latest
-
运行以下命令以验证本地 Docker 系统信息库中的映像。
docker images
-
我们可以使用 OCI Container Registry Classic 资料档案库的完整路径来标记 Spring Boot 应用程序映像。
docker tag springbootapp:latest gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
-
运行以下命令以在本地 Docker 系统信息库中进行验证。
docker images
-
运行以下命令将映像推送到经典 OCI 容器注册表。
docker push gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
-
要查看 OCI Container Registry Classic 中的 OKE 映像应用程序,请转到开发人员服务、容器和对象,然后单击容器注册表。
映像位于经典 OCI 容器注册表中后,我们可以转到开发环境并在 OKE 中部署此映像。对于本教程,请运行以下命令以创建所需的配置。
注:由于以前配置了名称空间和服务帐户,因此需要密钥。
-
运行以下命令以访问项目文件夹。
cd tutorial/
-
运行以下命令为 OKE 创建密钥。
kubectl create secret -n ns-tutorial generic ocir --from-file=.dockerconfigjson=../.docker/config.json --type=kubernetes.io/dockerconfigjson
-
-
我们已经准备好 OKE 环境,因此请将应用映像从经典 OCI 容器注册表部署到 OKE。
注:要部署应用程序映像,必须具有清单文件。在本教程中,以下
yaml
文件是清单文件,用于部署应用程序以及创建使用80
端口监听的 OCI 负载平衡器中表示的入站服务。-
springboot_application.yaml
:apiVersion: apps/v1 kind: Deployment metadata: name: soap-oci-queue-app namespace: ns-tutorial labels: app: soap-oci-queue-app spec: replicas: 6 selector: matchLabels: app: soap-oci-queue-app template: metadata: labels: app: soap-oci-queue-app spec: serviceAccountName: tutorialserviceaccount automountServiceAccountToken: true containers: - name: soap-oci-queue-app image: gru.ocir.io/xxxxxxxxxxxx/springboot/tutorialapp:latest ports: - containerPort: 8080 imagePullSecrets: - name: ocir-docker-config --- apiVersion: v1 kind: Service metadata: name: svc-dev-app namespace: ns-tutorial spec: selector: app: soap-oci-queue-app ports: - port: 80 targetPort: 8080 type: LoadBalancer
-
-
在保存清单文件的文件夹中运行
kubectl
命令。kubectl apply -f springboot_application.yaml
现在,已部署应用程序,并在 OKE 中创建入站负载平衡器服务。
-
要验证在 OKE 中创建的 pod 和服务,请运行以下命令。
kubectl get pods -A
kubectl get svc -A
注:从以下位置下载基于 Oracle GraalVM 的 Spring Boot Java 应用程序项目:tutorial.zip 。
任务 7:使用 JMeter 测试 Spring Boot Oracle Graal VM 应用程序
有关 JMeter 安装的更多信息,请参见 Getting Started with JMeter 。
安装和配置 JMeter 后,可以发送 HTTP POST SOAP 请求。例如,将线程数设置为 2 以表示 2 个同时用户或应用程序,并将循环计数设置为 3000,这意味着每个用户或应用程序将发送 3000 个请求,总共 6000 个 SOAP 请求。
在 JMeter 中,设置 OCI 负载平衡器 IP、在 Spring Boot 项目中配置的路径以及在正文中设置 SOAP XML。
对 6000 个 SOAP 事务运行 JMeter 并进行验证。
注:在模拟客户机应用程序的 SOAP 消息传送时,对于每个 SOAP HTTP 请求,信息与上面所示的 SOAP XML 文件中所示的信息相同,并且不会更改,但在实际客户环境中,信息肯定会有所不同。
运行以下语句:
-
查看 ATP 中存储的总数据。
-
查看 ATP 中每个表中存储的数据的详细信息。
-
CARS:
-
房屋:
-
USERS:
-
-
查看存储在 OCI 队列中的 OCI 队列请求总数。
-
以 JSON 格式查看 OCI 队列中的消息详细信息。
确认
- 作者 — Iván Alexander Vásquez Chinome(Oracle LAD A-Team 云解决方案专家)
更多学习资源
浏览 docs.oracle.com/learn 上的其他实验室,或者访问 Oracle Learning YouTube 渠道上的更多免费学习内容。此外,请访问 education.oracle.com/learning-explorer 成为 Oracle Learning Explorer。
有关产品文档,请访问 Oracle 帮助中心。
Create an Oracle GraalVM based Java Application using Spring Boot on OKE to Store SOAP Messages in ATP and Send to OCI Queue
G27170-01
February 2025
Copyright ©2025, Oracle and/or its affiliates.