Giới thiệu Google Guice – Binding

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

1. Linked Bindings

Linked bindings (ràng buộc)  ánh xạ một type với một implementation của nó. Cú pháp:

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

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.maixuanviet.patterns.creational.googleguice.binding.linked;
 
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 FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class).to(EmailService.class);
        bind(EmailService.class).to(Customer1EmailService.class);
    }
}
class UserController {
    private MessageService messageService;
 
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Linked Binding example");
    }
}
 
public class LinkedBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // Customer 1 email message: Linked Binding example
    }
}

2. 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. Cú pháp:

1
2
3
4
5
6
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
@interface CustomAnnotation {}
 
bind(Type.class).annotatedWith(CustomAnnotation.class).to(Implementation.class);

Trong đó:

  • @BindingAnnotation : đánh dấu đây là một binding annotation.
  • @Target : cho biết phạm vi sử dụng của Annotation này là FIELD, PARAMETER, METHOD.
  • @Retention : để chú thích mức độ tồn tại của annotation này là tại thời điểm runtime.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.maixuanviet.patterns.creational.googleguice.binding.custom_annotation;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
 
@BindingAnnotation
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@interface SmsMessageService {}
 
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println("Email message: " + message);
    }
}
class SmsService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println("Sms message: " + message);
    }
}
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class).to(EmailService.class);
        bind(MessageService.class).annotatedWith(SmsMessageService.class).to(SmsService.class);
    }
}
class User1Controller {
    private MessageService messageService;
 
    @Inject
    public User1Controller(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Default annotation binding example");
    }
}
class User2Controller {
    private MessageService messageService;
 
    @Inject
    public User2Controller(@SmsMessageService MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Sms annotation binding example");
    }
}
 
public class CustomAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        User1Controller user1Controller = injector.getInstance(User1Controller.class);
        user1Controller.send(); // Email message: Default annotation binding example
 
        User2Controller user2Controller = injector.getInstance(User2Controller.class);
        user2Controller.send(); // Sms message: Sms annotation binding example
    }
}

3. @Named Binding

Guice cung cấp một cách khác để map binding mà không cần tạo custom annotation là sử dụng @Named annotation. Cú pháp:

1
bind(Type.class).annotatedWith(Names.named("your_name")).to(Implementation.class);

Ví dụ: tương tự như ví dụ Binding Annotations trên, chúng ta chỉ việc thay thế @SmsMessageService bằng @Named(“SmsMessageService”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.google.inject.name.Named;
import com.google.inject.name.Names;
 
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class).to(EmailService.class);
        bind(MessageService.class).annotatedWith(Names.named("SmsMessageService")).to(SmsService.class);
    }
}
 
class User2Controller {
    private MessageService messageService;
 
    @Inject
    public User2Controller(@Named("SmsMessageService") MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Sms annotation binding example");
    }
}

4. 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. Cú pháp:

1
bind(Type.class).annotatedWith(Names.named("your_name")).toInstance(your_value);

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.maixuanviet.patterns.creational.googleguice.binding.constant;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
 
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(String.class).annotatedWith(Names.named("message")).toInstance("Hello constant bindings");
    }
}
class UserController {
     
    private String message;
 
    @Inject
    public UserController(@Named("message") String message) {
        this.message = message;
    }
 
    public void send() {
        System.out.println(message);
    }
}
public class ConstantAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // Hello constant bindings
    }
}

5. Constant Bindings với Generic Type

Đối với constant với giá trị là một Generic type, chẳng hạn List<String>. Chúng ta có thể sử dụng một trong các cách sau:

  • Sử dụng TypeLiteral.
  • Sử dụng phương thức @Provides.
  • Sử dụng Provide class.

Ví dụ sử dụng TypeLiteral:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.maixuanviet.patterns.creational.googleguice.binding.constant;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
 
class SecondModule extends AbstractModule {
    private final List<String> emails = Arrays.asList("email1", "email2");
     
