Spring MVC 筆記 (三) Spring Tag Libraries

1 JSP and JSTL tags

JavaServer Pages (JSP) is a technology that lets you embed Java code inside HTML pages. This code can be inserted by means of <% %> blocks or by means of JSTL tags. To insert Java code into JSP, JSTL tags are generally preferred, since tags adapt better to their own tag representation of HTML, making JSP pages look more readable.

JSTL is just a standard tag library provided by Oracle. We can add a reference to the JSTL tag library in our JSP pages as follows:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

Similarly, Spring MVC also provides its own tag library to develop Spring JSP views easily and effectively. These tags provide a lot of useful common functionality such as form binding, evaluating errors, and outputting messages, and more when we work with Spring MVC. In order to use these, We must add a reference to that tag library in our JSP pages as follows:

<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>

2 Serving and processing forms

What if we want to put data into the Model from the View? How do we retrieve that data in the Controller?
Spring tag library tags help us to bind the HTML tag element’s values to a form backing bean in the Model. Later, the Controller can retrieve the form backing bean from the Model using the @ModelAttribute annotation.

The form backing bean (sometimes called the form bean) is used to store form data. We can even use our domain objects as form beans; this works well when there’s a close match between the fields in the form and the properties in our domain object. Another approach is creating separate classes for form beans, which is sometimes called Data Transfer Objects(DTO).

step1,用戶點擊 http://localhost:8080/webstore/market/products/add 轉到 addProdut.jsp 頁面
step2, addProduct.jsp 頁面負責添加商品的UI,等用戶填寫完表單,點擊 Add 提交
step3, 點擊提交之後,將表單數據插入數據庫,成功之後返回到 products.jsp 頁面,這時的 products.jsp頁面必須更新,列出新添加的商品。

實現 step1 只需要處理一個 GET 請求,同時,我們要創建一個 Product 對象(DTO),並添加到 Model 對象中, 這樣 addProduct.jsp 頁面就可以使用這個對象來綁定表單數據。

    /**
     * 處理添加商品的 GET 請求,可以在 products.jsp 中留一個鏈接,點擊後進入 addProduct.jsp
     * 
     * @param model
     * @return 返回實際添加商品的頁面
     */
    @RequestMapping(value = "/products/add", method = RequestMethod.GET)
    public String getAddNewProductForm(Model model) {
        Product newProduct = new Product();
        model.addAttribute("newProduct", newProduct);
        return "addProduct";
    }

實現 step2 首先需要設計一個頁面 addProduct.jsp ,其中關鍵的就是表單的設計,可以使用 Spring 的 form tags 實現,它實現了很多HTML 標籤,並提供了數據綁定及驗證等功能。

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

在表單標籤中,使用 modelAttribute=”newProduct” 綁定DTO 。

<form:form method="POST" modelAttribute="newProduct" class="form-horizontal">

addProduct.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"
    href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
<title>Products</title>
</head>
<body>
    <section>
        <div class="jumbotron">
            <div class="container">
                <h1>Products</h1>
                <p>Add products</p>
            </div>
        </div>
    </section>
    <section class="container">
        <form:form method="POST" modelAttribute="newProduct" class="form-horizontal">
            <fieldset>
                <legend>Add new product</legend>
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="productId">Product Id</label>
                    <div class="col-lg-10">
                        <form:input id="productId" path="productId" type="text" class="form:input-large" />
                    </div>
                </div>
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="name">Product Name</label>
                    <div class="col-lg-10">
                        <form:input id="name" path="name" type="text" class="form:input-large" />
                    </div>
                </div>
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="unitPrice">Product Price</label>
                    <div class="col-lg-10">
                        <form:input id="unitPrice" path="unitPrice" type="text" class="form:input-large" />
                    </div>
                </div>              
                <div class="form-group">
                    <label class="control-label col-lg-2" for="description">Description</label>
                    <div class="col-lg-10">
                        <form:textarea id="description" path="description" rows="2" />
                    </div>
                </div>
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="manufacturer">Manufacturer</label>
                    <div class="col-lg-10">
                        <form:input id="manufacturer" path="manufacturer" type="text" class="form:input-large" />
                    </div>
                </div>  
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="category">Category</label>
                    <div class="col-lg-10">
                        <form:input id="category" path="category" type="text" class="form:input-large" />
                    </div>
                </div>  
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="unitsInStock">UnitsInStock</label>
                    <div class="col-lg-10">
                        <form:input id="unitsInStock" path="unitsInStock" type="text" class="form:input-large" />
                    </div>
                </div>  
                <div class="form-group">
                    <label class="control-label col-lg-2 col-lg-2" for="unitsInOrder">UnitsInOrder</label>
                    <div class="col-lg-10">
                        <form:input id="unitsInOrder" path="unitsInOrder" type="text" class="form:input-large" />
                    </div>
                </div>  
                <div class="form-group">
                    <label class="control-label col-lg-2" for="discontinued">Discontinued</label>
                    <div class="col-lg-10">
                        <form:checkbox id="discontinued" path="discontinued" />
                    </div>
                </div>
                <div class="form-group">
                    <label class="control-label col-lg-2" for="condition">Condition</label>
                    <div class="col-lg-10">
                        <form:radiobutton path="condition" value="New" />New
                        <form:radiobutton path="condition" value="Old" />Old
                        <form:radiobutton path="condition" value="Refurbished" />Refurbished
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-lg-offset-2 col-lg-10">
                        <input type="submit" id="btnAdd" class="btn btn-primary" value="Add" />
                    </div>
                </div>
            </fieldset>
        </form:form>
    </section>
