Bean và ApplicationContext trong Spring Boot: Tổng Quan và Kỹ Thuật Inject Bean

Trong thế giới phát triển ứng dụng Java với Spring Boot, hai khái niệm cốt lõi mà mọi lập trình viên cần nắm vững là BeanApplicationContext. Chúng đóng vai trò trung tâm trong việc quản lý các thành phần của ứng dụng và thiết lập mối quan hệ giữa chúng. Bài viết này sẽ đi sâu vào định nghĩa, cách sử dụng và các kỹ thuật liên quan đến Bean và ApplicationContext, giúp bạn xây dựng ứng dụng Spring Boot một cách hiệu quả.

1. Bean và ApplicationContext là gì?

1.1. Bean là gì?

Theo tài liệu chính thức của Spring Framework, Bean là các đối tượng tạo nên xương sống của ứng dụng và được quản lý bởi Spring IoC (Inversion of Control) Container. Hiểu đơn giản, Bean là một instance của một class được khởi tạo, kết nối và quản lý vòng đời bởi Spring IoC Container.

Các Bean có thể phụ thuộc lẫn nhau, tạo thành một mạng lưới các đối tượng tương tác để thực hiện các chức năng của ứng dụng. Sự phụ thuộc này được khai báo cho IoC Container thông qua cơ chế Dependency Injection (DI).

Để khai báo một class là một Bean, chúng ta thường sử dụng annotation @Component. Khi Spring Boot khởi động, nó sẽ quét các class được đánh dấu @Component và tạo ra các Bean tương ứng trong ApplicationContext.

Ví dụ, xét một ứng dụng quản lý xe hơi (Car), động cơ (Engine) và nhà sản xuất động cơ (ChinaEngine):

Trong đó, Car phụ thuộc vào Engine, và Engine có thể được triển khai bởi ChinaEngine.

1.2. ApplicationContext là gì?

ApplicationContext là giao diện (interface) đại diện cho Spring IoC Container trong Spring Boot. Nó chịu trách nhiệm quản lý vòng đời của các Bean, cung cấp các dịch vụ như Dependency Injection, quản lý cấu hình, và hỗ trợ các tính năng nâng cao như i18n (quốc tế hóa) và event publishing.

Ngoài ApplicationContext, bạn có thể gặp khái niệm BeanFactory. BeanFactory là một IoC container cơ bản, cung cấp các chức năng cốt lõi để quản lý Bean. ApplicationContext mở rộng BeanFactory, cung cấp nhiều tính năng hơn và phù hợp hơn cho các ứng dụng Spring Boot hiện đại.

Khi ứng dụng Spring Boot khởi chạy, Spring IoC Container sẽ thực hiện Component Scan, quét các packages để tìm kiếm các class được đánh dấu là Bean và đưa chúng vào ApplicationContext.

1.3. Cách lấy Bean ra từ Context

Để truy cập và sử dụng các Bean trong ứng dụng, chúng ta cần lấy chúng ra từ ApplicationContext. Biến context này được tạo ra ở dòng lệnh khởi động chương trình Spring Boot:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = sentayho.com.vn(Application.class, args);
    }
}

Phương thức sentayho.com.vn() trả về một đối tượng ApplicationContext, đại diện cho IoC Container. Chúng ta có thể sử dụng phương thức getBean() để lấy ra Bean từ Context:

// Lấy ra Bean có class cụ thể
Car car = sentayho.com.vnean(Car.class);

// Lấy ra Bean theo tên và class
Engine engine = sentayho.com.vnean("ChinaEngine", sentayho.com.vns);

2. Kỹ thuật inject Bean vào Bean khác

Dependency Injection (DI) là một kỹ thuật quan trọng trong Spring Boot, cho phép chúng ta inject các Bean vào các Bean khác, tạo ra sự liên kết và tương tác giữa các thành phần của ứng dụng.

2.1. Sử dụng @Autowired

Annotation @Autowired được sử dụng để đánh dấu các field, constructor hoặc setter methods mà Spring sẽ tự động inject các Bean phù hợp vào.