    @Override
    protected void configure() {
        bind(new TypeLiteral<List<String>>() {}).annotatedWith(Names.named("messages")).toInstance(emails);
    }
}
class EmailController {
     
    private List<String> messages;
 
    @Inject
    public EmailController(@Named("messages") List<String> messages) {
        this.messages = messages;
    }
 
    public void send() {
        System.out.println(messages);
    }
}
public class GenericTypeConstantAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new SecondModule());
        EmailController emailController = injector.getInstance(EmailController.class);
        emailController.send(); // [email1, email2]
    }
}

Đối với @Provides Annotation, chỉ đơn giản như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SecondModule extends AbstractModule {
    private final List<String> emails = Arrays.asList("email1", "email2");
     
    @Override
    protected void configure() {
        // Do nothing
    }
     
    @Provides
    @Named("messages")
    public List<String> providesListOfString() {
        return emails;
    }
}

6. @Provides Annotation

Guice cung cấp cho chúng ta để khởi tạo một binding value phức tạp thông qua @provides annotation.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.maixuanviet.patterns.creational.googleguice.binding.provide_annotation;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provides;
 
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println("Email message: " + message);
    }
}
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        // Do nothing
    }
     
    @Provides
    public MessageService provideMessageService() {
       // Do some complicated task
       return new EmailService();
    }
}
class UserController {
    private MessageService messageService;
 
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Provides Annotation Binding example");
    }
}
public class ProvidesAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // Email message: Provides Annotation Binding example
    }
}

7. Provider Class

Trong trường hợp 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.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.maixuanviet.patterns.creational.googleguice.binding.provide_annotation;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
 
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println("Email message: " + message);
    }
}
class MessageServiceProvider implements Provider<MessageService> {
       @Override
       public MessageService get() {
        // Do some complicated task
           return new EmailService();
       }
    }
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class).toProvider(MessageServiceProvider.class);
    }
}
class UserController {
    private MessageService messageService;
 
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Provider Class Binding example");
    }
}
public class ProviderClassBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // Email message: Provider Class Binding example
    }
}

8. Untargeted Bindings

Chúng ta có thể tạo một binding mà không có target, nghĩa là không cần gọi phương thức to(). Nó hữu ích cho các type cụ thể được chú thích bởi @ImplementedBy hoặc @ProvidedBy. Hai annotation này sẽ được giới thiệu ở phần tiếp theo của bài viết (Just-in-time Bindings).

Một untargeted binding thông báo cho injector biết về Type có thể được inject, vì vậy nó chuẩn bị các dependency một cách eager, không cần đến Just-in-time Bindings.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ImplementedBy(EmailService.class)
interface MessageService {
    void sendMessage(String message);
}
 
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println(message);
    }
}
 
class BaseModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(MessageService.class);
    }
}

Khi xác định binding một Type với một annotation, chúng ta bắt buộc phải xác định target ngay cả khi type và target class là giống nhau.

Ví dụ:

1
2
3
4
5
6
class BaseModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(EmailService.class).annotatedWith(Names.named("emailService")).to(EmailService.class);
    }
}

9. Constructor Bindings

Trong trường hợp một class có nhiều constructor, Guice cung cấp cho chúng ta một cách để tạo binding với constructor cụ thể của object thông qua phương thức toConstructor().

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.maixuanviet.patterns.creational.googleguice.binding.constructor;
 
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
 
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
     
    private String email;
     
    public EmailService() {
        this.email = "Default email";
    }
     
    public EmailService(@Named("email") String email) {
        this.email = email;
    }
     
    @Override
    public void sendMessage(String message) {
        System.out.println(email + ": " + message);
    }
}
class FirstModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(String.class).annotatedWith(Names.named("email")).toInstance("maixuanvietvn@gmail.com");
        // bind(MessageService.class).to(EmailService.class);
        try {
            bind(MessageService.class).toConstructor(EmailService.class.getConstructor(String.class));
        } catch (NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
}
class UserController {
    private MessageService messageService;
     
    @Inject
    public UserController(MessageService messageService) {
        this.messageService = messageService;
    }
 
