手记

Java 23,SpringBoot 3.3.4,Jakarta 10

一个专为应用开发设计的快速启动模板,特别适合基于微服务和云原生架构的方法。

Java 23,SpringBoot 3.3.4 版本,Jakarta 10 版本

Java 23 和 SpringBoot 334 模板的主要特点
  1. 代码的结构遵循了六边形架构(或称为Hexagonal架构),利用端口和适配器来保持关注点的清晰分离。
  2. 横切关注点,如异常处理日志记录安全性,采用面向切面编程(AOP)实现,并且
  3. 认证和授权使用 JSON Web Tokens (JWT),该令牌同时包含了认证信息和数据。
  4. 强大的Web 安全措施用于有效管理传入和传出的数据。
  5. 全面的日志管理措施,包括文件轮转机制、标准化的日志格式以及关键信息的日志记录。
  6. 支持多种Spring 配置文件:开发、staging和生产。开发环境使用 H2 数据库,staging和生产环境使用 PostgreSQL。
  7. Docker 容器化:构建 Docker 容器并附带全面的容器测试脚本和工具。
端口架构介绍

Netflix — 六角形架构:如何准备好变化

六边形架构,也称为 端口和适配器模式,是一种软件设计模式,旨在将应用程序的核心逻辑与其交互的不同外部系统之间进行清晰的分离。由Alistair Cockburn提出,它旨在创建灵活且易于维护的应用程序,可以轻松适应变化,而无需重大重写。这种架构风格尤其适用于 基于微服务的系统,在这样的系统中,服务需要保持模块化、易于扩展和松散耦合。

六角架构简介

Hexagonal架构由以下关键概念组成:

1. 领域/核心逻辑:
  • 六边形架构的核心是领域模型(即核心逻辑),即应用程序的业务逻辑。这部分纯粹专注于实现应用程序的核心功能,不依赖外部系统,如数据库或API。它定义了应用程序的行为和数据处理规则。
2. 端口:
  • 端口(Ports) 是定义与核心应用程序逻辑交互的输入和输出机制的接口。它们定义了在不依赖于任何特定技术或框架的情况下,核心应该如何被访问或扩展。端口定义了交互应该如何进行的契约。
  • 端口分为两种类型:驱动端口(driving ports)(核心的输入,例如来自用户的请求或来自外部系统的请求)和被驱动端口(driven ports)(核心的输出,例如对存储库的调用或与其他服务的交互)。
3. 适配器
  • 适配器 是具体的 实现,它们实现端口定义的契约。它们使外部组件(如数据库、消息队列或用户界面)能够与核心逻辑通信。适配器是连接外部系统与核心的具体实现方式。
  • 示例包括 REST API 控制器、仓储、Kafka 消费者等等。
4. 端点和接口的交互
  • 端点 提供了一组操作或交互,而 适配器 实现了这些操作。这种解耦意味着外部组件(如从一个数据库迁移到另一个数据库)的变化不会影响核心领域逻辑,从而确保应用程序的不同部分之间有一定的独立性。

微服务架构 中,各个服务可以独立部署、松耦合,并专注于单一领域。当应用到微服务架构中时,六边形架构(Hexagonal Architecture)提供了诸多好处,使其特别适合这种架构风格。

六边形架构的优势
  • 可测试性:通过将核心逻辑隔离来简化单元测试。
  • 灵活性:允许基础设施轻松替换或修改,对系统影响最小。
  • 关注点分离:确保领域逻辑与技术细节相分离。
  • 扩展性:通过模块化开发,促进单个微服务的扩展。
  • 并行开发:通过解耦组件来加快开发速度。
  • 技术独立性:允许灵活地选择和使用不同的工具和技术。

六边形架构(Hexagonal Architecture)通过强调清晰的边界、模块化以及与外部系统的独立性,为构建微服务提供了坚实的基础来构建微服务。这些优势与微服务的目标非常契合,使得每个服务能够独立开发、测试和演化,同时保持核心的业务逻辑简洁且专注。随着微服务变得越来越复杂,采用六边形架构可以使它们更加易于维护,更具韧性,并且在应对不断变化的需求和新技术时更加灵活。

依照端口和适配器架构的包层级结构

参考链接:GitHub 仓库链接

1. 适配器套装

适配器包负责承载直接与系统内外边界交互的核心实现。这其中包括所有的REST端点,用于公开微服务的API,允许外部客户端或其他服务通过这些端点与其通信。该包还包括仓库,用于管理数据访问逻辑的仓库,实现应用程序与底层数据存储之间的无缝连接。