Ví dụ:

// Annotation chỉ đánh dấu lên class
public interface Engine {
    void run();
}

@Component
public class ChinaEngine implements Engine {
    @Override
    public void run() {}
}

@Component
public class Car {
    // Báo cho Spring tìm Bean nào phù hợp với Engine interface
    // Và có một Bean phù hợp là ChinaEngine
    // Nó tương đương với = new ChinaEngine()
    @Autowired
    private final Engine engine;
}

Trong ví dụ trên, @Autowired được sử dụng trên field engine của class Car. Spring sẽ tự động tìm một Bean phù hợp với interface Engine (trong trường hợp này là ChinaEngine) và inject vào field engine.

Tuy nhiên, việc sử dụng @Autowired trực tiếp trên field không được khuyến khích, vì nó sử dụng Java Reflection để inject, có thể ảnh hưởng đến hiệu năng. Thay vào đó, chúng ta nên sử dụng constructor-based injection hoặc setter-based injection.

2.2. Inject qua Constructor hoặc Setter

Constructor-based injection được ưu tiên sử dụng khi các dependency là bắt buộc. Khi đó, Spring Boot sẽ gọi constructor của class và truyền các dependency vào như các tham số.

Ví dụ:

@Component
public class Car {
    private final Engine engine;

    // Các bản Spring Boot mới thì không cần @Autowired trên constructor
    public Car(Engine engine) {
        sentayho.com.vn = engine;
    }
}

Setter-based injection được sử dụng khi các dependency là tùy chọn. Spring Boot sẽ tạo Bean và sau đó gọi setter method để inject dependency.

Ví dụ:

@Component
public class Car {
    private Engine engine;

    // Thêm @Required để setter luôn được gọi để inject
    @Required
    public void setEngine(Engine engine) {
        sentayho.com.vn = engine;
    }
}

Setter-based injection thường được sử dụng trong trường hợp dependency vòng (circular dependency), khi hai Bean phụ thuộc lẫn nhau. Trong trường hợp này, nếu cả hai Bean đều sử dụng constructor-based injection, Spring Boot sẽ không biết nên tạo Bean nào trước. Giải pháp là một Bean sẽ sử dụng constructor-based injection, Bean còn lại sử dụng setter-based injection.

3. Khi Spring Boot không biết chọn Bean nào?

3.1. Khi tìm thấy nhiều Bean phù hợp

Trong trường hợp có nhiều Bean phù hợp với một dependency, Spring Boot sẽ báo lỗi.

Ví dụ, nếu chúng ta tạo thêm class VNEngine implements Engine:

@Component
public class VNEngine implements Engine {
    @Override
    public void run() {}
}

Khi đó, Spring Boot sẽ báo lỗi vì nó tìm thấy hai Bean (ChinaEngineVNEngine) phù hợp để inject vào Car.

3.2. Giải pháp

Có hai cách để giải quyết vấn đề này:

  1. Sử dụng @Primary: Đánh dấu một Bean là @Primary để Spring Boot ưu tiên chọn Bean này khi có nhiều Bean phù hợp.

    @Component
    @Primary
    public class VNEngine implements Engine {
        …
    }
  2. Sử dụng @Qualifier: Chỉ định rõ tên Bean cụ thể cần inject bằng annotation @Qualifier.

    @Component
    public class Car {
        @Autowired
        @Qualifier("VNEngine") // Phải khớp hoa thường luôn nhe
        private final Engine engine;
    }

Đối với constructor-based injection và setter-based injection, chúng ta cũng có thể sử dụng @Qualifier trước tên parameter hoặc setter method.

Tóm lại, việc hiểu rõ về Bean và ApplicationContext, cũng như các kỹ thuật Dependency Injection là rất quan trọng để xây dựng ứng dụng Spring Boot một cách hiệu quả. Nắm vững những kiến thức này sẽ giúp bạn quản lý các thành phần của ứng dụng một cách dễ dàng, tạo ra các ứng dụng linh hoạt và dễ bảo trì.