Tiếp tục đến với phần 2 của loạt bài viết này, hôm nay chúng ta sẽ học về khái niệm ApplicationContext, Bean (Spring core).
Dành cho các bạn chưa đọc phần 1 ở đây.
2.Spring Core
Quay lại ví dụ ở phần 1, chúng ta đã sử dụng class ApplicationContext để khởi tạo và quản lý vòng đời của FileService, inject giá trị thực thể GooleDriveService vào property FileService của FileController thông qua constructor.
Trong Spring, việc quản lý khởi tạo các Object như FileService ( các object này được gọi là Bean ) được thực hiện bởi đối tượng ApplicationContext. Và công việc này cũng là nội dung chính của Spring Core Component. ApplicationContext sẽ đọc config (từ file XML hoặc annotation) để khởi tạo, Inject các giá trị cho Bean, và trả về instance của Bean.
Bây giờ chúng ta sẽ xây dựng lại ứng dụng ở phần 1 bằng cách sử dụng Spring để hiểu rõ hơn về cách hoạt động. Mình sẽ giải thích code sau.
- Khởi tạo project maven, add dependencies :
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
</dependencies>
Bạn nào chưa biết sử dụng maven thì có thể đọc ở bài mình viết về maven.
2.Tạo 1 interface FileService ( đã tạo ở phần 1)
public interface FileService {
void uploadFile(String file);
}
3.Tạo class GoogleDriveService thực thi interface ở trên
public class GoogleDriveService implements FileService {
public void uploadFile(String file) {
System.out.println("Upload " + file + " to google drive");
}
}
4.Tạo class controller
public class FileController {
private FileService fileService;
public void doUploadFile(String file) {
this.fileService.uploadFile(file);
}
public FileService getFileService() {
return fileService;
}
public void setFileService(FileService fileService) {
this.fileService = fileService;
}
}
Các đoạn code trên không có gì thay đổi so với phần 1. Ngoài trừ ở FileController không có constructor để inject giá trị fileService. Thực tế Spring sẽ thực hiện inject giá trị fileService này thông qua setter.
Rồi đến phần quan trọng nhất.
- Tạo file spring config. Bạn tạo 1 file có tên là (thực ra tên j cũng được) spring-config.xml ở folder resources.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config />
<bean id="fileController" class="hoai.le.spring.core.controllers.FileController">
<property name="fileService" ref="fileService" />
</bean>
<bean id="fileService" class="hoai.le.spring.core.services.impl.GoogleDriveService" />
</beans>
Các bạn xem qua, mình sẽ giải thích chi tiết cụ thể hơn ở phía dưới.
6.Tạo file Main để chạy chương trình.
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
FileController fileController = ctx.getBean("fileController", FileController.class);
fileController.doUploadFile("profile.csv");
((ClassPathXmlApplicationContext) ctx).close();
}
Khi chạy chương trình bạn sẽ có kết quả như sau
Upload profile.csv to google drive
Kết quả tương đương với phần 1. FileController và FileService hoàn toàn độc lập với nhau, việc khởi tạo instance và inject giá trị fileService vào controller hoàn toàn do Spring đảm nhiệm.
Bây giờ chúng ta sẽ review lại code để hiểu rõ hơn chương trình hoạt động như thế nào.
Ở hàm main, chúng ta khởi tạo 1 biến ApplicationContext là instance của ClassPathXmlApplicationContext, (ApplicationContext là 1 interface). ApplicationContext sẽ đảm nhiệm việc khởi tạo và quản lý vòng đời của bean. Chúng ta sử dụng ClassPathXmlApplicationContext là bởi vì chương trình đang tiếp cận theo config bằng XML, và file XML được đặt ở classPath. Như đã nói ở phần trước bạn có thể thực hiện việc config này bằng nhiều cách khác nhau như thông qua Annotation, hoặc 1 file XML ở đâu đó chẳng hạn. Xem qua cây kế thừa, bạn sẽ thấy có khá nhiều cách để config.
ClassPathXmlApplicationContext chỉ là 1 trong số rất nhiều implements của ApplicationContext, chúng ta có thêm FileSystemXmlApplicationContext, AnnotationConfigApplicationContext… Bạn hoàn toàn có thể sử dụng chúng thay vì ClassPathXmlApplicationContext.
Đoạn code
ctx.getBean("fileController", FileController.class);
sẽ yêu cầu spring khởi tạo 1 bean với id là fileController, expected kiểu trả về là FileController (đỡ phải ép kiểu). Khi lấy được bean rồi, thì ta sử dụng nó như 1 object bình thường.
Thực ra ở đây không phải mỗi lần get bean, ApplicationContext đều khởi tạo 1 đối tượng mới cả. Bạn hoàn toàn có thể quản lý việc khởi tạo 1 hay nhiều đối tượng trong spring, bằng nhiều cách khác nhau. Mình sẽ trình bày ở dưới.
Quay lại file config spring-config.xml. Ở đây chú ý vào cách khai báo bean.
Ở đây mình khai báo 2 bean có id là fileController và fileService và class lần lượt là FileController và GoogleDriveService tương ứng. Khai báo này sẽ giúp cho spring hiểu rằng đây là các bean cần quản lý, ứng với mỗi bean Id sẽ là 1 class (full name của class, bao gồm cả package), khi Spring khởi tạo / get 1 bean theo ID, nó sẽ dựa vào property class để khởi tạo đúng đối tượng.
Đoạn code
<property name="fileService" ref="fileService" />
sẽ khai báo với spring là property fileService của bean fileController (class FileController), sẽ được inject (khởi tạo) là giá trị của bean fileService, tức là 1 instance của GoogleDriveService.
Nói nôm na dễ hiểu là, khi khởi tạo bean fileController, spring sẽ khởi tạo 1 instance của class FileController, sau đó khởi tạo 1 instance của GoogleDriveService, và truyền giá trị này vào biến fileService của fileController thông qua setter.
Bây giờ bạn thử xóa method setter của fileService trong class FileController, chạy chương trình bạ sẽ nhận được 1 lỗi sau:
Invalid property ‘fileService’ of bean class [*.FileController]: Bean property ‘fileService’ is not writable or has an invalid setter method
Còn có 1 cách khác, bạn không cần phải khai báo property fileService, spring có thể tự động tìm kiếm Implement của FileService và inject giá trị cho nó thông qua 1 annotation gọi là Autowired.
Xóa dòng khai báo propert của fileService đi, sau đó ở class FileController , thêm annotation @Autowired cho property fileService như sau:
@Autowired
private FileService fileService;
Lúc này chúng ta cũng không cần setter nữa, vì giá trị được khởi tạo và inject thông qua refection chứ không qua setter nữa. Spring sẽ tự động scan trong toàn bộ project, xem có class nào implement FileService hay không và khởi tạo instance tương ứng. Nếu trong project có 2 instance implement trở lên thì trường hợp này sẽ báo lỗi.
Chúng ta có thể giới hạn package scan bằng khai báo
<context:component-scan base-package="abc.def">
Khi đó Spring chỉ scan các class trong package đã được khai báo ở trên.
Nếu property fileService có kiểu là kiểu nguyên thủy (int, float,…) hay String, Map… thì có thể truyền trực tiếp như sau :
<property name="fileService" ref="fileService" value="123" />
Việc quản lý cách khởi tạo 1 bean có thể thực hiện thông qua property scope.
<bean scope="singleton" id="fileService" class="hoai.le.spring.core.services.impl.GoogleDriveService" />
Thuộc tính scope có 6 giá trị tương ứng
- singleton : sẽ chỉ khởi tạo 1 object duy nhất trong 1 IoC Container. ( Nghĩa là nếu bạn có nhiều ApplicationContext thì các bean này không có liên quan j tới nhau).
- prototype : đây là giá trị default, khởi tạo bao nhiêu tùy thích
- request / session / global session : đây là các scope của bean sử dụng trong ứng dụng web, vòng đời tương ứng với Http request, Http session và global session.
Liên quan đến vấn đề này còn có khái niệm Pooling trong Spring. Vì vấn đề này theo mình là khá phức tạp cho người mới học và kể cả những người đã làm việc với Spring trong vài năm, nên mình sẽ không trình bày ở đây, các bạn có thể tìm hiểu thêm ở đây
Config trong spring còn rất nhiều vấn đề mà bạn cần phải đọc thêm để có thể thực hiện 1 chương trình thực tế, vì scope của bài viết mình không trình bày hết ở đây, và cũng vì thực tế là mình cũng không thể nắm hết được các config của Spring, mình chỉ đưa ra những khái niệm cơ bản nhất.
Trong phần 2 này mình đã hướng dẫn cách bạn sử dụng ApplicationContext, Bean, làm quen với 1 ứng dụng Spring như thế nào. Có thể bạn vẫn đang mơ hồ, không biết 1 ứng dụng thực tế của Spring sẽ như thế nào. Không sao, vì khi mình mới học cũng vậy, đọc hết lý thuyết rồi mà vẫn không hiểu Spring là cái gì và ứng dụng thực tế như thế nào. Qua phần 3 của loạt bài viết này, chúng ta sẽ tìm hiểu về Spring MVC, khi đó bạn sẽ có 1 hình dung tổng quát hơn về Spring và những lợi ích của nó mang lại, mời các bạn tiếp tục đón đọc phần 3 sau vài ngày nữa
.