这一章跟Spring基本没有关系,权当是简单地复习了一下servlet的知识点。
示例实现了一个输入表单和输出信息的小功能,具体如下:
访问product_input.action跳转ProductForm.jsp,填写相关信息后点击提交按钮,访问product_save.action,跳转ProductDetails.jsp。
目录结构:
先来看web.xml,其中配置了servlet
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<!-- 只匹配.action的请求,嗯... -->
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ControllerServlet</servlet-name>
<servlet-class>app02a.servlet.ControllerServlet</servlet-class>
</servlet>
</web-app>
Servlet类ControllerServlet.java
package app02a.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import app02a.domain.Product;
import app02a.form.ProductForm;
public class ControllerServlet extends HttpServlet {
private static final long serialVersionUID = 1579L;
@Override // 养成添加Override注解的习惯,有利于避免重写失败
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
process(request, response); // 无论get还是post,最终都是调用process()
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
process(request, response);
}
private void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String uri = request.getRequestURI(); // eg: visit http://localhost:7676/product_input.action, uri: /product_input.action
/*
* uri is in this form: /contextName/resourceName,
* for example: /app10a/product_input.
* However, in the event of a default context, the
* context name is empty, and uri has this form
* /resourceName, e.g.: /product_input
*/
int lastIndex = uri.lastIndexOf("/"); // 获取最终action的名称,lastIndexOf(),避免/a/b/c.action有多个斜线造成的获取失败。
String action = uri.substring(lastIndex + 1);
// execute an action
if (action.equals("product_input.action")) {
// no action class, there is nothing to be done
} else if (action.equals("product_save.action")) {
// create form
ProductForm productForm = new ProductForm();
// populate action properties
productForm.setName(request.getParameter("name"));
productForm.setDescription(request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// create model
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
try {
product.setPrice(Float.parseFloat(productForm.getPrice()));
} catch (NumberFormatException e) {
}
// code to save product
// store model in a scope variable for the view
request.setAttribute("product", product);
}
// forward to a view
String dispatchUrl = null;
if (action.equals("product_input.action")) {
dispatchUrl = "/WEB-INF/jsp/ProductForm.jsp";
} else if (action.equals("product_save.action")) {
dispatchUrl = "/WEB-INF/jsp/ProductDetails.jsp";
}
if (dispatchUrl != null) {
RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); // 典型的servlet跳转方式
rd.forward(request, response);
}
}
}
再来看看两个类文件,先是ProductForm.java
package app02a.form;
public class ProductForm {
private String name;
private String description;
private String price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
Product.java
package app02a.domain;
import java.io.Serializable;
public class Product implements Serializable {
private static final long serialVersionUID = 748392348L;
private String name;
private String description;
private float price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
再来看看两个jsp文件,先是ProductForm.jsp
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Add Product Form</title>
<style type="text/css">@import url(/css/main.css);</style> <%-- /开头,根目录,另外 @import url()这个,没什么印象,需要注意--%>
</head>
<body>
<div id="global">
<form action="product_save.action" method="post">
<fieldset> <%--这个标签还是挺实用的,记住吧--%>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label>
<input type="text" id="name" name="name"
tabindex="1"> <%--tabindex 属性规定元素的 tab 键控制次序(当 tab 键用于导航时,1 是第一个)。--%>
</p>
<p>
<label for="description">Description: </label>
<input type="text" id="description" name="description" tabindex="2">
</p>
<p>
<label for="price">Price: </label>
<input type="text" id="price" name="price" tabindex="3">
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5" value="Add Product">
</p>
</fieldset>
</form>
</div>
</body>
</html>
再是ProductDetails.jsp
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <%--依然是模板,粘贴复制的节奏--%>
<title>Save Product</title>
<style type="text/css">@import url(/css/main.css);</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br/> <%--EL表达式,没啥好说的,不懂就看专门的章节去( ⊙ o ⊙ )啊!--%>
Description: ${product.description}<br/>
Price: $${product.price}
</p>
</div>
</body>
</html>
没有涉及到Spring,纯粹复习servlet内容。
在2.4小节,介绍了“解耦控制器代码”的操作,主要就是修改了原ControllerServlet.java,将处理跳转的那部分由硬编码改为调用接口方法(新增Controller接口以及InputProductController和SaveProductController两个实现类)。目录结构变为:
ControllerServlet.java修改了部分代码,重命名为DispatcherServlet.java,具体见截图:
新增的Controller.java
package app02a.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Controller {
String handleRequest(HttpServletRequest request,
HttpServletResponse response);
}
InputProductController.java
package app02a.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class InputProductController implements Controller {
@Override
public String handleRequest(HttpServletRequest request,
HttpServletResponse response) {
return "/WEB-INF/jsp/ProductForm.jsp";
}
}
SaveProductController.java
package app02a.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import app02a.domain.Product;
import app02a.form.ProductForm;
public class SaveProductController implements Controller {
@Override
public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
ProductForm productForm = new ProductForm();
// populate form properties
productForm.setName(request.getParameter("name"));
productForm.setDescription(request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// create model
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
try {
product.setPrice(Float.parseFloat(productForm.getPrice()));
} catch (NumberFormatException e) {
}
// insert code to add product to the database
request.setAttribute("product", product);
return "/WEB-INF/jsp/ProductDetails.jsp";
}
}
如果根据现有情况来看,2.4节的改动无疑是增加了工作量,但把业务逻辑代码提取到独立的类中可以避免Servlet控制器的不断膨胀,利大于弊。
2.5小节对“校验器”功能做了个小的开发,原理很简单,就是新建了一个ProductValidator.java,内含validate()方法(在新的SaveProductController.java中被调用)对ProductForm对象进行属性值的判断并得出结论。以下是新增的代码部分:
ProductValidator.java
package app02a.validator;
import java.util.ArrayList;
import java.util.List;
import app02a.form.ProductForm;
public class ProductValidator {
public List<String> validate(ProductForm productForm) {
List<String> errors = new ArrayList<String>();
String name = productForm.getName();
if (name == null || name.trim().isEmpty()) {
errors.add("Product must have a name");
}
String price = productForm.getPrice();
if (price == null || price.trim().isEmpty()) {
errors.add("Product must have a price");
} else {
try {
Float.parseFloat(price);
} catch (NumberFormatException e) {
errors.add("Invalid price value");
}
}
return errors;
}
}
新版SaveProductController.java
package app02a.controller;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import app02a.domain.Product;
import app02a.form.ProductForm;
import app02a.validator.ProductValidator;
public class SaveProductController implements Controller {
@Override
public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
ProductForm productForm = new ProductForm();
// populate action properties
productForm.setName(request.getParameter("name"));
productForm.setDescription(request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// validate ProductForm
ProductValidator productValidator = new ProductValidator();
List<String> errors = productValidator.validate(productForm);
if (errors.isEmpty()) {
// create Product from ProductForm
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
product.setPrice(Float.parseFloat(productForm.getPrice()));
// no validation error, execute action method
// insert code to save product to the database
// store product in a scope variable for the view
request.setAttribute("product", product);
return "/WEB-INF/jsp/ProductDetails.jsp";
} else {
// store errors and form in a scope variable for the view
request.setAttribute("errors", errors);
request.setAttribute("form", productForm);
return "/WEB-INF/jsp/ProductForm.jsp";
}
}
}
ProductForm.jsp也做了相应更改,添加了输出错误提示的部分
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(/css/main.css);</style>
</head>
<body>
<div id="global">
<c:if test="${requestScope.errors != null}">
<p id="errors">
Error(s)!
<ul>
<c:forEach var="error" items="${requestScope.errors}">
<li>${error}</li>
</c:forEach>
</ul>
</p>
</c:if>
<form action="product_save.action" method="post">
<fieldset>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label>
<input type="text" id="name" name="name"
tabindex="1">
</p>
<p>
<label for="description">Description: </label>
<input type="text" id="description"
name="description" tabindex="2">
</p>
<p>
<label for="price">Price: </label>
<input type="text" id="price" name="price"
tabindex="3">
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5"
value="Add Product">
</p>
</fieldset>
</form>
</div>
</body>
</html>
第二章的内容就是这些,算是简单的MVC实例,没有涉及Spring