[SSM框架]—SpringMVC

前言

SSM最后一节—SpringMVC

之前就已经了解过PHP的MVC模式,现在是Spring,原理上都是想通的,所以就不记MVC介绍了

PHP MVC框架初探_Sentiment.的博客-CSDN博客_phpmvc框架

入门案例

开发环境

创建maven工程

打包方式改成war,会自动生成web模块

<packaging>war</packaging>

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.22</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>
<!--Spring5和thymeleaf整合包-->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
</dependency>

web.xml

url-patten中/和/*的区别:

  • /:匹配浏览器向服务器发送的所有请求(不包括.jsp)
  • /*:匹配浏览器向服务器发送的所有请求(包括.jsp)
<!--配置SpringMVC的前端控制器DispatcherServlet-->
<servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

创建配置文件

配置文件的命名规则:<servlet-name>±servlet.xml

SpringMVC-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>

测试HelloWorld

启动一个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-name>+servlet.xml,而且它默认放在WEB-INF下,而一般资源都放在resources中,所以可以通过web.xml中的配置进行修改,这样就也无需按照之前的命名规则了

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:SpringMVC.xml</param-value>
</init-param>

除此外servlet-class设置的为:DispatcherServlet,而DispatcherServlet进行了很多配置,所以在初次访问时需要一段时间响应,这里就可以通过Serlvet中的load-on-startup配置设置启动后的优先级

<load-on-startup>1</load-on-startup>

@RequstMapping注解

@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系

注解位置

@RequestMapping标识一个类,设置映射请求的请求路径的初始信息

@RequestMapping标识一个方法:设置映射请求请求路径的具体信息

@Controller
@RequestMapping("/test")
public class RequestMappingTest {
    //此时请求hello方法路径为: /test/hello
    @RequestMapping("/hello")
    public String hello(){
        return "success";
    }
}

value属性

value属性是数组类型,即当前浏览器请求value属性中的任何一个值都会处理注解所标识的方法

@Controller
public class RequestMappingTest {
    @RequestMapping({"/hello","/aaa"})
    public String hello(){
        return "success";
    }
}

访问/aaa时,同样会执行hello()

method属性

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属性是数组类型,通过请求的请求参数进行匹配,即浏览器发送的请求参数必须满足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

headers属性通过请求的请求头信息匹配请求映射,报错信息为:404

也有四种表达式且跟params中的一样

如下表示必须携带referer,否则404

headers = {"referer"}

ant风格

@RequestMapping注解的value属性中可以设置一些特殊的字符:

?:任意的单个字符(不包括 ? 和 /)

*:任意个数的任意字符(不包括 ? 和 /)

**:任意层数的任意目录,但使用该字符时前后不能有任何其他字符

?和*就不看了 ,看下**

@RequestMapping({"/**/test"})
public String ant(){
    return "success";
}

使用该种方式,只要以任意形式访问/test目录都可跳转到success.html

路径占位符(rest)

传统:/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";
}

SpringMVC获取请求参数

通过ServletAPI获取参数

只需要在控制器方法的形参位置,设置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";
    }
}

@RequestParam注解

默认情况下其实只需要设置形参就可以匹配请求的参数

@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

  • value:参数名
  • required:设置是否必须传输value对应的请求参数,默认值为true(必须传),若没传则报错:400
  • defaultValue:设置value的默认值,设置该属性后required属性无论为true或false,都不会报错

@RequestHeader和@CookieValue注解

通过@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方式来进行管理,只需要请求参数名与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>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>ForceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-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编码问题的参数

域对象共享数据

使用ServletAPI向request域对象共享数据

跟Servlet的request作用域一样,不例举了

使用ModelAndView向域对象共享数据

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>Title</title>
</head>
<body>
    <h1>Success!</h1>
    <p th:text="${testRequestScope}"></p>
</body>
</html>

访问后的效果
在这里插入图片描述

Model、ModelMap、Map

三个用法差不多放到了一起

@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继承ExtendedModelMapExtendedModelMap中继承了ModelMap并实现了Model,而ModelMap继承于LinkedHashMap,LinkedHashMap又实现了Map

所以通过BindingAwareModelMap间接的实现了上述三个方法

Seesion、Application

用的是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同理

视图

InternalResourceView

