springMVC笔记系列(20)——控制器实现详解(下)

时间:2022-09-08 14:21:20

次接着上次的博客继续将springMVC控制器的东西说完。本篇主要说说控制器处理带属性参数的url请求的三种方式:参数风格、rest风格、传统的HttpServlet风格。

参数风格

其实,上篇博客已经在示例当中将参数风格的实现方式给出了,不过没有详细说明。

所谓参数风格,就是讲url的请求参数按照url请求参数的格式予以呈现,咳咳,似乎有点废话,不过这种方式应该是最一般的方式,也是过去一直用的。

比如,请求 http://localhost:8080/mvc/courses/view?courseId=123

控制器写法:(完整写法借鉴上一篇博客,或者在本文最后一并给出新的)

 //提供完成一个业务的方法:根据课程ID查询课程内容。
    //本方法将处理 /courses/view?courseId=123 形式的URL
    @RequestMapping(value="/view", method= RequestMethod.GET)
    public String viewCourse(@RequestParam("courseId") Integer courseId,
                             Model model) {
        //日志输出,查看请求的courseId是不是我们的courseId
        log.info("In viewCourse, courseId = {}", courseId);
        Course course = courseService.getCoursebyId(courseId);
        model.addAttribute(course);
        return "course_overview";
    }

这里,参数风格的处理方式,需要接收url中的参数,须要借助@RequestPara注解。@RequestPara注解在方法的某个参数变量上,然后注解@RequestPara的注解value设置为url中参数值,这样@RequestPara就建立起了url的参数与方法参数之间的映射,也就完成了参数的传递。比如,这里的请求 http://localhost:8080/mvc/courses/view?courseId=123中的请求参数courseId与方法参数上注解@RequestParam(“courseId”)中的”courseId”是同一个,或者说必须一致。而方法public String viewCourse的方法参数Integer courseId也未必一定要与其同名。

另外,方法的两个参数Integer courseId和Model model分别负责请求参数的接收 和 结果属性的返回。Model model可以接收我们需要返回或者说需要在view的JSP页面显示中要用到的某个属性对象。例如,这里的model.addAttribute(course);里的course必须与下面\WEB-INF\jsps\course_overview.jsp中的代码段中course同名,这是这种model.addAttribute默认的。当然将结果返回也有三种方式,即model、Map、ModelAndView。这个以前的博客中说过,不过springMVC会在内部自动将它们都转换成ModelAndView。

Map返回方式我在下面一个rest风格再作说明。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ 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=UTF-8">
<title>HappyBKs喜欢原先的osc博客页面</title>

<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
</head>
<body>
    <div id="main">

        <div class="newcontainer" id="course_intro">
            <div class="course-title">${course.title}</div>
            <div class="course_info">
                <div class="course-embed l">
                    <div id="js-course-img" class="img-wrap">
                        <img width="600" height="340" alt="" src="<%=request.getContextPath()%>/${course.imgPath}" class="course_video" />
                    </div>
                    <div id="js-video-wrap" class="video" style="display: none">
                        <div class="video_box" id="js-video"></div>
                    </div>
                </div>
                <div class="course_state">
                    <ul>
                        <li><span>学习人数</span> <em>${course.learningNum }</em></li>
                        <li class="course_hour"><span>课程时长</span> <em  class="ft-adjust"><span>${course.duration }</span></em></li>
                        <li><span>课程难度</span> <em>${course.levelDesc }</em></li>
                    </ul>
                </div>

            </div>
            <div class="course_list">
                <div class="outline">
                    <h3 class="chapter_introduces">课程介绍</h3>
                    <div class="course_shortdecription">${course.descr}</div>

                    <h3 class="chapter_catalog">课程提纲</h3>
                    <ul id="couList">
                        <c:forEach items="${course.chapterList}" var="chapter">
                            <li class="clearfix open"><a href="#">
                                    <div class="openicon"></div>
                                    <div class="outline_list l">
                                        <!-- <em class="outline_zt"></em> -->
                                        <h5 class="outline_name">${chapter.title }</h5>
                                        <p class="outline_descr">${chapter.descr }</p>
                                    </div>
                            </a></li>
                        </c:forEach>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

