Spring Framework được xây dựng dựa trên nguyên lý Inversion of Control (IoC). Kỹ thuật được sử dụng để triển khai IoC trong các ứng dụng chính là tiêm phụ thuộc (Dependency Injection).
Trong bài viết này, chúng ta sẽ đi sâu vào cơ chế hoạt động của Spring IoC Container và cách quản lý Spring Bean.

Spring IoC
Dưới đây là mục lục giúp bạn dễ dàng điều hướng đến các phần khác nhau trong hướng dẫn Spring IoC:
- Spring IoC Container
- Spring Bean
- Phạm vi (Scope) của Spring Bean
- Cấu hình Spring Bean
- Ví dụ Spring IoC và Spring Bean
- Cấu hình Spring Bean dựa trên XML
- Cấu hình Spring Bean dựa trên Annotation
- Cấu hình Spring Bean dựa trên Java
Spring IoC Container
Spring IoC là cơ chế giúp đạt được sự liên kết lỏng lẻo (loose-coupling) giữa các đối tượng và các phụ thuộc của chúng. Để thực hiện việc liên kết lỏng và ràng buộc động các đối tượng tại thời điểm chạy, các phụ thuộc của đối tượng được inject bởi các đối tượng assembler khác.
Spring IoC container chính là thành phần đảm nhiệm việc inject các phụ thuộc vào đối tượng và chuẩn bị đối tượng đó sẵn sàng để sử dụng.
Chúng ta đã tìm hiểu cách sử dụng Spring Dependency Injection để hiện thực nguyên lý Inversion of Control (IoC) trong các ứng dụng. Các lớp của Spring IoC container chủ yếu nằm trong hai gói: org.springframework.beans và org.springframework.context.
Spring IoC container cung cấp nhiều cơ chế để tách rời phụ thuộc giữa các đối tượng.
BeanFactorylà interface gốc đại diện cho Spring IoC containerApplicationContextlà interface mở rộng từBeanFactory, bổ sung thêm nhiều tính năng nâng cao như Spring AOP, quốc tế hóa (i18n), và nhiều tiện ích khác.
Một số interface con quan trọng của ApplicationContext bao gồm ConfigurableApplicationContext và WebApplicationContext.
Spring Framework cung cấp nhiều lớp triển khai ApplicationContext hữu ích mà chúng ta có thể sử dụng để lấy Spring context và từ đó truy xuất các Spring Bean. Một số lớp ApplicationContext thường được sử dụng bao gồm:
AnnotationConfigApplicationContext: Nếu chúng ta sử dụng Spring trong các ứng dụng Java độc lập và dùng annotation để cấu hình, thì có thể sử dụng lớp này để khởi tạo container và lấy các đối tượng bean.ClassPathXmlApplicationContext: Nếu ứng dụng độc lập sử dụng tệp cấu hình Spring bean ở trong classpath, thì có thể dùng lớp này để tải tệp cấu hình và lấy đối tượng container.FileSystemXmlApplicationContext: Tương tự nhưClassPathXmlApplicationContext, nhưng tệp cấu hình XML có thể được tải từ bất kỳ vị trí nào trong hệ thống tập tin.AnnotationConfigWebApplicationContextvàXmlWebApplicationContextđược sử dụng cho các ứng dụng web.
Thông thường, nếu bạn làm việc với ứng dụng Spring MVC và ứng dụng đã được cấu hình để sử dụng Spring Framework, IoC container sẽ được khởi tạo khi ứng dụng khởi động. Khi bean được yêu cầu, các phụ thuộc sẽ được tự động inject. Tuy nhiên, đối với một ứng dụng độc lập, bạn cần khởi tạo container tại một điểm nào đó trong ứng dụng, sau đó sử dụng nó để lấy các Spring Bean.
Spring Bean
Spring Bean không phải là một khái niệm đặc biệt. Bạn có thể hiểu đơn giản là bất kỳ đối tượng nào được khởi tạo thông qua Spring container đều được gọi là Spring Bean. Bất kỳ lớp Java POJO nào cũng có thể trở thành một Spring Bean nếu nó được cấu hình để khởi tạo thông qua container bằng cách cung cấp metadata cấu hình.
Phạm vi của Spring Bean
Có năm phạm vi được định nghĩa cho Spring Bean:
singleton– Chỉ một instance của bean được tạo ra trong mỗi container. Đây là phạm vi mặc định. Nếu sử dụng phạm vi này, cần đảm bảo bean không chứa các biến chia sẻ giữa các thread, tránh gây ra lỗi dữ liệu.prototype– Một instance mới sẽ được tạo mỗi khi bean được yêu cầu.request– Tương tự như phạm vi củaprototype, nhưng được dùng cho ứng dụng web. Mỗi HTTP request sẽ tạo ra một bean mới.session– Một bean mới được tạo cho mỗi HTTP session.global-session– Dùng để tạo bean phiên toàn cục trong các ứng dụng Portlet.
Spring Framework có thể mở rộng để hỗ trợ các phạm vi tùy chỉnh. Tuy nhiên, trong đa số trường hợp, năm phạm vi trên là đủ để đáp ứng nhu cầu.
Cấu hình Spring Bean
Spring cung cấp ba phương thức chính để cấu hình các bean sử dụng trong ứng dụng:
- Cấu hình bằng Annotation – Sử dụng các annotation như
@Service,@Component. Phạm vi có thể được chỉ định thông qua annotation@Scope. - Cấu hình bằng XML – Tạo file XML cấu hình các bean. Trong các ứng dụng Spring MVC, cấu hình XML có thể được tải tự động thông qua cấu hình trong file
web.xml. - Cấu hình bằng Java – Từ phiên bản Spring 3.0, chúng ta có thể cấu hình các bean bằng mã Java. Các annotation quan trọng bao gồm
@Configuration,@ComponentScanvà@Bean.
Dự án ví dụ Spring IoC và Spring Bean
Chúng ta sẽ tìm hiểu các khía cạnh khác nhau của Spring IoC container và cấu hình Spring Bean thông qua một ví dụ dự án đơn giản. Trong ví dụ này, tôi sẽ tạo một dự án Spring MVC trong Spring Tool Suite. Nếu bạn chưa quen với Spring Tool Suite hoặc Spring MVC, bạn có thể tham khảo thêm bài viết “Hướng dẫn Spring MVC với Spring Tool Suite”.
Cấu trúc dự án hoàn chỉnh được mô tả trong hình ảnh dưới đây.

