Spring Boot 构建 Modulith 指南

本文将教你如何使用 Spring Boot 构建 Modulith,并使用 Spring Modulith 项目的特性。Modulith 是一种软件架构模式,假设将你的单体应用程序组织成逻辑模块。这些模块应尽可能独立于彼此。Modulith 平衡了单体架构和基于微服务的架构。它可以成为组织应用程序的目标模型。但你也可以将其视为从单体架构向基于微服务的方法迁移的过渡阶段。Spring Modulith 将帮助我们构建结构良好的 Spring Boot 应用程序,并验证逻辑模块之间的依赖关系。 我们将比较当前的方法和基于微服务的架构。为此,我们将实现与我最近一篇关于 使用 Spring Cloud 和 Spring Boot 3 构建微服务 的文章中所描述的非常相似的功能。 源码 你可以克隆我的 GitHub Repository,然后按照说明操作即可。 在开始之前,我们先来看看下图。它展示了我们示例系统的架构。我们有三个独立的模块,它们相互通信:employee(员工)、department(部门)和 organization(组织)。此外还有 gateway(网关)模块。它负责将内部服务作为 REST 端点暴露在应用之外。我们的模块使用 Spring Modulith 项目提供的支持向 Zipkin 实例发送追踪。 如果你想将其与先前提到的文章中描述的类似微服务架构进行比较,这是该架构的图。 让我们来看看代码的结构。默认情况下,main 的每个直接子包都被视为一个应用模块包。因此有四个应用模块:department、employee、gateway 和 organization。每个模块都包含向其他模块开放的 “provided interfaces”(提供的接口)。我们需要将它们放在应用模块根目录下。其他模块不能访问应用模块子包中的任何类或 bean。我们将在接下来的章节中详细介绍。 src/main/java └── pl └── piomin └── services ├── OrganizationAddEvent.java ├── OrganizationRemoveEvent.java ├── SpringModulith.java ├── department │ ├── DepartmentDTO.java │ ├── DepartmentExternalAPI.java │ ├── DepartmentInternalAPI.

@Scope 注解失效的问题

scope 属性,相信大家都知道,一共有六种: 取值 含义 生效条件 singleton 表示这个 Bean 是单例的,在 Spring 容器中,只会存在一个实例。 prototype 多例模式,每次从 Spring 容器中获取 Bean 的时候,才会创建 Bean 的实例出来。 request 当有一个新的请求到达的时候,会创建一个 Bean 的实例处理。 web 环境下生效 session 当有一个新的会话的时候,会创建一个 Bean 的实例出来。 web 环境下生效 application 这个表示在项目的整个生命周期中,只有一个 Bean。 web 环境下生效 gloablsession 有点类似于 application,但是这个是在 portlet 环境下使用的。 web 环境下生效 这个用法也很简单,通过配置就可以设置一个 Bean 是否为单例模式。 1、问题呈现 今天我要说的不是基础用法,是另外一个问题,假设我现在有如下两个 Bean: @Service public class UserService { @Autowired UserDao userDao; } @Repository public class UserDao { } 在 UserService 中注入 UserDao,由于两者都没有声明 scope,所以默认都是单例的。 现在,如果我给 UserDao 设置 Scope,如下:

在 JPA 投影查询中使用 Record

Java 16 中引入的 Java Record 允许轻松地定义数据类(Data Class),这非常适合用于 JPA 中的投影查询。 Record 不能作为实体类 Record 只能用于投影查询。像 Hibernate 等流行的 JPA 实现创建代理对象时需要无参构造函数、非 final 字段、setter 方法和非 final 的实体类。而这些特性在 Record 中要么被不鼓励使用,要么被明确禁止使用。 Record 和 JPA 如果你在应用中直接使用 JPA,有几种不同的方法可以将记 Record 整合到 DAO 层中。 CriteriaBuilder Record 可与 CriteriaBuilder 一起使用,如下: public List<AdvocateRecord> findAllWithCriteriaBuilder() { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<AdvocateRecord> cq = cb.createQuery(AdvocateRecord.class); Root<AdvocateEntity> root = cq.from(AdvocateEntity.class); cq.select(cb.construct( AdvocateRecord.class, root.get("id"), root.get("fName"), root.get("lName"), root.get("region"), root.get("twitterFollowers"))); TypedQuery<AdvocateRecord> q = em.createQuery(cq); return q.getResultList(); } TypedQuery Record 也可以与 TypedQuery 一起使用,但需要在 JPQL 查询中提供完整类路径的构造函数。

Spring Security OAuth 2 教程 - 10:使用“客户端凭证模式”进行服务间的通信

