GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Design pattern Giới thiệu Google Guice – Injection, Scope

Giới thiệu Google Guice – Injection, Scope

Đăng vào 07/02/2019 . Được đăng bởi GP Coder . 5284 Lượt xem . Toàn màn hình

Trong bài trước, tôi đã giới thiệu với các bạn các loại binding được hỗ trợ bởi Google Guice. Trong bài này, chúng ta sẽ cùng tìm hiểu về các loại Injection và Scope được hỗ trợ bởi Guice.

Nội dung

  • 1 Injection
  • 2 Scope

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).

Trong Guice để inject một đối tượng chúng ta sẽ sử dụng @Inject annotation.

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.

Constructor Injection


class UserController {
    private MessageService messageService;
 
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Linked Binding example");
    }
}

Method Injection


class UserController {
    
	private MessageService messageService;

    @Inject
    public void setMessageService(MessageService messageService) {
		this.messageService = messageService;
	}

	public void send() {
        messageService.sendMessage("Linked Binding example");
    }
}

Field Injection


class UserController {
    @Inject
    private MessageService messageService;
 
    public void send() {
        messageService.sendMessage("Linked Binding example");
    }
}

Optional Injection


package com.gpcoder.patterns.creational.googleguice.injection.optional;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

interface MessageService {
	void sendMessage(String message);
}
class EmailService implements MessageService {
	@Override
	public void sendMessage(String message) {
		System.out.println("Email message: " + message);
	}
}
class Customer1EmailService extends EmailService {
	@Override
	public void sendMessage(String message) {
		System.out.println("Customer 1 email message: " + message);
	}
}
class BaseModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(MessageService.class).to(Customer1EmailService.class);
	}
}
class UserController {
	@Inject(optional=true)
	private MessageService messageService = new EmailService();
 
    public void send() {
        messageService.sendMessage("Optional injection example");
    }
}
public class OptionalInjectionExample {
	public static void main(String[] args) {
		// Inject by default
		Injector injector = Guice.createInjector();
		UserController userController = injector.getInstance(UserController.class);
		userController.send(); // Email message: Optional injection example
		
		// Inject by config
		injector = Guice.createInjector(new BaseModule());
		userController = injector.getInstance(UserController.class);
		userController.send(); // Customer 1 email message: Optional injection example
	}
}

On-demand Injection


package com.gpcoder.patterns.creational.googleguice.injection.on_demand;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import com.google.inject.name.Names;

@ImplementedBy(EmailService.class)
interface MessageService {
	void sendMessage(String message);
}
class EmailService implements MessageService {

	@Inject
	@Named("signature")
	private String signature;
	
	@Override
	public void sendMessage(String message) {
		System.out.println("Email message: " + message + " by " + signature);
	}
}

class BaseModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(String.class).annotatedWith(Names.named("signature")).toInstance("gpcoder.com");
	}
}
class UserController {
	
	private MessageService messageService;
 
    public UserController(MessageService messageService) {
		this.messageService = messageService;
	}

	public void send() {
        messageService.sendMessage("On-Demand injection example");
    }
}
public class OnDemandInjectionExample {
	public static void main(String[] args) {
		// Use exiting object
		EmailService emailService = new EmailService();

		// Create injector
		Injector injector = Guice.createInjector(new BaseModule());
		
		// initialize methods and fields
		injector.injectMembers(emailService);
		
		// Use emailService as a parameter of constructor  
		UserController userController = new UserController(emailService);
		userController.send(); // Email message: On-Demand injection example by gpcoder.com
	}
}

Như bạn thấy, chúng ta sử dụng phương thức injectMembers() để inject các field và method có đánh dấu là @Inject của EmailService đã được khởi tạo. Sau khi đã inject đầy đủ các giá trị, chúng ta có thể sử dụng trong UserController như một cách thông thường, không cần sử dụng injector cho UserController.

Static Injection

Trong ví dụ bên dưới tôi sẽ giới thiệu với các bạn cách Migrate một Static Factory Class sang Guice.

Giả sử chúng ta có một class đang sử sụng Static Factory Pattern như bên dưới:


package com.gpcoder.patterns.creational.googleguice.injection.static_injection.factory_pattern;

interface Bank {
    String getBankName();
}

class TPBank implements Bank {
    @Override
    public String getBankName() {
        return "TPBank";
    }
}

class VietcomBank implements Bank {
    @Override
    public String getBankName() {
        return "VietcomBank";
    }
}

enum BankType {
    VIETCOMBANK, TPBANK;
}

class BankFactory {
	 
	private static Bank tpBank = new TPBank();
	private static Bank vietcomBank = new VietcomBank();
	
	private BankFactory() {
		throw new UnsupportedOperationException();
	}
 
    public static final Bank getBank(BankType bankType) {
        switch (bankType) {
 
        case TPBANK:
            return tpBank;
 
        case VIETCOMBANK:
            return vietcomBank;
 
        default:
            throw new IllegalArgumentException("This bank type is unsupported");
        }
    }
}

public class StaticInjectionExample {
	public static void main(String[] args) {
		Bank bank = BankFactory.getBank(BankType.TPBANK);
        System.out.println(bank.getBankName()); // TPBank
	}
}

Chuyển sang Guice như sau:


package com.gpcoder.patterns.creational.googleguice.injection.static_injection.guice;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.google.inject.name.Names;

interface Bank {
    String getBankName();
}

class TPBank implements Bank {
    @Override
    public String getBankName() {
        return "TPBank";
    }
}

class VietcomBank implements Bank {
    @Override
    public String getBankName() {
        return "VietcomBank";
    }
}

enum BankType {
    VIETCOMBANK, TPBANK;
}

class BankFactory {
	
	@Inject
	@Named("tpBank")
	private static Bank tpBank ;
	
	@Inject
	@Named("vietcomBank")
	private static Bank vietcomBank;
	
	private BankFactory() {
		throw new UnsupportedOperationException();
	}
 
    public static final Bank getBank(BankType bankType) {
        switch (bankType) {
 
        case TPBANK:
            return tpBank;
 
        case VIETCOMBANK:
            return vietcomBank;
 
        default:
            throw new IllegalArgumentException("This bank type is unsupported");
        }
    }
}

class BaseModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(Bank.class).annotatedWith(Names.named("tpBank")).toInstance(new TPBank());
		bind(Bank.class).annotatedWith(Names.named("vietcomBank")).toInstance(new VietcomBank());
		requestStaticInjection(BankFactory.class);
	}
}

public class StaticInjectionExample {
	public static void main(String[] args) {
		Guice.createInjector(new BaseModule());
		Bank bank = BankFactory.getBank(BankType.TPBANK);
        System.out.println(bank.getBankName()); // TPBank
	}
}

Lưu ý: một phương thức quan trọng để hỗ trợ Injection cho static field là phương thức requestStaticInjection() trong class Module.

Injecting Provider

Các bạn hãy xem lại ví dụ @Provides Annotation và Provider Class ở bài viết trước.

@AssistedInject

@AssistedInject cho phép chúng ta cấu hình một dynamic factory để gán giá trị các parameter của một class thay vì code chúng một cách thủ công. Nó thường được sử dụng khi chúng ta có một object mà có các dependency được inject bởi Guice và các parameter được xác định trong quá trình khởi tạo object.

Để sử dụng @AssistedInject chúng ta cần thêm thư viện com.google.inject.extensions vào:


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

Ví dụ chúng ta có một class UserController. Class này có dependency với MessageService và có 2 giá trị callerId, signature được xác định trong quá trình sử dụng UserController.

Chúng ta có thể sử dụng cách inject thông thường được hỗ trợ bởi Guice và cung cấp thêm các setter để gán các giá trị cho các tham số cho UserController. Tuy nhiên, cách này không được tốt, do phía client có thể quên gán các giá trị cần thiết.

Chúng ta sẽ sử dụng @AssistedInject – một extension hỗ trợ Guice để giải quyết vấn đề này.