此外,适配器的包内不仅包含了业务服务的实现,这些实现包含处理数据和实现微服务核心功能所需的业务逻辑。除了这些实现外,适配器的包还包含各种过滤器,用于根据服务需求修改请求和响应。它利用面向切面编程(AOP)来处理诸如异常处理、日志记录和安全等横切关注点,从而促进代码的清晰和可维护性。

2. 端口包

The Domain 包作为一个抽象层,定义了表示基本数据模型的接口、实体和记录结构。领域包内的接口描述了服务和存储库的契约,这些契约由适配器包中的适配器实现。这种分离确保了核心业务逻辑与特定实现细节保持独立,从而遵循了六边形架构的设计理念。

该包还包含数据模型中的实体,这些实体定义了微服务处理的数据结构,以及表示不可变数据结构的记录。此外,所有必要的异常定义也在此处找到,这些定义将在适配器的实现中使用。通过将这些异常放在端口包中,您可以确保所有实现定义的接口都以一致的方式来处理错误,从而提高服务的健壮性和标准化水平。

3. 服务器组件包

服务器包封装了所有与服务器相关的功能,确保微服务被正确配置并能够有效执行其职责。它包括数据库配置,定义了服务如何连接和管理与底层数据库的交互。它还提供数据缓存配置以提高性能,通过减少对频繁请求的数据从数据库中重复获取的需求。

服务器包也可能包含消息配置,比如用于Kafka的配置,这使得服务能够在更大的分布式架构中生成和消费消息。此外,它包含从应用程序属性映射过来的配置对象,以便集中管理和方便定制。该包包含处理令牌重新生成的代码,例如刷新过期的令牌;但是值得注意的是,这样的逻辑最好由专用的认证服务来处理,而不是由核心服务器处理,从而促进职责分离并增强安全性。

第4部分 安全包内容

安全包包含了保护微服务所需的所有逻辑。它包括对 JSON Web 令牌 (JWT) 的实现,用于通过验证凭据和控制访问来认证和授权请求的实现。该包还包括创建和验证数字签名的组件,以及其他加密操作,以确保数据完整性和微服务内外的安全通信。

虽然这个包是全面的,但并非所有微服务都需要提供的全部安全功能。例如,某些服务可能只需进行简单的令牌验证,而其他服务可能需要更复杂的加密操作支持。因此,该包设计为模块化,允许根据每个服务的具体安全需求选择性地使用其中的功能。

5. 实用工具包

zh: utils 包包含了应用程序和多个服务中经常需要用到的一般性实用函数。这些实用工具可能包括字符串处理工具、日期和时间转换工具或在代码库各处频繁使用的验证工具。通过集中这些实用工具,可以避免冗余,减少不一致性的可能性,并使代码更容易维护和更新。utils 包在确保共享逻辑可重用,并且任何更新都能轻松应用于各个部分方面发挥着至关重要的作用,从而使整个开发过程更加高效和一致化。

启动服务引导 — Spring 应用
    public class ServiceBootStrap {  

     // ... 仅展示Swagger文档相关代码 - Open API v3  

     /**  

* 启动微服务程序  

* API URL : http://localhost:9334/ms-vanilla/api/v1/swagger-ui/index.html  

* @param args  
      */  
     public static void main(String[] args) {  
      start(args);   // 启动服务  
     }  

     /**  

* 启动服务  

* @param args  
      */  
     public static void start(String[] args) {  
      log.info("启动微服务...");  
      try {  
       context = SpringApplication.run(ServiceBootStrap.class, args);  
       // 如果没有指定其他配置文件,则设置默认配置文件  
       ConfigurableEnvironment environment = context.getEnvironment();  
       if (environment.getActiveProfiles().length == 0) {  
        log.info("配置缺失,因此使用默认配置 "+ activeProfile +" 配置!");  
        environment.addActiveProfile(activeProfile);  
       }  
       log.info("启动微服务...启动完成!");  
      } catch (Exception e) {  
       e.printStackTrace();  
      }  
     }  
      // ... 只显示相关代码...   
    }

当服务开始运行时,它会展示配置中的关键信息:

  1. 版本信息:包括服务版本、Java运行环境版本和Spring Boot版本。
  2. 构建详情:显示构建编号和构建日期。
  3. 服务部署模式:指示当前部署模式,包括开发(Dev)、测试(Staging)或生产(Prod)。
