spring

標籤:spring

升級 Spring Boot WebClient SSL (Reactor Netty 1.2.6):重新配置 SSL 設定

潮流特區
MacauYeah ・2025-08-27

因為SSL provider 更新了的關係,好多 HttpClient / WebClient 設定SSL的部份都要重寫以免出現 deprecated 問題 reactor.netty.http.client.HttpClient 在 1.0.x, 中可以這樣自行設定SSL逾時的部份,但當中的spec.sslContext().defaultConfiguration 在新版本,例如1.1.x後就會出現 deprecated。 // deprecated version HttpClient.create() .secure(spec -> spec.sslContext(SslContextBuilder.forClient()) .defaultConfiguration(SslProvider.DefaultConfigurationType.TCP) .handshakeTimeout(Duration.ofSeconds(30)) .closeNotifyFlushTimeout(Duration.ofSeconds(10)) .closeNotifyReadTimeout(Duration.ofSeconds(10))); 觀看各大網站,都未有更新,唯有自行研究官方說明。 筆者撰寫本文的時候,netty 發行版本為 1.2.6, 1.3.0 還里程碑(M6)的階段。所有參考皆來自1.2.6版本,實際上我們要使用新的後綴為ContextSpec類,看Class名應該有分http 1.1, 2, 3的版本,筆者就試用最基本的http 1.1。Http11SslContextSpec, (有條件的朋友可以試用Http2SslContextSpec, Http3SslContextSpec) import reactor.netty.http.Http11SslContextSpec; import reactor.netty.http.client.HttpClient; import java.time.Duration; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.http.client.reactive.ReactorClientHttpConnector; //... Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient(); HttpClient httpClient = HttpClient.create() .secure(spec -> spec.sslContext(http11SslContextSpec) .handshakeTimeout(Duration.ofSeconds(30)) .closeNotifyFlushTimeout(Duration.ofSeconds(10)) .closeNotifyReadTimeout(Duration.ofSeconds(10))); WebClient webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)) .build(); //... 雖然這個寫法來看netty 1.2.6,但似乎1.1.x 通用。大家有需要可以交互測試一下。 Reference netty 1.2.6 http-client-timeout 的設定 netty 1.1.30 timeout-configuration 的設定 netty 1.2.6 java api doc netty release version 更多筆者的程式開發分享,見請 github

Spring Boot Web App 更新期間的維護模式:從唯讀到全鎖的解決方案

潮流特區
MacauYeah ・2025-08-25

在營運 Web App 的時候,雖然我們有 Docker / K8s 可以滾動更新,但難保用戶在更新的過程中,有一半訪問去到了舊版,另一半去了新版。如果可以,Web App 本身自帶維護模式,可以自我判斷什麼時候應該忽略新的訪問,當然最好。但要做到這一點,前期需要很多規劃。狠心一點,可以直接關掉對外的服務,讓用戶無法訪問。 但在另一些情況下,例如升級/搬遷的情況,下線時間比較長,完全關掉服務並不是一個很好的方向,我們至少還可以提供唯讀的選擇。而且這個可以從資料庫出發,讓 Web App 少處理一點邏輯。 如果 Web App 背後的資料庫是 MSSQL 或 MySQL,唯讀這件事應該是簡單的,只要你把 service account 的權限改變就好。但如果你用Oracle,就要想想辦法。 筆者想到的方法,暫時有兩個。第一個就需要大家寫寫 Script ,一口氣把所有 Table 給鎖起來。例如: 第二個,就是生成一個新的唯讀 User schema,給他所有Select的權限。然後更新 Web App 使用那個唯讀 User schema存取資料。 兩個方法有什麼差異呢? 前者就全部鎖起來,沒有任何一個資料庫用戶可以改寫資料。如果你的業務沒有差異性,全部一起封起來就完事。但如果你只想 Web App 轉成唯讀,但其他背景程式還可以執行更新。那你就只能用後者了。但後著也不是百分百的完全無痛,至少你 Web App 要支援登入與操作的 Schema分離。 例如用Spring boot JPA的話,可以在 application.properties 可以讓登入及操作的Schema不一樣。 spring.datasource.username=READ_ONLY_USER spring.jpa.properties.hibernate.default_schema=ORIGINAL_SCHEMA 又或者在 java 層面指定。 @Table(schema = "ORIGINAL_SCHEMA") 這看上去,是很有彈性的。但其實也是有些局限。如果你本來的JPA有寫特制的 JPQL 或 Raw Query,又或者你在Java層面加了 @Subselect,由於這些都是程式原作者所 hard code 的,JPA沒法幫你改寫。改來改去,可能還是前述寫Script的方法,一口氣把所有 Table 給鎖起來實際一些。 Reference 更多筆者的程式開發分享,見請 github

