Mục lục
Bắt đầu từ Spring 2.5, framework đã giới thiệu một annotation mới @Autowired cho phép Spring tự động tìm kiếm và tiêm các bean tương ứng mà chúng ta đã khai báo trong class.
Trong bài viết này chúng sẽ cùng nhau tìm hiểu một số cách sử dụng @Autowired annotation trong Spring và cách để xử lý xung đột khi có nhiều hơn một bean có thể sử dụng trong một class cụ thể.
Kích hoạt @Autowired annotation trong Spring
Spring cho phép tự động tiêm các dependency cách tự động, vì vậy chúng ta chỉ cần khai báo bean bên trong các file cấu hình với @Bean annotation hay các class được chú thích với @Component annotation, Spring IoC container sẽ tự động tiêm các dependency tương ứng mà chúng ta đã khai báo để sử dụng.
Ví dụ dưới đây mình tạo Company bean thông qua @ComponentScan (được dùng để chỉ ra những nơi mà nó cần tìm những @Component class) và Address bean được đặt trong file cấu hình (được chú thích với @Configuration annotation)
@Component public class Company { private Address address; public Company(Address address) { this.address = address; } // getter, setter and other properties } @Configuration @ComponentScan(basePackageClasses = Company.class) public class Config { @Bean public Address getAddress() { return new Address("High Street", 1000); } }
Hơn ngoài mong đợi, với Spring Boot bạn chỉ cần sử dụng @SpringBootApplication annotation. Nó là một annotation kết hợp @Configuration, @EnableAutoConfiguration, và @ComponentScan. Thông thường nó được dùng với Main class trong ứng dụng Spring Boot, chỉ định cho Spring sẽ tìm kiếm các bean cùng cấp hoặc thuộc các package con của Main class.
@SpringBootApplication public class AutowiredDemoApplication { public static void main(String[] args) { SpringApplication.run(AutowiredDemoApplication.class, args); } }
Cách sử dụng @Autowired
Sau khi các bean đã được khởi tạo và đăng ký với Spring IoC, giờ đây bạn có thể sử dụng @Autowired để tiêm các bean tương ứng cần sử dụng. Spring cung cấp cho chúng ta 3 cách để sử dụng @Autowired annotation hoặc là chú thích trên contructor, setter hay trực tiếp trên thuộc tính.
@Autowired trực tiếp trên thuộc tính
Đây là cách sử dụng nhanh và ngắn gọn nhất, chúng ta chỉ cần chú thích @Autowired trực tiếp trên thuộc tính. Spring IoC sẽ tự động tìm kiếm và tiêm bean tương ứng thông qua Reflection API.
Trước tiên, giả sử mình có một ExampleClassBean bean như thế này
@Component public class FieldBean { public String getName() { return "Deft Blog"; } }
Sau đó mình tiến hành tiêm một ExampleClassBean bean vào ExampleService với @Autowired được chú thích trên thuộc tính.
public interface FieldExampleService { String getName(); } @Service public class FieldExampleServiceImpl implements FieldExampleService { @Autowired private FieldBean fieldBean; @Override public String getName() { return fieldBean.getName(); } }
Giờ đây, chúng ta có thể kiểm tra với mong muốn khi gọi đến getName() method sẽ nhận về giá trị “Deft Blog” như đã khai báo trước đó.
@SpringBootTest class AutowiredDemoApplicationTests { @Autowired private FieldExampleService fieldExampleService; @Test public void propertyAutowiredTest() { assertEquals(fieldExampleService.getName(), "Deft Blog"); } }
@Autowired trên setter method
Thông thường, setter method được dùng để khởi tạo hay cập nhật giá trị của một thuộc tính trong class. Đây cũng là một nới lý tưởng để Spring có thể tiêm dependency vào.
@Component public class SetterBean { public int getValue() { return 10; } }
Giờ, mình sẽ tạo một SetterExampleService tiêm SetterBean theo kiểu setter method.
public interface SetterExampleService { int getValue(); } @Service public class SetterExampleServiceImpl implements SetterExampleService { private SetterBean setterBean; @Autowired private void setBean(SetterBean setterBean) { this.setterBean = setterBean; } @Override public int getValue() { return setterBean.getValue(); } }
@Test public void setterAutowiredTest() { assertEquals(setterExampleService.getValue(), 10); }
@Autowired constructor
Cuối cùng, chúng ta có thể sử dụng @Autowired trên contructor, Spring sẽ tự động tìm kiếm các bean tương ứng được khai báo như là tham số trong constructor.
Trong ví dụ này mình sẽ sử dụng lại FieldBean và SetterBean là 2 tham số trong constructor, Spring sẽ tự động tìm kiếm 2 bean này vào service để sử dụng.
@Service public class ContructorExampleServiceImpl implements ConstructorExampleService { private final FieldBean fieldBean; private final SetterBean setterBean; @Autowired public ContructorExampleServiceImpl(FieldBean fieldBean, SetterBean setterBean) { this.fieldBean = fieldBean; this.setterBean = setterBean; } @Override public String print() { return fieldBean.getName() + " - " + setterBean.getValue(); } }
Các bạn có thể chạy unit-test sau để kiểm tra kết quả
@Test public void constructorAutowiredTest() { assertEquals(constructorExampleService.print(), "Deft Blog - 10"); }
@Autowired với các optional dependency
Khi Spring không thể tìm thấy bean trong container để tiêm chúng vào nơi cần sử dụng, nó sẽ ném ra một exception khiến chương trình không thể khởi chạy.
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.autowire.sample.FooDAO] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Để khắc phục lỗi này, chúng ta có thể sử dụng thuộc tính required trong @Autowired annotation chuyển nó thành FALSE, mặc định là TRUE sẽ gây ra lỗi khi một dependency được chú thích với @Autowired annotation không được tìm thấy.
@Service public class FieldExampleServiceImpl implements FieldExampleService { @Autowired(required = false) private FieldBean fieldBean; // .... }
Nhưng trong thực tế, cách này thường khá ít sử dụng. Thông thường, các bạn phải đảm bảo các bean được tạo đủ trong quá trình chạy ứng dụng.
Các trường lỗi khi sử dụng @Autowired
Mặc định, Spring sẽ tìm kiếm các dependency được chú thích bởi @Autowired annotation theo kiểu dữ liệu. Nếu có nhiều hơn một bean có cùng kiểu dữ liệu trong container nó sẽ ném ra một exception.
Để xử lý cho trường hợp xung đột này, chúng ta phải chỉ định rõ bean nào chúng ta đang cần dùng trong số những bean có cùng kiểu dữ liệu đang tồn tại trong container.
@Autowired với @Qualifier
Đây là một thông dụng dùng để chỉ rõ bean chúng ta cần dùng trong số nhiều bean có cùng kiểu dữ liệu.
Giả sử mình có một Formatter cùng với 2 bean đều implement từ nó
public interface Formatter { String format(); } @Component("bar") public class BarFormatter implements Formatter { @Override public String format() { return "Bar"; } } @Component("foo") public class FooFormatter implements Formatter { @Override public String format() { return "Foo"; } }
Trong trường hợp trên, chúng ta có đến 2 bean có cùng kiểu dữ liệu Formatter, nên khi mình khởi tạo ConflictServiceExample service với một dependency Formatter thì gây ra lỗi
No qualifying bean of type ‘com.deft.autowireddemo.conflict.Formatter’ available: expected single matching bean but found 2: bar,foo
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220)
@Service public class ConflictServiceExample { @Autowired private Formatter formatter; public String format() { return formatter.format() } }
Để tránh trường hợp xung đột này, chúng ta có thể sử dụng @Qualifier để chỉ rõ tên của bean muốn sử dụng.
@Service public class ConflictServiceExample { @Autowired @Qualifier("bar") private Formatter formatter; public String format() { return formatter.format() } }
Autowired by name
@Autowired private Formatter formatter;
Thì có thể ngầm hiểu như
@Autowired @Qualifier("formatter") private Formatter formatter;
Vì trong trường hợp này, không có bean nào có tên tương tự nên Spring chỉ tìm theo kiểu dữ liệu và tìm ra được 2 bean foo và bar tương ứng.
Do vậy, các bạn có thể không dùng @Qualifier annotation mà chỉ cần thay đổi tên tương ứng với tên của bean cần sử dụng.
Như đoạn code dưới đây, mình sử dụng foo bean mà không cần sử dụng @Qualifier mà chỉ cần thay đổi tên thuộc tính tương ứng với @Component name đã đặt trước đó (foo)
@Service public class ConflictByNameServiceExample { @Autowired private Formatter foo; public String format() { return foo.format(); } }
Kết bài
Qua bài viết này, chúng ta sẽ biết được cách sử dụng @Autowired để tiêm các dependency tự động. Có tới 3 cách áp dụng @Autowired tuy nhiên, thiên hạ đồn rằng nên sử dụng constructor autowired vì nó cho tốc độ nhanh hơn là dùng field autowired phải dùng reflection để khởi tạo. Không những vậy, khi sử dụng constructor mà các tham số quá nhiều, nghĩa là chúng ta có thể đang vi phạm nguyên tắc SOLID vì nhiều dependency đồng nghĩa với việc class của chúng ta đang chịu trách nhiệm xử lý quá nhiều logic.
Sau cùng các bạn có thể xem mã nguồn mình công khai trên gitlab để chạy và kiểm thử nếu cần thiết: autowired-demo.
Nguồn tham khảo
https://www.baeldung.com/spring-autowire