+10

[Spring] - Understanding Spring MVC (Hiểu hơn về 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 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 ControllerMVC.

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, ViewController với các vai trò khác nhau.

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ả 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ẽ đưa request này đến HandlerMapping xử lý để 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 request của người dùng. Các class Controller trả về đối tượng đóng gói có chứa các đối tượng của ModelView. 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 nói với 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 trong thẻ param-namecontextConfigLocation, giá trị tham số khởi tạo trong thẻ 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 thẻ 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 thẻ prop là mẫu URL request, value của từng thành phần thẻ 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 HttpServletRequestHttpServletReponse 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>

Tổng kết

Trên đây là một số kiến thức mình thu lượm được. Hi vọng bài viết hữu ích với mọi người.

Thanks for watching!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.