控制台输出:
springMVC笔记系列(20)——控制器实现详解(下)

Rest风格:

最新的处理请求的方式应该算是Rest风格了。

什么是Rest风格?

REST ( REpresentational State Transfer ),State Transfer 为 “状态传输” 或 “状态转移 “,Representational 中文有人翻译为”表征”、”具象”,合起来就是 “表征状态传输” 或 “具象状态传输” 或 “表述性状态转移”,不过,一般文章或技术文件都比较不会使用翻译后的中文来撰写,而是直接引用 REST 或 RESTful 来代表,因为 REST 一整个观念,想要只用六个中文字来完整表达真有难度。

REST的主要原则有:

用URL表示资源。资源就像商业实体一样,是我们希望作为API实体呈现的一部分。通常是一个名词,每个资源都用一个独一无二的URL来表示。

HTTP方法表示操作。REST充分利用了HTTP的方法,特别是GET、POST、PUT和DELETE。注意XMLHttpRequest对象实现了全部的方法,具体可以参看W3C HTTP 1.1 Specification。

也就是说,客户端的任何请求都包含一个URL和一个HTTP方法。回到上面的例子中,比赛显然是一个实体,那么对于一个特定比赛的请求就表示为:

http://example.com/matches/995
这种方式是清晰明了的,也许和精确命名的方式有所区别,但是只要遵循这种形式,我们就能很快的进行GET、DELETE、UPDATE和新建操作。

RESTful的原则:

URL表示资源
HTTP方法表示操作
GET只是用来请求操作,GET操作永远都不应该修改服务器的状态。但是这个也要具体情况进行分析,例如一个页面中的计数器,每次访问的时候确实引起了服务器数据的改变,但是在商业上来说,这并不是一个很重要的改变,所以仍然可以接收使用GET的方式来修改数据。
服务应该是无状态的
在有状态的会话中,服务器可以记录之前的信息。而RESTful风格中是不应该让服务器记录状态的,只有这样服务器才具备可扩展性。当然,我们可以在客户端使用cookie,而且只能用在客户端向服务器发送请求的时候。

服务应当是“幂等”的
“幂等”表示可以发送消息给服务,然后可以再次毫不费力的发送同样的消息给服务。例如,发送一个“删除第995场比赛”的消息,可以发送一次,也可以连续发送十次,最后的结果都会保持一致。当然,RESTful的GET请求通常是幂等的,因为基本上不会改变服务器的状态。注意:POST请求不能被定义为“幂等”,特别是在创建新资源的时候,一次请求创建一个资源,多次请求会创建多个资源。

拥抱超链接
服务应当自我说明
例如 http://example.com/match/995 请求了一个具体的比赛,但是 http://example.com/match 并没有对任何实体进行请求,因此,应当返回一些介绍信息。

服务约束数据格式。数据必须符合要求的格式

好好好,有个概念即可,还是用例子说话。刚才的需求我们用rest风格实现,请求应该变成这个样子:http://localhost:8080/mvc/courses/view2/345

当然这里因为是查询功能,所以请求的方法类型是Get,当然,默认也是Get。

于是,我们的控制器方法可以写为:

    //本方法将处理 /courses/view2/123 形式的URL
    @RequestMapping("/view2/{courseId}")
    public String viewCourse2(@PathVariable("courseId") Integer courseId,
                              Map<String, Object> model) {

        log.info("In viewCourse2, courseId = {}", courseId);
        Course course = courseService.getCoursebyId(courseId);
        model.put("course",course);
        return "course_overview";
    }

