目录
1 SpringMVC简介
1.1 什么是mvc
1.2 什么是SpringMVC
1.3 SpringMVC的特点
2 入门案例
2.1 开发环境
2.2 创建maven工程
2.3 配置web.xml
2.4 创建请求控制器
2.5 创建SpringMVC配置文件
2.6 测试HelloWorld
2.7 优化配置
3 @RequestMapping注解
3.1 @RequestMapping注解的功能
3.2 @RequestMapping注解的位置
3.3 @RequestMapping注解的value属性
3.4 @RequestMapping注解的method属性
3.5 @RequestMapping注解的params属性(了解)
3.6 @RequestMapping注解的headers属性(了解)
3.7 SpringMVC支持ant风格的路径
3.8 SpringMVC支持路径中的占位符(重点)
4 SpringMVC获取请求参数
4.1 通过ServletAPI获取
4.2 通过控制器方法的形参获取请求参数
4.3 @RequestParam
4.4 @RequestHeader
4.5 @CookieValue
4.6 通过pojo获取请求参数
4.7 解决获取请求参数乱码的问题
5 域对象共享数据
5.1 使用ServletAPI向request域对象共享数据
5.2 使用ModelAndView向Request域对象共享数据
5.3 使用Model向request对象共享数据
5.4 使用Map向request对象共享数据
5.5 使用ModelMap向request对象共享数据
5.6 Model、ModelMap、Map的关系
5.7 向session域共享数据
5.8 向Application域共享数据
6 SpringMVC的视图
6.1 ThymeleafView
6.2 转发视图
6.3 重定向视图
6.4 视图控制器view-controller
7 RESTful
7.1 RESTful简介
7.2 RESTful的实现
7.3 RESTful之查询和保存功能
7.4 RESTful之删除和更新功能
8 RESTful案例
8.1 准备工作
8.2 功能清单
8.3 访问首页
8.4 查询所有员工信息
8.5 添加员工信息
8.6 修改信息
8.7 删除信息
之前我们就接触过这个概念,MVC是一种软件架构思想,将软件按照模型、视图、控制器划分
①添加web模块
我们这次直接添加默认maven
②打包方式:war
我们打开模块添加一个web.xml,注意改好文件路径
这样就创建好了
③引入依赖
org.springframework
spring-webmvc
5.3.1
ch.qos.logback
logback-classic
1.2.3
javax.servlet
javax.servlet-api
3.1.0
provided
org.thymeleaf
thymeleaf-spring5
3.0.12.RELEASE
springmvc
org.springframework.web.servlet.DispatcherServlet
springmvc
/
这里和之前学的javaweb配置的一样只不过servlet-class标签使用了前端控制器DispatcherServlet,还有要注意的是:url-pattern的设置:我们这里使用“/”,它表示匹配浏览器向服务器发送的所有请求(不包括.jsp的文件),而“/*”表示匹配浏览器向服务器发送的所有请求(包括.jsp的文件),而我们的DispatcherServlet是无法访问jsp文件的。
SpringMVC中封装了servlet,我们不需要自己写servlet了,我们只需要创建一个普通java类即可也就是pojo类,通过@Controller注解将其标识为一个控制层组件,交给 spring容器的IoC容器管理,此时SpringMVC才能够识别控制器的存在。
package com.itzw.controller;
import org.springframework.stereotype.Controller;
@Controller
public class HelloController {
}
在DispatcherServlet初始化的时候就会加载SpringMVC配置文件,它是自动完成的,所以我们不能像之前加载spring配置文件那样随便起名字随便放位置。它有规定好的文件名和路径。SpringMVC配置文件默认的位置和名称:位置:WEB-INF下;名称:
值得注意的是:视图前缀+逻辑视图+视图后缀=物理视图,也就是说当我们想访问/WEB-INF/templates/index.html我们只要访问index即可。
我们先简单配置tomcat服务器,这是我们很熟悉的操作:
注意我们已经写了一个前端页面:
它的位置如上所示,我们此时启动服务器肯定是访问不到这个文件的。这个文件必须是在webapp目录下才是默认可以访问。那么我们怎么设置?
@Controller
public class HelloController {
@RequestMapping("/")
public String index(){
return "index";
}
}
如上,@RequestMapping注解可以接收到浏览器的请求,但是它怎么知道要接收哪个路径的请求呢?我们需要设置注解的value值为“/”,这个斜杠就表示当前工程的上下文路径,也就是:http://localhost:8080/springmvc/,然后我们返回逻辑视图的名称,因为之前我们已经设置好视图前缀和视图后缀,我们直接返回逻辑视图的名字即可。这就访问到了:
下面我们通过超链接跳转到指定界面:
注意th:是thymeleaf的语法,他有啥用呢?我们这里的hello路径前面的路径为上下文路径也就是http://localhost:8080/springmvc/
还记得我们之前想获取项目名称非常的麻烦,这里就很简单。但是下面的hello路径就没有上下文路径,我们看:
访问这个路径后我们要返回给它一个文件:
@RequestMapping("/hello")
public String hello(){
return "success";
}
注意这里的注解值就要 写上“/hello”了因为我们访问的路径是http://localhost:8080/springmvc/hello
之前我们是将springmvc配置文件放在WEB-INF下的,那是默认位置还有默认的名称,我们可以自己设置位置和名称:
contextConfigLocation
classpath:springmvc.xml
1
classpath表示从类路径查找文件,也就是resources路径下,而后面跟着的就是文件名称,我们可以把springmvc配置文件放在resources中了。
当我们浏览器第一次发送请求时会比较慢,这是因为DispatcherServlet需要初始化,我们可以如上设置,在服务器启动时就初始化。
这个注解我们在上面就使用过从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联 起来,建立映射关系。
SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。
演示:
我们重新配置一个模块:
还是上面一样的步骤引入依赖-配置web.xml文件-配置springmvc配置文件,然后编写pojo类,一定要注意pojo类要使用@Controller注解,将类交给spring容器管理
其它都一样,主要写pojo类和html界面:
package com.itzw.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexPage {
@RequestMapping("/")
public String index(){
return "index";
}
}
package com.itzw.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/xyz")
public class RequestMappingTest {
@RequestMapping("/test")
public String testRequestMapping(){
return "success";
}
}
注意这里我们使用@RequestMapping注解标注了类和方法,那么这个方法的路径最终是/xyz/test,所以我们在前端访问这个路径就要如下设置:
这个我们也已经使用过了,之前在这个注解写的值就是对value属性赋值,只不过它是可以省略不写直接写值的。
我们可以设置多个值:
这两个路径都能访问到资源。
method属性就是用来设置请求方式的,就是我们之前学的get请求和post请求
@RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射 @RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求
若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错 405:Request method 'POST' not supported
演示:
我们设置Method值为get请求
我们的请求地址一个是get请求一个是post请求:
结果是get请求没问题,post请求会出错:
简单举个例子:
以上表示请求的参数必须携带username并且携带password并且password的值不能为123
以上第一个是访问不成功了,报400错误,因为它的password值为123,值得注意的是我们之前想设置请求参数就是按照上面的第一种方式,但是这种方式在这里报错,但是运行是正常的,我们可以选择使用下面的方式,都是可以的效果一样。
这里的路径我们使用到了一个?
在写浏览器请求路径的时候这个问号就可以随意赋值,如下:
测试@RequestMapping的ant风格
举个例子:
测试@RequestMapping的占位符
这里的1和admin都是我想传的值,但是咋接收呢?
@RequestMapping("/test/{id}/{username}")
public String testRest(@PathVariable("id") Integer id,@PathVariable("username") String username){
System.out.println("id:"+id);
System.out.println("username:"+username);
return "success";
}
在路径中使用大括号将这些值括起来,然后在下面的方法中使用注解 @PathVariable接收
@RequestMapping("/param")
public String testParams(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username);
System.out.println("password:"+password);
return "success";
}
这种方式就是我们之前学过的方式,那么我们学了springmvc当然是有更简单的方式的。
@RequestMapping("/param2")
public String testParams2(String username,String password){
System.out.println("username:"+username);
System.out.println("password:"+password);
return "success";
}
这样很方便
上面虽然方便,但是如果名字对应不上呢,控制器方法的形参就是对应不上请求的参数怎么办,虽然目前来看解决方法就是你直接写对不就完事了,但是我就是故意不写对呢?
我们可以使用@RequestParam参数,@RequestParam是将请求参数和控制器方法的形参创建映射关系。@RequestParam注解一共有三个属性:
我们来演示一下:
这里的参数对应不上,看看结果:
username接收不到了,我们使用那个参数 :
这次就能接受到了
再简单测试一下其它属性:
请求头信息就是如下信息:
注意这里我们必须使用注解才能获取请求头信息,因为不使用注解默认是获取请求参数信息
没什么毛病,它的其它属性和上面是一样的,懒得测了。
我们先获取session对象,这样发送请求才会携带cookie:
接收到cookie值:
我们也发现一个问题,就是参数太多怎么办,我们可以使用pojo类,可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。
package com.itzw.springmvc.pojo;
public class User {
private Integer id;
private String username;
private String password;
public User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
我们创建一个类如上,它里面的属性有username和password,控制器方法的参数就可以直接使用user类
@RequestMapping("/param2")
public String testParam3(User user){
System.out.println(user);
return "success";
}
目前我们并没有发现乱码,我们使用post请求试试:
如上使用post请求的中文出现 乱码 。
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
CharacterEncodingFilter
/*
这样中文就不会乱码了。
测试通过servletAPI向request域共享数据
@RequestMapping("/servletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","Hello,ServletAPI");
return "success";
}
共享数据后我们在前端接收数据,只需要把数据的键值放在${}中即可。
上面的方式是传统方式,既然我们学了springmvc那么我们肯定有别的方法
@RequestMapping("/modelAndView")
public ModelAndView testmodelAndView(){
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("testRequestScope","hello,ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
return mav;
}
这种方式比上面还麻烦一点
@RequestMapping("/model")
public String testmodel(Model model){
model.addAttribute("testRequestScope","hello,model");
return "success";
}
这种方式就比较简单
@RequestMapping("/map")
public String testMap(Map map){
map.put("testRequestScope","helo,map");
return "success";
}
这个和Model方式一样,只是名字不一样:
@RequestMapping("/modelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope","hello,modelMap");
return "success";
}
@RequestMapping("/test")
public String test(Model model,ModelMap modelMap,Map map){
System.out.println(modelMap.getClass().getName());
System.out.println(map.getClass().getName());
System.out.println(model.getClass().getName());
return "success";
}
我们输出它们的名称:
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的,所以效果一样,那我们就挑一个最简单的来用就好了,比如Model。
这里使用springmvc的方式就复杂了,我们使用传统方式:使用HttpSession对象共享数据
@RequestMapping("/session")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","hello,session");
return "success";
}
@RequestMapping("/application")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope","hello,application");
return "success";
}
注意:
以上方式,我不管我们使用哪个方式往域对象中共享数据,不管用什么方式设置逻辑视图,最终都会封装到一个ModelAndView中。
@RequestMapping("/thymeleaf")
public String testThymeleaf(){
return "success";
}
还记得我们之前学过转发和重定向,它们的区别是:
@RequestMapping("/forward")
public String testForward(){
return "forward:/thymeleaf";
}
我们转发到上一个资源路径。因为我们前端页面使用了ThymeleafView语言,所以必须要使用ThymeleafView进行渲染才能识别,我们只能这样转发,不能直接转发到一个资源。
@RequestMapping("/redirect")
public String testRedirect(){
return "redirect:/thymeleaf";
}
注意我们这里的转发路径没有写上下文路径,它会自动添加。
这里的path设置处理的请求地址,view-name设置请求地址所对应的视图名称
但是我们使用这个方法访问index页面发现其它地址无法访问了。这是因为试图控制器为当前的请求设置视图名称实现页面跳转,若设置视图控制器,则只有视图控制器所设置的请求会被处理,其它请求全部404,此时需要配置一个标签
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE用来删除资源。
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。如下:
我们来演示一下
查询信息我们使用get请求:
查询所有信息
查询id为1的信息
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getAll(){
System.out.println("查询所有信息成功");
return "success";
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String getById(@PathVariable("id") Integer id){
System.out.println("查询id为"+id+"的信息成功");
return "success";
}
没什么问题,这两个我们在之前都使用过
保存信息我们使用post请求:
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String save(){
System.out.println("保存信息成功");
return "success";
}
这也没什么问题,将请求都改为post就好了。
发现一个有意思的事情,之前我们说请求控制器中的路径不能用一样的,因为当浏览器发送那个路径的请求时就不知道该去访问哪个。但是我们发现即使路径一样 但是请求方式不一样依然没问题,比如上面的一个是get请求一个是post请求但是路径都是user。
删除使用delete请求方式,但是我们没学过,这个咋搞?我们直接在form表单的method属性上修改为delete行不行?经我们测试不行。
浏览器只能发送get和post请求要想使用get和post之外的请求方式我们需要以下操作:
hiddenHttpMethodFilter
org.springframework.web.filter.HiddenHttpMethodFilter
hiddenHttpMethodFilter
/*
注意以上过滤器必须放在CharacterEncodingFilter过滤器后面
前端代码如下:
method必须设置为post请求,下面的type设置为hidden,因为我们不需要输入什么信息,所以隐藏就好。name必须设置为_method,value就是用来设置我们最终需要的请求方式的。
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
public String deleteById(@PathVariable("id") Integer id){
System.out.println("删除id为"+id+"信息成功");
return "success";
}
更新信息使用put请求,前端代码我们只需修改value部分即可
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String update(){
System.out.println("更新信息成功");
return "success";
}
package com.itzw.demo.pojo;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Employee(Integer id, String lastName, String email, Integer gender) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Employee() {
}
}
准备dao模拟数据,本次案例没有连接数据库:
package com.itzw.demo.dao;
import com.itzw.demo.pojo.Employee;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
private static Map employees = null;
static{
employees = new HashMap();
employees.put(1001, new Employee(1001, "E-AA", "[email protected]", 1));
employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1));
employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0));
employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0));
employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 1));
}
private static Integer initId = 1006;
/**
* 保存或修改信息
* @param employee
*/
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
/**
* 获取所有信息
* @return
*/
public Collection getAll(){
return employees.values();
}
/**
* 根据id获取信息
* @param id
* @return
*/
public Employee get(Integer id){
return employees.get(id);
}
/**
* 删除信息
* @param id
*/
public void delete(Integer id){
employees.remove(id);
}
}
配置view-controller:
编写首页:
访问员工信息
我们上面的首页访问员工信息,路径是/employee,我们在控制层查询信息:
package com.itzw.demo.controller;
import com.itzw.demo.dao.EmployeeDao;
import com.itzw.demo.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Collection;
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping(value = "/employee",method = RequestMethod.GET)
public String getAllEmployee(Model model){
Collection allEmployee = employeeDao.getAll();
model.addAttribute("allEmployee",allEmployee);
return "employee_list";
}
}
在employee_list.html展示信息:
员工信息
员工信息
编号
姓名
邮箱
性别
选项
删除
修改
我们先跳转到添加信息页面:
@RequestMapping(value = "/to/add",method = RequestMethod.GET)
public String addEmployeePage(){
return "employee_add";
}
编写employee_add.html页面:
注意添加信息要使用post请求了
添加信息
添加信息页面
@RequestMapping(value = "/addEmployee",method = RequestMethod.POST)
public String addEmployee(Employee employee){
//保存信息
employeeDao.save(employee);
//重定向到信息列表页面
return "redirect:/employee";
}
注意:修改时的路径要如下修改
修改
不能直接在后面加上employee.id,这浏览器会认为是一个名为“employee.id”的路径,我们需要用${}括起来,然后前面的路径要用引号括起来。
我们还是先跳转到更新界面,这里需要回显数据:
@RequestMapping(value = "/updateEmployeePage/{id}",method = RequestMethod.GET)
public String updateEmployeePage(@PathVariable("id") Integer id,Model model){
//获取到员工信息
Employee employee = employeeDao.get(id);
//响应到域中
model.addAttribute("employee",employee);
return "employee_update";
}
编写employee_update.html界面:
修改页面
修改员工信息
注意我们需要回显数据所以注意这里的value值的修改,还有在回显性别时使用的属性(th:field)这里我们使用的是put请求,注意如何设置put请求。其中id值我们可以隐藏,因为它不需要修改。
@RequestMapping(value = "/updateEmployee",method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
//修改信息
employeeDao.save(employee);
return "redirect:/employee";
}
修改完还是重定向到信息列表界面方便我们查看
比如我修改第一位员工信息 :
删除信息会有一点麻烦,因为我们需要使用delete请求,但是删除操作不像前面的修改操作需要先跳转到一个界面然后再进行修改操作,我们需要使用vue语法,但是我不会,所以这次先跳转到一个界面然后再进行delete请求:
删除
接收到信息跳转到一个新的页面:
@RequestMapping(value = "/deleteEmployeePage/{id}",method = RequestMethod.GET)
public String deleteEmployeePage(@PathVariable("id") Integer id,Model model){
//获取到员工信息
Employee employee = employeeDao.get(id);
//响应到域中
model.addAttribute("employee",employee);
return "employee_delete";
}
编写employee_delete.html页面:
删除信息
删除信息页面
执行删除操作:
@RequestMapping(value = "/deleteEmployee/{id}",method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
//删除信息
employeeDao.delete(id);
return "redirect:/employee";
}