SpringMVC

96 阅读25分钟

简介

什么是MVC

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分

M:Model,模型层,指工程中的JavaBean,作用是处理数据

  • JavaBean分为两类:
    • 一类称为实体类Bean:专门存储业务数据的,如Student、User等
    • 一类称为业务处理Bean:指Service或Dao对象,专门用于处理业务逻辑和数据访问

V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据 C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器

MVC的工作流程:

用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器

什么是SpringMVC

SpringMVC是Spring的一个后续产品,是Spring的一个子项目,SpringMVC是Spring为表述层开发提供的一整套完备的解决方案。在表述层框架历经Strust、WebWork,Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为JavaEE项目表述层开发的首选方案。

其实SpringMVC封装的就是Servlet

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,其中,表述层表示前台页面和后台servlet

SpringMVC的特点:

  • Spring家族原生产品,与IOC容器等基础设施无缝对接,基于原生的Servlet,通过功能强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

SpringMVC入门案例

  1. 先在pom.xml里引入依赖,打包方式改为war包

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.pan</groupId>
        <artifactId>sm01</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>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
            </dependency>
            <!--Spring5和Thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
        </dependencies>
    </project>
    
  2. 配置web.xml

    注册SpringMVC的前端控制器

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--配置SpringMVC的前端控制器DispatcherServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--这个配置可以将springmvc的配置文件放在resources目录下-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <!--服务器启动时就初始化DispatcherServlet,提高用户加载速度-->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
            <!--
                url-pattern里 "/" 和 "/*" 的区别
                /   匹配浏览器向服务器发送的所有请求(不包括.jsp)
                /*  匹配浏览器向服务器发送的所有请求(包括.jsp)
            -->
        </servlet-mapping>
    </web-app>
    
  3. 创建请求控制器

    由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器

    请求控制器中每一个处理请求的方法成为控制器方法

    因为SpringMVC的控制器由一个POJO(普通的Java类)担任,因此需要通过@Controller注解将其标识为一个控制层组件,交给Spring的IOC容器管理,此时SpringMVC才能够识别控制器的存在

    package com.pan.springmvc.controller;
    
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class HelloController {
        // @RequestMapping注解:处理请求和控制器方法之间的映射关系
    	// @RequestMapping注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
        @RequestMapping("/")
        public String toIndex() {
            // 返回逻辑视图
            return "index";
        }
    
        @RequestMapping("/hello")
        public String toHello() {
            // 返回逻辑视图
            return "hello";
        }
    }
    
  4. 创建SpringMVC的配置文件,可以放在resources目录下,因为在web.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"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--扫描控制层组件-->
        <context:component-scan base-package="com.pan.springmvc.controller"/>
    
        <!--配置Thymeleaf视图解析器-->
        <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>
    

    视图前缀+视图+视图后缀就是所访问页面资源的路径

  5. /WEB-INF/templates/里创建一个index.html和hello.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>index</h1>
    <a th:href="@{/hello}">点击跳转</a>
    </body>
    </html>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    hello
    </body>
    </html>
    

总结

浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方 法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面

@RequestMapping注解

从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。 SpringMVC接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。

  1. 这个注解可以加载一个方法上,也可以用在一个类上,一旦加在类上,里面所有的请求只能加上类上配置的路径了,给类加RequestMapping注解很常用!如可用来区分多个名为“list”的地址,可以是学生的列表,也可以是老师的列表,可以通过student/listteacher/list区分
package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/a")
public class IndexController {
    @RequestMapping("/b")
    public String toIndex(){
        return "index";//这时只能通过 /a/b 来访问index.html了
    }
}
  1. 当然注解的参数也可以是个路径数组,多个路径对应的是同一个响应
package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
    @RequestMapping({"/a","/b"})
    public String toIndex(){
        return "index";// 这时 /a 和 /b 都能访问index.html了
    }
}
  1. 在路径参数后面也可以加method属性,用来指定请求方式,当然可以以设置成数组形式,表示多种请求方式满足其一都行

目前POST请求只能在表单里设置成POST,否则一律是GET

package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class IndexController {
    @RequestMapping(value = {"/a", "/b"}, method = RequestMethod.GET)
    public String toIndex() {
        return "index";// 这时 /a 和 /b 都能访问index.html了,但是只能是GET请求,否则报错405
    }
}

这时发现请求方式来回就那几个get,post,put,delete...,,每次还要在RequestMapping注解里写method = RequestMethod.GET属性,太麻烦

可以不用RequestMapping注解了,换成 @GetMapping、@PostMapping、@DeleteMapping、@PutMapping,这样就不用写method属性了,只需写个路径即可

  1. params属性

作用:通过请求的请求参数匹配请求,即浏览器发送的请求的请求参数必须满足params属性里的设置 params可以使用四种表达式:

  • "param":表示当前所匹配请求的请求参数中必须携带都param参数
  • "!param":表示当前所匹配请求的请求参数中一定不能携带param参数
  • "param=value":表示当前所匹配请求的请求参数中必须携带param参数且值必须为value
  • "param!=value":表示当前所匹配请求的请求参数中可以不携带都param,若携带,值一定不能是value
package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class IndexController {
    @RequestMapping(value = {"/a", "/b"}, method = RequestMethod.GET, params = {"username"})
    public String toIndex() {
        return "index";// 这时 /a 和 /b 只有带上username的参数才能访问
        // 就像这样:http://localhost:8080/sm02/b?username=abc
    }
}

若浏览器所发送的请求的请求路径和@RequestMapping注解value属性匹配,但是请求参数不匹配,会报错400

  1. header属性

和上面params的用法一模一样,即指定发送的请求的请求头里得满足某些条件,不举例了

注意,假如满足了value和method属性,但是不满足header属性,这时报错是404

注意,SpringMVC支特和ant风格的路径,可以在@RequestMapping注解里的value属性值中设置一些特殊字符

  • ? 任意的单个字符(不包括?)
  • * 任意个数的任意字符(不包括?和/)
  • ** 标识任意层数的任意目录(只能写成/**/xxx)