    public void send() {
        messageService.sendMessage("Linked Binding example");
    }
}
public class ConstructorBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new FirstModule());
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // Hello constant bindings
    }
}

10. Inbuilt Bindings

Guice cung cấp built-in (được xây dựng bên trong Guice) binding cho java.util.logging.Logger class. Tên của Logger được tự động đặt thành tên của lớp mà Logger được đưa vào.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.maixuanviet.patterns.creational.googleguice.binding.builtin;
 
import java.util.logging.Logger;
 
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
 
class UserController {
    private Logger logger;
 
    @Inject
    public UserController(Logger logger) {
        this.logger = logger;
    }
 
    public void log(String message) {
        logger.info(message);
    }
}
public class BuiltInBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        UserController userController = injector.getInstance(UserController.class);
        userController.log("Hello built-in bindings");
    }
}

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). Các binding được định nghĩa trong Module được gọi là các ràng buộc rõ ràng (explicit binding) và có độ ưu tiên cao hơn. Trong khi các JIT binding được gọi là các ràng buộc ngầm (implicit binding). Nếu cả hai loại binding đều tồn tại, explicit binding sẽ được ưu tiên để sử dụng.

Có 3 loại JIT binding:

  • Injectable Constructors : các constructor với phạm vi truy cập không phải là private, không đối số có đủ điều kiện cho các JIT binding. Một cách khác là đánh dấu @Inject annotation với constructor.
  • @ImplementatedBy annotation : cho biết thông tin về class implementation cụ thể, không cần khai báo trong Module.
  • @ProvidedBy annotation : cho biết thông tin về provider của class implementation cụ thể, không cần khai báo trong Module.

11.1. Ví dụ Injectable Constructors

Ví dụ Inbuilt Bindings là một trường hợp Injectable Constructor sử dụng @Inject annotation.

Trong ví dụ bên dưới, chúng ta sẽ sử dụng Injectable Constructor với constructor với phạm vi truy cập không phải là private, không đối số.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.maixuanviet.patterns.creational.googleguice.binding.builtin;
 
import java.util.logging.Logger;
 
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
 
class UserController {
    @Inject
    private Logger logger;
 
    public void log(String message) {
        logger.info(message);
    }
}
public class BuiltInBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        UserController userController = injector.getInstance(UserController.class);
        userController.log("Hello built-in bindings");
    }
}

Nếu trong class trên, bạn cố gắng tạo constructor với phạm vi truy cập private hoặc không có hàm khởi tạo không đối số và không sử dụng @Inject, thì bạn sẽ gặp exception sau:

1
2
3
Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
 
1) Could not find a suitable constructor in com.gpcoder... . Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.

11.2. Ví dụ @ImplementatedBy annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.maixuanviet.patterns.creational.googleguice.binding.implementatedby_annotation;
 
import com.google.inject.Guice;
import com.google.inject.ImplementedBy;
import com.google.inject.Inject;
import com.google.inject.Injector;
 
@ImplementedBy(EmailService.class)
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println(message);
    }
}
class UserController {
    @Inject
    private MessageService messageService;
 
    public void send() {
        messageService.sendMessage("@ImplementatedBy annotation binding example");
    }
}
public class ImplementatedByAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // @ImplementatedBy annotation binding example
    }
}

11.3. Ví dụ @ProvidedBy annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.maixuanviet.patterns.creational.googleguice.binding.providedby_annotation;
 
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.ProvidedBy;
import com.google.inject.Provider;
 
@ProvidedBy(MessageServiceProvider.class)
interface MessageService {
    void sendMessage(String message);
}
class EmailService implements MessageService {
    @Override
    public void sendMessage(String message) {
        System.out.println(message);
    }
}
class MessageServiceProvider implements Provider<MessageService> {
    @Override
    public MessageService get() {
        // Do some complicated task
        return new EmailService();
    }
}
class UserController {
    @Inject
    private MessageService messageService;
 
