[Java] - [Understanding Spring MVC]
Hi all, Gần đây mình mới vọc vạch học Spring, hôm nay mình xin tản mạn về Spring Framework theo những gì mình học và hiểu. Vì kiến thức còn sơ sài, rất mong các anh chị, các bạn để lại comment góp ý. Chắc có lẽ không cần phải giới thiệu quá nhiều bởi vì các bài viết chính thống, không chính thống ...
Hi all, Gần đây mình mới vọc vạch học Spring, hôm nay mình xin tản mạn về Spring Framework theo những gì mình học và hiểu. Vì kiến thức còn sơ sài, rất mong các anh chị, các bạn để lại comment góp ý. Chắc có lẽ không cần phải giới thiệu quá nhiều bởi vì các bài viết chính thống, không chính thống bằng tiếng anh, tiếng việt về Spring Framework trên mạng quá nhiều. Mình chỉ tóm lược qua một chút, đây là framework được phát triển bởi Rod Johnson. Framework này giúp việc phát triển web trở lê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, trước hết chúng ta cùng khái quát về Front Controller và mô hình MVC và sau đó sẽ khám phá Spring MVC, kiến trúc và các thành phần của nó.
1. Architectute
Cùng nhìn lại khái quát về hai mẫu thiết kế phổ biến phát triển ứng dụng web mình nêu bên trên. (mình sẽ không đi vào chi tiết, các bạn có thể tìm hiểu các nguồn trên mạng)
Front Controller design pattern 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 đó sẽ được nắm giữ bởi bộ xử lý trung tâm, sau đó nó sẽ quyết định yêu cầu (request) nào được chuyển đến đối tượng nào để xử lý tiếp sao cho phù hợp. MVC design pattern 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, gồm 3 thành phần chính : Model, View và Controller
Spring’s MVC module Spring MVC module được xây dựng dựa trên hai mẫu thiết kế trên. Tất cả yêu cầu (request) gửi đến được xử lý bới một servlet duy nhất tên là DispatcherServlet - nó đóng vai trò giống như Front Controller. Sau đó DispatcherServlet sẽ đề cập đến HandlerMapping để tìm đến một đối tượng phù hợp xử lý yêu cầu. Dựa vào mapping, DispatcherServlet yêu cầu đối tượng điều khiển (class Controller) để nó thực hiện request của người dùng. Các class Controller trả về đối tượng đóng có chứa các đối tượng của Model và View. Trong Spring MVC, đối tượng đóng gói này là thể hiện của lớp ModelAndView. Trong trường hợp ModelAndView chứa tên logic của View, thì DispatcherServlet đề cập ViewResolver tìm các trang hiển thị căn cứ vào tên logic.
2. Dispatcher servlet
Dispatcher Servlet hoạt động giống như Front Controller, tất cả các request của người dùng được giữ lại ở servlet này. Dispatcher Servlet cũng như những servlet khác, nó được cấu hình trong file triển khai ứng dụng được mô tả trong file web.xml. Dưới đây là một file web.xml đơn giản
<web-app xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Library</display-name> <servlet> <servlet-name>frontControllerServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>frontControllerServlet</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>welcome.htm</welcome-file> </welcome-file-list> </web-app>
Trong file chúng ta cấu hình tên của servlet là frontControllerServlet, tức 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 gồm đuổi ".htm", như vậy có nghĩa là tất cả các request giống với mẫu URI bên trên đều được nắm giữ điều khiển bởi servlet có tên là frontControllerServlet.
3. Spring Application Context
Default Application context file
Mặc định dispatcher servlet sẽ load nội dụng của các file xml có tên dạng [tên servlet]-servlet.xml. Như vậy có nghĩa là khi servlet của chúng ta frontControllerServlet được load nó sẽ load nội dung từ file xml : “/WEB-INF/frontControllerServlet-servlet.xml”.
User defined application context file
Chúng ta có thể ghi đè tên và vị trí của file xml mặc định bằng cách cung cấp khởi tạo tham số cho dispatcher servlet. Tên của tham số khởi tạo (<param-name>) là contextConfigLocation, giá trị tham số khởi tạo (<param-value>) được quy định cụ thể bởi tên và vị trí của file xml mà chúng ta cần được load bởi container.
<servlet> <servlet-name>frontControllerServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:libraryAppContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>frontControllerServlet</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>welcome.htm</welcome-file> </welcome-file-list>
Trong cấu hình ở trên của frontControllerServlet , khi container khởi tạo dispatcher servlet nó sẽ load nội dung của file xml "classpath:libraryAppContext.xml" thay thế “/WEB-INF/frontControllerServlet-servlet.xml”.
Multiple application context files
Trong một vài trường hợp chúng ta cần load nhiều bối cảnh ứng dụng từ nhiều tập tin xml khác, chúng ta có thể làm theo cách dưới đây.
<servlet> <servlet-name>frontControllerServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:libraryAppContext.xml classpath:books.xml classpath:chapters.xml classpath:titles.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>frontControllerServlet</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>
Theo như cấu hình trên các file được khởi tạo bởi <init-param> sẽ được load bởi container trong servlet tên là frontControllerServlet.
4. Handler mappings
Dựa vào tên servlet cụ thể trong cấu hình, các request sẽ xác định được các xử lý tương ứng. Khi một request đến, dispatcher servlet dựa vào thông tin request để chỉ rõ xử lý mapping. Xử lý mapping sau đó chỉ rõ yêu cầu, xác định chuỗi việc thực hiện xử lý thích hợp.
Dưới đây là một số thiết lập mapping được cung cấp bởi Spring Framework, chúng đều implements interface org.springframework.web.servlet.HandlerMapping.
BeanNameUrlHandlerMapping
Thực hiện này của xử lý ánh xạ (mapping) phù hợp với URL của request với tên của controller beans. Các bean phù hợp sau đó được sử dụng như là bộ điều khiển cho các yêu cầu. Đây là xử lý ánh xạ mặc định được sử dụng bởi Spring MVC, trường hợp dispatcher servlet không tìm thấy bất kỳ sping bean nào được định nghĩa trong application context thì dispatcher servlet sẽ sử dụng BeanNameUrlHandlerMapping.
Chúng ta giả sử rằng chúng ta có ba trang web trong ứng dụng của chúng tôi. URL của trang là:
http://servername:PortNumber/ApplicationContext/welcome.htm http://servername:PortNumber/ApplicationContext/listBooks.htm http://servername:PortNumber/ApplicationContext/displayBookContent.htm
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 phải xác định các controller điều khiển trong tập tin ngữ cảnh ứ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.
<bean name = "/ welcome.htm" class = "net.codejava.frameorks.spring.mvc.controller.WelcomeController" /> <bean name = "/ listBooks.htm" class = "net.codejava.frameorks.spring.mvc.controller.ListBooksController" /> <bean name = "/ displayBookTOC.htm" class = "net.codejava.frameorks.spring.mvc.controller.DisplayBookTOCController" />
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 mặc định được sử dụng.
SimpleUrlHandlerMapping
BeanNameUrlHandlerMapping đặt một sự hạn chế về tên của các controller bean sao cho phù hợp với URL của request. SimpleUrlHandlerMapping đã loại bỏ những hạn chế 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".
<bean id="myHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/welcome.htm">welcomeController</prop> <prop key="/listBooks.htm">listBooksController</prop> <prop key="/displayBookTOC.htm">displayBookTOCController</prop> </props> </property> </bean> <bean name="welcomeController" class="net.codejava.frameorks.spring.mvc.controller.WelcomeController"/> <bean name="listBooksController" class="net.codejava.frameorks.spring.mvc.controller.ListBooksController"/> <bean name="displayBookTOCController" class="net.codejava.frameorks.spring.mvc.controller.DisplayBookTOCController"/>
Key của từng thành phần <prop> là mẫu URL request, value của từng thành phần <prop> là tên của các controller bean - mà sẽ thực hiện xử lý logic của các yêu cầu. So với BeanNameUrlHandlerMapping thì SimpleUrlHandlerMapping thường được sử dụng hơn.
5. Controllers
Controller là class thực hiện logic nghiệp vụ thi hành các yêu cầu đến. Controller cũng có thể ủy nhiệm điều này đến các đối tượng dịch vụ. Tất cả các controller hoặc là implements interface Controller hoặc là kế thừa abstract class AbstractController. Khi người dùng định nghĩa các controller cần override phương thức handleRequestInternal. Phương thức handleRequestInternal dùng HttpServletRequest và HttpServletReponse như input đầu vào và trả về một đối tượng ModelAndView.
Trong file application context của Spring, chúng ta định nghĩa một controller tên là welcomeController. Theo SimpleUrlHandlerMapping, tất cả các request mà URL phù hợp với mẫu "/welcome.htm" sẽ được giữ bởi controller này. Class WelcomeController phải kế thừa class 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 web pages cũng sẽ tăng lên. Để giải quyết các yêu cầu cho các web pages chúng ta cần định nghĩa nhiều các controller hơn cho mỗi page. Và đôi khi logic nghiệp vụ được thực thi để giải quyết cho những yêu cầu là tương tự nhau. Điều này tạo ra sự dư thừa trong các controller và làm cho việc bảo trì sau này trở lên gặp nhiều khó khăn hơn.
Spring MVC cung cấp cách để đối phó với điều này bằng cách tạo ra một controller điều khiển duy nhất hoàn thành các yêu cầu cho nhiều trang web. Tức là một controller điều khiển nhiều hành động. Nếu định nghĩa nhiều hành động (nhiều method) trong một controller thì nên kế thừa class org.springframework.web.servlet.mvc.multiaction.MultiActionController. Với mỗi hành động đó sẽ là một logic để thực hiện đầy đủ các yêu cầu cho một trang web cụ thể.
Bởi vì mặc định, các URL của các yêu cầu gửi đến (không bao gồm phần mở rộng) sẽ kết hợp với tên của các phương thức và việc ánh xạ phương thức sẽ thực hiện logic nghiệp vụ cho mỗi yêu cầu gửi đến. Vì vậy, đối với các yêu cầu gửi đến với URL "/welcome.htm" thì tên của phương thức nào được ánh xạ tới sẽ được thực hiện.
Giả sử rằng ứng dụng của chúng ta có một class là MyMultiActionController đáp ứng yêu cầu cho 3 web page với URL là "/welcome.htm", "/listBooks.htm" và "/displayBookTOC.htm". Như vậy class này phải kế thừa class MultiActionController và có 3 phương thức.
public class MyMultiActionController extends MultiActionController { // This method will server all the request matching URL pattern /welcome.htm public ModelAndView welcome(HttpServletRequest request, HttpServletResponse response) { // Business logic goes here // Return an object of ModelAndView to DispatcherServlet return new ModelAndView("Welcome"); } // This method will server all the request matching URL pattern // /listBooks.htm public ModelAndView listBooks(HttpServletRequest request, HttpServletResponse response) { // Business logic goes here // Return an object of ModelAndView to DispatcherServlet return new ModelAndView("listBooks"); } // This method will server all the request matching URL pattern // /displayBookTOC.htm public ModelAndView displayBookTOC(HttpServletRequest request, HttpServletResponse response) { // Business logic goes here // Return an object of ModelAndView to DispatcherServlet return new ModelAndView("displayBookTOC"); } }
6. MethodNameResolver
Spring MVC cung cấp một số cách để giải quyết vấn đề multi action dựa trên các request. Một số như:
ParameterMethodNameResolver
Một thông số cụ thể trong request chứa tên của phương thức. Tên của tham số được định nghĩa trong file application context khi định nghĩa ParameterMethodNameResolver. Trong ví dụ dưới đây, tham số controllerMethod trong request sẽ xác định hành động nào được thực thi để xử lý request.
<bean name="parameterMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver"> <property name="paramName"> <value>controllerMethod</value> </property> </bean>
**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 :
http://servername:portnumber/ProjectWebContext/welcome.htm?controllerMethod= handleWelcomePage http://servername:portnumber/ProjectWebContext/listBooks.htm?controllerMethod= handleListBooksPage http://servername:portnumber/ProjectWebContext/displayBookTOC.htm?controllerMethod= handleDisplayBookTOCPage
Trong cấu hình trên, request có URL là "/welcome.htm" được giải quyết bởi phương thức handleWelcomePage, request có URL là "/listBooks.htm" được giải quyết bởi phương thức handleListBooksPage, request có URL là "/displayBookTOC.htm" được giải quyết bở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 xác định trước để cung cấp tên các phương thức giải quyết trong các file application context. PropertiesMethodNameResolver trong file application context như sau :
<bean name="propertiesMethodNameResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver"> <property name="mappings"> <props> <prop key="/welcome.htm">handleWelcomePage</prop> <prop key="/listBooks.htm">handleListBooksPage</prop> <prop key="/displayBookTOC.htm">handleDisplayBookTOCPage</prop> </props> </property> </bean>
Tương tự như ParameterMethodNameResolver, một lần nữa request có URL là "/welcome.htm" được giải quyết bởi phương thức handleWelcomePage, request có URL là "/listBooks.htm" được giải quyết bởi phương thức handleListBooksPage, request có URL là "/displayBookTOC.htm" được giải quyết bởi phương thức handleDisplayBookTOC.
Chúng ta cần nói với các hành động rằng để sử dụng một phương thức cụ thể bởi thiết lập thuộc tính của nó là methodNameResolver.
<bean name="myMultiActionController" class="net.codejava.frameworks.spring.mvc.controller.MyMultiActionController"> <property name="methodNameResolver"> <ref bean="propertiesMethodNameResolver"/> </property> </bean>