注意:

在 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 技术:

其他技术:

OCI 高级别体系结构:

OCI 体系结构

使用案例体系结构

注:

目标

先决条件

任务 1:预配和配置 OKE 集群

在此任务中,我们将预配 Kubernetes 平台,在其中应用程序将支持要存储在 ATP 中的所有 SOAP 高事务性消息,并实时将每个消息发送到 OCI 队列。

  1. 登录到 OCI 控制台,导航到 Developer Services(开发人员服务)Kubernetes 集群 (OKE) ,然后选择首选的区间

    创建 OKE 群集有两种方法:

    • 快速创建
    • 定制创建
  2. 选择 Quick create(快速创建),因为此方法更简单、更快,并自动部署 OKE 所需的所有元素以执行其操作,例如:

    • 虚拟云网络 (VCN)。
    • Internet 网关。
    • 网络地址转换 (Network Address Translation,NAT) 网关。
    • 服务网关。
    • Kubernetes 集群。
    • Kubernetes worker 节点和节点池。

    注:对于已经拥有服务、网络、基础设施的企业环境,必须自定义 OKE 部署,使其符合合规性,并与客户机体系结构、资源保持一致并遵循最佳做法。

    快速创建

  3. 单击创建集群并输入以下信息。

    • 名称:输入 OKE 集群的名称。
    • 区间:选择为此项目创建的区间。
    • Kubernetes 版本:选择可用的最新 Kubernetes 版本。
    • Kubernetes API 端点:在本教程中,选择公共端点,但也可以选择专用端点
    • 节点类型:选择托管节点。
    • Kubernetes worker 节点:选择专用 worker
    • 形状和图像:选择 VM.Standard.E5。灵活,定制 OCPU 数 (2) 和内存 (16GB) 并保留默认 Oracle Linux 8 映像。
    • 节点计数:输入要与 OKE 节点池一起部署的 2 个 worker 节点。

    创建 OKE 集群

    查看 OKE 群集是否正常工作。

    OKE 正在运行

任务 2:对经典 OCI 容器注册表进行预配、配置和访问

我们需要在存储库中管理项目映像。为此,我们将预配经典 OCI 容器注册表。映像存储在 OCI 经典容器注册表中后,我们即可在 OKE 中部署这些映像。

  1. 转到 OCI 控制台,导航到 Developer Services(开发人员服务)Containers & Artifacts(容器和对象)Container Registry(容器注册表)并单击 Create Repository(创建资料档案库)

  2. 输入以下信息,然后单击创建

    • 在区间中创建:选择为此项目创建的区间。
    • 访问权限:选择公共
    • 资料档案库名称:输入 springboot/tutorialapp

    创建资料档案库

  3. 创建系统信息库后,从 Oracle 管理主机使用以下命令访问该系统信息库。

    docker login -u 'tenancy_namespace/domain/username' regionID.ocir.io
    
    Password: xxxxxx
    

    Oracle 注册表验证

    OK

任务 3:预配和配置 Oracle Autonomous Transaction Processing (ATP) 无服务器数据库

在 ATP 数据库中,我们将存储每个事务处理接收的每条 SOAP 消息的数据,大约每条消息的数据将以毫秒为单位,以并行和顺序插入为单位。

  1. 转到 OCI 控制台,导航到 Oracle Database ,然后单击 Autonomous Transaction Processing(自治事务处理)

  2. 单击创建 Autonomous Database 并输入以下信息。

    • 选择区间:选择为此项目创建的区间。
    • 显示名称:输入显示名称。
    • 显示数据库名:输入数据库名。
    • 选择工作量类型:选择事务处理
    • 选择部署类型:选择无服务器
    • 配置数据库:
      • 开发人员:取消选择它。
      • 选择数据库版本:选择 23ai
      • ECPU 计数:输入 2
      • 计算自动缩放:选择它。
      • 存储:输入 1024GB
    • 自动备份保留期(天):保留默认选项 60 天。
    • 创建管理员身份证明:
      • 用户名:默认情况下为 ADMIN,无法编辑。
      • 密码:输入您的首选密码。
      • 确认密码:再次输入密码。
    • 选择网络访问:选择仅专用端点访问,然后选择为此项目创建的 VCN 和子网。此设置仅限制与指定专用网络 (VCN) 的连接。但是,您可以选择其他选项,这取决于公司的需求。

    创建 Oracle ATP 数据库

    创建 Oracle ATP 数据库

    创建 Oracle ATP 数据库

    检查 ATP 数据库是否正在运行。

    ATP 正在运行

