Post

XML-Based Injection và getBean() API

Nội dung chính của bài viết này xoay quanh 2 khái niệm tách biệt nhau nhưng đều có những vai trò nhất định trong các ứng dụng Java Web. Đừng bỏ lỡ ít phút phí phạm mà không đọc qua nhé.

1. XML-Based Injection trong Spring

Để tìm hiểu về phần này chúng ta xét qua các đoạn code mẫu sau đây để làm cơ sở nền tảng cho việc triển khai demo chi tiết ở mục kế tiếp.

Hãy bắt đầu bằng cách thêm cách thư viện dependency của Spring vào pom.xml:

1
2
3
4
5
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.4.RELEASE</version>         
</dependency>

Ở các phần trước chúng ta đã cùng nhau tìm hiểu qua Dependency Injection và các kiến thức đó có dịp tái sử dụng trong phần này rồi nè.

Giả sử chúng ta có một lớp ứng dụng phụ thuộc vào các xử lý nghiệp vụ của service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IndexApp {
    private IService service;
    // standard constructors/getters/setters
}

Với một Service Interface:
public interface IService {
    public String serve();
}

Interface bên trên  nhiều khả năng để triển khai khác nhau,  dụ như:
public class IndexService implements IService {
    @Override
    public String serve() {
        return "Hello World";
    }
}

Ở đây, IndexApp là một component cấp cao phụ thuộc vào component cấp thấp hơn được gọi là Iservice. Về bản chất, chúng ta đang tách IndexApp khỏi một khỏi một triển khai cụ thể của IService, chúng có thể khác nhau tuỳ thuộc vào các yếu tố khác nhau.

2. Dependency Injection – in Action

Chúng ta có thể thêm các Dependency bằng các cách khác nhau.

Sử dụng Properties

Các dependency có thể được kết nối với nhau thông qua việc sử dụng XML-based configuration:

1
2
3
4
5
6
7
8
9
<bean 
  id="indexService" 
  class="com.thecorleone.di.spring.IndexService" />
     
<bean 
  id="indexApp" 
  class="com.thecorleone.di.spring.IndexApp" >
    <property name="service" ref="indexService" />
</bean>

Có thể thấy, chúng ta đang tạo một instance của IndexService và gán cho nó một id. Theo mặc định, bean là một singleton. Ngoài ra, chúng ta đang tạo một instance của IndexApp. Trong bean này, chúng ta đang inject bean khác bằng phương thức setter.

Sử dụng Constructor

Thay vì inject bean thông qua phương thức setter, chúng ta có thể inject dependency bằng cách sử dụng constructor:

1
2
3
4
5
<bean 
  id="indexApp" 
  class="com.thecorleone.di.spring.IndexApp">
    <constructor-arg ref="indexService" />
</bean>

Sử dụng Static Factory

Chúng ta cũng có thể inject một bean được trả về bởi factory. Để làm được việc này, chúng ta hãy tạo một factory đơn giản để trả về instance của IService dựa trên number được cung cấp làm đối số:

1
2
3
4
5
public class StaticServiceFactory {
    public static IService getNumber(int number) {
        // ...
    }
}

Bây giờ chúng ta hãy sử dụng triển khai ở trên để đưa một bean vào IndexApp bằng cách sử dụng XML-based configuration:

1
2
3
4
5
6
7
8
9
<bean id="messageService"
  class="com.thecorleone.di.spring.StaticServiceFactory"
  factory-method="getService">
    <constructor-arg value="1" />
</bean>   
  
<bean id="indexApp" class="com.thecorleone.di.spring.IndexApp">
    <property name="service" ref="messageService" />
</bean>

Trong ví dụ trên, chúng ta đang gọi phương thức getService tĩnh bằng cách sử dụng factory-method để tạo một bean với id messageService mà chúng ta đưa vào IndexApp.

Sử dụng Factory Method

Hãy xét trong trường hợp này xem một instance factory trả về một instance của IService dựa trên đối số truyền vào. Lần này khác với lần trước – không phải là static nữa.

1
2
3
4
5
public class InstanceServiceFactory {
    public IService getNumber(int number) {
        // ...
    }
}

Và tất nhiên, chúng ta có thể sử dụng triển khai ở trên để đưa một bean vào IndexApp bằng cách sử dụng cấu hình XML:

1
2
3
4
5
6
7
8
9
10
<bean id="indexServiceFactory" 
  class="com.thecorleone.di.spring.InstanceServiceFactory" />
<bean id="messageService"
  class="com.thecorleone.di.spring.InstanceServiceFactory"
  factory-method="getService" factory-bean="indexServiceFactory">
    <constructor-arg value="1" />
</bean>  
<bean id="indexApp" class"com.thecorleone.di.spring.IndexApp">
    <property name="service" ref="messageService" />
</bean>

Trong ví dụ trên, chúng ta đang gọi phương thức getService trên một instance của InstanceServiceFactory bằng cách sử dụng factory-method để tạo một bean với id messageService mà chúng ta đưa vào IndexApp.

Và đây là cách chúng ta có thể truy cập các bean đã cấu hình:

1
2
3
4
5
6
@Test
public void whenGetBeans_returnsBean() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("...");
    IndexApp indexApp = applicationContext.getBean("indexApp", IndexApp.class);
    assertNotNull(indexApp);
}

3. getBean() trong Spring