请求后会进行内部转发,但是不会经过thymeleaf渲染所以不常用

@Controller
public class TestViewController {
    @RequestMapping("/test/view/forward")
    public String testInterResourceView(){
        return "forward:/test/model";
    }
}

RedirectView

请求后会进行重定向

@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";
    }
}

但只为了实现这一个控制器而写了一个类未免有点小题大做,因此可以通过视图控制器<controller-view>方式来定义

<mvc:view-controller path="/" view-name="index"></mvc:view-controller>

这样在访问根路径时就会自动跳转到index.html,方便许多,但是又引入了一个新的问题:

​ 当时用该控制器后,只有视图控制器所设置的请求会被处理,其他的请求都是404

此时就必须配置一个新的标签<mvc:annotation-driven/>就可以直接解决该问题

RESTful

REST: Representational State Transfer,表现层资源状态转移。

可以理解为:restful只关心我们需要的资源,而不在意资源的操作方式,例:假如对user库进行资源的增删改查,那么这些操作都是对user库的操作,restful就可以将他设置为/user路径,之后的操作通过不同的请求方式来判断如何操作数据库即:

GET:获取资源

POST:新建资源

PUT:更新资源

DELETE:删除资源

操作传统方式REST风格
查询操作getUserById?id=1user/1 —> get请求方式
保存操作saveUseruser —> post请求方式
删除操作deleteUser?id=1user/1 —> delete请求方式
更新操作updateUseruser —> 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";
}

更新操作

HiddenHttpMethodFilter

表单中没有PUT和DELETE请求,所以可以通过HiddenHttpMethodFilter过滤器来获取这两种请求方式

web.xml加上

<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-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>

RESTful案例

准备工作

实体类

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
查询全部数据/employeeGET
删除/employee/2DELETE
跳转到添加数据页面/toAddGET
执行保存/employeePOST
跳转到更新数据页面/employee/2GET
执行更新/employeePUT

处理静态资源

控制层

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 list</title>
    <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
</head>
<body>
<table>
    <tr>
        <th colspan="5">employee list</th>
    </tr>
    <tr>
        <th>id</th>
        <th>lastName</th>
        <th>email</th>
        <th>gender</th>
        <th>options</th>
    </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="">delete</a>
            <a href="">update</a>
        </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无法处理静态资源

所以为了解决这个问题引入了一个配置标签:

<mvc:default-servlet-handler/>,此时浏览器发送的所有请求都会被DefaultServlet处理,但是我们处理用的并不是DefaultServlet,而是DispatcherServlet所以还需要加上<mvc:annotation-driven/>

此时浏览器发送的请求就会先被DispatcherServlet处理
在这里插入图片描述

添加功能

在options后边添加一个add按钮

<th>options(<a th:href="@{/to/add}">add</a>)</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 employee</title>
    <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
</head>
<body>
    <form th:action="@{/employee}" method="post">
        <table>
            <tr>
                <td colspan="2">add employee</td>
            </tr>
            <tr>
                <td>lastName</td>
                <td><input type="text" name="lastName"></td>
            </tr>
            <tr>
                <td>email</td>
                <td><input type="text" name="email"></td>
            </tr>
            <tr>
                <td>gender</td>
                <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}}">update</a>

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 employee</title>
    <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 employee</td>
            </tr>
            <tr>
                <td>lastName</td>
                <td><input type="text" name="lastName" th:value="${employee.lastName}"></td>
            </tr>
            <tr>
                <td>email</td>
                <td><input type="text" name="email" th:value="${employee.email}"></td>
            </tr>
            <tr>
                <td>gender</td>
                <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、修改后结果:

在这里插入图片描述

@RestController注解

标识在控制器上,就相当于添加了@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-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</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>
    <!-- 1、通过bean配置拦截器 -->
    <!-- <bean class="com.sentiment.interceptor.FirstInterceptor"/> -->
    
    <!-- 2、通过ref配置拦截器 -->
    <!-- <ref bean="firstInterceptor"/> -->
    
    <!-- 3、interceptor-->
    <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()按配置反序执行
在这里插入图片描述

为什么postHandle和afterCompletion是逆序执行的?

有三个拦截器:

  • conversion(系统自带)
  • first
  • second

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同理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值