SSM最后一节—SpringMVC
之前就已经了解过PHP的MVC模式,现在是Spring,原理上都是想通的,所以就不记MVC介绍了
PHP MVC框架初探_Sentiment.的博客-CSDN博客_phpmvc框架
打包方式改成war,会自动生成web模块
<packaging>warpackaging>
依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.22version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
<version>3.0.12.RELEASEversion>
dependency>
web.xml
url-patten中/和/*的区别:
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
创建配置文件
配置文件的命名规则:
±servlet.xml
SpringMVC-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<context:component-scan base-package="com.sentiment.controller">context:component-scan>
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<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>
beans>
启动一个tomcat环境,默认路径设为/SpringMVC
@RequestMapping(“/”),环境启动后,会默认访问/WEB-INF/templates/下的index.html
@Controller
public class HelloController {
@RequestMapping("/")
public String protal(){
return "index";
}
@RequestMapping("/hello")
public String hello(){
return "success";
}
}
在index设置一个链接,th表示thymeleaf标签需要导入命名空间xmlns:th="http://www.thymeleaf.org"
<a th:href="@{/hello}">测试a>
此时当点链接"测试"后,thymeleaf会根据当前路径访问/SpringMVC/hello
,返回success,跳转到/WEB-INF/templates/下的success.html中
这里如果用的是如下标签,则会默认访问绝对路径即:localhost:808/hello
,故请求错误(404)
<a href="/hello">测试a>
前边说到Thymeleaf-spring5的配置文件命名规则必须是:
+servlet.xml,而且它默认放在WEB-INF下,而一般资源都放在resources中,所以可以通过web.xml中的配置进行修改,这样就也无需按照之前的命名规则了
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:SpringMVC.xmlparam-value>
init-param>
除此外servlet-class设置的为:DispatcherServlet
,而DispatcherServlet
进行了很多配置,所以在初次访问时需要一段时间响应,这里就可以通过Serlvet中的load-on-startup
配置设置启动后的优先级
<load-on-startup>1load-on-startup>
@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系
@RequestMapping标识一个类,设置映射请求的请求路径的初始信息
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息
@Controller
@RequestMapping("/test")
public class RequestMappingTest {
//此时请求hello方法路径为: /test/hello
@RequestMapping("/hello")
public String hello(){
return "success";
}
}
value属性是数组类型,即当前浏览器请求value属性中的任何一个值都会处理注解所标识的方法
@Controller
public class RequestMappingTest {
@RequestMapping({"/hello","/aaa"})
public String hello(){
return "success";
}
}
访问/aaa时,同样会执行hello()
method属性是数组类型,即当前服务器请求method属性中的任何一个方式,都会被处理,报错信息为:405
测试
将/hello,/aaa路径设为post方式,此时就无法通过get方式访问了
@Controller
public class RequestMappingTest {
@RequestMapping(
value = {"/hello","/aaa"},
method = {RequestMethod.POST}
)
public String hello(){
return "success";
}
}
写个post方式的form表单,通过post请求成功访问
<form th:action="@{/hello}" method="post">
<input type="submit" value="method属性">
form>
由于是数组类型,所以也可以设置多个值,此时get、post就都可以访问了
@RequestMapping(
value = {"/hello","/aaa"},
method = {RequestMethod.POST,RequestMethod.GET}
)
除method属性外,还可以用@GetMapping、@PostMapping等注解实现同样功能
params属性是数组类型,通过请求的请求参数进行匹配,即浏览器发送的请求参数必须满足params属性的设置,报错信息为:400
params的四种表达式
“param”:表示当前所匹配的请求必须携带param参数
“!param”:表示当前所匹配请求的请求参数一定不能携带param参数
“param=value”:表示当前所匹配请求的请求参数必须鞋带param参数且值必须为value
“param!=value”:表示当前匹配请求的请求参数可以不携带param参数,若携带一定不能是value
如下为四种表达式分别表示:请求必须携带username,不能携带password,必须携带age且值为20,可以不鞋带gender若携带之不能为女
params = {"username","!password","age=20","gender!=女"}
headers属性通过请求的请求头信息匹配请求映射,报错信息为:404
也有四种表达式且跟params中的一样
如下表示必须携带referer,否则404
headers = {"referer"}
@RequestMapping注解的value属性中可以设置一些特殊的字符:
?:任意的单个字符(不包括 ? 和 /)
*:任意个数的任意字符(不包括 ? 和 /)
**:任意层数的任意目录,但使用该字符时前后不能有任何其他字符
?和*就不看了 ,看下**
@RequestMapping({"/**/test"})
public String ant(){
return "success";
}
使用该种方式,只要以任意形式访问/test目录都可跳转到success.html
传统:/deleteuser?id=1
rest: /user/delete/1
需要在@RequestNapping注解的value属性中所设置的路径中,使用{xxx}的方式表示路径中的数据
在通过@Pathvariable注解,将占位符所标识的值和控制器方法的形参进行绑定
@RequestMapping("/rest/{id}")
public String rest(@PathVariable("id") int id){
System.out.println(id);
return "success";
}
只需要在控制器方法的形参位置,设置HttpservletRequest类型的形参,就可以在控制器方法中使用request对象获取请求参数
@Controller
public class TestParamController {
@RequestMapping("/param/servletAPI")
public String getParamByServletAPI(HttpServletRequest request){
String uname = request.getParameter("uname");
String password = request.getParameter("password");
System.out.println("uname:"+uname+",password:"+password);
return "success";
}
}
默认情况下其实只需要设置形参就可以匹配请求的参数
@RequestMapping("/test")
public String getParamByRequestParam(String uname , int password){
System.out.println("uname:"+uname+",password:"+password);
return "success";
}
但若形参设置的和请求参数不一致时,就会匹配不到,此时就可以用@RequestParam注解,即:
public String getParamByRequestParam(
@RequestParam("uname") String uname,
@RequestParam("password") int password)
注解有三个属性:value、required、defaultValue
通过@RequestHeader获取referer头,@CookieValue获取XDEBUG_SESSION(Cookie中发现有之前配置的xdebug直接用了)
@RequestMapping("/test")
public String getParamByRequestParam(
@RequestParam(value = "uname",required = true,defaultValue = "hello") String uname,
@RequestParam("password") int password,
@RequestHeader("referer") String referer,
@CookieValue("XDEBUG_SESSION") String session
){
System.out.println("referer:"+referer);
System.out.println("XDEBUG_SESSION:"+session);
System.out.println("uname:"+uname+",password:"+password);
return "success";
}
前边提到可以通过注解获取请求参数,但是如果请求参数过多那么注解就会显得比较繁琐,这时候就可以通过pojo方式来进行管理,只需要请求参数名与pojo的属性名一致即可。
@RequestMapping("/test/pojo")
public String getParamByPojo(User user){
System.out.println(user);
return "success";
}
POJO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String uname;
private String password;
}
在/conf/server.xml中设置URLEncoding="UTF-8"
可解决乱码问题,但该种方式只针对于GET传参,若用POST传参仍会出现乱码,这是就可以通过配置filter
解决**(注:filter应放在其他配置之前,因为配置文件是按顺序执行的)**
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>ForceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
encoding代表请求编码,ForceEncoding代表响应编码
看下CharacterEncodingFilter的继承关系
Filter接口的doFilter()方法由OncePerRequestFilter
类实现,而在该类的doFilter方法中,最后的过滤是通过doFilterInternal
实现的
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
而doFilterInternal()
写在CharacterEncodingFilter
类中,
①:获取encoding参数即:UTF-8
②:if判断,后或运算后边的,request.getCharacterEncoding()
默认值为空,所以无论涉不设置ForceEncoding,都会执行if中的语句
③:判断isForceResponseEncoding的值,如果为真则为response设置编码,而我们传入的就是true,所以会执行
通过②、③可以看出,ForceEncoding可以控制request和response的编码方式,而request中由于是或运算默认就会执行if中的语
句,所以可以浅显的理解为ForceEncoding是控制response编码问题的参数
跟Servlet的request作用域一样,不例举了
ModelAndView中有Model和View功能,Model用于想请求域共享数据,View用于设置视图实现页面跳转
public class TestScopeController {
@RequestMapping("/test/mav")
public ModelAndView testMAV(){
ModelAndView mav = new ModelAndView();
//向请求域中共享数据
mav.addObject("testRequestScope", "Hello,Sentiment!");
//设置逻辑视图
mav.setViewName("success");
return mav;
}
}
success.html
获取请求域的testRequestScope数据
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>Success!h1>
<p th:text="${testRequestScope}">p>
body>
html>
访问后的效果
三个用法差不多放到了一起
@RequestMapping("/test/model")
public String testModel(Model model){
model.addAttribute("testRequestScope","Hello,Model!");
return "success";
}
@RequestMapping("/test/modelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope","Hello,ModelMap!");
return "success";
}
@RequestMapping("/test/map")
public String testMap(Map<String,Object> map){
map.put("testRequestScope","Hello,Map!");
return "success";
}
三种方法其实都是基于BindingAwareModelMap
类的
public class BindingAwareModelMap extends ExtendedModelMap {}
public class ExtendedModelMap extends ModelMap implements Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}
BindingAwareModelMap
继承ExtendedModelMap
,ExtendedModelMap
中继承了ModelMap
并实现了Model
,而ModelMap
继承于LinkedHashMap
,LinkedHashMap
又实现了Map
所以通过BindingAwareModelMap间接的实现了上述三个方法
用的是Servlet-api的形式,因为SpringMVC的方式相对而言更麻烦些
@RequestMapping("/test/session")
public String testSession(HttpSession httpSession){
httpSession.setAttribute("testSeesionScope","Hello,Seesion!");
return "success";
}
@RequestMapping("/test/application")
public String testApplication(HttpSession httpSession){
httpSession.getServletContext().setAttribute("testApplicationScope","Hello,Application!");
return "success";
}
调用thymeleaf时需要加上seesion或application
<p th:text="${seesion.testSeesionScope}">p>
<p th:text="${application.testApplicationScope}">p>
注:seesion是当前会话有效,所以当访问/test/session后,以后再访问其他页面后都会回显Hello,Seesion!
,Application同理
请求后会进行内部转发,但是不会经过thymeleaf渲染所以不常用
@Controller
public class TestViewController {
@RequestMapping("/test/view/forward")
public String testInterResourceView(){
return "forward:/test/model";
}
}
请求后会进行重定向
@RequestMapping("/test/view/redirect")
public String testRedirectView(){
return "redirect:/test/model";
}
之前定义了一个控制器,在访问首页时触发:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ProtalMapping {
@RequestMapping("/")
public String protal(){
return "index";
}
}
但只为了实现这一个控制器而写了一个类未免有点小题大做,因此可以通过视图控制器
方式来定义
<mvc:view-controller path="/" view-name="index">mvc:view-controller>
这样在访问根路径时就会自动跳转到index.html
,方便许多,但是又引入了一个新的问题:
当时用该控制器后,只有视图控制器所设置的请求会被处理,其他的请求都是404
此时就必须配置一个新的标签
就可以直接解决该问题
REST: Representational State Transfer,表现层资源状态转移。
可以理解为:restful只关心我们需要的资源,而不在意资源的操作方式,例:假如对user库进行资源的增删改查,那么这些操作都是对user库的操作,restful就可以将他设置为/user路径,之后的操作通过不同的请求方式来判断如何操作数据库即:
GET:获取资源
POST:新建资源
PUT:更新资源
DELETE:删除资源
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1 —> get请求方式 |
保存操作 | saveUser | user —> post请求方式 |
删除操作 | deleteUser?id=1 | user/1 —> delete请求方式 |
更新操作 | updateUser | user —> put请求方式 |
GET方式
@Controller
public class TestRestController {
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String getUserById(@PathVariable("id") int id){
System.out.println("根据id查询信息-->/user/"+id+"-->get");
return "success";
}
}
POST方式
@RequestMapping(value ="/user",method = RequestMethod.POST)
public String inserUser(){
System.out.println("成功添加用户信息");
return "success";
}
表单中没有PUT和DELETE请求,所以可以通过HiddenHttpMethodFilter
过滤器来获取这两种请求方式
web.xml加上
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
之后表单中加上name="_method" value="put"
即可**(需注意method必须为post才行)**
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="put">
<input type="submit" value="更新用户数据">
form>
这样就可以成功获取put参数了
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String updateUser(){
System.out.println("成功更新用户数据");
return "success";
}
删除操作同理,DELET请求方式
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
public String deleteUser(@PathVariable("id") int id){
System.out.println("根据id删除信息-->/user/"+id+"-->get");
return "success";
}
HTML
<form th:action="@{/user/2}" method="post">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="删除用户数据">
form>
实体类
package com.sentiment.pojo;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = 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;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", gender=" + gender +
'}';
}
}
dao模拟数据
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static{
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;
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
}
功能清单
功能 | URL地址 | 请求方式 |
---|---|---|
访问首页 | / | GET |
查询全部数据 | /employee | GET |
删除 | /employee/2 | DELETE |
跳转到添加数据页面 | /toAdd | GET |
执行保存 | /employee | POST |
跳转到更新数据页面 | /employee/2 | GET |
执行更新 | /employee | PUT |
控制层
package com.sentiment.controller;
import com.sentiment.dao.EmployeeDao;
import com.sentiment.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<Employee> allEmployee = employeeDao.getAll();
//将所有的员工信息在请求域中共享
model.addAttribute("allEmployee",allEmployee);
//跳转到列表页面
return "employee_list";
}
}
employee_list.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee listtitle>
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
head>
<body>
<table>
<tr>
<th colspan="5">employee listth>
tr>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>optionsth>
tr>
<tr th:each="employee : ${allEmployee}">
<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>
<a href="">deletea>
<a href="">updatea>
td>
tr>
table>
body>
html>
css
@charset "UTF-8";
form {
margin: 0px;
}
img {
border: medium none;
margin: 0;
padding: 0;
} /* img elements 图片元素 */
/** 设置默认字体 **/
body,button,input,select,textarea {
font-size: 12px;
font: 12px/1.5 ’宋体’, Arial, tahoma, Srial, helvetica, sans-serif;
}
h1,h2,h3,h4,h5,h6 {
font-size: 100%;
}
em {
font-style: normal;
}
/** 重置列表元素 **/
ul,ol {
list-style: none;
}
/** 重置超链接元素 **/
a {
text-decoration: none;
color: #4f4f4f;
}
a:hover {
text-decoration: underline;
color: #F40;
}
/** 重置图片元素 **/
img {
border: 0px;
margin-bottom: -7px;
}
body {
width: 80%;
margin: 40px auto;
font-family: 'trebuchet MS', 'Lucida sans', Arial;
font-size: 14px;
color: #444;
background: url(../css/img/body1.jpg);
background-repeat: no-repeat;
background-size: 100% auto;
/* background: #F5F5F5; */
}
table {
border: solid #ccc 1px;
-webkit-border-radius: 6px;
border-radius: 6px;
/* -webkit-box-shadow: 0 1px 1px #ccc;
box-shadow: 0 1px 1px #ccc; */
-webkit-box-shadow: 0px 2px 1px 5px rgba(242, 242, 242, 0.1);
box-shadow: 5px 20px 30px 30px rgba(242, 242, 242, 0.1);
width: 100%;
}
table thead th {
background:url(../css/img/zebratable.png);
background-repeat:no-repeat;
background-position: 0px center;
}
table tr {
background: #D5EAF0;
-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
}
table tr:nth-child(even) {
background: #D7E1C5;
-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
}
table tr:hover {
background: #91C5D4;
-o-transition: all 0.1s ease-in-out;
-webkit-transition: all 0.1s ease-in-out;
-ms-transition: all 0.1s ease-in-out;
transition: all 3s ease-in-out;
background-image: -webkit-gradient(linear, left top, left bottom, from(#151515), to(#404040)) !important;
background-image: -webkit-linear-gradient(top, #151515, #404040) !important;
background-image: -moz-linear-gradient(top, #151515, #404040) !important;
background-image: -ms-linear-gradient(top, #151515, #404040) !important;
background-image: -o-linear-gradient(top, #151515, #404040) !important;
background-image: linear-gradient(top, #151515, #404040) !important;
color:#fff !important;
}
table td,table th {
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
padding: 10px;
text-align: center;
}
table th {
background-color: #66a9bd;
background-image: -moz-linear-gradient(top, #dce9f9, #66a9bd);
-webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset;
border-top: none;
text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
}
table td:first-child,table th:first-child {
border-left: none;
}
table th:first-child {
-webkit-border-radius: 6px 0 0 0;
border-radius: 6px 0 0 0;
}
table th:last-child {
-webkit-border-radius: 0 6px 0 0;
border-radius: 0 6px 0 0;
}
table th:only-child {
-webkit-border-radius: 6px 6px 0 0;
border-radius: 6px 6px 0 0;
}
table tr:last-child td:first-child {
-webkit-border-radius: 0 0 0 6px;
border-radius: 0 0 0 6px;
}
table tr:last-child td:last-child {
-webkit-border-radius: 0 0 6px 0;
border-radius: 0 0 6px 0;
}
input[type="button"],input[type="submit"],input[type="reset"] {
border: solid #ccc 1px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
box-shadow: 0 1px 1px #ccc;
background: #B0CC7F;
margin: 0 2px 0;
}
input[type="text"],input[type="password"] {
border: solid #ccc 2px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
box-shadow: 0 1px 1px #ccc;
background: #efefef;
margin: 0 2px 0;
text-indent: 5px;
}
select {
width:200px;
border: solid #ccc 2px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
background: #efefef;
margin: 0 2px 0;
text-indent: 5px;
}
option {
width:180px;
border: solid #ccc 2px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
background: #efefef;
margin: 0 2px 0;
text-indent: 5px;
}
input[name="page.now"] {
border: solid #ccc 1px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
box-shadow: 0px 0px 0px #CEB754;
background: #D5EAF0;
margin: 0px 10px 0px 0px;
padding-bottom: 0px;
padding-top: 5px;
width: 24px;
line-height:10px;
height: 12xp;
}
此时当访问employee
时,会发现我们调用的css样式并没有被渲染,这是因为:
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是 /,而 tomcat的web.xml配置的DefaultServlet的url-pattern也是 /,此时浏览器发送的请求会优先被DispatcherServlet进行处理,但是DispatcherServlet无法处理静态资源
所以为了解决这个问题引入了一个配置标签:
,此时浏览器发送的所有请求都会被DefaultServlet处理,但是我们处理用的并不是DefaultServlet
,而是DispatcherServlet
所以还需要加上
此时浏览器发送的请求就会先被DispatcherServlet处理
在options后边添加一个add按钮
<th>options(<a th:href="@{/to/add}">adda>)th>
跳转到,/to/add,所以写一个add界面,employee_add.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>add employeetitle>
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
head>
<body>
<form th:action="@{/employee}" method="post">
<table>
<tr>
<td colspan="2">add employeetd>
tr>
<tr>
<td>lastNametd>
<td><input type="text" name="lastName">td>
tr>
<tr>
<td>emailtd>
<td><input type="text" name="email">td>
tr>
<tr>
<td>gendertd>
<td>
<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female
td>
tr>
<tr>
<td colspan="2"><input type="submit" value="add">td>
tr>
table>
form>
body>
html>
当点击添加之后,通过post方式跳转到/employee,并通过DAO将数据保存,之后重定向到GET请求的/employee回到首页,发现添加成功
@RequestMapping(value = "/employee",method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
记录一下流程:
1、为update标签添加链接
<a th:href="@{'/employee/'+${employee.id}}">updatea>
2、访问指定路径,并转到update界面
@RequestMapping(value = "/employee/{id}",method = RequestMethod.GET)
public String toUpdate(@PathVariable("id") int id,Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee",employee);
return "employee_update";
}
3、注册界面employee_update.html
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>update employeetitle>
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
<table>
<tr>
<td colspan="2">update employeetd>
tr>
<tr>
<td>lastNametd>
<td><input type="text" name="lastName" th:value="${employee.lastName}">td>
tr>
<tr>
<td>emailtd>
<td><input type="text" name="email" th:value="${employee.email}">td>
tr>
<tr>
<td>gendertd>
<td>
<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
<input type="radio" name="gender" value="0" th:field="${employee.gender}">female
td>
tr>
<tr>
<td colspan="2"><input type="submit" value="update">td>
tr>
table>
form>
body>
html>
4、提交表单用的是put方式,所以再写个接受put请求的方法
@RequestMapping(value = "/employee",method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
5、修改后结果:
标识在控制器上,就相当于添加了@Controller,并且给每个方法添加了@ResponseBody注解
@RequestMapping("/test/down")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("img");
realPath = realPath + File.separator+"1.jpg";
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
//is.available() is字节流对应的所有字节数
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=Sentiment.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
需要用到commons-fileupload
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
上传页面 (enctype 属性:表示将数据回发到服务器时浏览器使用的编码类型)
<form th:action="@{/test/upload}" enctype="multipart/form-data" method="post">
图片:<input type="file" name="photo" ><br>
<input type="submit" value="上传">
form>
文件上传
@RequestMapping("/test/upload")
public String testUpload(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取当前工程下photo目录的真实路径
String photoPath = servletContext.getRealPath("photo");
//创建photoPath所对应的File对象
File file = new File(photoPath);
//判断file所对应目录是否存在
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//上传文件
photo.transferTo(new File(finalPath));
return "success";
}
上传后发现空指针,因为形参MultipartFile photo
获取不到,这是就需要在SpringMVC.xml设置文件上传解析器 (由于这种bean管理方式不是基于类型的,所以需要加上id)
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
bean>
这是上传文件后,就会上传到photo目录中
如果此时再上传一个1.jpg,那么新上传的图片的二进制数据就会替换到原来的图片数据,呈现出将图片替换了的现象
此时就可以通过命名规范的方式解决此问题,一般可以用uuid或时间戳命名解决:
//获取文件后缀
String hzName = fileName.substring(fileName.lastIndexOf("."));
//通过uuid生成文件名
String uuid = UUID.randomUUID().toString();
//通过uuid和后缀拼接一个新文件
fileName=uuid+hzName;
拦截器用于拦截控制器方法的执行
拦截器需要实现HandlerInterceptor
拦截器必须在SpingMVC的配置文件中进行配置
创建拦截器,注:preHandle
是boolean类型的,他的返回值代表是否拦截,false代表丽拦截
@Component("firstInterceptor")
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor -> preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor -> postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor -> afterCompletion");
}
}
配置文件
有三种方式配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/abc"/>
<ref bean="firstInterceptor"/>
mvc:interceptor>
mvc:interceptors>
preHandle()
在控制器方法执行之前执行,起返回值表示对控制器方法的拦截(false)和放行(true)
postHandle()
在控制器方法执行之后执行
afterCompletion()
在控制器方法执行之后,且渲染视图完毕之后执行
当存在多个拦截器时,拦截器的执行顺序和SpringMVC配置文件中的配置顺序有关
再写个SecondInterceptor,看下执行顺序:
perHandle()按配置顺序执行,postHandle()和afterCompletion()按配置反序执行
有三个拦截器:
preHandle
这里经过三轮遍历,interceptorIndex的值为2,最后retrun true
postHandle
刚刚返回true后,向下继续执行postHandle,而这里for循环,是通过自减的方式执行的,所以下边的interceptor.postHandle(request, response, this.handler, mv);
首先调用的就是索引为2的interceptor,所以先调用了SecondInterceptor的postHandle方法,接着经过自减后i=1,调用FirstInterceptor的postHandle方法
所以这也就是逆序执行的原因
afterCompletion
与postHandle同理