Mục lục
Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu cách sử dụng @Import annotation trong Spring cũng như tìm ra điểm khác nhau giữa nó và @ComponentScan.
Nếu sử dụng sai cách, có thể chúng ta có thể sẽ đảo ngược cách sử dụng giữa 2 annotation này, nhưng kết quả vẫn không thay đổi, điều này rất nguy hiểm vì sẽ không có cách nào để chúng ta nhận ra sai xót.
Configuration và Bean
Trước khi bắt đầu tìm hiểu @Import annotation, các bạn cần chắc chắn rằng mình đã hiểu bean là gì? và cách hoạt động của @Configuration annotation trong Spring.
Giả sử chúng ta có 2 class Dog và Cat
public class Dog { public Dog() { System.out.println("Dog bean created!"); } } // -------------------------------------------------- public class Cat { public Cat() { System.out.println("Cat bean created!"); } }
Và 2 @Configuration CatConfig và DogConfig class
@Configuration public class CatConfig { @Bean public Cat cat() { return new Cat(); } } //--------------------------- @Configuration public class DogConfig { @Bean public Dog dog() { return new Dog(); } }
Bây giờ, để triển khai một unit-test, bao gồm Dog và Cat bean, chúng ta cần cung cấp DogConfig và CatConfig cho context của unit-test như sau
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { CatConfig.class, DogConfig.class }) class ImportAnnotationApplicationTests { @Autowired ApplicationContext context; @Test void testBeanImport() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); } private void assertThatBeanExists(String beanName, Class<?> beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
Nhóm các Configuration class với @Import annotation
Ở ví dụ trên việc khai báo 2 class DogConfig và CatConfig là không có vấn đề gì, nhưng hãy tưởng tượng rằng nếu có đến 10 hoặc thậm chí là 20 class như vậy thì việc cấu hình trên sẽ gây khó khăn và làm xấu mã nguồn.
@Import annotation được sinh ra để giải quyết vấn đề này, nó chịu trách nhiệm nhóm các @Configuration class lại thành một nhóm.
Ví dụ nhóm DogConfig và CatConfig lại thành 1 nhóm
@Configuration @Import({ DogConfig.class, CatConfig.class }) class MammalConfiguration { }
@SpringBootApplication @Import(AnimalScanConfiguration.class) public class ImportAnnotationApplication { public static void main(String[] args) { SpringApplication.run(ImportAnnotationApplication.class, args); } }
Và sử dụng nó để thay thế cho cả 2 class này
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = { MammalConfiguration.class }) class ImportAnnotationApplicationTests { @Autowired ApplicationContext context; @Test void testBeanImport() { assertThatBeanExists("dog", Dog.class); assertThatBeanExists("cat", Cat.class); } private void assertThatBeanExists(String beanName, Class<?> beanClass) { Assertions.assertTrue(context.containsBean(beanName)); Assertions.assertNotNull(context.getBean(beanClass)); } }
@Import vs @ComponentScan
Cả 2 annotation đều có thể dùng để truy cập các @Component và @Configuration class
@Configuration @Import(Bug.class) class BugConfig { } @Component(value = "bug") class Bug { }
Như đoạn mã trên bằng cách @Import(Bug.class), chúng ta đã có thể đăng ký một Bug bean với container trong khi chúng ta cũng có thể sử dụng @ComponentScan để quyết Bug class và đăng ký một Bug bean mới.
Mặc dù 2 cách trên đều có chung kết quả, thế nhưng Spring khuyến khích chúng ta nên khởi tạo và đăng ký các bean thông qua @Configuration và dùng @ComponentScan để tìm kiếm và đăng ký bean. Vì vậy, trong trường hợp này chúng ta nên sử dụng @ComponentScan thay vì @Import.
Cách kết hợp giữa @ComponentScan và @Import
Thông thường, chúng ta thường sử dụng @ComponentScan tại root package, nó sẽ tìm kiếm tất cả các component trong ứng dụng để khởi tạo cho chúng ta. Nếu bạn đang sử dụng Spring Boot thì @SpringBootApplication đã bao gồm @ComponentScan chúng ta không cần phải làm điều tương tự.
Trong thực tế chúng ta có thể kết hợp cả 2 annotation này trong trường hợp. Giả sử chúng ta có 1 animal package có thể xem như là một component hoặc một module của ứng dụng.
Do vậy chúng ta tạo ra một AnimalScanConfiguration dùng để quản lý các bean có trong animal package.
package com.deft.animal; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class AnimalScanConfiguration { }
Và sau đó, sử dụng @Import để thêm các bean vào trong context, nhưng phần quản lý sẽ được trao lại cho AnimalScanConfiguration. Nó quyét và sử dụng các bean nào thì ứng dụng có các bean đó.
Kết bài
Trong bài viết này chúng ta đã tìm hiểu về @Import annotation và cách tổ chức các config class. Chúng ta cũng biết được rằng @Import rất giống với @ComponentScan, ngoại trừ thực tế là @Import có cách tiếp cận rõ ràng trong khi @ComponentScan sử dụng cách tiếp cận ngầm.
Các bạn có thể tham khảo mã nguồn được công khai trên gitlab: import-annotation
Nguồn