Chúng ta hãy lần lượt tìm hiểu các thành phần khác nhau trong dự án Spring IoC và Spring Bean.
- Cấu hình Spring Bean bằng XML
MyBean là một lớp Java POJO đơn giản.
package com.journaldev.spring.beans;
public class MyBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Tệp cấu hình Spring XML
Mã trong tệp servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="<https://www.springframework.org/schema/mvc>"
xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>"
xmlns:beans="<https://www.springframework.org/schema/beans>"
xmlns:context="<https://www.springframework.org/schema/context>"
xsi:schemaLocation="<https://www.springframework.org/schema/mvc> <https://www.springframework.org/schema/mvc/spring-mvc.xsd>
<https://www.springframework.org/schema/beans> <https://www.springframework.org/schema/beans/spring-beans.xsd>
<https://www.springframework.org/schema/context> <https://www.springframework.org/schema/context/spring-context.xsd>">
<!-- DispatcherServlet Context: định nghĩa cơ sở hạ tầng xử lý request cho servlet này -->
<!-- Kích hoạt mô hình lập trình @Controller của Spring MVC -->
<annotation-driven />
<!-- Xử lý các yêu cầu HTTP GET đến /resources/** bằng cách phục vụ hiệu quả tài nguyên tĩnh trong thư mục ${webappRoot}/resources -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Phân giải view được chọn bởi @Controller sang tài nguyên .jsp trong thư mục /WEB-INF/views -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.journaldev.spring" />
<beans:bean name="myBean" class="com.journaldev.spring.beans.MyBean" scope="singleton" ></beans:bean>
</beans:beans>
Lưu ý, MyBean được cấu hình bằng phần tử bean với phạm vi là singleton.
- Cấu hình Spring Bean bằng Annotation
package com.journaldev.spring.beans;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.web.context.WebApplicationContext;
@Service
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class MyAnnotatedBean {
private int empId;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
}
MyAnnotatedBean được cấu hình thông qua annotation @Service và phạm vi được đặt là Request.
- Lớp Controller trong Spring IoC
Lớp HomeController sẽ xử lý các yêu cầu HTTP cho trang chủ của ứng dụng. Các Spring Bean sẽ được inject vào lớp controller này thông qua container WebApplicationContext.
package com.journaldev.spring.controller;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.journaldev.spring.beans.MyAnnotatedBean;
import com.journaldev.spring.beans.MyBean;
@Controller
@Scope("request")
public class HomeController {
private MyBean myBean;
private MyAnnotatedBean myAnnotatedBean;
@Autowired
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
@Autowired
public void setMyAnnotatedBean(MyAnnotatedBean obj) {
this.myAnnotatedBean = obj;
}
/**
* Trả về tên của view trang chủ để render.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
System.out.println("MyBean hashcode=" + myBean.hashCode());
System.out.println("MyAnnotatedBean hashcode=" + myAnnotatedBean.hashCode());
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate );
return "home";
}
}
- Tệp cấu hình triển khai (Deployment Descriptor)
Cần cấu hình ứng dụng để Spring Framework có thể tải metadata cấu hình và khởi tạo context.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="<https://java.sun.com/xml/ns/javaee>"
xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>"
xsi:schemaLocation="<https://java.sun.com/xml/ns/javaee> <https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd>">
<!-- Định nghĩa Root Spring Container dùng chung cho tất cả các Servlet và Filter -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Tạo Spring Container dùng chung cho tất cả các Servlet và Filter -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Xử lý các request của ứng dụng -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Hầu hết cấu hình trên là mã mẫu được công cụ STS tự động tạo ra.
- Chạy ứng dụng Spring IoC Bean Example
Khi bạn khởi chạy ứng dụng web, trang chủ sẽ được tải và trong console sẽ in ra các log sau khi bạn làm mới trang nhiều lần:
MyBean hashcode=118267258
MyAnnotatedBean hashcode=1703899856
MyBean hashcode=118267258
MyAnnotatedBean hashcode=1115599742
MyBean hashcode=118267258
MyAnnotatedBean hashcode=516457106
Lưu ý, MyBean được cấu hình là singleton, vì vậy container luôn trả về cùng một instance và giá trị hashcode không đổi. Ngược lại, mỗi request sẽ tạo ra một instance mới cho MyAnnotatedBean, nên hashcode luôn khác nhau.
- Cấu hình Spring Bean bằng Java
Đối với các ứng dụng độc lập, có thể sử dụng cấu hình annotation hoặc XML. Yêu cầu duy nhất là cần khởi tạo context ở một điểm nào đó trong chương trình trước khi sử dụng.
package com.journaldev.spring.main;
import java.util.Date;
public class MyService {
public void log(String msg){
System.out.println(new Date() + "::" + msg);
}
}
MyService là một lớp Java đơn giản chứa một số phương thức.
package com.journaldev.spring.main;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value="com.journaldev.spring.main")
public class MyConfiguration {
@Bean
public MyService getService(){
return new MyService();
}
}
Đây là lớp cấu hình dùng annotation để khởi tạo Spring container.
package com.journaldev.spring.main;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyMainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
MyConfiguration.class);
MyService service = ctx.getBean(MyService.class);
service.log("Hi");
MyService newService = ctx.getBean(MyService.class);
System.out.println("service hashcode=" + service.hashCode());
System.out.println("newService hashcode=" + newService.hashCode());
ctx.close();
}
}
Đây là chương trình kiểm thử đơn giản trong đó chúng ta khởi tạo AnnotationConfigApplicationContext và sử dụng phương thức getBean() để lấy instance của MyService. Lưu ý, phương thức getBean được gọi hai lần và chúng ta in ra giá trị hashcode. Vì không khai báo phạm vi cho MyService, mặc định nó là singleton, do đó hai giá trị hashcode sẽ giống nhau. Khi chạy ứng dụng trên, output console xác nhận như sau:
Sat Dec 28 22:49:18 PST 2013::Hi
service hashcode=678984726
newService hashcode=678984726
Nếu bạn muốn sử dụng cấu hình bằng XML, chỉ cần tạo tệp cấu hình XML cho Spring và khởi tạo context bằng đoạn mã sau:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
MyService app = context.getBean(MyService.class);
Trên đây là toàn bộ nội dung ví dụ về Spring IoC, phạm vi của Spring Bean và chi tiết cấu hình. Bạn có thể tải dự án mẫu Spring IoC và Spring Bean về từ liên kết bên dưới và thực hành để hiểu rõ hơn về bài viết này.