这里,我们在仍然需要为rest风格中的请求实体与控制器方法中的方法参数建立映射对应关系。这里,由于rest风格在url中并没有提供实体参数的名称,只是在url的某个子串内提供,所以需要对该子串的位置进行标明,即用花括号{}在@ResquestMapping的value中进行标注,并取名。如,本例中的@RequestMapping(“/view2/{courseId}”)。在控制器方法中,对映射的方法参数需要用另一个注解@PathVariable来注解相应的方法参数,@PathVariable注解value为@RequestMapping的value中的花括号内的名称,即@RequestMapping(“/view2/{courseId}”)的{courseId}与@PathVariable(“courseId”)中的”courseId”是一致的,而控制器方法参数本身的名称无所谓。

控制输出如下:

springMVC笔记系列(20)——控制器实现详解(下)

传统的HttpServlet风格

这种方式其实还是处理的是带参数的url请求的方法,只不过控制器方法是针对底层servlet的处理方式,这种方式是为了体现一般的servlet编写方式可以与springMVC框架相兼容。

请求http://localhost:8080/mvc/courses/view3?courseId=678

控制器的方法参数用的是HttpServletRequest request,当然,取参数什么的用的还是传统的HttpServletRequest的请求实体对象的参数取出方法。实体对象的返回用的也是request.setAttribute(“course”,course);的处理方式。

    //本方法将处理 /courses/view3?courseId=123 形式的URL
    @RequestMapping("/view3")
    public String viewCourse3(HttpServletRequest request) {

        Integer courseId = Integer.valueOf(request.getParameter("courseId"));

        log.info("In viewCourse3, courseId = {}", courseId);

        Course course = courseService.getCoursebyId(courseId);
        request.setAttribute("course",course);

        return "course_overview";
    }

这里值得注意的是,返回结果实体的方式不是利用Model、Map或ModelAndView,而是用最老土的方式,request.setAttribute方法来将属性结果按照键值对的形式给出,键的名称是JSP中的实体名称,值则是控制器方法中处理的实体对象。

这里还需要注意的是,HttpServletRequest并不是标准Java SDK中的类,因此须要为项目添加依赖jar包:

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>

引入:

import javax.servlet.http.HttpServletRequest;

控制台输出:
springMVC笔记系列(20)——控制器实现详解(下)

以上就是控制器处理url请求参数数据的三种方法,下面我们模拟一个示例,添加一个课程,然后重定向到显示页面。