任务 4:在 Oracle Autonomous Transaction Processing (ATP) 中连接和创建项目表

现在,我们需要在任务 3 中生成的 ATP 数据库中配置、连接和创建项目表。

  1. 转到 OCI 控制台,导航到 Oracle DatabaseAutonomous Transaction Processing ,然后单击 Database Connection 。选择 TLS 验证TLS ,然后单击 Download Wallet(下载 Wallet)

    数据库连接

  2. 解压缩 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)))
    
  3. 现在需要访问数据库。预配 ATP 时,已启用 Oracle REST Data Services (ORDS) 访问。有关更多信息,请参见 Oracle REST Data Services

    转到 Autonomous Database 详细信息页面,单击数据库操作。请注意,只能从在同一虚拟云网络 (VCN) 中运行的计算实例进行访问。

    Database Actions

  4. 将 URL 粘贴到浏览器中,并使用之前在 ATP 数据库中输入的用户和密码访问 ORDS,然后访问 Oracle SQL Developer Web 模块。

    ORDS

  5. 使用以下查询创建与我们将接收的 SOAP 消息相关的表 USERSCARSHOUSES

    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 在特定时间段内存储高度事务性消息。然后,消费者可以立即或方便地读取和删除消息,从而确保分离和防止数据丢失。

  1. 转到 OCI 控制台,导航到开发人员服务应用程序集成,然后单击队列

  2. 单击创建队列,输入以下信息,然后单击创建队列

    • 名称:输入适合队列的名称。
    • 区间:选择您的工作区间。
    • 队列设置:在本教程中,我们将选择默认配置,但如果您愿意,您可以根据业务需求自定义多个选项,例如:可见性超时最大保留期最大通道消耗量死信队列设置。
    • 配置加密设置:选择 Oracle 管理的密钥,但客户管理的密钥也是一个选项。

    创建队列

    检查 OCI 队列是否正在运行。

    运行的 OCI 队列

任务 6:使用 Spring Boot 构建基于 Oracle GraalVM 的 Java 应用程序并在 OKE 中部署该应用程序

现在,我们将在 Spring Boot 上开发和部署基于 Oracle GraalVM 的 Java 应用,该应用将执行以下任务:

注:在开始之前,务必创建管理主机和开发环境,如先决条件 - 管理主机先决条件 - 开发环境部分所示。