Các bước cần thiết để sử dụng @AssistedInject:

  • Tạo một Factory với phương thức có thể gán các parameter cần được xác định trong quá trình sử dụng với kiểu trả về là một class hoặc interface của client sử dụng (target). Lưu ý: thứ tự các parameter phải đúng với thứ tự parameter của constructor trong target.
  • Khai báo Factory trong Module.
  • Đánh dấu @Assisted cho các parameter (có thể xác định name cho param nếu cần thiết).
  • Khởi tạo Factory từ Injector, gán giá trị cho các paramter và sử dụng target nhận được.

Chương trình của chúng ta như sau:


package com.gpcoder.patterns.creational.googleguice.injection.assisted_inject;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryModuleBuilder;

// 01. Defining a factory
// Create an interface whose methods return the constructed type, or any of its supertypes. 
// The method's parameters are the arguments required to build the constructed type.
interface CallerFactory {
	UserController create(Integer callerId, String signature);
}

interface MessageService {
	void sendMessage(String message);
}

class EmailService implements MessageService {
	@Override
	public void sendMessage(String message) {
		System.out.println(message);
	}
}

class BasicModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(MessageService.class).to(EmailService.class);
		// 02. Configuring simple factories
		install(new FactoryModuleBuilder().build(CallerFactory.class));
	}
}

class UserController {
	private MessageService messageService;
	private Integer callerId;
	private String signature;

	// 03. @Assisted: Annotates an injected parameter or field whose value comes
	// from an argument to a factory method
	@Inject
	public UserController(@Assisted Integer callerId, @Assisted String signature, MessageService messageService) {
		this.callerId = callerId;
		this.signature = signature;
		this.messageService = messageService;
	}

	public void send() {
		messageService.sendMessage("CallerId " + callerId + ": Assisted Inject example by " + signature);
	}
}

public class AssistedInjectExample {
	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new BasicModule());

		// 04. Inject a factory that combines the caller's arguments with
		// injector-supplied values to construct objects
		CallerFactory callerFactory = injector.getInstance(CallerFactory.class);
		UserController userController = callerFactory.create(1, "gpcoder.com");
		userController.send(); // CallerId 1: Assisted Inject example by gpcoder.com
	}
}

Khi chúng ta thêm cấu hình install(new FactoryModuleBuilder().build(CallerFactory.class)) trong class Module, Guice sẽ tự thêm implement cho CallerFactory và handle các parameter cần thiết.

Chi tiết về @AssistedInject các bạn tham khảo thêm trên Wiki của Guice.

Guice quyết định inject như thế nào?

Đầu tiên là lựa chọn Constructor. Khi Guice khởi tạo một kiểu (Type) bằng cách sử dụng constructor của nó, nó sẽ quyết định constructor nào được gọi dựa theo các quy tắc sau:

  • Tìm constructor được đánh dấu với @Inject. Nếu có nhiều @Inject cho constructor hoặc constructor được đánh dấu là @Inject(optional=true) hoặc @Named thì sẽ throw một exception.
  • Nếu không có constructor nào được đánh dấu vơi @Inject. Nó sẽ  tìm constructor không có tham số và không phải là private. Nếu constructor được đánh dấu là @Named thì sẽ throw một exception.
  • Nếu không thõa mãn bất kỳ quy tắc nào ở trên sẽ throw một exception.

Sau khi đã có được thể hiện, nó sẽ tiến hành inject cho các field và method theo thứ tự. Tất cả field được inject trước, sau đó đến các method. Các field và method ở class cha sẽ được inject trước class con.

Nhận được gì sau khi Guice đã inject?

Guice inject các instance cho field và method:

  • Tất cả các value được binding với toInstance(), được inject tại thời điểm tạo injector.
  • Tất cả các provider được binding với toProvider(Provider), được inject tại thời điểm tạo injector.
  • Tất cả các instance được đăng ký để inject sử dụng requestInjection, được inject tại thời điểm tạo injector.
  • Tất cả các argument của Injector.injectMembers khi phương thức được gọi.
  • Tất cả các value được khởi tạo bởi Guice thông qua constructor được khởi tạo ngay sau khi đối tượng được tạo. Nó chỉ được inject 1 lần.

Guice inject các instance cho các static method và field:

  • Tất cả các class được đăng ký static injection thông qua requestStaticInjection.