在本文中,我们将学习如何使用 “客户端凭证模式”(Client Credentials Flow)实现服务间的通信。我们将创建 archival-service,在其中通过定时任务使用 “客户端凭证模式” 来调用 messages-service API 以归档消息。 我们还会在 archival-service 中实现 POST /api/messages/archive API 端点,只有拥有 ROLE_ADMIN 角色的用户才能调用。 有鉴于此,archival-service 既是资源服务器(Resource Server),也是客户端。 资源服务器 - 暴露 POST /api/messages/archive API 端点,该端点将由 messages-webapp 调用。 客户端 - 调用 messages-service API 来归档消息。 你可以从 Github 仓库 获取到当前项目的完整源码。 在 Keycloak 中启用客户端凭证模式,创建 archival-service 客户端 创建一个名为 archival-service 的新客户端: General Settings: Client type:OpenID Connect Client ID:archival-service Capability config: Client authentication:On Authorization:Off Authentication flow:选中 Service accounts roles,取消选中其余复选框 Login settings: Root URL: http://localhost:8282 Home URL: http://localhost:8282 使用上述配置创建客户端后,你将进入新创建的客户端 “Settings” 页面。

Spring Security OAuth 2 教程 - 9:客户端调用资源服务器 API

在前面的文章中,我们创建了 messages-webapp 和 messages-service,并使用 Postman 调用了 API 端点。在本文中,我们将学习如何从客户端应用 messages-webapp 调用受保护的 messages-service API 端点。 你可以从 Github 仓库 获取到完整的源码。 展示消息列表 由于 messages-service 中的 GET /api/messages API 端点是可公开访问的,因此我们可以从 messages-webapp 调用它,而无需任何身份认证。 RestTemplate 和 RestClient 我们使用传统的 RestTemplate 来调用 messages-service 中的 API 端点。但在 Spring Boot 3.2.0 后,建议改用 RestClient。 在 messages-webapp 中,创建 AppConfig 类,如下: package com.sivalabs.messages.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class AppConfig { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } } 我们注册了一个 RestTemplate Bean,以便将其注入到其他组件中。

Spring Security OAuth 2 教程 - 8:资源服务器

在 上一篇文章 中,我们创建了 messages-webapp,并使用 “授权码模式” 通过 Spring Security OAuth 2.0 对其进行了访问控制。在本文中,我们将创建 messages-service(Spring Boot 资源服务器),并使用 Spring Security OAuth 2.0 进行访问控制。 你可以在 Github 仓库 找到该项目完整的源码。 创建 messages-service 点击此 链接 可使用 Spring Initializr 生成 messages-service。我们选择了 Web、Validation、Security 和 OAuth2 Resource Server Starter。应用生成后,在 IDE 打开它。 配置 OAuth 2.0 资源服务器属性 messages-service 是 bearer-only 类型的资源服务器。这意味着如果有人使用有效的 access_token 作为 Authorization 头发送请求到受保护的 API 端点,该服务将返回响应。否则,它将只会返回 401 或 403 的 HTTP 状态码,而不会启动 OAuth 2.0 的授权流程。 bearer-only 类型的资源服务器无需向授权服务器(Keycloak)注册。我们只需在 application.properties 文件中配置 issuer-uri 如下: spring.application.name=messages-service server.

Spring Security OAuth 2 教程 - 7:Spring MVC 客户端应用

在本文中,我们将创建一个名为 messages-webapp 的 Spring MVC + Thymeleaf Web 应用,并使用 Keycloak 进行访问控制,使用 Spring Security OAuth 2.0 进行认证。 你可以在 Github 上找到该项目的完整源码。 使用 Docker Compose 安装 Keycloak 在上一篇文章中,我们已经了解了如何使用 Docker Compose 安装 Keycloak。 创建 docker-compose.yml 文件,内容如下: version: '3.8' name: spring-security-oauth2-microservices-demo services: keycloak: image: quay.io/keycloak/keycloak:22.0.3 command: ['start-dev'] container_name: keycloak hostname: keycloak environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin1234 ports: - "9191:8080" 运行以下命令启动 Keycloak 实例: $ docker compose up -d 现在,你可以访问 Keycloak 管理控制台 http://localhost:9191/,并使用 admin/admin1234 登录。 创建 Keycloak Realm、客户端和用户 在前面的文章中,我们已经学习了如何创建 Realm、客户端和用户。请按照 前文 中提到的步骤创建新的 Realm、客户端和用户,只需更改 “Valid redirect URIs”。

Spring Security OAuth 2 教程 - 6:微服务项目设置