    public void send() {
        messageService.sendMessage("@ProvidedBy annotation binding example");
    }
}
public class ProvidedByAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // @ProvidedBy annotation binding example
    }
}

12. Overriding Binding trong Guice

Trong Guice, một service đã được binding chúng ta không thể binding một lần thứ 2. Ví dụ:

Các service và module ở project cha (base). Trong project này chúng ta có các ServiceA, ServiceB và các implement tương ứng của nó (ConcreteA, ConcreteB). Chúng ta có một BaseLogic có dependency với ServiceA và ServiceB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public interface ServiceA {
    void log();
}
 
public class ConcreteA implements ServiceA {
    @Override
    public void log() {
        System.out.println("ConcreteA");
    }
}
 
public interface ServiceB {
    void sendMail();
}
 
public class ConcreteB implements ServiceB {
    @Override
    public void sendMail() {
        System.out.println("ConcreteB");
    }
}
 
public class BaseLogic {
 
    @Inject
    private ServiceA serviceA;
 
    @Inject
    private ServiceB serviceB;
 
    public void execute() {
        serviceA.log();
        serviceB.sendMail();
    }
}
 
public class BaseModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceA.class).to(ConcreteA.class);
        bind(ServiceB.class).to(ConcreteB.class);
    }
}
class UserController {
    @Inject
    private MessageService messageService;
 
    public void send() {
        messageService.sendMessage("@ProvidedBy annotation binding example");
    }
}
public class ProvidedByAnnotationBindingExample {
    public static void main(String[] args) {
        Injector injector = Guice.createInjector();
        UserController userController = injector.getInstance(UserController.class);
        userController.send(); // @ProvidedBy annotation binding example
    }
}

Các service và module ở project con (customer1). Trong project con này, chúng ta kế thừa lại tất cả các service ở project base, nhưng có thêm một serviceC và ở ServiceB cần custom lại đôi chút (Customer1ConcreteB). Logic ở project con có khác biệt đôi chút, nó cần sử dụng thêm ServiceC và Customer1ConcreteB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface ServiceC {
    void save();
}
 
public class ConcreteC implements ServiceC {
    @Override
    public void save() {
        System.out.println("ConcreteC");
    }
}
 
public class Customer1ConcreteB extends ConcreteB {
    @Override
    public void sendMail() {
        System.out.println("Customer1ConcreteB");
    }
 
    public void doAnythingElse() {
        // Do any thing else
    }
}
 
public class Customer1Logic extends BaseLogic {
 
    @Inject
    private ServiceC serviceC;
     
    @Override
    public void execute() {
        super.execute();
        serviceC.save();
    }
}
 
public class Customer1Module extends BaseModule {
    @Override
    protected void configure() {
        super.configure();
        bind(ServiceC.class).to(ConcreteC.class);
        bind(ServiceB.class).to(Customer1ConcreteB.class); // ServiceB was already configured
    }
}

Chương trình chính chúng ta như sau:

1
2
3
4
5
6
7
8
public class OverrideBindingExample {
 
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new Customer1Module());
        BaseLogic customer1Logic = injector.getInstance(Customer1Logic.class);
        customer1Logic.execute();
    }
}

Chương trình trên sẽ throw một ngoại lệ CreationException như sau:

1
2
3
4
Exception in thread "main" com.google.inject.CreationException: Unable to create injector, see the following errors:
 
1) A binding to com.maixuanviet....ServiceB was already configured at com.maixuanviet...BaseModule.configure(OverrideBindingExample.java:34).
at com.maixuanviet...Customer1Module.configure(OverrideBindingExample.java:65)

Ngoại lệ trên xảy ra do ServiceB đã được binding ở BaseModule, trong Customer1Module chúng ta gọi binding lại lần nữa cho ServiceB nên xảy ra lỗi.

