Các cách sử dụng @Autowired annotation trong Spring

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 FieldBeanSetterBean 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

Spring sử dụng tên của bean làm giá trị mặc định trong @Qualifier. Nghĩa là khi chúng ta khai báo
@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 foobar 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

3.9 7 votes
Article Rating
Subscribe
Notify of
guest
6 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
6
0
Would love your thoughts, please comment.x
()
x