Nhân tiện trong đoạn code bên trên, chúng ta dành ra ít thời gian để hiểu cho rõ về getBean() luôn nhá – xem xét các biến thể khác nhau của phương thức BeanFactory.getBean().

Nói một cách đơn giản, như tên của phương thức, nó chịu trách nhiệm lấy một instance bean từ Spring container.

Đầu tiên, hãy xác định một vài Spring bean để thử nghiệm. Có một số cách mà chúng ta có thể cung cấp các định nghĩa bean cho Spring container, nhưng trong các ví dụ sau chúng ta sẽ sử dụng các chú thích dựa trên Java config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
class AnnotationConfig {
 
    @Bean(name = {"tiger", "kitty"})
    @Scope(value = "prototype")
    Tiger getTiger(String name) {
        return new Tiger(name);
    }
 
    @Bean(name = "lion")
    Lion getLion() {
        return new Lion("Hardcoded lion name");
    }
 
    interface Animal {}
}

Chúng ta đã tạo ra hai bean. Lion có singleton scope mặc định. Tiger được đặt thành prototype scope một cách rõ ràng.

Hãy lưu ý tên các bean mà chúng ta đã xác định để sử dụng trong phần tiếp theo.

Lấy bean theo Name

Và đây là cách chúng ta lấy Lion bean thông qua tên của nó:

1
2
3
Object lion = context.getBean("lion");
 
assertEquals(Lion.class, lion.getClass());

Lấy Bean theo Name và Type

Cách này chúng ta cần chỉ định cả Name và Type được yêu cầu:

1
Lion lion = context.getBean("lion", Lion.class);

So với phương pháp trước, phương pháp này an toàn hơn vì chúng ta nhận được thông tin về Type không khớp ngay lập tức:

1
2
3
assertThrows(BeanNotOfRequiredTypeException.class, () -> 
    context.getBean("lion", Tiger.class));
}

Lấy Bean theo Type

Với biến thể thứ ba này của getBean(), chúng ta chỉ cần chỉ định Type của Bean là được.

1
Lion lion = context.getBean(Lion.class);

Trong trường hợp này, chúng ta cần đặc biệt chú ý đến một kết quả có thể không rõ ràng:

1
2
3
assertThrows(NoUniqueBeanDefinitionException.class, () -> 
    context.getBean(Animal.class));
}

Trong ví dụ trên, vì cả LionTiger đều triển khai Interface Animal, nên việc chỉ định Type đơn thuần là không đủ để xác định kết quả rõ ràng. Do đó, chúng ta nhận được một NoUniqueBeanDefinitionException.

Lấy Bean theo Name với Constructor Parameters

Ngoài tên của bean, chúng ta có thể truyển thêm các tham số cho constructor:

1
Tiger tiger = (Tiger) context.getBean("tiger", "Siberian");

Phương pháp này hơi khác một chút vì nó chỉ áp dụng cho các bean có prototype scope. Trong trường hợp của các singlelet, chúng ta sẽ nhận được một BeanDefinitionStoreException. Bởi vì một prototype bean sẽ trả về một instance mới được tạo mỗi khi nó được yêu cầu từ container ứng dụng, chúng ta có thể cung cấp các tham số cho constructor khi gọi getBean():

1
2
3
4
5
Tiger tiger = (Tiger) context.getBean("tiger", "Siberian");
Tiger secondTiger = (Tiger) context.getBean("tiger", "Striped");
 
assertEquals("Siberian", tiger.getName());
assertEquals("Striped", secondTiger.getName());

Như chúng ta thấy, mỗi Tiger có một tên khác nhau theo những gì chúng ta đã chỉ định làm tham số thứ hai khi yêu cầu bean.

Lấy Bean theo Type với Constructor Parameters

Phương thức này tương tự với phương thức chúng ta vừa lướt qua, nhưng cần truyền kiểu thay vì tên như đối số đầu tiên:

1
2
3
Tiger tiger = context.getBean(Tiger.class, "Shere Khan");
 
assertEquals("Shere Khan", tiger.getName());

Tương tự như việc truy xuất một bean theo Name với các constructor parameter, phương pháp này chỉ áp dụng cho các bean có prototype scope.

Mặc dù được định nghĩa trong BeanFactory Interface, nhưng phương thức getBean() được truy cập thường xuyên nhất thông qua ApplicationContext. Thông thường, chúng ta không muốn sử dụng phương thức getBean() trực tiếp trong chương trình của mình.

Các Bean nên được quản lý bởi container. Nếu muốn sử dụng một trong số chúng, chúng ta nên dựa vào dependency injection hơn là gọi trực tiếp đến ApplicationContext.getBean(). Bằng cách đó, chúng ta có thể tránh nhầm logic ứng dụng với các chi tiết liên quan đến Framework.

Trong bài viết này, chúng ta đã lướt qua các hướng dẫn và ví dụ về cách inject dependency sử dụng XML-based configuration – một phần của Spring Framework và cách sử dụng getBean() API trong việc triển khai các dự án Java Spring.

Hy vọng nó sẽ hữu ích cho tất cả các bạn.

Bài viết mang tính chất “ghi chú, lưu trữ, chia sẻ và phi lợi nhuận”.
Nếu bạn thấy hữu ích, đừng quên chia sẻ với bạn bè và đồng nghiệp của mình nhé!

Happy coding! 😎 👍🏻 🚀 🔥

Đọc thêm:

This post is licensed under CC BY 4.0 by the author.