Swagger UI — 开放API规范 v3

GitHub 项目 —ms-springboot-334-vanilla

开放API接口的配置 — 在服务启动时

以下代码定义了几种使用 @Bean 注解的方法,这些方法利用 Springdoc OpenAPI 库生成不同 API 组的 Swagger 文档。目的是将 API 文档划分为逻辑部分,例如核心服务、系统健康和配置端点。这种划分让开发人员和用户能够专注于查看特定部分的 API 文档。

1. allPublicApi()
  • 定义一个名为 serviceName-service(如 myapp-service)的API分组。
  • 该分组包括服务的所有公开API路径,使用模式(pathsToMatch)来匹配服务配置中的每个路径(serviceConfig.getServiceApiPath())。
  • 这有助于提供所有可用端点的全面概览。
2. 公共API (appPublicApi()):
  • 定义了一个名为 serviceName-service-core 的分组 API 接口 — 这是特定应用的 API 调用。
  • 该组包括所有核心服务 API 路径,但明确地排除特定路径,比如 /service/ 和 /config/。这些排除是通过 pathsToExclude 列表中的路径指定的。
  • 这有助于将服务的核心功能模块与其他配置和健康检查端点隔离。
3. 配置公共API (configPublicApi())
  • 定义了一个名为 serviceName-service-config 的分组 API,该组专注于配置相关的端点,通过匹配路径如 /config/**。
  • 该分组着重于配置相关的端点,通过匹配路径如 /config/**。
  • 注释表明此文档组引用了 HealthController,这意味着它包括了服务配置和健康检查相关的端点。
4. systemPublicApi():
  • 定义了一个名为 serviceName-service-health 的分组 API 接口。
  • 此组匹配与 系统健康服务级别监控 相关的路径,通常路径类似于 /service/*。
  • 它用于将 健康监测端点 与核心 API 功能区分开来。
5. 构建OpenAPI():
  • 定义服务的总体 OpenAPI 规格
  • 设置元数据,如 标题描述版本许可 ,以及到 外部文档 (例如源代码仓库)的链接。
  • 包含一个 SecurityScheme 组件,指定 Bearer Token 认证(JWT) 方案。这对需要认证和授权的服务至关重要,可以确保 OpenAPI 文档正确展示受保护的端点。
  • 使用 servers 字段提供服务器配置详情(通过 getServers() 方法提供服务器 URL 列表,以供配置)。
    public class ServiceBootStrap {  

     // ... 只显示与 Swagger 文档相关的代码 - 开放 API v3  

     /**  

* 开放 API v3 文档 - 全部  

* 参考: https://springdoc.org/faq.html  

* @return  
      */  
     @Bean  
     public GroupedOpenApi allPublicApi() {  
      return GroupedOpenApi.builder()  
        .group(serviceConfig.getServiceName()+"-service")  
        .pathsToMatch(serviceConfig.getServiceApiPath()+"/**")  
        .build();  
     }  

     /**  

* 开放 API v3 文档 - 微服务端  

* 参考: https://springdoc.org/faq.html  

* @return  
      */  
     @Bean  
     public GroupedOpenApi appPublicApi() {  
      return GroupedOpenApi.builder()  
        .group(serviceConfig.getServiceName()+"-service-core")  
        .pathsToMatch(serviceConfig.getServiceApiPath()+"/**")  
        .pathsToExclude(serviceConfig.getServiceApiPath()+"/service/**", serviceConfig.getServiceApiPath()+"/config/**")  
        .build();  
     }  

     /**  

* 开放 API v3 文档 - 核心模块  

* 参考: https://springdoc.org/faq.html  

* 请参阅 HealthController 中的资源映射更改  

*  

* @see HealthController  
      */  
     @Bean  
     public GroupedOpenApi configPublicApi() {  
      // System.out.println;  
      return GroupedOpenApi.builder()  
        .group(serviceConfig.getServiceName()+"-service-config")  
        .pathsToMatch(serviceConfig.getServiceApiPath()+"/config/**")  
        .build();  
     }  

     @Bean  
     public GroupedOpenApi systemPublicApi() {  
      return GroupedOpenApi.builder()  
        .group(serviceConfig.getServiceName()+"-service-health")  
        .pathsToMatch(serviceConfig.getServiceApiPath()+"/service/**")  
        .build();  
     }  

     /**  

* 开放 API v3 文档  

* 参考: https://springdoc.org/faq.html  

* @return  
      */  
     @Bean  
     public OpenAPI buildOpenAPI() {  
      return new OpenAPI()  
        .servers(getServers())  
        .info(new Info()  
          .title(serviceConfig.getServiceName()+" 服务")  
          .description(serviceConfig.getServiceDetails())  
          .version(serviceConfig.getServerVersion())  
          .license(new License().name("许可证: "+serviceConfig.getServiceLicense())  
            .url(serviceConfig.getServiceUrl()))  
        )  
        .externalDocs(new ExternalDocumentation()  
          .description(serviceConfig.getServiceName()+" 服务源代码")  
          .url(serviceConfig.getServiceApiRepository())  
        )  
        .components(new Components().addSecuritySchemes("bearer-key",  
          new SecurityScheme()  
            .type(SecurityScheme.Type.HTTP)  
            .scheme("bearer")  
            .bearerFormat("JWT"))  
        );  
     }  

     // ... 仅显示相关代码...   
    }

