[Series] Spring Core - Keep It Simple (P3): Dependency Injection

Dependency Injection là một design pattern implement nguyên lý/pattern Inversion of Control (IoC). DI đảo ngược việc khởi tạo các đối tượng phụ thuộc. Theo đó, chúng ta sẽ cung cấp (inject - tiêm) cho đối tượng A các đối tượng B, C, D, ... (dependency - phụ thuộc, những đối tượng mà một đối tượng sẽ làm việc cùng) thay vì để đối tượng A trực tiếp khởi tạo các đối tượng B, C, D, ...

Ưu điểm lớn nhất của DI chính là nới lỏng ràng buộc giữa một lớp và các phụ thuộc của nó. Cũng chính vì vậy, code sẽ dễ test hơn rất nhiều vì chúng ta hoàn toàn có thể giả lập được các dependency.

Có 3 cách để thực hiện DI là:

  • DI thông qua constructor
  • DI thông qua setter
  • DI thông qua interface

Trong phần 3 của series, mình sẽ đề cập đến việc thực hiện DI trong ứng dụng Spring.


1. Dependency Injection trong Spring

Spring Framework chỉ hỗ trợ 2 cách thực hiện Dependency Injection (DI):

  • DI thông qua constructor
  • DI thông qua setter

Spring IoC container sẽ inject các dependency của bean khi nó tạo bean. Các dependency của một bean được liệt kê thông qua danh sách tham số của phương thức tạo bean.

Ví dụ: Bean fooService có 2 dependency là barRepositorybazRepository. Mình sẽ inject barRepository thông qua constructor và inject bazRepository thông qua setter.

BarRepository.java

package org.ashina.tutorial.spring;

public class BarRepository {

    @Override
    public void doStuff() {
        System.out.println("This is barRepository bean");
    }

}

BazRepository.java

package org.ashina.tutorial.spring;

public class BazRepository {

    @Override
    public void doStuff() {
        System.out.println("This is bazRepository bean");
    }

}

FooService.java

package org.ashina.tutorial.spring;

public class FooService {

    private BarRepository barRepository;
    private BazRepository bazRepository;

    public FooService(BarRepository barRepository) {
        this.barRepository = barRepository;
    }

    public void setBazRepository(BazRepository bazRepository) {
        this.bazRepository = bazRepository;
    }

    @Override
    public void doStuff() {
        barRepository.doStuff();
        bazRepository.doStuff();
    }

}

Trong AppConfig.java, chúng ta định nghĩa 3 bean barRepository, bazRepositoryfooService.

AppConfig.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public BarRepository barRepository() {
        return new BarRepository();
    }

    @Bean
    public BazRepository bazRepository() {
        return new BazRepository();
    }

    @Bean
    public FooService fooService(BarRepository barRepository, BazRepository bazRepository) {
        // DI thông qua constructor
        FooService fooService = new FooService(barRepository);

        // DI thông qua setter
        fooService.setBazRepository(bazRepository);

        return fooService;
    }

}

Main.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        FooService fooService = ctx.getBean(FooService.class);
        fooService.doStuff();
        ctx.close();
    }

}

Kết quả:

This is barRepository bean
This is bazRepository bean

Chúng ta nên thực hiện DI thông qua constructor hay setter?

Chúng ta hoàn toàn có thể sử dụng kết hợp cả 2 cách. Nhưng theo khuyến nghị của Spring, chúng ta nên:

  • DI thông qua constructor với các dependency bắt buộc phải có.
  • DI thông qua setter với các dependency không bắt buộc phải có.

Điều này có thể giúp DI trở nên linh động hơn. Tuy nhiên, có một số điều nên tránh cũng như hạn chế khi thực hiện DI là:

  • DI thông qua constructor: một constructor có thể có quá nhiều tham số => code smell => cần phải refactor code.
  • DI thông qua setter: dependency instance có thể bị null => cần phải check null trước khi thực hiện DI.

2. Autowiring

Trong phần 1, chúng ta đã học được cách thực hiện DI một cách tường minh trong Spring:

// DI thông qua constructor
FooService fooService = new FooService(barRepository);

// DI thông qua setter
fooService.setBazRepository(bazRepository);

Bên cạnh đó, Spring còn cho phép chúng ta thực hiện DI một cách tự động thông qua cơ chế autowiring. Chúng ta chỉ cần sử dụng annotation org.springframework.beans.factory.annotation.Autowired mà thôi.

Có 4 autowiring mode là:

  1. no (mặc định): autowiring bị off.
  2. byType
  3. byName
  4. constructor

2.1. Autowiring byType

Spring IoC container tìm kiếm bean theo class type đã được autowired:

  • Nếu trong container chỉ có duy nhất một bean phù hợp, autowiring sẽ được thực hiện.
  • Nếu trong container có nhiều hơn một bean phù hợp, một fatal error sẽ được ném ra và autowiring không được thực hiện.
  • Nếu trong container không có bean nào phù hợp, không có error nào được ném ra và autowiring cũng không được thực hiện.

@Autowired được đặt ở trên thuộc tính.

FooService.java

package org.ashina.tutorial.spring;

import org.springframework.beans.factory.annotation.Autowired;

public class FooService {

    @Autowired
    private BarRepository barRepository;

    @Autowired
    private BazRepository bazRepository;

    @Override
    public void doStuff() {
        barRepository.doStuff();
        bazRepository.doStuff();
    }

}

AppConfig.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public BarRepository barRepository() {
        return new BarRepository();
    }

    @Bean
    public BazRepository bazRepository() {
        return new BazRepository();
    }

    @Bean
    public FooService fooService() {
        return new FooService();
    }

}

