SpringMVC 之 RESTful 风格的增删改查

时间:2022-10-20 21:00:15

1. 视图和视图解析器

  1. 视图解析器
    • 请求处理方法执行完成后,最终返回一个ModelAndView对象,对于返回String,View 或 ModelMap
      等类型的处理方法, SpringMVC 也会在内部将它们装配成一个ModelAndView对象;
    • SpringMVC借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP,
      EXCEL,PDF等各种表现形式的视图;
  2. 视图
    • 视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户;
    • View 接口,位于 org.springframework.web.servlet包中;
    • 视图对象由视图解析器负责实例化.由于视图是无状态的,所以它们不会有线程安全的问题;

SpringMVC 之 RESTful 风格的增删改查

2. mvc:view-controller标签

  • 作用: 在不需要Controller处理request请求的情况下,直接将设置的View交给相应的视图解析器解析为
    视图;
// 在 springDispatcherServlet-servlet.xml 中配置
<mvc:view-controller path="/自定义网址" view-name="View视图页面名称"/>

// 还需要使用 mvc:annotation-driven 标签,否则,在使用 Controller 处理request请求时,访问
// 该页面,会报异常;
<mvc:annotation-driven></mvc:annotation-driven>

3. 自定义视图

3.1 具体实现步骤

  • 建立专门的view包: cn.itcast.springmvc.views;
  • 编写一个View接口的实现类;
  • BeanNameViewResolver配置进springmvc配置文件;
// 1. 创建View接口的实现类
@Component
public class HelloView implements View{

    public String getContentType(){
        return "text/html;charset=UTF-8";
    }

    public void render(Map<String,?> model, HttpServletRequest request,
                                HttpServletResponse response) throws Exception{

        response.setContentType("text/html;charset=utf-8");

        response.getWriter().write("自定义视图显示");
        response.getWriter().flush();
        response.getWriter().close();                            
        }
}

// 2. springDispatcherServlet-servlet.xml 中配置视图解析器
<bean id="BeanNameViewResolver"
                  class="org.springframework.web.servlet.view.BeanNameViewResolver">

    <!-- 自定义order,越小越靠前 --
    <property name="order" value="30"></property>
</bean>

// 3. demo.java
@Controller
public class HelloWorld{
    @RequestMapping(value="/helloworld",method=RequestMethod.GET)
    public String helloworld(){
        System.out.println("执行成功");

        return "helloView";
    }
}

4. 请求转发和重定向

  • 地址栏是否变化,参数能否取得,发送了几次请求;
  • 请求转发:一个请求一个响应;
  • 重定向:两个请求两个响应,两个请求之间毫无关系,所以第一个请求里面保存的信息在第二个请求里面无法获得;
// demo.java
@Controller
public class Helloworld{

    // 重定向
    @RequestMapping(value="/redirect",method=RequestMethod.GET)
    public String helloworld(){
        System.out.println("程序运行正常");
        return "redirect:/1.jsp";
    }

    // 转发
    @RequestMapping(value="/forward",method=RequestMethod.GET)
    public String helloworld(){
        System.out.println("程序运行正常");
        return "forward:/2.jsp";
    }
}

5. RESTful SpringMVC CRUD

5.1 REST(Representational State Transfer) 架构的主要原则
  • 网络上的所有事物都可被抽象为资源(Resource);
  • 每个资源都有一个唯一的资源标识符(Resource Identifier);
  • 同一资源具有多种表现形式(xml,json等);
  • 对资源的各种操作不会改变资源标识符;
  • 所有的操作都是无状态的(Stateless);
  • 符合REST原则的架构方式,即可称为RESTful;
// 需求:
//   1. 查询
//        * URI: emps
//        * 请求方式: GET
//   2. 添加所有员工信息
//        2.1 显示添加页面:
//                * URI: emp
//                * 请求方式: GET
//        2.2 添加员工
//                * URI: emp
//                * 请求方式: POST
//                * 添加完成后,重定向到 list 页面
//   3. 删除
//        * URI: emp/{id}
//        * 请求方式: DELETE
//   4. 修改操作 (其中 lastName 不可修改!!)
//       4.1 显示修改页面
//              * URI: emp/{id}
//              * 请求方式: GET
//       4.2 修改员工信息
//              * URI: emp
//              * 请求方式: PUT
//              * 完成修改,重定向到 list 页面