SpringMVC支持路径中的占位符(重点)

请求路径的传统风格:/deleteUser?id=99

请求路径的rest风格:/user/delete/99

那么rest风格该怎样接收参数呢?

  • 需要在@RequestMapping注解的value属性中所设置的路径中,使用{xxx}的方式表示路径中的数据
  • 再通过@PathVariable注解,将占位符所标识的值和控制器方法的形参进行绑定
package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class IndexController {
    @RequestMapping("/test/{id}")
    public String testRest(@PathVariable("id") Integer id) {
        System.out.println(id);
        return "index";
    }
}

// 前端输入 http://localhost:8080/sm02/test/99
// 服务器输出了id的值99

SpringMVC获取请求参数

请求的过程:

获取请求参数 ==> 调用service处理业务逻辑 ==> 往域对象中共享数据 ==> 实现页面跳转

怎样获取请求参数呢?

  1. 通过servletAPI获取参数

    只需在控制方法的形参位置设置HttpServletRequest request参数就可以在控制器方法中使用request对象获取请求参数,调用servlet原生的方法

    可以但没必要,你都用springmvc了还用servlet干嘛

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    public class TestParamController {
        @RequestMapping("/login")
        public String getParamsByServletApi(HttpServletRequest request) {
            String username = request.getParameter("username");
            String pwd = request.getParameter("pwd");
            System.out.println(username + pwd);
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <form th:action="@{/login}" method="post">
        用户名: <input type="text" name="username"><br/>
        密码: <input type="password" name="pwd"><br/>
        <input type="submit" value="login">
    </form>
    </body>
    </html>
    
  2. 在控制方法的形参位置设置和请求参数一样的形参即可

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class TestParamController {
        @RequestMapping("/login")
        public String getParams(String username, String pwd) {
            System.out.println(username + pwd);
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <form th:action="@{/login}" method="post">
        用户名: <input type="text" name="username"><br/>
        密码: <input type="password" name="pwd"><br/>
        <input type="submit" value="login">
    </form>
    </body>
    </html>
    

    注意,假如前端form里的name属性叫userName,后端的形参叫username,还想获取,该咋办?这时就可以给形参加个@RequestParam注解,还可以指定该参数是否必须(默认为true,若设置为false不传输,则值为null),以及设置默认值

    这里除了@RequestParam注解,还有@RequestHeader和@CookieValue这两个注解,他俩用法和@RequestParam一模一样

    如想获取JSESSIONID,可以@CookieValue("JSESSIONID") String jsessionId

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @Controller
    public class TestParamController {
        @RequestMapping("/login")
        public String getParams(@RequestParam(value = "userName", required = false, defaultValue = "admin") String username, String pwd) {
            System.out.println(username + pwd);
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <form th:action="@{/login}" method="post">
        用户名: <input type="text" name="userName"><br/>
        密码: <input type="password" name="pwd"><br/>
        <input type="submit" value="login">
    </form>
    </body>
    </html>
    
  3. 通过pojo类获取

    package com.pan.sp.pojo;
    
    public class User {
        private String username;
        private String pwd;
    
        @Override
        public String toString() {
            return "User{" + "username='" + username + '\'' + ", pwd='" + pwd + '\'' + '}';
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        public User() {
        }
    
        public User(String username, String pwd) {
            this.username = username;
            this.pwd = pwd;
        }
    }
    
    package com.pan.sp.controller;
    
    import com.pan.sp.pojo.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class TestParamController {
        @RequestMapping("/login")
        public String getParams(User user) {
            System.out.println(user.getUsername() + user.getPwd());
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <form th:action="@{/login}" method="post">
        用户名: <input type="text" name="username"><br/>
        密码: <input type="password" name="pwd"><br/>
        <input type="submit" value="login">
    </form>
    </body>
    </html>
    

    可以看到这种只需要有个pojo类,里面的属性和前端的一一参数对应,在控制方法的形参处声明个实体类对象即可,这时就可以通过对象获取参数了

解决获取请求参数时的乱码问题

在web.xml里添加一个过滤器即可,SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置Spring的编码过滤器-->
    <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>
    <!--配置Spring的编码过滤器-->


    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

注意,要是还乱码则需要在启动服务器时添加虚拟机选项-Dfile.encoding=UTF-8

域对象共享数据

向request请求域中共享数据一共有5种方式

  1. 使用ServletAPI向request域对象共享数据,看一下,以后用不上

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    public class TestParamController {
        @RequestMapping("/login")
        public String testServletAPI(HttpServletRequest request) {
            request.setAttribute("name", "tom");
            return "index";
        }
    }
    
  2. 使用ModelAndView向request域对象共享数据

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class TestScopeController {
        @RequestMapping("/test/mav")
        public ModelAndView testMAV() {
            // ModelAndView包含Model和View的功能
            // 1 Model:向请求域中共享数据
            // 2 View:设置逻辑视图实现页面跳转
            ModelAndView mav = new ModelAndView();
            // 向请求域中共享数据
            mav.addObject("name", "tom");
            // 设置逻辑视图
            mav.setViewName("index");
            return mav;
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <p th:text="${name}"></p>
    </body>
    </html>
    
  3. 单独使用Model向请求域中共享数据

    package com.pan.sp.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class TestScopeController {
        @RequestMapping("/test/model")
        public String testModel(Model model) {
            model.addAttribute("age", 18);
            return "index";
        }
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <p th:text="${age}"></p>
    </body>
    </html>
    
  4. 使用ModelMap向请求域中共享数据

    @RequestMapping("/test/model")
    public String testModel(Model model) {
        model.addAttribute("age", 18);
        return "index";
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <p th:text="${age}"></p>
    </body>
    </html>
    
  5. 使用Map向请求域中共享数据

    @RequestMapping("/test/map")
    public String testMap(Map<String, Object> map) {
        map.put("hobby", "抽烟");
        return "index";
    }
    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>index</h1>
    <p th:text="${hobby}"></p>
    </body>
    </html>
    

Model、ModelMap、Map类型的参数其实本质上都是BindingAwareModelMap类型的

向session会话域和application应用域中共享数据

这里建议使用原生的servletAPI

@RequestMapping("/test/session")
public String testSession(HttpSession session) {
    session.setAttribute("name", "tom");
    return "index";
}

@RequestMapping("/test/application")
public String testApplication(HttpSession session) {
    ServletContext servletContext = session.getServletContext();
    servletContext.setAttribute("age", 99);
    return "index";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>index</h1>
<p th:text="${session.name}"></p>
<p th:text="${application.age}"></p>
</body>
</html>

SpringMVC的视图

SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户,SpringMVC视图的种类很多,默认有转发视图和重定向视图,当工程引入jstl的依赖,转发视图会自动转换为jstlView,若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView

一般业务逻辑处理失败用转发,成功用重定向

ThymeleafView

thymeleaf的转发,当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转

package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestViewController {
    @RequestMapping("test/view/thymeleaf")
    public String testThymeleafView() {
        return "index";// 转发
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描控制层组件-->
    <context:component-scan base-package="com.pan.sp.controller"/>

    <!--配置Thymeleaf视图解析器-->
    <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>

转发视图

这也是转发,但是thymeleaf的转发用的最多

SpringMVC中默认的转发视图是InternalResourceView SpringMVC中创建转发视图的情况: 当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转,例如"forward:/","forward:/employee"

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestViewController {
    @RequestMapping("test/view/thymeleaf")
    public String testThymeleafView() {
        return "index";// thymeleaf的转发
    }

    @RequestMapping("test/view/forward")
    public String testForwardView() {
        return "forward:/test/view/thymeleaf";// 转发
    }
}

重定向视图

SpringMVC中默认的重定向视图是RedirectView 当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转,例如"redirect:/","redirect:/employee"

package com.pan.sp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestViewController {
    @RequestMapping("test/view/thymeleaf")
    public String testThymeleafView() {
        return "index";
    }

    @RequestMapping("test/view/forward")
    public String testThymeleafForwardView() {
        return "forward:/test/view/thymeleaf";
    }

    @RequestMapping("test/view/redirect")
    public String testThymeleafRedirect() {
        return "redirect:/test/view/forward"; // 重定向
    }
}

视图控制器

假如想设置访问/路径时跳转到首页,不用写一个控制方法,直接在springmvc中开启mvc的注解驱动,然后配置视图控制器即可

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--扫描控制层组件-->
    <context:component-scan base-package="com.pan.sp.controller"/>

    <!--配置Thymeleaf视图解析器-->
    <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>
    
    <!--开启mvc的注解驱动-->
    <mvc:annotation-driven/>

    <!--视图控制器:为当前的请求直接设置视图名称实现页面跳转-->
    <mvc:view-controller path="/" view-name="index"/>

</beans>

RESTful

rest风格具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。 它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。 REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发关给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。

操作传统方式REST风格
查询操作getUserByld?id=1user/1(get请求方式)
保存操作saveUseruser(post请求方式)
删除操作deleteUser?id=1user/1(delete请求方式)
更新操作updateUseruser(put请求方式)

一个restful的小案例

  1. pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.pan</groupId>
        <artifactId>sm03</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>
            <!--servlet-api-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
            </dependency>
            <!--Spring5和Thymeleaf整合包-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.15.RELEASE</version>
            </dependency>
        </dependencies>
    </project>
    
  2. web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <!--配置Spring的编码过滤器-->
        <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>
    
    
        <!--设置处理请求方式的过滤器-->
        <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>
    
    
        <!--配置SpringMVC的前端控制器DispatcherServlet-->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  3. springmvc.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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--扫描控制层组件-->
        <context:component-scan base-package="com.pan.springmvc.controller"/>
    
        <!--配置Thymeleaf视图解析器-->
        <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>
        
        <!--
            配置默认的servlet处理静态资源
            当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/
            tomcat的web.xml配置DefaultServlet的url-pattern也是/
            此时,浏览器发送的请求会优先被DispatcherServlet进行处理,但是DispatcherServlet无法处理静态资源
            若配置了<mvc:default-servlet-handler/>,此时刘览器发送的所有请求都会被DefaultServlet处理
            若配置了<mvc:default-servlet-handler/>和<mvc:annotation-driven/>
            浏览器发送的请求会先被DispatcherServlet处理,无法处理再交给DefaultServlet处理
        -->
        <mvc:default-servlet-handler/>
    
        <!--开启mvc的注解驱动-->
        <mvc:annotation-driven/>
    
        <!--视图控制器:为当前的请求直接设置视图名称实现页面跳转-->
        <mvc:view-controller path="/" view-name="index"/>
    
    </beans>
    
  4. index.html测试四种restful风格的请求

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>主页</title>
    </head>
    <body>
    index<br/>
    <!--查询所有(get)-->
    <a th:href="@{/user}">查询所有用户</a><br/>
    <!--查询指定(get)-->
    <a th:href="@{/user/1}">查询id为1的用户</a><br/>
    <!--添加(post)-->
    <form th:action="@{/user}" method="post">
        name:<input type="text" name="username"/><br/>
        password:<input type="password" name="password"/><br/>
        <input type="submit" value="submit"/><br/>
    </form>
    <!--修改(put)-->
    <!--注意:浏览器目前只能发送get和post清求-->
    <!--若要发送put和delete清求,需要在web.xml中配置一个过滤器HiddenHttpMethodFilter-->
    <!--而且表单得写一个隐藏类型的输入框,里面的name="_method" value="put"得写上-->
    <!--注意,发送put和delete外层的表单类型还是post,只是里面说明了而已-->
    <form th:action="@{/user}" method="post">
        <input type="hidden" name="_method" value="put">
        <input type="submit" value="modify">
    </form>
    <!--删除(delete)-->
    <form th:action="@{/user/1222}" method="post">
        <input type="hidden" name="_method" value="delete">
        <input type="submit" value="del">
    </form>
    </body>
    </html>
    

    成功页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>成功</title>
    </head>
    <body>
    success
    </body>
    </html>
    
  5. 控制器

    package com.pan.springmvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    @Controller
    public class TestRestfulController {
        @RequestMapping(value = "/user", method = RequestMethod.GET)
        public String getAllUser() {
            System.out.println("查询所有用户(get)");
            return "success";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
        public String getUserById(@PathVariable("id") Integer id) {
            System.out.println("根据id查询用户(get)" + id);
            return "success";
        }
    
        @RequestMapping(value = "/user", method = RequestMethod.POST)
        public String addUser(String username, String password) {
            System.out.println("添加用户(post)" + username + password);
            return "success";
        }
    
        @RequestMapping(value = "/user", method = RequestMethod.PUT)
        public String modifyUser() {
            System.out.println("修改用户(put)");
            return "success";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
        public String modifyUser(@PathVariable("id") Integer id) {
            System.out.println("删除用户(delete)" + id);
            return "success";
        }
    }
    

注意,这种要注意的是想发送接收PUT和DELETE请求,前端想发送得加隐藏标签,用POST的方式发送,而且后端得在web.xml里配置处理请求方式的过滤器,注意要想访问静态资源,就得配置<mvc:default-servlet-handler/>和<mvc:annotation-driven/>

还有就是上面的5里也可以将不同的@RequestMapping替换成如PutMapping、DeleteMapping等,这样就不用写method属性了,少写代码了解一下

SpringMVC处理Ajax请求

接收JSON请求用@RequestBody注解

@RequestBody

将请求体中的内容和控制器方法的形参进行绑定,使用@RequestBody注解将json格式的请求参数转换为java对象,前提是

  1. 导入jackson的依赖

    <!--jackson处理json格式-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.4</version>
    </dependency>
    
  2. 在SpringMVC的配置文件中设置

    <!--开启mvc的注解驱动-->
    <mvc:annotation-driven/>
    

前端发了axios异步请求

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>

<div id="app">
    <input type="button" value="测试spring的ajax处理" @click="testAjax()">
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script type="text/javascript">
    var vue = new Vue({
        el: "#app",
        methods: {
            testAjax() {
                axios({
                    method: 'post',
                    url: '/sm/test/ajax?id=1001',// 普通键值对的数据
                    data: {
                        username: 'root',
                        password: 'root'// json的数据
                    }
                }).then(response => {
                    console.log(response.data);// 这时发现前端控制台有hello输出
                });
            }
        }
    })
</script>


<script type="text/javascript">
    // testAjax() {
    //     axios({
    //         url: "",    // 请求路径
    //         method: "", // 请求方式
    //         params: {}, // 请求参数,以name=xxx&age=12的方式,不管使用的请求方式是get或post,请求参数都会被拼接到请求地址后,此种方式的请求参数可以通过request.getParameter()获取
    //         data: {}    // 请求参数,以json的方式,请求参数会被保存到请求报文的请求体传输到服务器,不可以通过request.getParameter()获取
    //     }).then(response => {
    //
    //     });
    // }
</script>

</body>
</html>

后端接收name=xxx&age=12形式的参数:

package com.pan.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class TestAjaxController {
    @RequestMapping("/test/ajax")
    public void testAjax(Integer id, HttpServletResponse response) throws IOException {
        System.out.println(id);// 获取前端传来的name=xxx&age=12形式的数据很简单,在形参处声明一个同名变量即可
        response.getWriter().write("hello");
    }
}

怎么接收body里的json格式的数据呢?再加一个字符串类型形参,并用 @RequestBody注解标注一下

package com.pan.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class TestAjaxController {
    @RequestMapping("/test/ajax")
    public void testAjax(Integer id, @RequestBody String requestBody, HttpServletResponse response) throws IOException {
        System.out.println(id);// 1001
        System.out.println(requestBody);// {"username":"root","password":"root"}
        response.getWriter().write("hello");
    }
}

这时发现接收是能接收JSON,但是格式很恶心,还是原始的一长串,这时就可以使用SpringMVC为我们提供的方式接收JSON数据了,很方便

前端发了个json字符串

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>

<div id="app">
    <input type="button" value="测试spring的ajax处理" @click="testAjax()">
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script type="text/javascript">
    var vue = new Vue({
        el: "#app",
        methods: {
            testAjax() {
                // json类型的数据
                axios.post(
                    '/sm/test/ajax/json',
                    {
                        username: 'tom',
                        password: '123',
                    }
                ).then(res=>{
                    console.log(res.data)
                });
            }
        }
    })
</script>

</body>
</html>

后端接收先创建一个pojo类,属性和json里的键一一对应

package com.pan.springmvc.pojo;

public class User {
    private String username;
    private String password;

    @Override
    public String toString() {
        return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}';
    }

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

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}
  1. 用pojo对象接收,前提是给pojo对象加 @RequestBody注解
package com.pan.springmvc.controller;

import com.pan.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Controller
public class TestAjaxController {
    // 接收json格式
    @RequestMapping("/test/ajax/json")
    public void testAjaxBody(@RequestBody User user, HttpServletResponse response) throws IOException {
        System.out.println(user + user.getUsername() + user.getPassword());//User{username='tom', password='123'}tom123
        response.getWriter().write("body-json");
    }
}
  1. 当然没有实体类也可以用map接收
package com.pan.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@Controller
public class TestAjaxController {
    // 接收json格式
    @RequestMapping("/test/ajax/json")
    public void testAjaxBody(@RequestBody Map<String, Object> map, HttpServletResponse response) throws IOException {
        System.out.println("map" + map);// map{username=tom, password=123}
        response.getWriter().write("body-json");
    }
}

@ResponseBody(非常常用)

用的特别特别特别多,将所标识的控制器方法的返回值作为响应报文的响应体响应到浏览器,返回JSON格式的数据

  1. 导入jackson的依赖

    <!--jackson处理json格式-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.4</version>
    </dependency>
    
  2. 在SpringMVC的配置文件中设置

    <!--开启mvc的注解驱动-->
    <mvc:annotation-driven/>
    

将需要转换为Json字符串的java对象直接作为控制器方法的返回值,使用@ResponseBody注解标识控制器方法就可以将Java对象直接转换为json字符串,并响应到浏览器(可以返回对象、数组、集合)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>

<div id="app">
    <input type="button" value="使用@ResponseBody注解响应json格式的数据" @click="testResponseJSON()">
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script type="text/javascript">
    var vue = new Vue({
        el: "#app",
        methods: {
            testResponseJSON(){
                axios.post("/sm/test/responsebody/json").then(res=>{
                    console.log(res.data)
                })
            }
        }
    })
</script>

</body>
</html>
package com.pan.springmvc.controller;

import com.pan.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestAjaxController {
    // 使用@ResponseBody响应请求
    @RequestMapping("/test/responsebody/json")
    @ResponseBody
    public User testResponseBodyJSON() {
        User user = new User("admin", "radasdfadsf");
        return user;
    }
}

常用的Java对象转换为json的结果: 实体类===>json对象 map=====>json对象 list======>json数组

@RestController

是一个复合注解,给控制器类上加,作用相当于给类添加了@Controller注解,并为类中每个方法加了@ResponseBody注解

文件上传与下载

文件下载

固定模板

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>

<div id="app">
    <a th:href="@{/test/download}">点击下载</a>
</div>

</body>
</html>
package com.pan.springmvc.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@Controller
public class TestDownloadAndUploadController {
    @RequestMapping("/test/download")
    public ResponseEntity<byte[]> testDownload(HttpSession session) throws IOException {
        // ResponseEntity:可以作为控制器方法的返回值,表示响应到浏览器的完整的响应报文

        // 获取servletContext对象
        ServletContext servletContext = session.getServletContext();
        // 获取服务器中文件的真实路径
        String realPath = servletContext.getRealPath("img/1.jpg");// 放在webapp的img目录下了
        // 创建输入流
        InputStream is = new FileInputStream(realPath);
        // 创建字节数组
        byte[] bytes = new byte[is.available()];
        // 将流读到字节数组中
        is.read(bytes);
        // 创建HttpHeaders对象设置响应头信息
        MultiValueMap<String, String> headers = new HttpHeaders();
        // 设置要下载方式以及下载文件的名字
        headers.add("Content-Disposition", "attachment;filename=1.jpg");
        // 设置响应状态码
        HttpStatus statusCode = HttpStatus.OK;
        // 创建ResponseEntity对象
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
        // 关闭输入流
        is.close();
        return responseEntity;
    }
}

文件上传

想上传,先得在springmvc配置文件里加一个文件上传解析器

<!--配置文件上传解析器,id是固定的!-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

在pom中加依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

然后才能上传

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>

<div id="app">
    <form th:action="@{/test/upload}" method="post" enctype="multipart/form-data">
        头像:<input type="file" name="photoupload"><br/>
        <input type="submit" value="上传">
    </form>
</div>

</body>
</html>
package com.pan.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

@Controller
public class TestDownloadAndUploadController {
    @RequestMapping("/test/upload")
    public String testUpload(MultipartFile photoupload, HttpSession session) throws IOException {
        // 获取上传的文件的文件名
        String filename = photoupload.getOriginalFilename();
        // 获取上传文件的后缀名
        String lastFileName = filename.substring(filename.lastIndexOf("."));
        // 获取uuid
        String uuid = UUID.randomUUID().toString();
        // 拼接一个新的文件名
        filename = uuid + lastFileName;
        // 获取ServletContext对象
        ServletContext servletContext = session.getServletContext();
        // 获取当前工程下photo目录的真实路径
        String photoPath = servletContext.getRealPath("photo");
        // 创建photoPath对应的文件对象
        File file = new File(photoPath);
        // 判断file所对应的目录是否存在
        if (!file.exists()) {
            file.mkdir();
        }
        String finalPath = photoPath + File.separator + filename;
        // 上传文件
        photoupload.transferTo(new File(finalPath));
        return "index";
    }
}

拦截器

先创建一个interceptor包,里面放拦截器类,实现HandlerInterceptor接口

拦截器的三个方法: preHandle():在控制器方法执行之前执行,其返回值表示对控制器方法的拦截(false)或放行(true) postHandle():在控制器方法执行之后执行 afterCompletion():在控制器方法执行之后,且渲染视图完毕之后执行

package com.pan.sm.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("控制器方法执行前执行");
        return true;//返回false则控制器方法不执行,下面两个方法自然也不执行
        // 返回true,下面两个方法就都会执行了
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("控制器方法执行之后执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("渲染后执行");
    }
}

在springmvc配置文件中导入拦截器

方式1

<mvc:interceptors>
    <bean class="com.pan.sm.interceptor.FirstInterceptor"/>
</mvc:interceptors>

方式2导入的方式也可以是

<bean id="firstInterceptor" class="com.pan.sm.interceptor.FirstInterceptor"/>
<mvc:interceptors>
    <ref bean="firstInterceptor"/>
</mvc:interceptors>

方式3也可以是,这种需要在拦截器类上加一个@Component注解

<!--扫描控制层组件-->
<context:component-scan base-package="com.pan.sm"/>

<mvc:interceptors>
    <ref bean="firstInterceptor"/>
</mvc:interceptors>

注意,上面bean和ref标签所配置的拦截器默认对DispatcherServlet处理的所有的请求进行拦截

方式4,和3差不多,但是更精确,可以指定拦截路径和排除路径

<!--扫描控制层组件-->
<context:component-scan base-package="com.pan.sm"/>

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*"/>  <!-- /*表示拦截一层,/**表示拦截所有路径,要注意 -->
        <mvc:exclude-mapping path="/abc"/>
        <ref bean="firstInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

多个拦截器的执行顺序和在SpringMVC的配置文件中配置的顺序有关

preHandle()按照配置的顺序执行,postHandle()和afterCompletion()按照配置的反序执行

若拦截器中有某个拦截器的preHandle()返回了False 拦截器的preHandle()返回false和它之前的拦截器的preHandle()都会执行 所有的栏戴器的postHandle()都不执行 拦截器的preHandle()返回false之前的拦截器的afterCompletion()会执行

异常处理器

SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver HandlerExceptionResolver接☐的实现类有:DefaultHandlerExceptionResolver(默认就是这个)和SimpleMappingExceptionResolver

SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver使用方式:

准备一个error.html页面,在springmvc配置文件里加如下代码,只要出现数学异常就跳转到error页面,key值填异常的全类名

package com.pan.sm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestController {
    @RequestMapping("/test/hello")
    public String testHello() {
        int a = 1 / 0;
        return "index";
    }
}
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--key设置要处理的异常,value设置出现该异常时要跳转的页面所对应的逻辑视图-->
            <prop key="java.lang.ArithmeticException">error</prop>
        </props>
    </property>
    <!--设置共享在请求域中的异常信息的属性名-->
    <property name="exceptionAttribute" value="ex"/>
</bean>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>error</title>
</head>
<body>
error
<p th:text="${ex}"></p>
</body>
</html>

结果
error
java.lang.ArithmeticException: / by zero

还可以用注解的方式处理异常,创建一个异常处理控制器

package com.pan.sm.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

// 将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(ArithmeticException.class)
    public String handleException(Throwable ex, Model model) {
        model.addAttribute("ex", ex);
        return "error";
    }
}

用注解的方式配置SpringMVC

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。

Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletlnitializer,当我们的类扩展了AbstractAnnotationConfigDispatcherServletlnitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

一通操作完就不需要web.xml、springmvc.xml配置文件了,用配置类代替

首先在项目里创建一个config包,里面放下面所有的配置类

第一,得有一个WebInit类,用来代替web.xml

package com.pan.sm.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

// 代替web.xml
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    // 设置一个配置类代替Spring的配置文件
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    // 设置一个配置类代替SpringMVC的配置文件
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    // 设置SpringMVC的前端控制器DispatcherServlet的url-pattern
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    // 设置当前的过滤器
    @Override
    protected Filter[] getServletFilters() {
        // 创建编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        // 创建处理请求方式的过滤器
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

然后创建SpringConfig类,代替Spring的配置文件

package com.pan.sm.config;

import org.springframework.context.annotation.Configuration;

// 代替Spring的配置文件

// 将类标识为配置类
@Configuration
public class SpringConfig {
}

接着创建WebConfig类,代替SpringMVC的配置文件

package com.pan.sm.config;

import com.pan.sm.interceptor.FirstInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import java.util.List;
import java.util.Properties;

// 代替SpringMVC的配置文件
// 扫描组件,视图解析器,默认的Servlet,mvc的注解驱动,视图控制器,文件上传解析器,拦截器,异常解析器

@Configuration// 将类标识为配置类
@ComponentScan("com.pan.sm.controller")// 扫描组件
@EnableWebMvc// 开启mvc的注解驱动
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { // 默认的Servlet处理静态资源
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) { // 视图控制器
        registry.addViewController("/").setViewName("index");
    }

    @Bean // 注解可以将标识的方法的返回值作为bean进行管理,bean的id为方法的方法名
    public CommonsMultipartResolver multipartResolver() { // 文件上传解析器
        return new CommonsMultipartResolver();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) { // 拦截器
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/adf");
    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { // 异常解析器
        SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("java.lang.ArithmeticException", "error");
        simpleMappingExceptionResolver.setExceptionMappings(properties);
        simpleMappingExceptionResolver.setExceptionAttribute("ex");
        resolvers.add(simpleMappingExceptionResolver);
    }

    // 视图解析器

    // 配置生成模板解析器
    @Bean
    public ITemplateResolver templateResolver() {
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setCharacterEncoding("UTF-8");
        templateResolver.setTemplateMode(TemplateMode.HTML);
        return templateResolver;
    }

    // 生成模板引擎并为模板引擎注入模板解析器
    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        return templateEngine;
    }

    // 生成视图解析器并将解析器注入模板引擎
    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setCharacterEncoding("UTF-8");
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }
}

经过上面的操作,配置文件就不要了,用配置类代替了

上面的配置类中还配置了拦截器,拦截器也是个类,放在interceptor包下

package com.pan.sm.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("控制器方法执行前执行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("控制器方法执行之后执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("渲染后执行");
    }
}

控制器的类在Controller包下,在WEB-INF下创建templates目录,放index.html,发现项目正常运行

SpringMVC的执行流程

  • DispatcherServlet:前端控制器,不需要工程师开发,由框架提供 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
  • HandlerMapping:处理器映射器,不需要工程师开发,由框架提供 作用:根据请求的url、method等信息查找Handler,即控制器方法
  • Handler:处理器,需要工程师开发 作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
  • HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供 作用:通过HandlerAdapter对处理器(控制器方法)进行执行
  • ViewResolver:视图解析器,不需要工程师开发,由框架提供 作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
  • View:视图 作用:将模型数据通过页面展示给用户

具体执行流程

  1. 用户向服务器发送请求,请求被SpringMVC前端控制器DispatcherServlet捕获
  2. 前端控制器DispatcherServlet对请求URL(网络上的地址)进行解析,得到请求资源标识符(URI,服务器上的地址),判断请求URI对应的映射:
    • 不存在
      • 判断是否配置了默认的servlet,<mvc:default-servlet-handler/>,若默认的没有处理,404
      • 没有配置默认的servlet,控制台报映射查找不到,404
      • 若有配置,则访问目标资源(一般为静态的html,css,js),找不到也会404
    • 存在
      • 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
      • DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。
      • 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler()方法,正向的
      • 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
        • HttpMessageConveter:将请求消息(如json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
        • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
        • 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等
        • 数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
      • Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象。
      • 此时将开始执行拦截器的postHandle()方法,逆向的。
      • 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
      • 渲染视图完毕执行拦截器的afterCompletion()方法,逆向的。
      • 将渲染结果返回给客户端。