Spring MVC 框架学习五:使用 SpringMVC 实现RESTful风格的 CRUD

我们来看如何编写 RESTful 风格的 CRUD 同时复习之前学过的知识

新建一个名为 springmvc-crud 的 javaweb 工程,导入 springmvc 依赖包

 工程结构如图

配置 web.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    id="WebApp_ID" version="3.1">

    <!-- 自定义的spring 配置文件可以放置在类路径 src下,名字在 param-value 属性中指定 -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>

类路径 src 下新建一个为名 springmvc.xml 的 springmvc 的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="com.bupt.springmvc.crud"/>
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

WEB-INF 下新建一个 index.jsp 页面

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
    <a href="emps">List All Employees</a>
</body>
</html>

src 创建一个名为 com.bupt.springmvc.crud.entity 的包,新建两个实体类 Department 和 Employee

package com.bupt.springmvc.crud.entity;

public class Department {

    private Integer id;
    private String departmentName;

  //生成 getter 和 setter 方法,生成带参和不带参的构造器,重写 toString 方法
}
package com.bupt.springmvc.crud.entity;

import java.util.Date;

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;
    
    private Department department;

  //生成 getter 和 setter 方法,生成带参与不带参的构造器,重写 toString
}

我们编写了两个 Dao 并使用静态的数据来模拟数据库,而没有真正的去连接数据库

package com.bupt.springmvc.crud.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Repository;

import com.bupt.springmvc.crud.entity.Department;

@Repository
public class DepartmentDao {

    private static Map<Integer, Department> departments = null;
    
    static{
        departments = new HashMap<Integer, Department>();
        
        departments.put(101, new Department(101, "D-AA"));
        departments.put(102, new Department(102, "D-BB"));
        departments.put(103, new Department(103, "D-CC"));
        departments.put(104, new Department(104, "D-DD"));
        departments.put(105, new Department(105, "D-EE"));
    }
    
    public Collection<Department> getDepartments(){
        return departments.values();
    }
    
    public Department getDepartment(Integer id){
        return departments.get(id);
    }
}
package com.bupt.springmvc.crud.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.bupt.springmvc.crud.entity.Department;
import com.bupt.springmvc.crud.entity.Employee;

@Repository
public class EmployeeDao {

    private static Map<Integer, Employee> employees = null;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    static{
        employees = new HashMap<Integer, Employee>();

        employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1, new Department(101, "D-AA")));
        employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1, new Department(102, "D-BB")));
        employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0, new Department(103, "D-CC")));
        employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0, new Department(104, "D-DD")));
        employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1, new Department(105, "D-EE")));
    }
    
    private static Integer initId = 1006;
    
    public void save(Employee employee){
        if(employee.getId() == null){
            employee.setId(initId++);
        }
        employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
        employees.put(employee.getId(), employee);
    }
    
    public Collection<Employee> getAll(){
        return employees.values();
    }
    
    public Employee get(Integer id){
        return employees.get(id);
    }
    
    public void delete(Integer id){
        employees.remove(id);
    }
}

现在来创建我们的方法处理器,在com.bupt.springmvc.crud.handler 包下新建名为 EmployeeHandler 的处理类

package com.bupt.springmvc.crud.handler;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.bupt.springmvc.crud.dao.DepartmentDao;
import com.bupt.springmvc.crud.dao.EmployeeDao;
import com.bupt.springmvc.crud.entity.Employee;

@Controller
public class EmployeeHandler
{
    @Autowired
    private EmployeeDao employeeDao;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    @RequestMapping("/emps")
    public String list(Map<String, Object> map)
    {
        map.put("employees", employeeDao.getAll());
        return "list";
    }
    
}

我们要的效果是:来一个请求,springmvc 把所有的 Employee 对象放到 map 内,从而放到请求域中,再转向显示页面。页面从请求域获取对象按需求显示,所以我们还需要在WEB-INF 下新建一个名为 views 的 Folder,在 views 下新建一个名为 list.jsp 的显示页面。

在编写显示页面时我们需要用到原生的JSTL标签,所以需要我们导入 JSTL 依赖包

list.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>

</head>
<body>

    <c:if test="${empty requestScope.employees }">
        没有任何员工信息.
    </c:if>
    <c:if test="${!empty requestScope.employees }">
        <table border="1" cellpadding="10" cellspacing="0">
            <tr>
                <th>ID</th>
                <th>LastName</th>
                <th>Email</th>
                <th>Gender</th>
                <th>Department</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            
            <c:forEach items="${requestScope.employees }" var="emp">
                <tr>
                    <td>${emp.id }</td>
                    <td>${emp.lastName }</td>
                    <td>${emp.email }</td>
                    <td>${emp.gender == 0 ? 'Female' : 'Male' }</td>
                    <td>${emp.department.departmentName }</td>
                    <td><a href="">Edit</a></td>
                    <td><a href="">Delete</a></td>
                </tr>
            </c:forEach>
        </table>
    </c:if>
    <br><br>
    
