Bài tập

[Spring] - Hiểu rõ hơn về Spring MVC

Huy Erick

Xin chào các bạn, Gần đây mình mới bắt đầu học về Spring, và hôm nay mình muốn chia sẻ với các bạn về Spring Framework dựa trên những kiến thức mình đã học và...

Xin chào các bạn,

Gần đây mình mới bắt đầu học về Spring, và hôm nay mình muốn chia sẻ với các bạn về Spring Framework dựa trên những kiến thức mình đã học và hiểu. Vì mình vẫn còn mới nên hy vọng các bạn đóng góp ý kiến để cùng nhau học hỏi. Mình không muốn giới thiệu quá nhiều vì trên mạng có rất nhiều bài viết (bằng tiếng Anh và tiếng Việt) về Spring Framework, có chính thống và không chính thống. Mình chỉ muốn tóm lược một chút về framework này được phát triển bởi Rod Johnson. Framework này giúp việc phát triển web trở nên dễ dàng hơn với thiết kế nhiều module. Spring MVC Module được thiết kế dựa trên hai mẫu thiết kế web phổ biến: Front Controller và MVC.

Trong bài viết này, chúng ta sẽ tìm hiểu về Front Controller, mô hình MVC và sau đó khám phá Spring MVC, kiến trúc và các thành phần của nó.

1. Kiến trúc

Hãy cùng nhìn lại về hai mẫu thiết kế phổ biến để phát triển ứng dụng web, như đã đề cập ở trên.

Mẫu thiết kế Front Controller

Mẫu thiết kế này cung cấp một điểm truy cập duy nhất cho tất cả các yêu cầu đến (incoming request). Tất cả các yêu cầu đều được xử lý bởi một trình xử lý trung tâm, sau đó trình xử lý này sẽ quyết định yêu cầu nào được chuyển đến đối tượng nào để xử lý sao cho phù hợp.

Mẫu thiết kế MVC

Mẫu thiết kế này giúp ứng dụng phát triển các thành phần riêng biệt, bao gồm 3 thành phần chính: Model, View và Controller với các vai trò khác nhau.

Spring MVC

Spring MVC được xây dựng dựa trên hai mẫu thiết kế trên. Tất cả các request được gửi đến sẽ được xử lý bởi một servlet duy nhất gọi là DispatcherServlet - nó đóng vai trò như Front Controller. Sau đó, DispatcherServlet sẽ chuyển tiếp request này đến HandlerMapping để tìm đến một đối tượng phù hợp để xử lý yêu cầu. Dựa vào thông tin mapping mà HandlerMapping xử lý, DispatcherServlet sẽ yêu cầu đối tượng Controller phù hợp để thực hiện yêu cầu của người dùng. Các đối tượng Controller trả về một đối tượng đóng gói chứa các đối tượng của Model và View. Trong Spring MVC, đối tượng đóng gói này là một thể hiện của lớp ModelAndView. Trong trường hợp ModelAndView chứa tên logic của View, thì DispatcherServlet sẽ yêu cầu ViewResolver tìm các trang hiển thị dựa trên tên logic.

2. DispatcherServlet

DispatcherServlet hoạt động giống như Front Controller, tất cả các yêu cầu của người dùng đều được duy trì trong servlet này. DispatcherServlet được cấu hình trong file triển khai ứng dụng được miêu tả trong file web.xml. Dưới đây là một ví dụ về file web.xml đơn giản:


Library

frontControllerServlet
org.springframework.web.servlet.DispatcherServlet
1


frontControllerServlet
*.htm


welcome.htm

Trong ví dụ trên, chúng ta cấu hình tên servlet là frontControllerServlet, điều này có nghĩa là chúng ta đang cấu hình load nội dung của file frontControllerServlet-servlet.xml. Và mẫu URI trong phần servlet-mapping phải có phần mở rộng ".htm", điều này có nghĩa là tất cả các yêu cầu có URL phù hợp với mẫu URI trên đều được duy trì bởi servlet có tên là frontControllerServlet.

3. Spring Application Context

Ứng dụng Spring MVC của chúng ta sẽ có một tệp tin cấu hình ứng dụng mặc định. Theo mặc định, DispatcherServlet sẽ load nội dung của các tệp tin xml có tên dạng [tên servlet]-servlet.xml. Ví dụ: khi servlet của chúng ta là frontControllerServlet, nó sẽ load nội dung từ tệp tin xml "/WEB-INF/frontControllerServlet-servlet.xml".

Tuy nhiên, chúng ta cũng có thể ghi đè tên và vị trí của tệp tin xml mặc định bằng cách cung cấp tham số khởi tạo cho DispatcherServlet. Tham số khởi tạo này có tên là contextConfigLocation, giá trị của tham số khởi tạo này sẽ xác định tên và vị trí của tệp tin xml mà chúng ta muốn load bởi container.

