今天繼續聊上手Quarkus框架這件事情,
Quarkus的Dependency Injection是透過Contexts and Dependency框架來實作。依賴注入容器主要用來管理物件相依性,能自動實體化類別,並透過容器來判斷物件生命週期。注入模式分別有
物件創建時通過建構子來提供該物件所需的依賴。當 ServiceA
被創建時,ServiceB
與ServiceC
通過建構子被注入。
@ApplicationScoped
public class ServiceA {
private final ServiceB serviceB;
private final ServiceC serviceC;
@Inject
public ServiceA(ServiceB serviceB, ServiceC serviceC) {
this.serviceB = serviceB;
this.serviceC = serviceC;
}
// ... methods using serviceB and serviceC
}
@ApplicationScoped
public class ServiceB {
// ... implementation
}
@ApplicationScoped
public class ServiceC {
// ... implementation
}
使用 @Inject
直接在類別屬性上進行注入,屬性注入通常較少使用,因為它允許相依關係在物件建立後被變更(可以加final,不過通常還是建議使用建構子注入)。
import javax.inject.Inject;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ServiceA {
@Inject
private ServiceB serviceB;
public String serve() {
return serviceB.doSomething();
}
}
較為少見的注入方式,常用於需要在方法層級進行依賴注入的場景。
import javax.inject.Inject;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ServiceA {
private ServiceB serviceB;
// Method Injection
@Inject
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
public String serve() {
return serviceB.doSomething();
}
}
假設有兩個類型相同的實現,但它們的行為不同,我們可以為每個 Bean 使用不同的 @Qualifier
來區分它們。
@Qualifier
註解,使用 @Qualifier
和 @Retention(RetentionPolicy.RUNTIME)
,確保這個註解在運行時存在,並可以用來標記不同的 Bean。import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomQualifier {
String value();
}
@Retention
指定註解的生命週期,即註解應該在什麼時候被保留,從編譯時期到執行時期。
.class
檔案中,但 JVM 在執行時不會加載它。@Qualifier
來區分它們:import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
@MyCustomQualifier("beanA")
public class MyBeanA implements MyService {
@Override
public void execute() {
System.out.println("Executing Bean A");
}
}
@ApplicationScoped
@MyCustomQualifier("beanB")
public class MyBeanB implements MyService {
@Override
public void execute() {
System.out.println("Executing Bean B");
}
}
此範例為 MyBeanA
和 MyBeanB
分別定義了 @MyCustomQualifier("beanA")
和 @MyCustomQualifier("beanB")
來區分這兩個不同的實現。
@Qualifier
注入特定的 Bean 最後,可以在需要的地方,透過指定 @Qualifier
來注入特定的 Bean:import javax.inject.Inject;
public class MyServiceClient {
@Inject
@MyCustomQualifier("beanA")
MyService service;
public void performTask() {
service.execute(); // 這裡會調用 MyBeanA 的 execute 方法
}
}
MyServiceClient
類別將會注入 MyBeanA
,因為使用了 @MyCustomQualifier("beanA")
。
依賴注入的生命週期是透過 CDI 的範疇 (Scopes) 來管理的。簡單來說,範疇決定了物件被注入後,可以存活多久,也就是這個物件會在什麼時候被建立、何時會被銷毀。
最常見的範疇,Bean 的實例在應用的整個生命週期內持續存在,且在應用範圍內是單例的。
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ApplicationService {
public String serve() {
return "Application scoped service";
}
}
Bean 的生命週期與 HTTP 請求同步,請求結束時,Bean 也會被銷毀。
import javax.enterprise.context.RequestScoped;
@RequestScoped
public class RequestService {
public String processRequest() {
return "Processing request";
}
}
Bean 的生命週期與 HTTP Session 相同,在 session 結束時銷毀。
import javax.enterprise.context.SessionScoped;
import java.io.Serializable;
@SessionScoped
public class SessionService implements Serializable {
public String getSessionId() {
return "Session scoped service";
}
}
Quarkus 中最短的範疇,每次注入或每次被呼叫時,Bean 都會被創建。
import javax.enterprise.context.Dependent;
@Dependent
public class DependentService {
public String getDependentMessage() {
return "I am a new instance every time!";
}
}
因為我擅長Net Core,這邊順手整理一下與Net Core的DI方法對應
Quarkus 範疇 | .NET Core 生命週期 | 說明 |
---|---|---|
@ApplicationScoped |
Singleton | 在應用程式生命週期內只建立一次的單例,適合共享狀態或配置管理。 |
@RequestScoped |
Scoped | 針對每個 HTTP 請求建立一次,請求結束時銷毀,適用於處理每個請求都需要不同狀態的情境。 |
@SessionScoped |
Session | 針對每個使用者的 Session 建立一次,並在 Session 結束時銷毀,通常用於需要保持使用者狀態的服務(如認證)。 |
@Dependent |
Transient | 每次注入或每次使用時,Bean 都會重新建立,適合不需要保存狀態的短期物件。 |
根據不同環境注入對應的設定變數。以下是在 Quarkus 中實現環境設定管理的具體方法:
首先,為每個環境建立一個設定檔。通常,會有以下檔案:
src/main/resources/application.properties (預設設定)
src/main/resources/application-sit.properties (SIT 環境)
src/main/resources/application-uat.properties (UAT 環境)
src/main/resources/application-prod.properties (正式環境)
在每個環境特定的設定檔中,加入該環境所需的設定。例如,在 application-sit.properties
中:
quarkus.datasource.jdbc.url=jdbc:postgresql://sit-db-server/mydb
quarkus.http.port=8080
custom.api.url=http://sit-api.example.com
有幾種方式可以啟用特定環境的設定:
a. 使用系統屬性:
java -Dquarkus.profile=sit -jar quarkus-app.jar
b. 使用環境變數:
export QUARKUS_PROFILE=sit
java -jar quarkus-app.jar
c. 在 application.properties
中設定預設 profile:
quarkus.profile=sit
使用 @ConfigProperty
註解來注入設定值:
import org.eclipse.microprofile.config.inject.ConfigProperty;
@Path("/hello")
public class GreetingResource {
@ConfigProperty(name = "custom.api.url")
String apiUrl;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from " + apiUrl;
}
}
Quarkus 遵循一定的優先順序來解析設定:
application-sit.properties
)application.properties
)可以透過系統屬性或環境變數覆寫任何設定值,無需更改設定檔。
對於更複雜的設定,可以使用設定群組:
@ConfigMapping(prefix = "myapp.service")
public interface ServiceConfig {
String url();
int port();
}
@Inject
ServiceConfig serviceConfig;
進行資料物件與 JSON 等不同格式的轉換,可以使用 JSON-B (JSON Binding) 或 Jackson 這兩個常用工具,以下說明如何在 Quarkus 中實作物件與格式的相互轉換:
Quarkus 預設支援 JSON-B,可以簡單地將 Java 物件序列化(轉換成 JSON)或反序列化(從 JSON 轉換為物件)。以下是範例:
步驟 1:新增相依套件
./mvnw quarkus:add-extension -Dextensions="jsonb"
步驟 2:建立 POJO(Plain Old Java Object)
public class User {
public String name;
public int age;
// 可選擇性地加入建構子、getter 和 setter
}
步驟 3:序列化與反序列化
Java 物件與 JSON 的轉換
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
@Path("/user")
public class UserResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String getUser() {
User user = new User();
user.name = "John";
user.age = 30;
Jsonb jsonb = JsonbBuilder.create();
return jsonb.toJson(user); // 將物件序列化為 JSON
}
}
需要將 JSON 轉換為 Java 物件(反序列化),可以這樣做
String json = "{\"name\":\"John\",\"age\":30}";
User user = jsonb.fromJson(json, User.class); // 將 JSON 反序列化為物件
Quarkus 也支援 Jackson
步驟 1:新增相依套件
./mvnw quarkus:add-extension -Dextensions="jackson"
步驟 2:使用 Jackson 註解
在POJO 類別加上 Jackson 的註解
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
@JsonProperty("name")
public String name;
@JsonProperty("age")
public int age;
// 建構子、getter 和 setter
}
步驟 3:使用 Jackson 進行序列化與反序列化
使用 Jackson 的 ObjectMapper
來進行轉換
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.fasterxml.jackson.databind.ObjectMapper;
@Path("/user")
public class UserResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String getUser() throws Exception {
User user = new User();
user.name = "John";
user.age = 30;
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(user); // 將物件序列化為 JSON
}
}
將 JSON 反序列化為 Java 物件,可以這樣做:
String json = "{\"name\":\"John\",\"age\":30}";
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class); // 將 JSON 反序列化為物件
輕鬆自動將一個物件的屬性值映射到另一個物件上
步驟 1:新增相依套件
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version> <!-- 可根據需要選擇最新版本 -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
步驟 2:定義映射介面
定義一個介面,使用 MapStruct 的 @Mapper
註解,並描述要進行映射的來源和目標物件:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 定義物件轉換方法
UserDTO toUserDTO(User user);
User toUser(UserDTO userDTO);
}
步驟 3:建立 POJO 類別
定義你需要映射的資料物件:
public class User {
public String name;
public int age;
// 建構子、getter 和 setter
}
public class UserDTO {
public String name;
public int age;
// 建構子、getter 和 setter
}
步驟 4:使用映射器
在你的業務邏輯中使用 MapStruct 自動生成的映射器進行物件轉換:
// User 物件轉換成 UserDTO
User user = new User();
user.setName("John");
user.setAge(30);
UserDTO userDTO = UserMapper.INSTANCE.toUserDTO(user);