</body>
</html>

将项目部署到 tomcat 上,启动 tomcat,访问 index.jsp 页面,点击超链接,页面跳转后的结果页面如图所示

完成显示功能后,我们来实现添加功能。添加一个员工的信息

我们需要完成两个工作:1. 显示添加页面;2. 点击提交按钮,完成添加,重定向到 list.jsp 页面

我们在list.jsp页面中新增超链接

    <a href="emp">Add New Employee</a>

 EmployeeHandler 里面新增方法,由于此方法是用来处理新增员工请求的,它是一个 GET 请求,所以我们在注解中指明它的请求方式为 GET

    @RequestMapping(value="/emp", method=RequestMethod.GET)
    public String input(Map<String, Object> map)
    {
        map.put("departments", departmentDao.getDepartments());
        map.put("employee", new Employee());
        return "input";
    }

在views下新建 input.jsp 页面,我们在编写 input.jsp 页面时需要用到 springmvc 的表单标签。使用 springmvc 的表单标签可以更快速的开发出表单页面,并且还可以进行表单值的回显。

input.jsp

<%@page import="java.util.HashMap"%>
<%@page import="java.util.Map"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>

    <!-- 使用绝对路径来指定映射路径,提交方式为POST -->
    <form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
        <!-- path 属性对应 html 表单标签的 name 属性,支持级联属性 -->
        LastName: <form:input path="lastName"/><br>        
        Email: <form:input path="email"/><br>
        <%
            Map<String, Object> genders = new HashMap<>();
            genders.put("1", "Male");
            genders.put("0", "Female");
            request.setAttribute("genders", genders);
        %>
        Gender: <form:radiobuttons path="gender" items="${genders}"/><br>
        Department : <form:select path="department.id" items="${requestScope.departments }" 
        itemLabel="departmentName" itemValue="id"></form:select><br>
        <input type="submit" value="submit">
    </form:form>
    
</body>
</html>

这里有几点需要注意:

使用 SpringMVC 的表单标签在默认情况下是一定要进行回显的(比如提交某个表单,但是表单验证错误,程序就重新跳转回提交页面,这时表单上就会回显上一次填写的表单值)

可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,则默认从 request 域对象中读取 command 的表单 bean,如果该属性不存在则会发生错误。

所以我们在 input 方法中添加这么一段代码 map.put("employee", new Employee()); 同时在 index.jsp 表单标签中添加 modelAttribute 属性,并赋值为 employee,此时表单 bean 就能与 modelAttribute 值所指代的 bean 相对应了。其实在跳转到 input.jsp 页面的所有请求方法中,都需要在请求域中添加名为 employee 值为 Employee 类型的键值对。

其中 modelAttribute 的默认值为 command

与 modelAttribute 实现相同功能的属性还有 commandName,用法与 modelAttribute 也基本一致

 <form:form action="${pageContext.request.contextPath }/emp" method="POST" commandName="employee">

处理完显示之后,我们再来完成添加操作,在 EmployeeHandler 类中添加处理方法。因为我们提交表单请求指明为POST,所以在注解中也要指定为POST

    @RequestMapping(value="/emp", method=RequestMethod.POST)
    public String save(Employee employee)
    {
        employeeDao.save(employee);
        return "redirect:/emps";
    }

现在我们来看效果,点击 Add New Employee 超链接,页面跳转到 input.jsp 页面,填写要增加的员工信息,点击提交效果如下图所示

现在我们来完成删除功能,首先我们来完善方法处理器,在 EmployeeHandler 类中新增方法

    @RequestMapping(value="/emp/{id}", method=RequestMethod.DELETE)
    public String delete(@PathVariable("id") Integer id)
    {
        employeeDao.delete(id);
        return "redirect:/emps";
    }

我们看到方法处理器是处理 DELETE 请求的,所以我们还需要在 web.xml 重配置 HiddenHttpMethodFilter 来完成请求方式的转换,在web.xml 中新增如下代码

    <!-- 配置 HiddenHttpMethodFilter:可以把POST请求转为 DELETE 或 POST请求 -->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

但是我们在 index.jsp 中发送的 删除请求是以超链接的形式发送的,这是一个 GET 请求,所以我们还需要使用到 JavaScript 的帮助来将 GET 请求方式转成 POST 请求方式

WebContent 下新建文件夹  script,将 jQuery 文件导入,list.jsp 页面改为如下所示

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
<script type="text/javascript" src="script/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
    $(function()
    {
        $(".delete").click(function()
            {
                var href = $(this).attr("href");
                $("form").attr("action", href).submit();
                return false;
            });
    })