// index.jsp
显示所有员工信息: <a href="${pageContext.request.contextPath}/emps">显示所有</a>

// list.jsp (显示所有员工信息页面)
<head>
    <!-- 引入 jquery 脚本 -->
    <script type="text/javascript"
            src="${pageContext.request.contextPath}/scripts/jquery-3.2.1.min.js">
    </script>
    <!-- 使用表单提交 delete 请求 -->
    <script type="text/javascript">
        $(function(){
            $(".deleteCss").click(function(){
                var action = $(this).attr("href");
                $("#deleteForm").attr("action",action).submit();

                <!-- 取消 click 的默认事件 -->
                return false;
            });
        });
    </script>
</head>
<body>
    <h2>显示所有员工</h2>
    <c:if test="${empty requestScope.employees}">
        it's nothing...
    </c:if>
    <c:if test="${not empty requestScope.employees}">
        <table border="1" cellpadding="10" cellspacing="2">
        <tr>
            <td>id</td>
            <td>lastName</id>
            <td>email</td>
            <td>gender</id>
            <td>department</td>
            <td>edit</id>
            <td>delete</td>
        </tr>
        <c:forEach items="${requestScope.employees}" var="employee">
            <tr>
                <td>${employee.id}</td>
                <td>${employee.lastName}</id>
                <td>${employee.email}</td>
                <td>${employee.gender == 1 ? 'male' : 'female'}</id>
                <td>${employee.department.departmentName}</td>
                <td>
                <a href="${pageContext.request.contextPath}/emp/${employee.id}">
                    edit</a></id>
                <td>
                    <a class="deleteCss"
                    href="${pageContext.request.contextPath}/emp/${employee.id}>
                        delete
                    </a>
                </td>
            </tr>
        </c:forEach>
    </c:if>

    添加员工: <a href="${pageContext.request.contextPath}/emp">添加</a>
    <br/>

    <!-- 需要执行RESTful风格的删除,需要使用form表单 post 提交 -->
    <form id="deleteForm" action="" method="post">
        <input type="hidden" name="_method" value="DELETE"/>
    </form>


</body>

// add.jsp (添加之前的编辑页面)

// 需要引入 springframework 的标签
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<body>
<h2>添加客户</h2>
<form:form action="${pageContext.request.contextPath}/emp" method="post"
                                                            modelAttribute="employee">
    lastName:<form:input path="lastName"/><br/>
    email:<form:input path="email"/><br/>
    gender:<form:radiobuttons path="gender" items="${requestScope.genders}"/><br/>
    department:<form:select path="department.id" items="${requestScope.departments}"
                    itemLabel="departmentName" itemValue="id"/></form:select><br/>
    <input type="submit" value="保存"/>
</form:form>
</body>

// edit.jsp (修改页面)
<h2>修改客户</h2>
<form:form action="${pageContext.request.contextPath}/emp" method="post"
                                                            modelAttribute="employee">
    <input type="hidden" name="_method" value="PUT"/><br/>
    <form:hidden path="id"/>

    email:<form:input path="email"/><br/>
    gender:<form:radiobuttons path="gender" items="${requestScope.genders}"/><br/>
    department:<form:select path="department.id" items="${requestScope.departments}"
                    itemLabel="departmentName" itemValue="id"/></form:select><br/>
    <input type="submit" value="修改"/>
</form:form>
</body>