Ví dụ trong ví dụ bên dưới, khi container khởi tạo DispatcherServlet, nó sẽ load nội dung từ tệp tin xml "classpath:libraryAppContext.xml" thay vì "/WEB-INF/frontControllerServlet-servlet.xml".


frontControllerServlet
org.springframework.web.servlet.DispatcherServlet

contextConfigLocation
classpath:libraryAppContext.xml

1


frontControllerServlet
*.htm


welcome.htm

Trong một số trường hợp, chúng ta có thể cần load nhiều tệp tin cấu hình ứng dụng từ nhiều tệp tin xml khác nhau, và chúng ta có thể làm như sau:


frontControllerServlet
org.springframework.web.servlet.DispatcherServlet

contextConfigLocation
classpath:libraryAppContext.xml classpath:books.xml classpath:chapters.xml classpath:titles.xml

1


frontControllerServlet
*.htm

Theo cấu hình trên, các tệp tin được khởi tạo bởi thẻ init-param sẽ được load bởi container trong servlet có tên là frontControllerServlet.

4. Xác định đối tượng xử lý

Dựa vào tên servlet trong cấu hình, các yêu cầu sẽ được ánh xạ (mapping) tới các xử lý tương ứng. Khi một yêu cầu đến, DispatcherServlet xác định xử lý mapping dựa trên thông tin yêu cầu. Các xử lý mapping sau đó xác định yêu cầu và xác định chuỗi các công việc thực hiện xử lý phù hợp.

Dưới đây là một số thiết lập mapping được cung cấp bởi Spring Framework, tất cả chúng đều implements interface org.springframework.web.servlet.HandlerMapping.

BeanNameUrlHandlerMapping

Thiết lập này xử lý ánh xạ (mapping) phù hợp với URL của yêu cầu với tên của các controller beans. Các bean phù hợp sau đó được sử dụng như là bộ điều khiển cho yêu cầu. Đây là mapping mặc định được sử dụng bởi Spring MVC, nếu DispatcherServlet không tìm thấy bất kỳ spring bean nào được định nghĩa trong application context thì nó sẽ sử dụng BeanNameUrlHandlerMapping.

Giả sử rằng chúng ta có ba trang web trong ứng dụng của chúng ta. URL của trang là:

Các controller thực hiện các yêu cầu cho các trang trên là:

  • net.codejava.frameorks.spring.mvc.controller.WelcomeController
  • net.codejava.frameorks.spring.mvc.controller.ListBooksController
  • net.codejava.frameorks.spring.mvc.controller.DisplayBookTOCController

Vì vậy chúng ta cần định nghĩa các controller trong tệp tin ứng dụng (Spring application context). Tức là tên của controller phù hợp với URL được yêu cầu. Các controller bean đó trong tệp tin cấu hình XML sẽ trông như dưới đây.



Lưu ý rằng chúng ta không cần phải định nghĩa BeanNameUrlHandlerMapping trong các file application context bởi vì đây là một trong những mapping mặc định được sử dụng.

SimpleUrlHandlerMapping

BeanNameUrlHandlerMapping đặt một giới hạn về tên của các controller bean để phù hợp với URL của yêu cầu. SimpleUrlHandlerMapping loại bỏ các giới hạn này và sử dụng cơ chế mapping để ánh xạ các controller bean với URL sử dụng thuộc tính "mappings".




welcomeController
listBooksController
displayBookTOCController





Key của mỗi thành phần thẻ props là mẫu URL của yêu cầu, value của mỗi thành phần thẻ props là tên của các controller bean - mà sẽ thực hiện logic nghiệp vụ cho mỗi yêu cầu gửi đến. So với BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping thường được sử dụng hơn.

5. Controllers

Controller là class thực hiện logic nghiệp vụ để xử lý các yêu cầu đến. Controller cũng có thể ủy quyền điều này cho các đối tượng dịch vụ. Tất cả các controller là implements interface Controller hoặc kế thừa abstract class AbstractController. Khi bạn định nghĩa các controller, bạn cần ghi đè phương thức handleRequestInternal. Phương thức handleRequestInternal nhận HttpServletRequest và HttpServletResponse như đầu vào và trả về một đối tượng ModelAndView. Trong tệp tin ứng dụng Spring, chúng ta định nghĩa một controller tên là welcomeController. Dựa trên SimpleUrlHandlerMapping, tất cả các yêu cầu có URL phù hợp với mẫu "/welcome.htm" sẽ được xử lý bởi controller này. Lớp WelcomeController phải kế thừa lớp AbstractController và ghi đè phương thức handleRequestInternal.

public class WelcomeController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception {
return new ModelAndView("welcome");
}
}