2.2. Autowiring byName

Spring IoC container tìm kiếm bean có cùng tên với thuộc tính.

@Autowired được đặt ở trên setter.

FooService.java

package org.ashina.tutorial.spring;

import org.springframework.beans.factory.annotation.Autowired;

public class FooService {

    private BarRepository barRepository;
    private BazRepository bazRepository;

    @Autowired
    public void setBarRepository(BarRepository barRepository) {
        this.barRepository = barRepository;
    }

    @Autowired
    public void setBazRepository(BazRepository bazRepository) {
        this.bazRepository = bazRepository;
    }

    @Override
    public void doStuff() {
        barRepository.doStuff();
        bazRepository.doStuff();
    }

}

AppConfig.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public BarRepository barRepository() {
        return new BarRepository();
    }

    @Bean
    public BazRepository bazRepository() {
        return new BazRepository();
    }

    @Bean
    public FooService fooService() {
        return new FooService();
    }

}

2.3. Autowiring constructor

Spring IoC container tìm kiếm bean theo class type của các đối số trong constructor đã được autowired. Với mỗi đối số, nếu trong container không có bean nào hoặc có nhiều hơn một bean phù hợp thì một fatal error sẽ được ném ra. Ngược lại, autowiring sẽ được thực hiện.

@Autowired được đặt ở trên constructor. Các thuộc tính dependency nên được set là final để đảm bảo thread-safety.

package org.ashina.tutorial.spring;

import org.springframework.beans.factory.annotation.Autowired;

public class FooService {

    private final BarRepository barRepository;
    private final BazRepository bazRepository;

    @Autowired
    public FooService(BarRepository barRepository, BazRepository bazRepository) {
        this.barRepository = barRepository;
        this.bazRepository = bazRepository;
    }

    @Override
    public void doStuff() {
        barRepository.doStuff();
        bazRepository.doStuff();
    }

}

AppConfig.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public BarRepository barRepository() {
        return new BarRepository();
    }

    @Bean
    public BazRepository bazRepository() {
        return new BazRepository();
    }

    @Bean
    public FooService fooService(BarRepository barRepository, BazRepository bazRepository) {
        return new FooService(barRepository, bazRepository);
    }

}

Chú ý: Chúng ta cũng có thể autowire ApplicationContext như thế này:

@Autowired
private ApplicationContext ctx;

3. Qualifying

Như chúng ta đã biết, lớp bean có thể implement một interface.

Khi khởi tạo bean, Spring IoC container có thể tự động tìm kiếm implementation của một interface. Tuy nhiên, nếu interface có nhiều implementation thì khi autowiring, Spring IoC container sẽ không thể biết chúng ta đang muốn inject implementation nào. Ngoại lệ org.springframework.beans.factory.NoUniqueBeanDefinitionException sẽ được ném ra.

Lúc này, chúng ta cần sử dụng đến annotation org.springframework.beans.factory.annotation.Qualifier với thuộc tính value là tên của bean (implementation).

FooRepository.java

package org.ashina.tutorial.spring;

public interface FooRepository {

    void doStuff();

}

BarRepository.java

package org.ashina.tutorial.spring;

public class BarRepository implements FooRepository {

    @Override
    public void doStuff() {
        System.out.println("This is barRepository bean");
    }

}

BazRepository.java

package org.ashina.tutorial.spring;

public class BazRepository implements FooRepository {

    @Override
    public void doStuff() {
        System.out.println("This is bazRepository bean");
    }

}

FooService.java

package org.ashina.tutorial.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class FooService {

    @Autowired
    @Qualifier("barRepository")
    private FooRepository fooRepository;

    // @Autowired
    // public FooService(@Qualifier("bazRepository") FooRepository fooRepository) {
    //     this.fooRepository = fooRepository;
    // }

    @Override
    public void doStuff() {
        fooRepository.doStuff();
    }

}

AppConfig.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public FooRepository barRepository() {
        return new BarRepository();
    }

    @Bean
    public FooRepository bazRepository() {
        return new BazRepository();
    }

    @Bean
    public FooService fooService() {
        return new FooService();
    }

}

Main.java

package org.ashina.tutorial.spring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        FooService fooService = ctx.getBean(FooService.class);
        fooService.doStuff();
        ctx.close();
    }

}

Kết quả:

This is barRepository bean

4. @Primary


Tài liệu tham khảo

  1. https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-dependencies
  2. https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation
  3. https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation-qualifiers
Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Nguyễn Tuấn Anh

24 bài viết.
151 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
45 41
MyContact là một ứng dụng mà mình thường viết mỗi khi học một ngôn ngữ hay công nghệ mới. MyContact chỉ là một ứng dụng CRUD đơn giản, cho phép ngư...
Nguyễn Tuấn Anh viết hơn 2 năm trước
45 41
White
27 14
Hướng dẫn lập trình Spring Security Trong bài viết lần này, mình sẽ giúp các bạn bước đầu tìm hiểu (Link) thông qua xây dựng các chức năng: Đăng ...
Nguyễn Tuấn Anh viết hơn 2 năm trước
27 14
White
15 1
Giới thiệu Spring Framework Trong bài viết này, mình sẽ giới thiệu cho các bạn về một trong những Java EE framework rất nổi bật và phổ biến hiện n...
Nguyễn Tuấn Anh viết hơn 2 năm trước
15 1
Bài viết liên quan
White
0 0
Trong bài viết này, một số hình ảnh hoặc nọi dung có thể bị thiếu do quá trình chế bản. Vui lòng xem nội dung ở blog gốc sau: (Link) (Link), chúng...
programmerit viết gần 4 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
24 bài viết.
151 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!