此代码利用了 Springdoc OpenAPI 来提供一个完整且用户友好的 Swagger UI,用于提供 API 文档,使与端点的交互更加容易,更好地理解微服务的工作原理,并安全地测试端点功能。

开放API接口 — REST端点说明

这是用Spring Boot编写的Java代码,定义了一个用于管理产品REST控制器。这个控制器利用OpenAPI(Swagger)注解生成API文档,这非常有助于理解和测试服务,以及集成服务。接下来我们从OpenAPI的角度来解析这些相关的注解和元素。

1. 类级别的标注 @RestController: //标记一个控制器类, 允许它使用REST风格的HTTP操作来处理请求
  • 这个注解表示此类是一个 RESTful控制器,它将处理HTTP请求并以RESTful风格作出响应。
@RequestMapping("${service.api.path}/product"):
  • 这个注解将所有请求映射到控制器的基础URL路径上,这个路径通过属性service.api.path动态设置(例如,/ms-vanilla/api/v1)。加上/product,这将基础路径设置为如/ms-vanilla/api/v1/product。
  • 使用动态属性可以通过配置文件更改基础路径而不必修改代码。
@RequestScope:
  • 此注解表明将为每个请求创建一个新的ProductControllerImpl实例。这有助于确保每个请求都是独立的,但这与Swagger文档没有直接关系。
@Tag(name = “产品API”,description = “搜索、创建、激活/取消激活、删除和更新产品”):
  • @Tag 注解是 OpenAPI 的一部分,在 Swagger UI 中,将 API 端点按通用名称和描述进行分组。
  • 在这种情形下,此控制器中的所有端点都将被归类在 “产品 API” 下,并附有如下功能描述:搜索、创建、激活/停用、删除和更新产品的功能。
2. 方法级别的注释

getProductStatus 方法负责通过其 UUID 检索产品的状态。此方法使用 OpenAPI 注解 来生成 Swagger 文档的相关信息。

@Operation(摘要 = “通过产品UUID,来获取产品”):
  • 此注释用于为 Swagger 文档中的端点提供一个 概述

  • 它帮助消费者更好地理解端点的功能。在这里,它提供了这个端点 根据其 UUID 获取产品 的信息。
@ApiResponse // 响应数据注解s:
  • 此注释描述了端点可能返回的各种回复,帮助用户理解可能的结果以及潜在的错误情况。

  • @ApiResponses 注解包含了多个 @ApiResponse 注解,以便指定不同的可能的 HTTP 回应。
@ApiResponse
  • responseCode = “200”,描述 = “获取产品的状态进行检查”,内容 = {@Content(mediaType = “application/json”)}):
  • 描述了具有HTTP状态码 200 的成功响应。
  • 响应的内容类型为 application/json,这意味着响应将以产品数据的JSON格式呈现。
  • responseCode = “400”,描述 = “提供的产品ID无效。”,内容 = @Content):
  • 描述了当提供的产品ID无效时的 400 错误请求 响应。
  • 这有助于清晰地记录错误场景,让API使用者知道应预期哪些错误及其处理方法。
/状态/{productId}: 获取指定产品ID的状态
  • 将 HTTP GET 请求映射到 /status/{productId} 路径中。这意味着可以通过类似 /ms-vanilla/api/v1/product/status/{productId} 的 URL 来访问该端点。

  • URL 的这一部分是动态的 ({productId}),即需要在请求 URL 中提供一个具体的产品 ID。
