Giới thiệu Google Guice – Dependency injection (DI) framework

Trong bài trước, tôi đã giới thiệu với các bạn Dependency Injection (DI) và cách tự xây dựng 1 framework đảo ngược điều khiển (IoC) để quản lý các dependency. Trong bài này, chúng ta sẽ cùng tìm hiểu về Google Guice – một framework giúp chúng ta dễ dàng quản lý và sử dụng các dependency.

1. Google Guice là gì?

Google Guice (phát âm là juice), là một framework DI gọn nhẹ, mã nguồn mở, giúp chúng ta phát triển các ứng dụng dạng module. Guice được phát triển và quản lý bởi Google.

Guice tận dụng thế mạnh của Generic và Annotation – các tính năng mới được giới thiệu từ phiên bản java 5, giúp chúng ta dễ dàng quản lý và sử dụng các Dependency.

Trong Guice, Annotaion @inject được sử dụng để tiêm phụ thuộc. Nó cho phép chúng ta inject sự phụ thuộc tại các constructor, field hoặc method. Sử dụng Guice, chúng ta có thể xác định scope của instance đối tượng phụ thuộc. Nó cũng có các tính năng để tích hợp với Spring và Aspect Oriented Programming (AOP).

2. Cài đặt Google Guice

Để sử dụng Google Guice, chúng ta cần thêm thư viện Guice vào trong project. Trong bài viết này, tôi sử dụng maven project, chúng ta sẽ thêm thư viện này như sau:

<!-- https://mvnrepository.com/artifact/com.google.inject/guice -->
<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.2.2</version>
</dependency>

Các version khác của Guice, các bạn xem tại link sau: https://mvnrepository.com/artifact/com.google.inject/guice

3. Ví dụ cơ bản về Dependency Injection với Google Guice

Để sử dụng Guice cần nhớ 2 thông tin quan trọng sau:

  • Nói với Guice cái gì cần được tiêm phụ thuộc (inject) với @Inject.
  • Nói với Guice cách tiêm mô-đun: implement Asbstract Module, bind các interface với các implementation mong muốn.

Chúng ta sẽ viết lại ví dụ ở bài viết Dependency Injection Pattern bằng cách sử dụng thư viện Google Guice.

package com.maixuanviet.patterns.creational.googleguice.firstexample;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
 
// Step 1: Create Interface
interface MessageService {
    void sendMessage(String message);
}
 
// Step 2: Create Implementation
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println("Email message: " + message);
    }
}
 
// Step 3: Create Bindings Module
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class).to(EmailService.class);
    }
}
 
// Step 4: Create Class with dependency
class UserController {
    private MessageService messageService;
  
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
  
    public void send() {
        messageService.sendMessage("Dependency injection with Google Guice example");
    }
}
 
// Step 5: Run application
public class GoogleGuiceFirstApplication {
 
    public static void main(String[] args) {
        // Step 5.1: Create Injector
        Injector injector = Guice.createInjector(new FirstModule());
         
        // Step 5.2: Get Object with dependency fulfilled
        UserController userController = injector.getInstance(UserController.class);
         
        // Step 5.3: Use the object
        userController.send();
    }
}

Output của chương trình:

Email message: Dependency injection with Google Guice example

Trong ví dụ trên, chúng ta thấy một số phần thay đổi so với ví dụ ở bài trước:

  • Step 3: chúng ta tạo một class extends từ AbstractModule, lớp này được sử dụng để định nghĩa cách mà một class/ interface sẽ được inject bởi một implement cụ thể nào. Trong ví dụ này, class MessageService sẽ được inject bởi implement EmailService.
  • Step 4: lớp UserController có dependency với MessageService. Trong ví dụ này, chúng ta sử dụng @Inject tại hàm xây dựng (constructor) để inject đối tượng EmailService đã được khai báo tại Step 3.
  • Step 5.1: Khởi tạo Guice Injector với config đã tạo ở Step 3.
  • Step 5.2: Lấy thể hiện đối tượng với các dependency để sử dụng.

4. Các loại Binding

Binding trong Buice là cách chúng ta nói với Guice sẽ tiêm phụ thuộc vào vào một class như thế nào. Để làm được điều này, chúng ta sẽ tạo một class kế thừa từ AbstractModule và override phương thức configure(). Trong phương thức này chúng ta gọi phương thức bind() để xác định mỗi binding của một type ứng với một implement cụ thể. Sau khi đã binding các type trong Module, chúng ta sẽ sử dụng phương thức Guice.createInjector(module) để build một injector.