</body>
</html>

實現 step3 要處理一個 POST請求,它由 addProduct.jsp 頁面的表單提交動作觸發,這裏要將提交的表單數據持久化到數據庫,成功之後返回 products.jsp 頁面。

首先要使用 @ModelAttribute(“newProduct”) 獲取DTO,插入數據庫之後,使用return “redirect:/market/products” 將頁面重定向到 products.jsp 。

    /**
     * 處理添加商品的 POST 請求,一般在 addProduct.jsp 的 from表單中的 action
     * @param newProduct
     * @return
     */
    @RequestMapping(value = "/products/add", method = RequestMethod.POST)
    public String processAddNewProductForm(@ModelAttribute("newProduct") Product newProduct) {
        productService.addProduct(newProduct);
        return "redirect:/market/products";
    }

As a matter of fact, when we return any request path with the redirect: prefix from a request mapping method, Spring will use a special View object called RedirectView to issue the redirect command behind the scenes.

Instead of landing on a web page after the successful submission of a web form, we are spawning a new request to the request path /market/products with the help of RedirectView. This pattern is called redirect-after-post; it is a commonly used pattern with web-based forms. We are using this pattern to avoid double submission of the same form.

3 Customizing data binding

In order to do the binding, Spring MVC internally uses a special binding object called WebDataBinder. WebDataBinder extracts the data out of the HttpServletRequest object and converts it to a proper data format, loads it into a form backing bean, and validates it.

To customize the behavior of data binding, we can initialize and configure the WebDataBinder object in our Controller. The @InitBinder annotation designates a method to initialize WebDataBinder. Technically speaking, the process of explicitly
telling which fields are allowed for binding is called whitelisting binding in Spring MVC.

step1, 爲 ProductController 添加 whitelisting binding

/**
     * initialize and configure the WebDataBinder object
     * 
     * 添加商品的時候並不用到 unitsInOrder
     * 
     * @param binder
     */
    @InitBinder
    public void initialiseBinder(WebDataBinder binder) {
        binder.setAllowedFields("productId", "name", "unitPrice", "description", "manufacturer", "category",
                "unitsInStock", "condition");
    }

step2,Add an extra parameter of the type BindingResult to the processAddNewProductForm method,通過判斷 getSuppressedFields()返回的結果判斷驗證是否通過。

    /**
     * 處理添加商品的 POST 請求,一般在 addProduct.jsp 的 from表單中的 action
     * 
     * @param newProduct
     * @return
     */
    @RequestMapping(value = "/products/add", method = RequestMethod.POST)
    public String processAddNewProductForm(@ModelAttribute("newProduct") Product newProduct, BindingResult result) {
        String[] suppressedFields = result.getSuppressedFields();
        if (suppressedFields.length > 0) {
            throw new RuntimeException("Attempting to bind disallowed fields: "
                    + StringUtils.arrayToCommaDelimitedString(suppressedFields));
        }
        productService.addProduct(newProduct);
        return "redirect:/market/products";
    }

4 Externalizing text messages

Externalizing these texts from a View file into a properties file will help us to have a single centralized control for all label messages. Moreover, it will help us to make our web pages ready for internationalization.

step1, 新建一個配置文件:/webstore/src/main/resources/messages.properties

addProduct.form.productId.label = New Product ID

step2, 使用:<spring:message code="addProduct.form.productId.label" />
step3, 配置MessageSource

@Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource resource = new ResourceBundleMessageSource();
        resource.setBasename("messages");
        return resource;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章