Để giải quyết vấn đề trên, chúng ta có thể sử dụng một trong những cách sau:

  • Sử dụng Linked binding: cách này có hạn chế là service class ở project con phải extend từ service cha.
  • Thiết kế các module theo cách mà chúng ta không cần override. Có nghĩa là chia nhỏ module, các service có thể override tách ra một module khác, trong project con chúng ta sẽ binding các module chung và binding một module khác ở con.
  • Sử dụng phương thức override module được hỗ trợ bởi Guice: Modules.override(basic_module).with(override_module)

12.1. Sử dụng Linked binding

Đơn giản chỉ việc sửa lại Customer1Module như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.maixuanviet.patterns.creational.googleguice.binding.override.module_v2;
 
import com.maixuanviet.patterns.creational.googleguice.binding.override.base_service.ConcreteB;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.ConcreteC;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.Customer1ConcreteB;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.ServiceC;
 
public class Customer1Module extends BaseModule {
    @Override
    protected void configure() {
        super.configure();
        bind(ServiceC.class).to(ConcreteC.class);
        bind(ConcreteB.class).to(Customer1ConcreteB.class); // ServiceB -&gt; ConcreteB -&gt; Customer1ConcreteB
    }
}

Thực thi chương trình trên chúng ta có kết quả như sau:

1
2
3
ConcreteA
Customer1ConcreteB
ConcreteC

12.2. Chia nhỏ module

Chúng ta cố gắng gom nhóm các service ra thành từng Module riêng lẻ (trường hợp lý tưởng là ở project con không cần override lại module), ở project con chỉ việc tạo injector với các Module cần thiết.

Chia nhỏ BaseModule thành BaseModule1 và BaseModule2:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BaseModule1 extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceA.class).to(ConcreteA.class);
    }
}
 
public class BaseModule2 extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceB.class).to(ConcreteB.class);
    }
}

Ở project con chỉ khai báo các binding ứng với các service mới của nó.

1
2
3
4
5
6
7
public class Customer1Module extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceC.class).to(ConcreteC.class);
        bind(ServiceB.class).to(Customer1ConcreteB.class);
    }
}

Chương trình chính của chúng ta bây giờ như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.maixuanviet.patterns.creational.googleguice.binding.override.module_v3;
 
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.maixuanviet.patterns.creational.googleguice.binding.override.base_service.BaseLogic;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.Customer1Logic;
 
public class OverrideBindingExample {
 
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new BaseModule1(), new Customer1Module());
        BaseLogic customer1Logic = injector.getInstance(Customer1Logic.class);
        customer1Logic.execute();
    }
}

12.3. Sử dụng Modules.override()

Giữ nguyên khai báo Module ở lớp cha. Ở project con chỉ khai báo các binding ứng với các service mới của nó.

1
2
3
4
5
6
7
8
9
10
11
12
13
import com.google.inject.AbstractModule;
import com.maixuanviet.patterns.creational.googleguice.binding.override.base_service.ServiceB;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.ConcreteC;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.Customer1ConcreteB;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.ServiceC;
 
public class Customer1Module extends AbstractModule {
    @Override
    protected void configure() {
        bind(ServiceC.class).to(ConcreteC.class);
        bind(ServiceB.class).to(Customer1ConcreteB.class);
    }
}

Sử dụng phương thức Modules.override() để override các binding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.maixuanviet.patterns.creational.googleguice.binding.override.module_v4;
 
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Modules;
import com.maixuanviet.patterns.creational.googleguice.binding.override.base_service.BaseLogic;
import com.maixuanviet.patterns.creational.googleguice.binding.override.customer1_service.Customer1Logic;
 
public class OverrideBindingExample {
 
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(
                Modules.override(new BaseModule()).with(new Customer1Module()));
        BaseLogic customer1Logic = injector.getInstance(Customer1Logic.class);
        customer1Logic.execute();
    }
}

Trên đây là các kiến thức cơ bản về các loại Binding được hỗ trợ bởi Google Guice. Trong bài viết tiếp theo chúng ta sẽ cùng tìm hiểu chi tiết về các loại Injection, Scope trong Guice.

Be the first to comment

Leave a Reply

Your email address will not be published.


*