八、RESTFul案例
1、准备工作
和传统CRUD一样,实现对员工信息的增删改查。
-
搭建环境
具体步骤:
(1)pom.xml中配置依赖,并导入
<groupId>com.atguigu.mvc</groupId> <artifactId>SpringMVC-RESTFul-2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <!--导入依赖--> <dependencies> <!--SpringMVC依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.1</version> </dependency> <!--日志--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!--ServletAPI--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!--Spring和Thymeleaf整合包--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.12.RELEASE</version> </dependency> </dependencies>
(2)main目录下新建webapp目录
(3)项目结构中配置好web.xml路径并导入
路径中加入:src\main\webapp
(4)配置web.xml文件
<!--配置编码过滤器--> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!--设置编码方式--> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!--设置响应编码方式--> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!--设置编码过滤器的映射--> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <!--用/*表示所有请求--> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置HiddenHttpMethodFilter过滤器,以适配浏览器发送put和delete请求--> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <!--设置HiddenHttpMethodFilter过滤器的映射--> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <!--使用'/*'表示对所有请求进行处理--> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置SpringMVC的前端控制器DispatcherServlet,对浏览器发送的请求统一进行处理--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置和名称--> <init-param> <!--contextConfigLocation为固定值--> <param-name>contextConfigLocation</param-name> <!--使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources--> <param-value>classpath:springMVC.xml</param-value> </init-param> <!-- 作为框架的核心组件,在启动过程中有大量的初始化操作要做而这些操作放在第一次请求时才执行会严重 影响访问速度,因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时 --> <load-on-startup>1</load-on-startup> </servlet> <!--设置servlet的映射--> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <!-- 设置springMVC的核心控制器所能处理的请求的请求路径'/'所匹配的请求可以是'/ login'或'.html'或'.js'或'.css'方式的请求路径但是'/'不能匹配'.jsp'请求路径的请求 --> <url-pattern>/</url-pattern> </servlet-mapping>
注:在配置HiddenHttpMethodFilter过滤器和CharacterEncodingFilter编码过滤器时,要先配置CharacterEncodingFilter编码过滤器,然后再配置HiddenHttpMethodFilter过滤器。因为如果先配置HiddenHttpMethodFilter过滤器,再配置CharacterEncodingFilter编码过滤器的话,可能会出现乱码问题。(因为设置编码之前不能获取任何请求参数,不然编码设置就无效了)
(5)在java目录下创建存放控制器的包
路径为com.atguigu.rest.controller
(6)在resources根目录下,创建springMVC.xml配置文件
分为两步配置:
1)配置自动扫描包,设置好所需扫描的控制器的路径,即com.atguigu.rest.controller
2)配置Thymeleaf视图解析器
<!--自动扫描包--> <context:component-scan base-package="com.atguigu.rest.controller" /> <!--配置Thymeleaf视图解析器--> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!--order:设置Thymeleaf视图解析器优先级--> <property name="order" value="1"/> <!--characterEncoding:Thymeleaf视图解析器解析视图时的编码--> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine"> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!--视图前缀--> <property name="prefix" value="/WEB-INF/templates/"/> <!--视图后辍--> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8"/> </bean> </property> </bean> </property> </bean>
-
准备实体类
(7)在rest包下创建bean包,用以存放实体类
bean包下创建Employee类
private Integer id; private String lastName; private String email; private Integer gender; //Getter和Setter 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() { }
(8)在rest包下创建Dao包,用以存放Dao类
Dao包下创建EmployeeDao类
//@Repository为Spring中创建对象的注解,将对象交给容器去控制 @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; static { //把员工的id作为键,把员工对象作为值,放入Map集合 employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1)); employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1)); employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0)); employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0)); employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1)); } private static Integer initId = 1006; //所用方法 //save方法:保存功能,可能是保存添加的信息也可能是保存修改的信息 public void save(Employee employee){ //如果employee的id为空,则添加新员工的id及信息 if(employee.getId() == null){ employee.setId(initId++); } //如果employee的id不为空,则修改(覆盖)原来的信息 employees.put(employee.getId(), employee); } //getAll方法:获取Map集合中所有员工的信息 public Collection<Employee> getAll(){ return employees.values(); } //get方法:根据id获得相应员工的信息 public Employee get(Integer id){ return employees.get(id); } //delete方法:根据id删除员工的信息 public void delete(Integer id){ employees.remove(id); } }
(9)新添加扫描路径
由于Dao包中的类也需要被springMVC.xml文件中的扫描组件扫描,因此将com.atguigu.rest.dao路径添加到扫描组件中,或者也可以直接使用com.atguigu.rest路径。
也就是将springMVC.xml中的
<!--自动扫描包--> <context:component-scan base-package="com.atguigu.rest.controller" />
变为
<!--自动扫描包--> <context:component-scan base-package="com.atguigu.rest.controller, com.atguigu.rest.dao" />
或者
<!--自动扫描包--> <context:component-scan base-package="com.atguigu.rest" />
2、功能清单
功能 | 地址 | 请求方式 |
---|---|---|
访问首页 | / | GET |
查询全部数据 | /employee | GET |
删除 | /employee/2 | DELETE |
跳转到添加数据页面 | /toAdd | DET |
执行保存 | /employee | POST |
跳转到更新数据页面 | /employee/2 | GET |
执行更新 | /employee | PUT |
3、具体功能:访问首页
a>配置view-controller
<!--配置视图控制器。使用view-controller来代替首页的控制器方法--> <mvc:view-controller path="/" view-name="index" /> <!--开启MVC的注解驱动 开启注解驱动后,由于view-controller导致的原来在控制器中的所有请求映射 全部重新有效。 --> <mvc:annotation-driven />
b>创建页面
1)在WEB-INF目录下创建templates目录,用来放置html文件
2)在templates目录下,创建index.html文件作为网页的首页
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <a th:href="@{/employee}">查看员工信息</a> </body> </html>
3)在templates目录下,创建employee_list.html文件作为显示员工信息的页面,并且设置相关的update、delete、add操作
delete功能:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Employee Info</title> </head> <body> <!--使用表格来展示员工信息(表格用来展示数据,表单用来提交数据) border="1" cellpadding="0" cellspacing="0" style="text-align: center"用来 添加和设置表格 --> <table id="dataTable" border="1" cellpadding="0" cellspacing="0" style="text-align: center"> <tr> <!--th:为表头,自动加粗并居中。colspan="5"表示为合并5列--> <th colspan="5">Employee Info</th> </tr> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>gender</th> <th>options</th> </tr> <!--循环输出employList中的员工信息--> <tr th:each="employee : ${employeeList}"> <td th:text="${employee.id}"></td> <td th:text="${employee.lastName}"></td> <td th:text="${employee.email}"></td> <td th:text="${employee.gender}"></td> <td> <!--RESTFul中的请求参数以/a的形式拼接在请求地址后面,例:/employee/1 此处的传参路径不能写为@{/employee/${employee.id}},因为此时${employee.id} 中的'${'和'}'会被解析为其它字符,造成解析错误。应该以拼接的方式写为: @{/employee/}+${employee.id}或者@{'/employee/'+${employee.id}} @click:作用是为该超链接绑定点击事件 --> <a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a> <a href="">update</a> </td> </tr> </table> <!--此为form表单 <form method="post">实际为 <form action="@{'/employee/'+${employee.id}}" method="post"> 但由于@{'/employee/'+${employee.id}}已经在上面写过了,所以可以不写。 并且也不用submit按钮,因为该表单通过上面的delete超链接来控制。 --> <form id="deleteForm" method="post"> <input type="hidden" name="_method" value="delete"> </form> <!--引入js文件--> <script type="text/javascript" th:src="@{/static/js/vue.js}"></script> <script type="text/javascript"> var vue = new Vue({ el:"#dataTable", <!--绑定上面超链接中的点击事件deleteEmployee--> methods:{ deleteEmployee:function (event){ <!--根据id获取form表单--> var deleteForm = document.getElementById("deleteForm"); <!--作用是将触发点击事件的超链接的href属性赋值给表单的action。 即确定form表单的action值,action值表示表单提交到的页面, 如果action没有值或者没有写,则提交到当前页面。 此处需要将表单提交超链接设置的地址。 event表示当前触发的事件 target表示当前触发事件的元素,即此处的超链接 href表示当前触发事件的超链接的href --> deleteForm.action = event.target.href; <!--提交form表单--> deleteForm.submit(); <!--取消超链接的默认行为--> event.preventDefault(); } } }); </script> </body> </html>
add功能:
employee_list.html文件设置
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Employee Info</title> </head> <body> <!--使用表格来展示员工信息(表格用来展示数据,表单用来提交数据) border="1" cellpadding="0" cellspacing="0" style="text-align: center"用来 添加和设置表格 --> <table id="dataTable" border="1" cellpadding="0" cellspacing="0" style="text-align: center"> <tr> <!--th:为表头,自动加粗并居中。colspan="5"表示为合并5列--> <th colspan="5">Employee Info</th> </tr> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>gender</th> <th>options (<a th:href="@{/toAdd}">add</a> ) </th> </tr> <!--循环输出employList中的员工信息--> <tr th:each="employee : ${employeeList}"> <td th:text="${employee.id}"></td> <td th:text="${employee.lastName}"></td> <td th:text="${employee.email}"></td> <td th:text="${employee.gender}"></td> <td> <!--RESTFul中的请求参数以/a的形式拼接在请求地址后面,例:/employee/1 此处的传参路径不能写为@{/employee/${employee.id}},因为此时${employee.id} 中的'${'和'}'会被解析为其它字符,造成解析错误。应该以拼接的方式写为: @{/employee/}+${employee.id}或者@{'/employee/'+${employee.id}} @click:作用是为该超链接绑定点击事件 --> <a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a> <a href="">update</a> </td> </tr> </table> <!--此为form表单 <form method="post">实际为 <form action="@{'/employee/'+${employee.id}}" method="post"> 但由于@{'/employee/'+${employee.id}}已经在上面写过了,所以可以不写。 并且也不用submit按钮,因为该表单通过上面的delete超链接来控制。 --> <form id="deleteForm" method="post"> <input type="hidden" name="_method" value="delete"> </form> <!--引入js文件--> <script type="text/javascript" th:src="@{/static/js/vue.js}"></script> <script type="text/javascript"> var vue = new Vue({ el:"#dataTable", <!--绑定上面超链接中的点击事件deleteEmployee--> methods:{ deleteEmployee:function (event){ <!--根据id获取form表单--> var deleteForm = document.getElementById("deleteForm"); <!--作用是将触发点击事件的超链接的href属性赋值给表单的action。 即确定form表单的action值,action值表示表单提交到的页面, 如果action没有值或者没有写,则提交到当前页面。 此处需要将表单提交超链接设置的地址。 event表示当前触发的事件 target表示当前触发事件的元素,即此处的超链接 href表示当前触发事件的超链接的href --> deleteForm.action = event.target.href; <!--提交form表单--> deleteForm.submit(); <!--取消超链接的默认行为--> event.preventDefault(); } } }); </script> </body> </html>
再新建employee_add.html,并设置
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>add employee</title> </head> <body> <form th:action="@{/employee}" method="post"> lastName:<input type="text" name="lastName"><br> email:<input type="text" name="email"><br> gender:<input type="radio" name="gender" value="1">male <input type="radio" name="gender" value="0">female<br> <input type="submit" value="add"><br> </form> </body> </html>
update功能:
employee_list.html文件设置
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Employee Info</title> </head> <body> <!--使用表格来展示员工信息(表格用来展示数据,表单用来提交数据) border="1" cellpadding="0" cellspacing="0" style="text-align: center"用来 添加和设置表格 --> <table id="dataTable" border="1" cellpadding="0" cellspacing="0" style="text-align: center"> <tr> <!--th:为表头,自动加粗并居中。colspan="5"表示为合并5列--> <th colspan="5">Employee Info</th> </tr> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>gender</th> <th>options (<a th:href="@{/toAdd}">add</a> ) </th> </tr> <!--循环输出employList中的员工信息--> <tr th:each="employee : ${employeeList}"> <td th:text="${employee.id}"></td> <td th:text="${employee.lastName}"></td> <td th:text="${employee.email}"></td> <td th:text="${employee.gender}"></td> <td> <!--RESTFul中的请求参数以/a的形式拼接在请求地址后面,例:/employee/1 此处的传参路径不能写为@{/employee/${employee.id}},因为此时${employee.id} 中的'${'和'}'会被解析为其它字符,造成解析错误。应该以拼接的方式写为: @{/employee/}+${employee.id}或者@{'/employee/'+${employee.id}} @click:作用是为该超链接绑定点击事件 --> <a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a> <a th:href="@{'/employee/'+${employee.id}}">update</a> </td> </tr> </table> <!--此为form表单 <form method="post">实际为 <form action="@{'/employee/'+${employee.id}}" method="post"> 但由于@{'/employee/'+${employee.id}}已经在上面写过了,所以可以不写。 并且也不用submit按钮,因为该表单通过上面的delete超链接来控制。 --> <form id="deleteForm" method="post"> <input type="hidden" name="_method" value="delete"> </form> <!--引入js文件--> <script type="text/javascript" th:src="@{/static/js/vue.js}"></script> <script type="text/javascript"> var vue = new Vue({ el:"#dataTable", <!--绑定上面超链接中的点击事件deleteEmployee--> methods:{ deleteEmployee:function (event){ <!--根据id获取form表单--> var deleteForm = document.getElementById("deleteForm"); <!--作用是将触发点击事件的超链接的href属性赋值给表单的action。 即确定form表单的action值,action值表示表单提交到的页面, 如果action没有值或者没有写,则提交到当前页面。 此处需要将表单提交超链接设置的地址。 event表示当前触发的事件 target表示当前触发事件的元素,即此处的超链接 href表示当前触发事件的超链接的href --> deleteForm.action = event.target.href; <!--提交form表单--> deleteForm.submit(); <!--取消超链接的默认行为--> event.preventDefault(); } } }); </script> </body> </html>
再新建employee_update.html,并设置
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>update employee</title> </head> <body> <form th:action="@{/employee}" method="post"> <!--获得员工信息修改菜单--> <input type="hidden" name="_method" th:value="put"> <input type="hidden" name="id" th:value="${employee.id}"> lastName:<input type="text" name="lastName" th:value="${employee.lastName}"><br> email:<input type="text" name="email" th:value="${employee.email}"><br> gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}">male <input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br> <input type="submit" value="update"><br> </form> </body> </html>
4)在webapp下创建static.js目录,用于存放JavaScript(js)文件。并导入vue.js文件。
c>控制器实现功能
在controller包中新建EmployeeController类
实现获取全部员工信息的功能(getAll)
//@Autowired用于给引用类型赋值。默认使用byType,也可以使用byName @Autowired private EmployeeDao employeeDao; //获取所有员工信息 @RequestMapping(value = "/employee", method = RequestMethod.GET) //使用Model向request域对象共享数据 public String getAllEmployee(Model model){ Collection<Employee> employeeList = employeeDao.getAll(); //设置名称和共享数据 model.addAttribute("employeeList", employeeList); //将employeeList中的所有员工信息转发到employee_list视图中 return "employee_list"; }
实现删除员工信息的功能(delete)
//输出员工信息 @RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE) //通过@PathVariable("id")将@RequestMapping占位符{}中的id与形参id绑定 public String deleteEmployee(@PathVariable("id") Integer id){ employeeDao.delete(id); /** * 由于执行删除操作后不能直接跳转到列表页面,所以使用重定向到上面的/employee页面。 * 不使用转发是因为,转发会保留原来的页面地址,但是执行删除操作后,已经和原页面没 * 有关系了。所以需要使用重定向来跳转到列表页面。 */ return "redirect:/employee"; }
配置好并运行后,此时还是会报错:GET http://localhost:8080/SpringMVC/static/js/vue.js net::ERR_ABORTED 404。即不能找到js文件。这是由于默认配置中springMVC无法处理静态资源,而js为静态资源。因此还需要在resources根目录下的springMVC.xml配置文件中设置标签,以开放对静态资源的访问。并且还需要和<mvc:annotation-driven />标签一同使用,否则无效。
<!--开放对静态资源的访问--> <mvc:default-servlet-handler />
实现添加员工信息的功能(add)
//添加员工信息 @RequestMapping(value = "/employee", method = RequestMethod.POST) //利用实体类Employee来添加员工的信息 public String addEmployee(Employee employee){ employeeDao.save(employee); return "redirect:/employee"; }
实现修改/更新员工信息的功能(update)
先实现根据员工id获取要修改员工的信息修改菜单功能,再实现修改/更新信息的功能。
//使用GET,根据id获得员工信息 @RequestMapping(value = "/employee/{id}", method = RequestMethod.GET) public String getEmployeeById(@PathVariable("id") Integer id, Model model){ Employee employee = employeeDao.get(id); model.addAttribute("employee", employee); return "employee_update"; } //使用PUT,修改/更新员工信息 @RequestMapping(value = "/employee", method = RequestMethod.PUT) public String updateEmployee(Employee employee){ employeeDao.save(employee); return "redirect:/employee"; }