这里,仅仅是个示例,我不想再添加有关数据库的操作,所以显示部分我还是用
@RequestMapping(“/view2/{courseId}”)
public String viewCourse2(@PathVariable(“courseId”) Integer courseId,
Map

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ 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=UTF-8">
    <title>HappyBKs课程录入页面</title>

    <link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css" />
</head>
<body>
<div id="main">
    <div class="newcontainer" id="course_intro">
        <form name="mainForm" action="<%= request.getContextPath()%>/courses/save" method="post">
            <div>
                <span>课程名称:</span><input type="text" id="title" name="title">
            </div>
            <div>
                <span>课程时长:</span><input type="text" id="duration" name="duration"></div>
            <div>
                <span>课程难度:</span>
                <select id="level" name="level">
                    <option value="0">初级</option>
                    <option value="1" selected="selected">中级</option>
                    <option value="2">高级</option>
                </select>
            </div>
            <div>
                <span>课程介绍:</span>
                <textarea id="descr" name="descr" rows="5" style="width:480px"></textarea>
            </div>
            <div>
                <input type="submit" id="btnPass" value="提交" />
            </div>
        </form>
    </div>
</div>
</body>
</html>

插一句吧,request.getContextPath()应该是得到项目的名字(如果项目为根目录,则得到一个”“,即空的字条串)。

然后,我们在控制器中继续添加一个方法:

    @RequestMapping(value="/admin", method = RequestMethod.GET, params = "add")
    public String createCourse(){
        return "course_admin/edit";
    }

运行时请求 http://localhost:8080/mvc/courses/admin?add 的显示效果如下:
springMVC笔记系列(20)——控制器实现详解(下)

当然我们还得添加一个表单提交之后相应的控制器方法,即表单中action=”<%= request.getContextPath()%>/courses/save”所指示的请求url。

控制器中添加方法如下:

    @RequestMapping(value="/save", method = RequestMethod.POST)
    public String  doSave(@ModelAttribute Course course){

        log.info("Info of Course:");
        log.info(ReflectionToStringBuilder.toString(course));

        //在此进行业务操作,比如数据库持久化
        course.setCourseId(123);
        return "redirect:view2/"+course.getCourseId();
    }

这里值得注意的有三点,一个是,标记解释某个model实体,可以在控制器方法对应的实体参数上注解@ModelAttribute,相应的实体会被最终传递到对应的view资源。

第二,重定向到某个页面,只需要在返回的结果字符串前加上“redirect:”即可。

第三,ReflectionToStringBuilder.toString是org.apache.commons.lang.builder.ReflectionToStringBuilder中的方法,当然,添加相应的POM坐标也是必然的。详细可以参见往前本系列特别篇开始以后的博客文章。

像之前说的,由于没有修改Service中的实现,数据是模拟的,这里就不把展示页面截图了。

这里只把控制台输出:

表单提交后,接收请求的控制器方法doSave输出的实体对象键值对(ReflectionToStringBuilder真好用啊)。
springMVC笔记系列(20)——控制器实现详解(下)
重定向之后的输出日志。
springMVC笔记系列(20)——控制器实现详解(下)
好,最好我把本篇的控制器类完整给出:

package com.happyBKs.controller;

import com.happyBKs.model.Course;
import com.happyBKs.service.CourseService;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/** * Created by happyBKs on 2016/6/15. */

@Controller
@RequestMapping("/courses")
// /courses/**
public class CourseController {


    //完成日志信息
    private static Logger log= LoggerFactory.getLogger(CourseController.class);

    private CourseService courseService;

    //使用spring容器管理里了对应的依赖关系
    @Autowired
    public void setCourseService(CourseService courseService) {
        this.courseService = courseService;
    }

    //提供完成一个业务的方法:根据课程ID查询课程内容。
    //本方法将处理 /courses/view?courseId=123 形式的URL
    @RequestMapping(value="/view", method= RequestMethod.GET)
    public String viewCourse(@RequestParam("courseId") Integer courseId,
                             Model model) {
        //日志输出,查看请求的courseId是不是我们的courseId
        log.info("In viewCourse, courseId = {}", courseId);
        Course course = courseService.getCoursebyId(courseId);
        model.addAttribute(course);
        return "course_overview";
    }


    //本方法将处理 /courses/view2/123 形式的URL
    @RequestMapping("/view2/{courseId}")
    public String viewCourse2(@PathVariable("courseId") Integer courseId,
                              Map<String, Object> model) {

        log.info("In viewCourse2, courseId = {}", courseId);
        Course course = courseService.getCoursebyId(courseId);
        model.put("course",course);
        return "course_overview";
    }

    //本方法将处理 /courses/view3?courseId=123 形式的URL
    @RequestMapping("/view3")
    public String viewCourse3(HttpServletRequest request) {

        Integer courseId = Integer.valueOf(request.getParameter("courseId"));

        log.info("In viewCourse3, courseId = {}", courseId);

        Course course = courseService.getCoursebyId(courseId);
        request.setAttribute("course",course);

        return "course_overview";
    }

    @RequestMapping(value="/admin", method = RequestMethod.GET, params = "add")
    public String createCourse(){
        return "course_admin/edit";
    }

    @RequestMapping(value="/save", method = RequestMethod.POST)
    public String  doSave(@ModelAttribute Course course){

        log.info("Info of Course:");
        log.info(ReflectionToStringBuilder.toString(course));

        //在此进行业务操作,比如数据库持久化
        course.setCourseId(123);
        return "redirect:view2/"+course.getCourseId();
    }


}