次接着上次的博客继续将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>
控制台输出:
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”是一致的,而控制器方法参数本身的名称无所谓。
控制输出如下:
传统的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;
控制台输出:
以上就是控制器处理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 的显示效果如下:
当然我们还得添加一个表单提交之后相应的控制器方法,即表单中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真好用啊)。
重定向之后的输出日志。
好,最好我把本篇的控制器类完整给出:
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();
}
}