</script>
</head>
<body>

    <form action="" method="post">
        <input type="hidden" name="_method" value="DELETE">
    </form>

    <c:if test="${empty requestScope.employees }">
        没有任何员工信息.
    </c:if>
    <c:if test="${!empty requestScope.employees }">
        <table border="1" cellpadding="10" cellspacing="0">
            <tr>
                <th>ID</th>
                <th>LastName</th>
                <th>Email</th>
                <th>Gender</th>
                <th>Department</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            
            <c:forEach items="${requestScope.employees }" var="emp">
                <tr>
                    <td>${emp.id }</td>
                    <td>${emp.lastName }</td>
                    <td>${emp.email }</td>
                    <td>${emp.gender == 0 ? 'Female' : 'Male' }</td>
                    <td>${emp.department.departmentName }</td>
                    <td><a href="">Edit</a></td>
                    <td><a class="delete" href="emp/${emp.id}">Delete</a></td>
                </tr>
            </c:forEach>
        </table>
    </c:if>
    <br><br>
    
    <a href="emp">Add New Employee</a>
    
</body>
</html>

但是此时点击 Delete 页面会报错,这是因为还涉及到 springmvc 处理静态资源的问题

优雅的 REST 风格的资源 URL 不希望带有 .html 或 .do 等后缀。若将 DispatcherServlet 请求映射配置为 "/",则 SpringMVC 将捕获 WEB 容器的所有请求。包括静态资源请求,SpringMVC 会将它们当成一个普通的请求处理,因为找不到相应的处理器将导致错误。如在本程序中的 jquery 就是一个静态资源而且并没有经过映射,所以程序会报错。

我们可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/> 的方式来解决静态资源的问题:

1. <mvc:default-servlet-handler/> 将会在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。

2. 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。

所以要正确处理这里的静态资源,我们还需要在 springmvc.xml 中配置 <mvc:default-servlet-handler/>,同时还需要配置 <mvc:annotation-driven"/>

<mvc:default-servlet-handler/>
<mvc:annotation-driven/>

此时程序就能正常执行删除员工信息的操作了

我们再来看看 <mvc:default-servlet-handler/> 和 <mvc:annotation-driven/> 之间的关系

在 Employee 实体类的 setLastName() 方法内设置断点的,点击 Add New Employee 超链接,填写表单提交数据

既没有配置 <mvc:default-servlet-handler/> 也没有配置 <mvc:annotation-driven/>

配置了 <mvc:default-servlet-handler/> 但没有配置 <mvc:annotation-driven/>

 

既配置了 <mvc:default-servlet-handler/> 又配置 <mvc:annotation-driven/>

 

其中 AnnotationMethodHandlerAdapter 是 RequestMappingHandlerAdapter 之前的版本,它是 Deprecated 的。它与 RequestMappingHandlerAdapter 的作用都是处理 @RequestMapping 映射的。当没有这个值时是不能处理映射请求的,程序就报错了。

最后我们来实现修改操作

修改 list.jsp 页面,在 Edit 超链接中添加提交的地址和参数

<td><a href="emp/${emp.id}">Edit</a></td>

EmployeeHandler 中新增处理方法,通过传递占位符确定修改的员工信息具体为多少,并在修改页面进行回显。Edit 请求为 GET 请求

    @RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
    public String input(@PathVariable("id") Integer id, Map<String, Object> map)
    {
        //用于回显的 employee,键名要和 input.jsp 的 modelAttribute 的值一致
        map.put("employee", employeeDao.get(id));
        map.put("departments", departmentDao.getDepartments());
        return "input";
    }

由于修改时 lastName 不进行修改,所以我们要对 input.jsp 页面进行修改。在 LastName 处增加判断语句,同时将 POST 请求转成 PUT请求,其他不变

        <c:if test="${employee.id == null }">
            LastName: <form:input path="lastName"/><br>        
        </c:if>
        <c:if test="${employee.id != null }">
        <%-- _method 属性未使用 form:hidden 的原因是,modelAttribute 对应的 bean 中没有 _method 属性 --%>
            <form:hidden path="id"/>
            <input type="hidden" name="_method" value="PUT">
        </c:if

再在 EmployeeHandler 类中增加方法,此时需要注意的是,因为我们没有提交 lastName 这个属性,这个属性值来自于修改前的员工信息,所以我们还需要增加一个被@ModelAttribute 修饰的 getEmployee() 方法,来保证lastName属性有值,这个值通过传递来的id属性确定。此时传递的参数不再是通过占位符的方式传递而是通过请求参数来传递。具体@ModelAttribute 实现的功能可以查看

    @ModelAttribute
    public void getEmployee(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map)
    {
        if(id != null)
        {
            map.put("employee", employeeDao.get(id));
        }
    }
    
    @RequestMapping(value="/emp", method=RequestMethod.PUT)
    public String update(Employee employee)
    {
        employeeDao.save(employee);
        return "redirect:/emps";
    }

现在我们队 ID 为 1005 的员工信息进行修改,将 gender 改为 Female,Department 由 D-EE 改为 D-AA

点击提交可以看到结果页面效果如图

 以上就是 RESTful 风格的 CRUD

源码在这

http://files.cnblogs.com/files/2015110615L/springmvc-crud.rar

原文地址:https://www.cnblogs.com/2015110615L/p/5639441.html