// springDispatcherServlet-servlet.xml 配置文件
// 若将 web.xml 中 的 DispatcherServlet 请求映射配置为 "/", 则 SpringMVC 将捕获 Web 容器
// 的所有请求,包括静态资源的请求, SpringMVC 会将它们当成一个普通请求处理,会找不到对应处理器;
// 可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler/>,以解决静态资源的问题:
//    * <mvc:default-servlet-handler/> 将在 SpringMVC 上下文中定义一个
//    * DefaultServletHttpRequestHandler, 它会对 DispatcherServlet 的请求进行筛查,
//    * 如果发现是没有经过映射的请求,就将该请求交由WEB应用服务器默认的 Servlet 处理,如果不是
//    * 静态资源的请求,才由 DispatcherServlet 继续处理;
//    一般 WEB 应用服务器默认的 Servlet 的名称都是 default, 若所使用的 WEB 服务器的默认
//    Servlet 名称不是 default, 则需要通过 default-servlet-name 属性显示指定;

<!-- 处理静态资源导入, 例如导入 jquery -->
<mvc:default-servlet-handler/>
<!-- 如果只有 mvc:default-servlet-handler, 注解类失效, 还需要配置 annotation-driven -->
<mvc:annotation-driven></mvc:annotation-driven>

// web.xml
// 默认情况下,PUT和DELETE请求是无法提交表单数据的;
// 解决方案: 在web.xml中配置spring提供的过滤器解决或者在页面中使用form表单设置
<filter>
    <filter-name>HttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!--
      将POST请求转化为DELETE或者是PUT
       要用_method指定真正的请求参数
-->
<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>


// EmployeeHandler.java

@Controller
public class EmployeeHander{

    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private DepartmentService departmentService;

    // 跳转进入添加员工信息页面
    // 需要查询出性别,全部部门
    @RequestMapping(value="/emp",method=RequestMethod.GET)
    public String addPre(Map<String,Object> map){

        // 1. 查询出全部部门
        map.put("departments",departmentService.getDepartments());
        // 2. 查出性别
        map.put("genders",getGenderUtils());

        // 3. 新建承载的 bean, 实现与前台 form 表单的对应

        // 在进入 spring form 标签设定 binding 对象页面前,必须有一个 binding 的对象
        // 放在 context 中,spring 才能 binding.类似struts的context statck和value stack.

        // 可以在 form 表单上通过 modelAttribute 属性指定绑定的模型属性,
        // 如果没有指定该属性, 则默认从 request 域对象中读取 command 的表单 bean.
        // 如果bean不存在,则会引发异常.

        map.put("employee", new Employee());

        return "add";
    }

    // 保存员工信息
    @RequestMapping(value="/emp",method=RequestMethod.POST)
    public String add(Employee employee){
        employeeService.add(employee);
        return "redirect:/emps";
    }

    // 删除员工信息
    @RequestMapping(value="/emp/{id}",method=RequestMethod.DELETE)
    public String delete(@PathVariable("id") Integer id){
        employeeService.delete(id);
        return "redirect:/emps";
    }

    // 修改之前,跳转到修改页面
    // lastName 不可修改
    @RequestMapping(value="/emp/{id}",method=RequestMethod.GET)
    public String editPre(@PathVariable("id") Integer id, Map<String,Object> map){
        map.put("departments",departmentService.getDepartments());
        map.put("genders",getGenderUtils());
        map.put(employee,employeeService.get(id));
        return "edit";
    }

    // 修改
    @RequestMapping(value="/emp",method=RequestMethod.PUT)
    public String update(Employee employee){

        employeeService.save(employee);
        return "redirect:/emps";
    }

    // 保证 lastName 不能修改,且值不变
    @ModelAttribute
    public void getEmployeeById(@RequestParam(value="id",required=false) Integer id,
                                            Map<String,Object> map){
        if(null != id){
            map.put("employee",employeeService.get(id));
        }
    }


    // 查询所有员工
    @RequestMapping(value="/emps",method=RequestMethod.GET)
    public String list(Map<String,Object> map){

        map.put("employees",employeeService.findAll());
        return "list";
    }

    // 模拟从 LOV(list of value) 数据库查询出 gender 的值
    private Map<String, String> getGenderUtils(){
        Map<String,String> genders = new HashMap<String,String>();

        genders.put("1","male");
        genders.put("0","female");

        return genders;
    }
}


参考资料