在之前的文章中,我们学习了使用 Web 浏览器、cURL 和 Postman 进行各种 OAuth 2.0 / OpenID Connect Flow。现在是时候学以致用了。 在使用像 Spring Security 这样的安全框架实现基于 OAuth 2.0 / OpenID Connect 的 Security 时,许多活动都是由框架在幕后执行的。了解框架内部发生的情况非常重要,这样我们才能有效地使用框架。 在本文中,我将简要介绍我们将在 Spring Security OAuth 2 教程系列中使用的基于微服务的示例项目。 示例 Spring Boot 微服务项目 我们将构建一个包含以下组件的微服务示例项目: Authorization Server - 这是 OAuth 2.0 授权服务器,将向客户端发放访问令牌。我们将使用 Keycloak 作为授权服务器。 messages-webapp - 终端用户将使用的基于 Spring MVC 的客户端应用。 messages-service - Spring Boot REST API(资源服务器)用于管理用户数据(消息),并将由 messages-webapp 应用使用。 archival-service - Spring Boot 应用可使用定时任务定期处理消息存档。它还提供了一个 API 端点来触发存档过程。该服务将同时扮演资源服务器和客户端的角色。 让我们来了解一下这些组件各自提供的功能。 messages-webapp(客户端) 这是一个使用 Spring MVC 和 Thymeleaf 渲染用户界面的 Spring Boot Web 应用。该应用将受到授权服务器(Keycloak)的保护,并将使用 “授权码模式” 来认证用户身份并获取访问令牌。

Spring Security OAuth 2 教程 - 5:隐式模式 & 资源所有者密码模式

在 “Spring Security OAuth 2 教程 - 4:PKCE 授权码模式” 中,我们学习了如何通过 PKCE 授权码模式获取访问令牌(access_token)。在本文中,我们将了解如何使用隐式模式(Implicit Flow)和资源所有者密码凭证模式(Resource Owner Password Credentials Flow)。 注意 隐式模式和资源所有者密码凭证模式已被淘汰,没有特别理由不应该再使用。 隐式模式 隐式模式(Implicit Flow)是授权码模式(Authorization Code Flow)的一种简化版本,你可以直接通过授权端点(authorization_endpoint)获取访问令牌(access_token)。 如果你一直关注本系列,就会知道如何创建一个启用了特定 “Authentication flow” 的客户端。这里,我们直接为 messages-webapp 客户端启用隐式模式。 要了解如何创建新客户端,请参阅 创建客户端。 在浏览器窗口中打开以下 URL: http://localhost:9191/realms/sivalabs/protocol/openid-connect/auth? response_type=id_token%20token &client_id=messages-webapp &redirect_uri=http://localhost:8080/callback &scope=openid%20profile &state=randomstring &nonce=another_randomstring 然后,你将被重定向到 Keycloak 的登录页面。 使用凭证 siva/siva1234 登录。 然后,你将被重定向到包含 access_token 和 id_token 查询参数的重定向 URI。 http://localhost:8080/callback# state=randomstring &session_state=51692fdb-8b72-45b7-a341-fa73e97b5139 &id_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeVVPTDg4LVBGM3BYQzFpN3BIeGdFZTJwaWZJY3RyTXJiNklHOElmRTlVIn0.eyJleHAiOjE2OTU2MTUzOTQsImlhdCI6MTY5NTYxNDQ5NCwiYXV0aF90aW1lIjoxNjk1NjE0NDk0LCJqdGkiOiI2MjFmNTJmMC0wMDBmLTQ1ZmUtYWYzOC1iY2YzZWM2ZDk1MTEiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkxOTEvcmVhbG1zL3NpdmFsYWJzIiwiYXVkIjoibWVzc2FnZXMtd2ViYXBwIiwic3ViIjoiY2ExYTJmMzQtMTYxNC00NWRkLTg2YzEtNWVhZmZmMDg1ZDhhIiwidHlwIjoiSUQiLCJhenAiOiJtZXNzYWdlcy13ZWJhcHAiLCJub25jZSI6ImFub3RoZXJfcmFuZG9tc3RyaW5nIiwic2Vzc2lvbl9zdGF0ZSI6IjUxNjkyZmRiLThiNzItNDViNy1hMzQxLWZhNzNlOTdiNTEzOSIsImF0X2hhc2giOiJiamR6MC1NeWltQ0xrSzdqaWRRbHp3IiwiYWNyIjoiMSIsInNfaGFzaCI6IlJtVE5Ld0lYaTNXRFhzRFlObTQtUHciLCJzaWQiOiI1MTY5MmZkYi04YjcyLTQ1YjctYTM0MS1mYTczZTk3YjUxMzkiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6IlNpdmEgS2F0YW1yZWRkeSIsInByZWZlcnJlZF91c2VybmFtZSI6InNpdmEiLCJnaXZlbl9uYW1lIjoiU2l2YSIsImZhbWlseV9uYW1lIjoiS2F0YW1yZWRkeSIsImVtYWlsIjoic2l2YUBnbWFpbC5jb20ifQ.TIcmVBti96HuZvrYe_14mVJlfopXI2PhdMdWBtPPASpJc-DKrL9argy08sYZKqJTTcmWwnIwKK2o1vddVxA4zUP2tnqqg6ymz1trN3J8r4h-WSvIp907vnS0R7iHei56L6MQX2DZLJ8pOdSmti8wg_9fu4gQJBE2sHRTlrlOP39dh8yohMGidM-Z5iFbLCIzOQXA6B6ewMZll5iwL3ssJ716Ve9cO4qHGCneRGpb3mO7jclY87YSGM-wqr6ur00ylQ_BCGyCdl-f-xskSeDX09iQKFSTX_acMxB7FNi21BL7dMx8_22XPFOwNkX8ha8Vb7eTRMYEMyB776i33FLu0A &access_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeVVPTDg4LVBGM3BYQzFpN3BIeGdFZTJwaWZJY3RyTXJiNklHOElmRTlVIn0.eyJleHAiOjE2OTU2MTUzOTQsImlhdCI6MTY5NTYxNDQ5NCwiYXV0aF90aW1lIjoxNjk1NjE0NDk0LCJqdGkiOiI1N2NiYmRkMC0wNGFmLTRlMDctYWZlNC02ZmQ5MmY0YjA2MzAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkxOTEvcmVhbG1zL3NpdmFsYWJzIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImNhMWEyZjM0LTE2MTQtNDVkZC04NmMxLTVlYWZmZjA4NWQ4YSIsInR5cCI6IkJlYXJlciIsImF6cCI6Im1lc3NhZ2VzLXdlYmFwcCIsIm5vbmNlIjoiYW5vdGhlcl9yYW5kb21zdHJpbmciLCJzZXNzaW9uX3N0YXRlIjoiNTE2OTJmZGItOGI3Mi00NWI3LWEzNDEtZmE3M2U5N2I1MTM5IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtc2l2YWxhYnMiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInNpZCI6IjUxNjkyZmRiLThiNzItNDViNy1hMzQxLWZhNzNlOTdiNTEzOSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiU2l2YSBLYXRhbXJlZGR5IiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2l2YSIsImdpdmVuX25hbWUiOiJTaXZhIiwiZmFtaWx5X25hbWUiOiJLYXRhbXJlZGR5IiwiZW1haWwiOiJzaXZhQGdtYWlsLmNvbSJ9.XsYc69HnM9VaJZFF568nRiZhh8RYEw6Hq2WGnJ4jr3tmZvgMF0QK2RtlBT9BuX4A11XHjyNZqGYNf55x0k4bPXjhPzWI-lC0shhsKrXGYrnhVcComXxMbO_38ypRY_EMeBWRTXu0bvcKInYMjVoItfoLheH-kbcziK6O16yFGftOG-YYw0uVzs_DrOkjQjs1BS2L56yXcRgN72EBXMT-Cv6OLMTSj6WXjfg1nmRl0NRJdeZv0iafSolmqSpJeqXwPzM2hgZ2hPzaq90qipndQrZ05xesMtzXMNlev0ozYPN7xKSa7arHMYky8y4OMpCQDJzkSwekjUEQUSU9Sqg_VA &token_type=Bearer &expires_in=900 如你所见,access_token 和 id_token 是通过浏览器 URL 前端返回的,这并不安全。 资源所有者密码凭证模式 在 “资源所有者密码凭证模式” 中,我们将使用资源所有者(终端用户)凭证获取 access_token 和 id_token。