Scope

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.
  • Custom scopes: ngoài các scope được hỗ trợ sẵn bởi Guice như trên, chúng ta có thể tự xây dựng một Scope cho riêng mình.

Các cách để khai báo scope

Chúng ta có thể khai báo scope bằng một trong các cách sau:

  • Class level
  • Configuration level
  • Method level

package com.gpcoder.patterns.creational.googleguice.scope.level;

import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;

interface MessageService {
	void sendMessage(String message);
}

// Class level
// @Singleton
class EmailService implements MessageService {
	@Override
	public void sendMessage(String message) {
		System.out.println(message);
	}
}

class BasicModule extends AbstractModule {
	// Configuration level
	@Override
	protected void configure() {
		bind(MessageService.class).to(EmailService.class); // .in(Singleton.class);
	}

	// Method level
	// @Singleton
	@Provides
	public MessageService provideMessageService() {
		return new EmailService();
	}
}

Ví dụ @Singleton

Trước hết chúng ta sẽ xem kết quả của chương trình dưới đây khi không sử dụng @Singleton scope


package com.gpcoder.patterns.creational.googleguice.scope.withoutsingleton;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

interface MessageService {
	void sendMessage(String message);
}

class EmailService implements MessageService {

	@Override
	public void sendMessage(String message) {
		System.out.println(message + ": " + System.identityHashCode(this));
	}
}

class FirstModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(MessageService.class).to(EmailService.class);
	}
}

class UserController {
	private MessageService messageService;

	@Inject
	public UserController(MessageService messageService) {
		this.messageService = messageService;
	}

	public void send() {
		messageService.sendMessage("Singleton Scoped example");
	}
}

public class GuiceScopedExample {
	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new FirstModule());

		UserController userController = injector.getInstance(UserController.class);
		userController.send();

		userController = injector.getInstance(UserController.class);
		userController.send();
	}
}

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


Singleton Scoped example: 861842890
Singleton Scoped example: 553871028

Như bạn thấy, chúng ta có 2 giá trị hash code khác nhau cho instance của EmailService. Điều này có nghĩa là EmailService sẽ được tạo mới mỗi khi nó cần được inject.

Bây giờ chúng ta sẽ thêm cấu hình Singleton vào chương trình trên:


class FirstModule extends AbstractModule {
	@Override
	protected void configure() {
		// Singleton: one instance (per Injector) to be reused for all injections for
		// that binding
		bind(MessageService.class).to(EmailService.class).in(Singleton.class);
	}
}

Chạy lại chương trình trên, chúng ta có kết quả như sau:


Singleton Scoped example: 817406040
Singleton Scoped example: 817406040

Như bạn thấy, bây giờ class EmailService chỉ được khởi tạo một lần duy nhất.

Mặc định, Guice sẽ sử dụng cơ chế lazy cho Singleton, chúng ta có thể chỉ định nó sử dụng cơ chế eager một cách tường minh với asEagerSingleton().

 
bind(Type.class).to(Implementation.class).asEagerSingleton(); 

Tài liệu tham khảo:

  • https://github.com/google/guice
  • https://www.tutorialspoint.com/guice/index.htm
  • https://www.baeldung.com/guice
5.0
01
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Creational Pattern, Design pattern, Java library Được gắn thẻ: Creational Design Pattern, Dependency Injection, Design pattern, Google Guice

Giới thiệu Google Guice – Binding
Giới thiệu Aspect Oriented Programming (AOP)

Có thể bạn muốn xem:

  • Giới thiệu Java Logging (18/04/2019)
  • Hướng dẫn Java Design Pattern – Adapter (15/10/2018)
  • Giới thiệu thư viện phổ biến của java – Commons Lang3 (16/04/2018)
  • Hướng dẫn Java Design Pattern – Object Pool (08/10/2018)
  • Hướng dẫn Java Design Pattern – State (31/12/2018)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (98056 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (97696 lượt xem)
  • Giới thiệu Design Patterns (87759 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (86428 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83831 lượt xem)

Nội dung bài viết

  • 1 Injection
  • 2 Scope

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link