Spring boot 10 - openapi 生成器 - spring boot java client

潮流特區
MacauYeah ・2025-08-19

之前我們在介紹Spring Boot Web 調試工具 ,就試安裝 openapi 相關的元件。其實 openapi 並不單是為了提供 swagger 測試介面,它主要是提供一個描述的方式,讓我們針對一個特定 openapi 文件,生成對應的 api server 或 api client 接口。也就是,如果 server 方有提供該文件,道理上可以經 openapi 的工具,生成一個可以直接訪問 server 的 client library。本節,可以沿用之前的 spring boot web api doc ,為它產生一個client library 作為實驗。 在生成 client library 之前,我們還需要一個工具 openapi-generator-cli 。最簡單的取得方式,就是經過 npm , 在你需要生成 client library 的專案中,安裝你需要的 openapi-generator-cli 版本。 npm install @openapitools/openapi-generator-cli 那怕你不是使用 nodejs 作為開發,也可以經過這個方法安裝。它只提供使用 cmd 指令的捷徑。 生成 Java Client Library 我們先把 backend server 起好 cd somewhere && mvn spring-boot:run,然後使用 openapi-generator-cli 去生成以 java spring boot 3 為底的 client library 。 npx openapi-generator-cli generate \ -i http://localhost:8080/v3/api-docs \ --api-package io.github.macauyeah.springboot.tutorial.openapiclient.api \ --model-package io.github.macauyeah.springboot.tutorial.openapiclient.model \ --invoker-package io.github.macauyeah.springboot.tutorial.openapiclient.invoker \ --group-id io.github.macauyeah.springboot.tutorial \ --artifact-id spring-boot-web-api-open-api-client \ --artifact-version 0.0.1-SNAPSHOT \ -g java \ -p useJakartaEe=true \ -p useSpringBoot3=true \ --library webclient \ -o spring-boot-web-api-open-api-client 生成的 source code 就像是 spring-boot-web-api-open-api-client ,具體的使用方式,可以看看測試用例 ApiControllerApiTest.java private final ApiControllerApi api = new ApiControllerApi(); @Test public void postDateQueryTest() { // default call ApiDateRequest apiDateRequest = new ApiDateRequest(); apiDateRequest.setInputDate(OffsetDateTime.now()); LOG.debug("default web client postDateQuery:{}", api.postDateQuery(apiDateRequest).block()); // replace webClient in ApiClient if you have special auth config on // webClient, you can also change basePath during new obj creation ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat()); mapper.registerModule(new JavaTimeModule()); WebClient webClient = WebClient.builder() .codecs(configurer -> { configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper)); configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper)); }) .build(); ApiControllerApi api2 = new ApiControllerApi( new ApiClient(webClient) .setBasePath("http://localhost:8080/")); LOG.debug("create api2 by local web client postDateQuery:{}", api2.postDateQuery(apiDateRequest).block()); // use webClient directly String response = webClient.post().uri("http://localhost:8080/api/record").bodyValue(apiDateRequest).retrieve() .bodyToMono(String.class).block(); LOG.debug("request by local web client postDateQuery:{}", response); } 上述例子中,如果大家沒有任何特殊要求,其實經過 api.postDateQuery(apiDateRequest).block() 就完成了。有需要改 api endpoint 的,只要生成新的 ApiClient 並設定 basePath new ApiClient().setBasePath("XXXXXX") 就好。真的要加入更多權限設定,就需要生成新的 ApiClient 並設定 webClient new ApiClient(webClient) 這個生成的 Java Client Library 道理上還是要經過 maven 等打包,變成 jar 檔,才能被其他 Java 專案所引用。筆者就建議大家直接把成生的視為獨立的 module (sub module) 存放,其他專案就以 maven dependency 的方式引用。想要混合現有專案,動態生成專案內某些 java package,暫時不太可行。因為它也有大量的 dependency ,交由 openapi-generator-cli 自己管理會比較好,它們升級時,你也可以完整升級。 openapi-generator-cli https://github.com/OpenAPITools/openapi-generator-cli spring-boot-web-api-open-api-client