Spring Security OAuth 2 教程 - 4:PKCE 授权码模式

在 “Spring Security OAuth 2 教程 - 3:客户端凭证模式” 中,我们学习了如何通过客户端凭证模式获取访问令牌(access_token)。在本文中,我们将了解如何使用 PKCE 授权码模式。 PKCE 授权码模式 PKCE 授权码模式是 OpenId Connect Flow,主要用于保护本地、移动应用和单页应用 (SPA) 的安全。PKCE 是代码交换证明密钥(Proof Key for Code Exchange)的首字母缩写。 注意 PKCE 授权码模式也可用于保护服务器上运行的 Web 应用的安全。在这种情况下,PKCE 起到了额外保护层的作用。 创建 “public” 客户端 创建一个名为 messages-spa 的新客户端。 General Settings: Client type:OpenID Connect Client ID:messages-spa Capability config: Client authentication:Off Authorization:Off Authentication flow:选中 Standard flow,取消选中其余复选框 Login settings: Root URL:http://localhost:3000 Home URL:http://localhost:3000 Valid redirect URIs:http://localhost:3000/callback Valid post logout redirect URIs:http://localhost:3000 Web origins:http://localhost:3000 使用上述配置创建客户端后,你将进入新创建的客户端 “Settings” 页面。单击 “Advanced” 选项卡,转到 “Advanced Settings” 部分,将 “Proof Key for Code Exchange Code Challenge Method” 值更新为 S256。