public class BasicModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(Type.class).to(TypeImpl.class);
    }
}
 
Injector injector = Guice.createInjector(new BasicModule());

Các loại Binding được hỗ trợ bởi Google Guice:

  • Linked Bindings : ánh xạ một type với một implementation cụ thể của nó.
  • Binding Annotations : Trong trường hợp, chúng ta muốn map một Type với nhiều implementation. Chúng ta có tạo một custom Annotation để sử dụng.
  • @Named Binding : một cách khác để map binding mà không cần tạo custom annotation là sử dụng @Named annotation.
  • Constant Bindings : ngoài cách binding với các class/ interface, Guice còn cung cấp một cách để tạo binding với một giá trị của một object hay constant.
  • @Provides Annotation : khi cần khởi tạo một binding value phức tạp.
  • Provider Class : có nhiều @Provide method được định nghĩa trong một module, làm cho class này quá phức tạp, chúng ta có thể move nó sang một class độc lập và implement một Provider interface.
  • Constructor Bindings : khi một class có nhiều constructor, chúng ta có thể tạo binding với constructor cụ thể của object thông qua phương thức toConstructor().
  • In-built Bindings : binding được xây dựng sẵn bên trong Guice, chúng ta chỉ việc gọi @Inject và sử dụng.
  • Just-in-time Bindings : các binding được định nghĩa trong Module, Guice sử dụng chúng bất cứ khi nào cần tiêm phụ thuộc (inject). Trong trường hợp các binding không tồn tại, nó có thể cố gắng tạo ra các binding chỉ khi cần thiết (JIT – just-in-time).

5. Các loại Injection

Dependency Injection Pattern tách hành vi khỏi các phụ thuộc (dependency). Thay vì tìm kiếm các phụ thuộc trực tiếp hoặc từ các factory, mẫu này khuyến nghị rằng các phụ thuộc được truyền vào từ bên ngoài. Quá trình thiết lập các phụ thuộc từ bên ngoài vào một đối tượng được gọi là tiêm (injection).

Các loại Injection được hỗ trợ bởi Google Guice:

  • Constructor Injection : đối tượng được inject thông qua constructor.
  • Method Injection : đối tượng được inject thông qua method.
  • Field Injection : đối tượng được inject thông qua field.
  • Optional Injection : đối tượng được inject được truyền từ bên ngoài thông qua constructor, method, field hoặc sử dụng một giá trị mặc định.
  • On-demand Injection : các method hoặc field có thể được khởi tạo bằng cách sử dụng một instance đã tồn tại thông qua phương thức injector.injectMembers().
  • Static Injection : đối tượng được inject thông qua static field, loại injection này thích hợp khi migrate ứng dụng sử dụng static factory sang Guice.
  • Injecting Provider : thông thường mỗi loại (type) sẽ nhận được chính xác một thể hiện của từng loại phụ thuộc. Đôi khi chúng ta muốn có nhiều hơn một instance của type phụ thuộc. Guice cúng cấp một Provider với phương thức get(). Một instance mới được tạo ra khi phương thức get() được gọi.

6. Scope trong Google Guice

Theo mặc định, Guice trả về một instance mới mỗi lần nó cần cung cấp một giá trị. Hành vi này có thể cấu hình thông qua phạm vi (scope). Scope cho phép chúng ta tái sử dụng instance.

Các loại Scope được hỗ trợ bởi Google Guice:

  • @Singleton : một instance duy nhất được sử dụng trong toàn bộ ứng dụng.
  • @SessionScoped : mỗi session sẽ có một instance khác nhau.
  • @RequestScoped : mỗi request sẽ có một instance khác nhau.

Trên đây là giới thiệu cơ bản về Google Guice. Nó cung cấp một cách tiếp cận mới cho Dependency Injection, tận dụng thế mạnh của Annotation và Generic để giúp chúng ta dễ dàng hơn trong việc quản lý và sử dụng các Dependency, cũng như dễ dàng phát triển các ứng dụng dạng module.

Trong các bài viết tiếp theo chúng ta sẽ cùng tìm hiểu chi tiết hơn về Google Guice với các các loại Binding, Injection, Scope, AOP, …