MultiActionController

Trong hầu hết các ứng dụng web lớn, số lượng các trang web cũng tăng lên. Để giải quyết các yêu cầu cho các trang web đó, chúng ta cần định nghĩa nhiều controller hơn cho mỗi trang. Và đôi khi, logic nghiệp vụ để xử lý các yêu cầu là tương tự nhau. Điều này dẫn đến sự lặp lại trong các controller và làm cho việc bảo trì sau này trở nên khó khăn hơn.

Spring MVC cung cấp cách để giảm thiểu sự lặp lại này bằng cách tạo ra một controller duy nhất để xử lý nhiều yêu cầu. Đó là một controller đa hành động. Nếu bạn định nghĩa nhiều hành động (nhiều phương thức) trong một controller, bạn nên kế thừa lớp org.springframework.web.servlet.mvc.multiaction.MultiActionController. Mỗi hành động sẽ có một logic để xử lý đầy đủ các yêu cầu cho một trang cụ thể.

Ví dụ, nếu ứng dụng của chúng ta có một lớp MyMultiActionController để phục vụ các yêu cầu cho 3 trang web với các URL "welcome.htm", "listBooks.htm" và "displayBookTOC.htm". Lớp này phải kế thừa lớp MultiActionController và có 3 phương thức.

public class MyMultiActionController extends MultiActionController {
// Phương thức này sẽ xử lý tất cả các yêu cầu khớp với mẫu URL /welcome.htm
public ModelAndView welcome(HttpServletRequest request, HttpServletResponse response) {
// Logic nghiệp vụ được thực hiện ở đây
// Trả về một đối tượng ModelAndView cho DispatcherServlet
return new ModelAndView("Welcome");
}
// Phương thức này sẽ xử lý tất cả các yêu cầu khớp với mẫu URL /listBooks.htm
public ModelAndView listBooks(HttpServletRequest request, HttpServletResponse response) {
// Logic nghiệp vụ được thực hiện ở đây
// Trả về một đối tượng ModelAndView cho DispatcherServlet
return new ModelAndView("listBooks");
}
// Phương thức này sẽ xử lý tất cả các yêu cầu khớp với mẫu URL /displayBookTOC.htm
public ModelAndView displayBookTOC(HttpServletRequest request, HttpServletResponse response) {
// Logic nghiệp vụ được thực hiện ở đây
// Trả về một đối tượng ModelAndView cho DispatcherServlet
return new ModelAndView("displayBookTOC");
}
}

6. MethodNameResolver

Spring MVC cung cấp một số cách để giải quyết vấn đề đa hành động dựa trên các yêu cầu. Một số ví dụ như:

ParameterMethodNameResolver

Một tham số cụ thể trong yêu cầu chứa tên của phương thức. Tên của tham số được định nghĩa trong tệp tin ứng dụng khi định nghĩa ParameterMethodNameResolver. Trong ví dụ dưới đây, tham số controllerMethod trong yêu cầu sẽ xác định hành động nào được thực thi để xử lý yêu cầu.



controllerMethod

Ghi chú: Các trang web cụ thể bây giờ cần có một tham số bổ sung với tên là controllerMethod. Và các URL yêu cầu sẽ có dạng:

Trong cấu hình trên, yêu cầu có URL là "/welcome.htm" sẽ được ánh xạ tới phương thức handleWelcomePage, yêu cầu có URL là "/listBooks.htm" sẽ được ánh xạ tới phương thức handleListBooksPage, yêu cầu có URL là "/displayBookTOC.htm" sẽ được ánh xạ tới phương thức handleDisplayBookTOC.

PropertiesMethodNameResolver

Tên của các phương thức được xác định từ danh sách các thuộc tính được định nghĩa trước để cung cấp tên các phương thức giải quyết trong tệp tin ứng dụng. PropertiesMethodNameResolver trong tệp tin ứng dụng sẽ trông như sau:




handleWelcomePage
handleListBooksPage
handleDisplayBookTOCPage


Tương tự như ParameterMethodNameResolver, một lần nữa yêu cầu có URL là "/welcome.htm" sẽ được ánh xạ tới phương thức handleWelcomePage, yêu cầu có URL là "/listBooks.htm" sẽ được ánh xạ tới phương thức handleListBooksPage, yêu cầu có URL là "/displayBookTOC.htm" sẽ được ánh xạ tới phương thức handleDisplayBookTOC.

Chúng ta cần nói với các controller để sử dụng một phương thức cụ thể bằng cách thiết lập thuộc tính methodNameResolver.





Tóm lại, đó là một số kiến thức mà mình đã tìm hiểu. Hi vọng bài viết này hữu ích với mọi người.

Cảm ơn các bạn đã đọc!

1