1. Khỏi Tạo Bean với @Autowired, @Primary, @Qualifier
1.1 @Autowired và Vấn Đề Của Nó
Ở bài trước mình đã nói về sự hạn chế của @Autowired trong việc khởi tạo và quản lý bean, để làm rõ hơn vấn đề này mình xin đưa ra ví dụ sau:
– Tạo interface Animal
public interface Animal { void shouting(); }
– Tạo class Dog implements Animal
@Component public class Dog implements Animal { @Override public void shouting() { System.out.println("Con chó sửa gâu gâu !"); } }
– Tạo class Cat implements Animal
@Component public class Cat implements Animal { @Override public void shouting() { System.out.println("Con mèo kêu meo meo !"); } }
– Inject bean Animal vào class TayJavaApplication
@SpringBootApplication public class TayJavaApplication implements CommandLineRunner { @Autowired private Animal animal; public static void main(String[] args) { SpringApplication.run(TayJavaApplication.class, args); } @Override public void run(String... args) throws Exception { animal.shouting(); } }
– Lỗi của ứng dụng:
*************************** APPLICATION FAILED TO START *************************** Description: Field animal in vn.tayjava.TayJavaApplication required a single bean, but 2 were found: - dog: defined in file [/Users/tayluong/Workspace/sample/tayjava/target/classes/vn/tayjava/model/Dog.class] - cat: defined in file [/Users/tayluong/Workspace/sample/tayjava/target/classes/vn/tayjava/model/Cat.class] This may be due to missing parameter name information Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
– Giải thích nguyên nhân: Ờ ví dụ trên mình đã tạo ra 1 interface Animal nhưng mình lại thực thi ở 2 lớp là Dog và Cat. Mọi việc bình yên cho tới khi inject bean animal vào class TayJavaApplication. Như bạn có thể thấy ứng dụng in ra log như phía trên: Field animal in vn.tayjava.TayJavaApplication required a single bean, but 2 were found
. Dịch ra câu này có nghĩa là field animal yêu cầu chỉ 1 bean duy nhất nhưng tìm thấy 2 bean là: dog và cat. Lỗi này xảy ra khi bạn áp dụng mô hình đa kế thừa vào thiết kế và inject bean bằng @Autowired nhưng lại không chỉ định bean nào sẽ được dùng để inject. Để fix lỗi này mà vẫn đảm bảo thiết kế ban đầu theo nguyên tắc đa kế thừa trong java chúng ta có thể dùng 2 annotation sau: @Primary hoặc @Qualifier.
Đoạn log phía trên Spring Boot đưa ra Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans
. Vậy @Primary là gì mà Spring Boot lại đề xuất như vậy ?
1.2 Khỏi tạo bean với @Primary
@Primary là annotation được áp dụng cho class với quyền ưu tiên cao nhất. Nghĩa là khi bạn áp dụng đa kế thừa thì bạn có thể có n
bean khác nhau nhưng khi bạn inject bean thì Spring Boot mặc định sẽ inject bean nào được gán @Primary
– Fix bug ví dụ trên bằng cách gán @Primary cho class Dog → chạy lại ứng dụng.
@Component @Primary public class Dog implements Animal { @Override public void shouting() { System.out.println("Con chó sửa gâu gâu !"); } }
– Kết quả in ra:
Con chó sửa gâu gâu !
1.3 Khỏi tạo bean với @Qualifier
@Qualifier là annotation được áp dụng cho variable để chỉ định chính xác bean bạn muốn inject. Trong trường hợp bạn không chỉ định bean với @Qualifier thì ứng dụng sẽ nhận từ bean @Primary. Ngược lại nếu không có 1 trong 2 annotation @Primary hoặc @Qualifier thì ứng dụng sẽ lỗi như đã đề cập phía trên. Để hiểu rõ hơn mời bạn xem ví dụ sau:
– Inject thêm 1 bean là cat
vào TayJavaApplication → chạy lại ứng dụng.
@SpringBootApplication public class TayJavaApplication implements CommandLineRunner { @Autowired private Animal animal; @Autowired private Animal cat; public static void main(String[] args) { SpringApplication.run(TayJavaApplication.class, args); } @Override public void run(String... args) throws Exception { animal.shouting(); cat.shouting(); } }
– Kết quả in ra:
Con chó sửa gâu gâu ! Con chó sửa gâu gâu !
Như bạn thấy chúng ta đã khởi tao bean cat
nhưng lại in ra “Con chó sửa gâu gâu !”
– Fix bug: Giờ chúng ta sẽ chỉnh định inject bean bằng @Qualifier → chạy lại ứng dụng.
@SpringBootApplication public class TayJavaApplication implements CommandLineRunner { @Autowired private Animal animal; @Autowired @Qualifier("cat") private Animal cat; public static void main(String[] args) { SpringApplication.run(TayJavaApplication.class, args); } @Override public void run(String... args) throws Exception { animal.shouting(); cat.shouting(); } }
– Kết quả in ra:
Con chó sửa gâu gâu ! Con mèo kêu meo meo !
– Kết luận: khi bạn muốn chỉ định bean nào thì hãy dùng @Qualifier, Và nếu muốn gán quyền mặc định ưu tiên cao nhất thì dùng @Primary.
2. Khởi Tạo Bean Theo Các Điều Kiện
Spring Boot cho phép chúng ta khởi tạo bean theo nhiều cách khác nhau như: @Component, @Configuration + @Bean, @Service, Repository,.. Ngoài ra Spring Boot hỗ trợ chúng ta khởi tạo bean theo các điều kiện khác nhau nhằm đáp ứng các điều kiện khác nhau trong phát triển ứng dụng.
2.1 Khởi Tạo Bean Theo @ConditionalOnBean
@ConditionalOnBean là annotation cho phép bạn khởi tạo bean dựa trên điều kiện 1 bean khác phải được khỏi tạo trước đó hoặc nó đã tồn tại trong application context.
– Ví dụ:
Chúng ta sẽ khởi tạo một bean DBConnectionBean. Sau đó chúng ta sẽ khỏi tạo bean DBConnectionService dựa trên điều kiện DBConnectionBean phải được khởi tạo.
– DBConnectionBean.java
@Component public class DBConnectionBean { }
– DBConnectionService.java
public class DBConnectionService { }
– Khởi tạo bean DBConnectionService trong AppConfig.java
@Configuration public class AppConfig { @Bean @ConditionalOnBean(DBConnectionBean.class) DBConnectionService dbConnectionService () { System.out.println("===> Init DBConnectionService successfully"); return new DBConnectionService(); } }
– Inject bean DBConnectionService vào TayJavaApplication.java → chạy ứng dụng.
@SpringBootApplication public class TayJavaApplication implements CommandLineRunner { @Autowired private DBConnectionService dbConnectionService; public static void main(String[] args) { SpringApplication.run(TayJavaApplication.class, args); } @Override public void run(String... args) throws Exception { System.out.println("=== dbConnectionService class name: =========> " + dbconnectionService.getClass()); } }
– Kết quả in ra:
=== dbconnectionService class name: =========> class vn.tayjava.service.DBConnectionService
→ Như vậy là bạn đã khởi tạo bean DBConnectionService thành công bởi vì trước đó bạn đã khỏi tạo bean DBConnectionBean bằng annotation @Component. Bây giờ bạn comment // @Component
như phía dưới bạn sẽ thấy nó bị lỗi do DBConnectionBean chưa tồn tại trong application context vì nó chưa được khỏi tạo.
– // @Component
// @Component public class DBConnectionBean { }
– Lỗi ứng dụng:
*************************** APPLICATION FAILED TO START *************************** Description: Field dbconnectionService in vn.tayjava.TayJavaApplication required a bean of type 'vn.tayjava.service.DBConnectionService' that could not be found. The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true) Action: Consider defining a bean of type 'vn.tayjava.service.DBConnectionService' in your configuration.
2.2 Khởi Tạo Bean Theo @ConditionalOnMissingBean
@ConditionalOnMissingBean là annotation cho phép khởi tạo một bean dựa trên điều kiện một bean cụ thể nào đó không tồn tại.
– Ví dụ: Khởi tạo bean KafkaConnectionService khi và chỉ khi bean KafkaConnectionBean không được khởi tạo.
– Tạo class KafkaConnectionBean (Không gán @Component vì không khởi tạo bean)
public class KafkaConnectionBean { }
– Khỏi tạo bean KafkaConnectionService trong AppConfig
@Configuration public class AppConfig { @Bean @ConditionalOnMissingBean(KafkaConnectionBean.class) KafkaConnectionService kafkaConnectionService () { System.out.println("===> Init kafkaConnectionService when KafkaConnectionBean is missed connection"); return new KafkaConnectionService(); } }
– Inject bean KafkaConnectionService vào TayJavaApplication.java → chạy ứng dụng.
@SpringBootApplication public class TayJavaApplication implements CommandLineRunner { @Autowired private KafkaConnectionService kafkaConnectionService; public static void main(String[] args) { SpringApplication.run(TayJavaApplication.class, args); } @Override public void run(String... args) throws Exception { System.out.println("=== kafkaConnectionService class name: =========> " + kafkaConnectionService.getClass()); } }
– Kết quả khởi tạo bean kafkaConnectionService thành công:
=== kafkaConnectionService class name: =========> class vn.tayjava.service.KafkaConnectionService
2.3 Khởi Tạo Bean Theo @ConditionalOnClass
Khởi tạo bean theo điều kiện 1 Class chỉ định bắt buộc phải tồn tại trong classpath.
/** * Khoi tao bean InitBeanByConditionalOnClass khi va chi khi class SomeOne ton tai trong classpath */ @Configuration @ConditionalOnClass(name = "vn.tayjava.model.SomeOne") public class InitBeanByConditionalOnClass { }
2.4 Khởi Tạo Bean Theo @ConditionalOnMissingClass
Khởi tạo bean theo điều kiện một Class nào đó không tồn tại trong classpath.
/** * Khoi tao bean InitBeanByConditionalOnMissingClass khi va chi khi class SomeOne khong ton tai trong classpath */ @Configuration @ConditionalOnMissingClass(value = "vn.tayjava.model.SomeOne") public class InitBeanByConditionalOnMissingClass { }
2.5 Khởi Tạo Bean Theo @ConditionalOnProperty
Khởi tạo bean khi các giá trị phù hợp với giá trị tồn tại trong file application.properties
@Configuration public class AppConfig { /** * Khoi tao InitBeanByConditionalOnProperty khi va chi khi vn.tayjava.allowed ton tai trong application.properties voi gia tri la true * * @return */ @Bean @ConditionalOnProperty(value = "vn.tayjava.allowed", // key nam trong file application.properties havingValue = "true", // Khoi tao bean voi gia tri true matchIfMissing = false) // Khong khoi tao bean khi gia tri la false InitBeanByConditionalOnProperty initBeanByConditionalOnProperty() { return new InitBeanByConditionalOnProperty(); } }
2.6 Khởi Tạo Bean Theo @ConditionalOnExpression
Khởi tạo bean theo biểu thức điều kiện Regular Expression.
/** * Khoi tao bean InitBeanByConditionalOnExpression khi cac dieu kien phu hop voi gia tri trong file application.properties */ @Configuration @ConditionalOnExpression( "${vn.tayjava.allowed:true} and ${vn.tayjava.enabled:true}" ) public class InitBeanByConditionalOnExpression { }
2.7 Khởi Tạo Bean Theo @ConditionalOnResource
Khởi tạo bean theo điều kiện resources bắt buộc phải tồn tại.
/** * Khoi tao bean InitBeanByConditionalOnResource khi file application.properties khon ton tai */ @Configuration @ConditionalOnResource(resources = "/application.properties") public class InitBeanByConditionalOnResource { }
2.8 Khởi Tạo Bean Theo @ConditionalOnJava
@ConditionalOnJava cho phép khởi tạo bean theo Java version chỉ định
/** * Khoi tao bean InitBeanByConditionalOnJava trong java version 17 */ @Configuration @ConditionalOnJava(JavaVersion.SEVENTEEN) public class InitBeanByConditionalOnJava { }
Xem source code: https://github.com/luongquoctay87/tayjava-sample-code/tree/init-bean-in-spring-boot
// Để clone source code $ git clone https://github.com/luongquoctay87/tayjava-sample-code.git $ git checkout init-bean-in-spring-boot
Để hiểu cặn kẽ và chi tiết mời các bạn xem video tại TayJava – Lập Trình Java Từ A-Z