@ResponseBody: 该注解用于标记返回响应体的方法。
  • 表示该方法的返回值将直接写入响应体中,并以 JSON格式 直接返回(由 Spring Boot 的 @RestController 行为启用)。
    @RestController  
    // "/ms-vanilla/api/v1"  
    @RequestMapping("${service.api.path}/product")  
    @RequestScope  
    @Tag(name = "产品API", description = "搜索、创建、激活/停用、删除和更新产品")  
    public class ProductControllerImpl extends AbstractController {  

      // ... 只显示相关代码...  

     /**  

* 通过产品UUID来检查产品状态  

*   

* @return  
      */  
        @Operation(summary = "通过产品UUID获取产品状态")  
        @ApiResponses(value = {  
                @ApiResponse(responseCode = "200",  
                description = "成功获取产品状态",  
                content = {@Content(mediaType = "application/json")}),  
                @ApiResponse(responseCode = "400",  
                description = "无效的产品ID。",  
                content = @Content)  
        })  
     @GetMapping("/status/{productId}")  
     @ResponseBody  
     public ResponseEntity<StandardResponse> getProductStatus(@PathVariable("productId") UUID _productId,  
                  HttpServletRequest request,  
                  HttpServletResponse response) throws Exception {  
      log.debug("|"+name()+"|请求获取产品状态的请求和产品ID.. "+_productId);  
      ProductEntity product = productServiceImpl.getProductById(_productId);  
      StandardResponse stdResponse = createSuccessResponse("数据获取成功!");  
      stdResponse.setPayload(product);  
      return ResponseEntity.ok(stdResponse);  
     }  
      // ... 只显示相关代码...   
    }
  • ProductControllerImpl 类定义了用于管理产品的 RESTful 端点,重点是使用 Swagger 注解来生成 OpenAPI 文档。

  • 类级别的注解 @Tag 用于将所有产品相关的端点分组,而方法级别的注解 @Operation 和 @ApiResponses 提供了每个端点的功能及其可能响应的详细信息。
Swagger — 开放API规范
    <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.6.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-common -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-common</artifactId>
        <version>1.8.0</version>
    </dependency>

生成的Swagger文档让开发人员和用户可以轻松地理解、互动并将其API集成到他们的应用程序中。它有助于提高产品API的透明度、易用性,并简化测试过程。

在下一节(第二部分(Part 2)),我们将看看与异常处理、日志记录、安全(特别是使用JWT)和过滤器相关的更多代码示例。这些实现旨在作为可以在任何微服务中使用的标准组件。

GitHub 仓库: ms-springboot-334-vanilla
一个使用 SpringBoot 3.3.4、Java 23 和 Jakarta EE 10 构建的微服务,包括 AOP、异常处理、日志记录、加密和 JWT 安全功能,等等。

享受一周,带着一杯爪哇咖啡……

Java 23,Spring Boot 3.3.4 和 Jakarta EE 10 系列
  1. Java 23, SpringBoot 3.3.4 & Jakarta 10 — 第1部分 (本文)
  2. Java 23, SpringBoot 3.3.4: AOP 异常处理机制 — 第2部分
  3. Java 23, SpringBoot 3.3.4: Logback 配置项 — 第3部分
  4. Java 23, SpringBoot 3.3.4: AOP 日志记录 — 第4部分 即将推出
  5. Java 23, SpringBoot 3.3.4: AOP 安全 — 第5部分 即将推出
  6. Java 23, SpringBoot 3.3.4: CRUD (创建、读取、更新、删除) — 第6部分 即将推出
更多的研究
  1. 六角形架构 — NetFlix 技术博客文章, 2020
  2. 六角形架构 — 维基百科
  3. Amazon — 六角形架构
  4. Spring: Spring 中的切面编程 (AOP)
  5. Naveen Metta — Spring 中的切面编程 (AOP) 深入解析
  6. Geek for Geeks — Spring 框架中的切面编程 (AOP)
  7. JWT — JSON Web Tokens
  8. Descope — JWT 是什么,它是如何工作的?
  9. Security Flag — 破损的 JWT
  10. Swagger IO — 开放 API 规范

👋 如果你觉得这有帮助,请点击下面的👏赞几下以示支持,以表达对作者的支持👇 🚀加入我们的FAUN开发者社区,每周收到邮箱里的类似故事
0人推荐
随时随地看视频
慕课网APP