配置管理员主机和开发环境并准备就绪后,即可开始开发 Spring Boot 项目。

  1. 转到 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 文件中添加更多依赖项。

    Springboot 项目

  2. 现在我们有弹簧启动结构项目。

    结构 Spring Boot 项目

    查看 pom.xml 文件,我们将开始对其进行处理。

    原始 POM 文件

    根据本教程中建议的范围更新 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 文件中添加的以下依赖项。

    已修改 POM 文件

    已修改 POM 文件

  3. 下载库并运行以下命令。

    1. 在开发环境中,运行以下命令以访问项目。

      cd tutorial
      

      Spring Boot 项目文件夹

    2. 清除项目并删除以前构建生成的所有文件。

      mvn clean
      

      Maven 清除

    3. 从本地 maven 资料档案库中清除(删除和(可选)重新解析)构件。

      mvn dependency:purge-local-repository
      

    Maven 清除相关性

  4. 我们已经在项目中配置了依赖项和 pom.xml 文件,我们将继续检查 SOAP XML 文件,因为它表示来自客户端的请求,以及 XSD 文件,它解释了 Spring Boot 项目端的请求。

    1. 此 SOAP XML 文件包含两条消息,其中包含来自两个不同客户机的个人信息和其他类型的属性,我们将按请求发送这些信息,如下图所示。

      Soap 文件

    2. 现在,在我们的 Spring Boot 项目端,必须使用 XML 模式来定义 Spring Web 服务自动导出为 WSDL 的 Web 服务域,下图显示了为此教程定义的 messages.xsd 文件。

      messages.xsd:

      XSD 文件

    3. messages.xsd 文件保存在 Spring Boot 项目的资源文件夹中。

      文件夹中的 XSD 文件

  5. 在 jar 文件中构建和安装项目文件。运行以下命令并确保您位于 Spring Boot 项目文件夹中。

    mvn install
    

    注:执行 maven install 命令后,将自动生成目标文件夹,并以同样的方式根据之前创建的 XSD 文件和项目的可执行 .jar 文件生成 Java 类。

    目标文件夹

  6. 现在,我们可以将所需的 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
    

    运行 Spring Boot 项目

    运行 Spring Boot 项目

    项目运行且 Web 服务启动后,使用 curl 运行本地 SOAP HTTP 请求,如下所示:

    curl --location 'http://localhost:8080/ws/'
    

    您将从我们的 Spring Boot 项目中公开的 Web 服务获得响应。

    SOAP Web 服务

  7. 创建一个名为 model 的文件夹,在此文件夹中,我们将添加以下 Java 类。

    模型文件夹

    注:这些 Java 类 CarHouseUser 将根据从 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 类表示一个用户对象及其属性,其中包含 CarHouse 对象。

        //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;
           }
        }
        
  8. 现在,我们将在 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 类,如下图所示。

      Json 文件夹

    • 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;
         }
      }
      
  9. 现在,我们可以将 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 队列所需的队列参数。

    OCIID 设置

    • 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);
            }
         }
      }
      
  10. 为数据库、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());
      
         }
      }
      
  11. 现在我们已经完成了 Spring Boot 项目的整个构建,我们将在 project 文件夹中创建 Dockerfile

  1. 运行以下命令以在本地 Docker 系统信息库中构建和推送项目映像。

    docker build . -t springbootapp:latest
    

    构建 Docker 映像

  2. 运行以下命令以验证本地 Docker 系统信息库中的映像。

    docker images
    

    Docker 映像本地资料档案库

  3. 我们可以使用 OCI Container Registry Classic 资料档案库的完整路径来标记 Spring Boot 应用程序映像。

    docker tag springbootapp:latest gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    
  4. 运行以下命令以在本地 Docker 系统信息库中进行验证。

    docker images
    

    标记 OKE 应用程序

  5. 运行以下命令将映像推送到经典 OCI 容器注册表。

    docker push gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    

    推送 OKE 应用程序

  6. 要查看 OCI Container Registry Classic 中的 OKE 映像应用程序,请转到开发人员服务容器和对象,然后单击容器注册表

    OKE Containe Registry 中的图像

    映像位于经典 OCI 容器注册表中后,我们可以转到开发环境并在 OKE 中部署此映像。对于本教程,请运行以下命令以创建所需的配置。

    注:由于以前配置了名称空间和服务帐户,因此需要密钥。

    1. 运行以下命令以访问项目文件夹。

      cd tutorial/
      
    2. 运行以下命令为 OKE 创建密钥。

      kubectl create secret -n ns-tutorial generic ocir --from-file=.dockerconfigjson=../.docker/config.json --type=kubernetes.io/dockerconfigjson
      

      创建密钥

  7. 我们已经准备好 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
      
  8. 在保存清单文件的文件夹中运行 kubectl 命令。

    kubectl apply -f springboot_application.yaml
    

    现在,已部署应用程序,并在 OKE 中创建入站负载平衡器服务。

    在 OKE 中应用清单

  9. 要验证在 OKE 中创建的 pod 和服务,请运行以下命令。

    kubectl get pods -A
    

    OKE PODS

    kubectl get svc -A
    

    OKE 服务

    注:从以下位置下载基于 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 请求。

SOAP 消息

在 JMeter 中,设置 OCI 负载平衡器 IP、在 Spring Boot 项目中配置的路径以及在正文中设置 SOAP XML。

HTTP SOAP 请求

对 6000 个 SOAP 事务运行 JMeter 并进行验证。

注:在模拟客户机应用程序的 SOAP 消息传送时,对于每个 SOAP HTTP 请求,信息与上面所示的 SOAP XML 文件中所示的信息相同,并且不会更改,但在实际客户环境中,信息肯定会有所不同。

运行以下语句:

  1. 查看 ATP 中存储的总数据。

    Oracle ATP

  2. 查看 ATP 中每个表中存储的数据的详细信息。

    • CARS:

      汽车

    • 房屋:

      房屋

    • USERS:

      USERS

  3. 查看存储在 OCI 队列中的 OCI 队列请求总数。

    OCI 队列请求总数

  4. 以 JSON 格式查看 OCI 队列中的消息详细信息。

    OCI 队列消息详细信息

确认

更多学习资源

浏览 docs.oracle.com/learn 上的其他实验室,或者访问 Oracle Learning YouTube 渠道上的更多免费学习内容。此外,请访问 education.oracle.com/learning-explorer 成为 Oracle Learning Explorer。

有关产品文档,请访问 Oracle 帮助中心