SpringMVC详解(超全面)

目录

一、SpringMvc入门

1、回顾MVC模式

1.1 概念

​ MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。

​ 在 Web 项目的开发中,能够及时、正确地响应用户的请求是非常重要的。用户在网页上单击一个 URL 路径,这对 Web 服务器来说,相当于用户发送了一个请求。而获取请求后如何解析用户的输入,并执行相关处理逻辑,最终跳转至正确的页面显示反馈结果,这些工作往往是控制层(Controller)来完成的。

​ 在请求的过程中,用户的信息被封装在 User 实体类中,该实体类在 Web 项目中属于数据模型层(Model)。

​ 在请求显示阶段,跳转的结果网页就属于视图层(View)。

​ 像这样,控制层负责前台与后台的交互,数据模型层封装用户的输入/输出数据,视图层选择恰当的视图来显示最终的执行结果,这样的层次分明的软件开发和处理流程被称为 MVC 模式。

​ 在学习 Servlet 及 JSP 开发时,JavaBean 相当于 Model,Servlet 相当于 Controller,JSP 相当于 View。

总结如下:

  • 视图层(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、界面设计等功能。
  • 控制层(Controller):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。
  • 数据模型层(Model):模型对象拥有最多的处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理和实现数据操作(即在数据库中存取数据)。

1.2 优缺点

​ 任何一件事都有利有弊,下面来了解一下 MVC 的优缺点。

1.2.1 优点

  • 多视图共享一个模型,大大提高了代码的可重用性
  • MVC 三个模块相互独立,松耦合架构
  • 控制器提高了应用程序的灵活性和可配置性
  • 有利于软件工程化管理

总之,我们通过 MVC 设计模式最终可以打造出一个松耦合+高可重用性+高可适用性的完美架构。

1.2.2 缺点

  • 原理复杂
  • 增加了系统结构和实现的复杂性
  • 视图对模型数据的访问效率低

2、SpringMVC概念

1、概念

  • Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
  • Spring MVC 是结构最清晰的 Servlet+JSP+JavaBean 的实现。
  • 由于 Spring MVC 本身就是 Spring 框架的一部分,可以说和 Spring 框架是无缝集成。性能方面具有先天的优越性,是当今业界最主流的 Web 开发框架,最热门的开发技能。
  • 在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。
  • Spring MVC 的注解驱动和对 REST 风格的支持。

2、优点

  • 清晰地角色划分,Spring MVC 在 Model、View 和 Controller 方面提供了一个非常清晰的角色划分,这 3 个方面真正是各司其职,各负其责。
  • 灵活的配置功能,可以把类当作 Bean 通过 XML 进行配置。
  • 提供了大量的控制器接口和实现类,开发者可以使用 Spring 提供的控制器实现类,也可以自己实现控制器接口。
  • 真正做到与 View 层的实现无关。它不会强制开发者使用 JSP,可以根据项目需求使用 Velocity、FreeMarker 等技术。
  • 国际化支持
  • 面向接口编程
  • 与 Spring 框架无缝集成

3、第一个Spring MVC程序

3.1 使用步骤

3.1.1 创建 Web 应用并导入依赖

<properties>
    <!-- spring版本号 -->
    <spring.version>4.0.2.RELEASE</spring.version>
    <!-- log4j日志文件管理包版本 -->
    <slf4j.version>1.7.7</slf4j.version>
    <log4j.version>1.2.17</log4j.version>
  </properties>
  <!--依赖-->
  <dependencies>
    <!-- junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <!-- JSTL标签类 -->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <!-- servlet依赖的jar包start -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.1</version>
    </dependency>
    <!-- 映入JSON -->
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-core-asl</artifactId>
      <version>1.9.4</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.4</version>
    </dependency>
    <!-- spring核心包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>${log4j.version}</version>
    </dependency>
    <!-- 格式化对象,方便输出日志 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.1.41</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.2.2</version>
    </dependency>
  </dependencies>
  <build>
    <!--resources配置解决mybatis的mapperXml配置在java路径不被扫描的问题 -->
    <resources>
      <resource>
        <directory>src/main/java</directory>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!-- 跳过测试 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
    </plugins>
  </build>

3.1.2 Spring MVC 配置

​ Spring MVC 是基于 Servlet 的,DispatcherServlet 是整个 Spring MVC 框架的核心,主要负责截获请求并将其分派给相应的处理器处理。所以配置 Spring MVC,首先要定义 DispatcherServlet。跟所有 Servlet 一样,用户必须在 web.xml 中进行配置。

在main目录下创建java和resource目录。

3.1.2.1 定义DispatcherServlet(web.xml)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <!-- 部署 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:config/springmvc-servlet.xml</param-value>
    </init-param>
    <!-- 表示容器再启动时立即加载servlet -->
    <load-on-startup>1</load-on-startup>
    <!--支持异步处理-->
    <async-supported>true</async-supported>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!-- 处理所有URL -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

注意事项:

  1. Spring MVC 初始化时将在应用程序的 WEB-INF 目录下查找配置文件,该配置文件的命名规则是“servletName-servlet.xml”,例如 springmvc-servlet.xml。
  2. 也可以将 Spring MVC 的配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件,通过 contextConfigLocation 参数来指定 Spring MVC 配置文件的位置。
<!-- 部署 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:config/springmvc-servlet.xml</param-value>
    </init-param>
    <!-- 表示容器再启动时立即加载servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
3.1.2.2 创建Controller

在 src 目录下创建 com.hqyj.cl.controller 包,并在该包中创建 RegisterController 和 LoginController 两个传统风格的控制器类(实现 Controller 接口),分别处理首页中“注册”和“登录”超链接的请求。Controller 是控制器接口,接口中只有一个方法 handleRequest,用于处理请求和返回 ModelAndView。

  1. LoginController
public class LoginController implements Controller {
    public ModelAndView handleRequest(HttpServletRequest arg0,
                                      HttpServletResponse arg1) throws Exception {
        return new ModelAndView("/WEB-INF/jsp/login.jsp");   //注意文件路径是否在WEB-INF文件夹中
    }
}
  1. RegisterController
public class RegisterController implements Controller {
    public ModelAndView handleRequest(HttpServletRequest arg0,
                                      HttpServletResponse arg1) throws Exception {
        return new ModelAndView("/WEB-INF/jsp/register.jsp");
    }
}
3.1.2.3 创建Spring MVC配置文件

创建 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">

    <!-- LoginController控制器类,映射到"/login" -->
    <bean name="/login"
          class="com.hqyj.cl.controller.LoginController"/>
    <!-- LoginController控制器类,映射到"/register" -->
    <bean name="/register"
          class="com.hqyj.cl.controller.RegisterController"/>
</beans>
3.1.2.4 创建页面
  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h2>Hello World!</h2>
未注册的用户,请
<a href="${pageContext.request.contextPath}/register"> 注册</a>!
<br /> 已注册的用户,去
<a href="${pageContext.request.contextPath}/login"> 登录</a>!
</body>
</html>
  1. login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>login</title>
</head>
<body>
login
</body>
</html>
  1. register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>register</title>
</head>
<body>
register
</body>
</html>
3.1.2.5 配置Tomcat服务器

3.1.2.6 测试

二、Spring MVC视图解析器(ViewResolver)

​ 视图解析器(ViewResolver)是 Spring MVC 的重要组成部分,负责将逻辑视图名解析为具体的视图对象。

1、常用的视图解析类

1.1 URLBasedViewResolver

1.1.1 概述

​ UrlBasedViewResolver 是对 ViewResolver 的一种简单实现,主要提供了一种拼接 URL 的方式来解析视图。

1.1.2 原理

​ UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。

1.1.3 配置

​ 使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下

<!--配置视图解析器之URLBasedViewResolver-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <!--不能省略-->
    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>
1.1.3.1 说明
  • 上述视图解析器配置了前缀和后缀两个属性,这样缩短了 view 路径。在第一个Spring MVC程序中的 RegisterController 和 LoginController 控制器类的视图路径仅需提供 register 和 login,视图解析器将会自动添加前缀和后缀,此处解析为 /WEB-INF/jsp/register.jsp 和 /WEB-INF/jsp/login.jsp。
  • UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用JstlView。
  • 存放在 /WEB-INF/ 目录下的内容不能直接通过 request 请求得到,所以为了安全性考虑,通常把 jsp 文件放在 WEB-INF 目录下

1.2 InternalResourceViewResolver

1.2.1 概述

InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLBasedViewResolver 的子类,拥有 URLBasedViewResolver 的一切特性。

1.2.2 原理

UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。

1.2.3 配置

使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下

<!--配置视图解析器之InternalResourceViewResolver-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--可以省略-->
    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/jsp/"/>    <!--注意路径-->
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>
1.2.3.1 说明
  • InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。
  • InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。

三、Spring MVC执行流程

1、执行流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPAiYIZX-1682512822611)(G:桌面备课上课ssm课件图Spring MVC执行流程图.png)]

2、执行流程概述

SpringMVC 的执行流程如下

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
  10. 视图负责将结果显示到浏览器(客户端)。

3、Spring MVC接口

​ Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。下面对各个组件的功能说明如下

3.1 DispatcherServlet

​ DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。

​ DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

3.2 HandlerMapping

​ HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)。

3.3 HandlerAdapter

​ HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler),按照特定规则执行相关的处理器(Handler)。

​ servlet阶段为了解决一个请求对应一个servlet两种方法

​ 1.方法传递参数的时候多传递一个,找到对应的servlet方法

​ 2.封装一个baseServlet,用来继承httpservlet,通过反射的方式,在运行中调用。之后定义的servlet都去继承baseServlet

3.4 Handler

​ Handler 是处理器,常称Controller,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

3.5 View Resolver

​ View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

3.6 View

​ View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Thymeleaf等)。

四、@Controller、@RequestMapping

​ Spring 2.5 版本新增了 Spring MVC 注解功能,用于替换传统的基于 XML 的 Spring MVC 配置。

1、使用注解的优点

  • 在基于注解的控制器类中可以编写多个处理方法,进而可以处理多个请求(动作),这就允许将相关的操作编写在同一个控制器类中,从而减少控制器类的数量,方便以后维护。
  • 基于注解的控制器不需要在配置文件中部署映射,仅需要使用 @RequestMapping 注解一个方法进行请求处理即可。

2、@Controller

@Controller 注解用于声明某类的实例是一个控制器。

2.1 使用步骤

  1. 在Controller类中加上注解
package com.hqyj.cl.controller;

import org.springframework.stereotype.Controller;

@Controller
public class AnnotationTest {
    // 方法代码
}
  1. springmvc-servlet.xml配置包扫描

Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 <context:component-scan/> 元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。

<!-- 使用扫描机制扫描控制器类,控制器类都在com.hqyj.controller包及其子包下 -->
<context:component-scan base-package="com.hqyj.controller" />

3、@RequestMapping

  • 一个控制器内有多个处理请求的方法,如 UserController 里通常有增加用户、修改用户信息、删除指定用户、根据条件获取用户列表等。每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。
  • @RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。

3.1 常用属性

3.1.1 value属性

​ value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。

3.1.1.1 示例代码
@RequestMapping(value="user")
@RequestMapping("user")

3.1.2 path属性

​ 同value属性一样用法

3.1.3 name属性

​ name属性相当于方法的注释,使方法更易理解。

3.1.3.1 示例代码
@RequestMapping(value = "user",name = "用户操作")

3.1.4 method属性

​ method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。

3.1.4.1 示例代码
// 只支持 GET 请求
@RequestMapping(value = "selectAllUser",method = RequestMethod.GET)
// 指定支持多个 HTTP 请求,该方法同时支持 GET 和 POST 请求。
@RequestMapping(value = "checkUser",method = {RequestMethod.GET,RequestMethod.POST})

3.1.5 params属性

​ params 属性用于指定请求中规定的参数。

3.1.5.1 示例代码
// 请求中必须包含 type 参数时才能执行该请求
@RequestMapping(value = "selectAllUser",params = "type")
public String toUser() {
    return "userList";
}
// 请求中必须包含 type 参数,且 type 参数值为 1 时才能够执行该请求
@RequestMapping(value = "selectAllUser",params = "type=1")
public String toUser() {
    return "userList";
}

3.2 通过请求URL进行映射

3.2.1 方法级别注解

@Controller
public class UserController {
    @RequestMapping(value = "/user/login")
    public String login() {
        return "login";
    }
    @RequestMapping(value = "/user/register")
    public String register() {
        return "register";
    }
}

3.2.2 类级别注解

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
    @RequestMapping("/register")
    public String register() {
        return "register";
    }
}

3.3 通过请求参数或方法进行映射

​ @RequestMapping 的 value 表示请求的 URL;method 表示请求方法,此处设置为 GET 请求,若是 POST 请求,则无法进入 selectUserByUsername 这个处理方法中。params 表示请求参数,此处参数名为 username。

@Controller
public class UserController {
    @RequestMapping(value = "/user/userList" method=RequestMethod.GET, params="username")
    public String selectUserByUsername(String username) {
        return "userList";
        
    @RequestMapping(value = "/user/userList2" method=RequestMethod.GET)
    public String selectUserByUsername2(@RequestParam("username") String username) {
        return "userList";
}

3.4 参数类型

​ org.springframework.ui.Model 类型,该类型是一个包含 Map 的 Spring MVC类型。在每次调用请求处理方法时 Spring MVC 都将创建 org.springframework.ui.Model 对象。

3.4.1 Model类型

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/register")
    public String register(Model model) {
        /*在视图中可以使用EL表达式${success}取出model中的值*/
        model.addAttribute("success", "注册成功");
        return "register";
    }
}

3.4.1 案例

  1. controller
@Controller
@RequestMapping(value = "user",name = "用户操作")
public class UserController {
    // 登录
    @RequestMapping("/login")
    public String login(Model model){
        /*在视图中可以使用EL表达式${info}取出model中的值*/
        model.addAttribute("info", "登录成功");
        return "index";
    }
}
  1. index页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
    ${info},系统首页!
</body>
</html>

五、Spring MVC传递参数

​ Spring MVC Controller 接收请求参数的方式有很多种,有的适合 get 请求方式,有的适合 post 请求方式,有的两者都适合。主要有以下几种方式:

  • 通过实体 Bean 接收请求参数
  • 通过处理方法的形参接收请求参数
  • 通过 HttpServletRequest 接收请求参数
  • 通过 @PathVariable 接收 URL 中的请求参数
  • 通过 @RequestParam 接收请求参数

1、通过实体Bean接收请求参数

实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。示例代码如下

@RequestMapping("/login")
public String login(User user, Model model) {
    if ("admin".equals(user.getUsername())
            && "112112".equals(user.getPassword())) {
        model.addAttribute("message", "登录成功");
        return "index"; // 登录成功,跳转到 index.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

url get请求后台方法拼接参数

2、通过处理方法的形参接收请求参数

​ 通过处理方法的形参接收请求参数就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。该接收参数方式适用于 get 和 post 提交请求方式。示例代码如下

@RequestMapping("/login")
public String login(String username, String password, Model model) {
    if ("admin".equals(username)
            && "112112".equals(password)) {
        model.addAttribute("message", "登录成功");
        return "index"; // 登录成功,跳转到 index.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}

3、通过HttpServletRequest接收请求参数

​ 同servlet阶段获取方式,controller层方法参数中包含HttpServletRequest参数,可以获取请求中的参数。

4、通过@PathVariable接收URL中的请求参数

@RequestMapping(value = "/login/{username}/{password}")
public String login(@PathVariable("username") String username, @PathVariable("password") String password) {
    System.out.println(username);
    System.out.println(password);
    return "index";
}

​ 在访问http://localhost:8080/user/login/admin/123456路径时,上述代码会自动将 URL 中的模板变量 {username} 和 {password} 绑定到通过 @PathVariable 注解的同名参数上,即 username=admin、password=123456。

5、通过@RequestParam接收请求参数

在方法参处使用 @RequestParam 注解指定其对应的请求参数。@RequestParam 有以下三个参数:

  • value:参数名;
  • required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错;
  • defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

通过 @RequestParam 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下

  1. UserController
@RequestMapping("/goRequestParam")
private String goRequestParam(){
    return "requestParam";
}
@RequestMapping("/requestParam")
public String requestParam(
    @RequestParam(value = "username",required = true) String username, 
    @RequestParam(value = "password",required = false,defaultValue = "111") String password){
    System.out.println(username + " " + password);
    return "index";
}
  1. requestParam.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>RequestParam</title>
</head>
<body>
<form action="/user/requestParam" method="get">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="submit">
</form>
</body>
</html>
  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
<a href="../user/login">登录</a><br>
<a href="../user/register">注册</a><br>
<a href="../user/goRequestParam">RequestParam</a><br>
</body>
</html>

​ 该方式与“通过处理方法的形参接收请求参数”的区别如下:当请求参数与接收参数名不一致时,“通过处理方法的形参接收请求参数”不会报 400 错误,而“通过 @RequestParam 接收请求参数”会报 400 错误。

六、Spring MVC重定向和转发

  • Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。
  • 在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。
  • 重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域;
  • 转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。
  • 转发是服务器行为,重定向是客户端行为。

1、转发过程

  • 客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。
  • 在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

2、重定向过程

  • 客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。
  • 在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

3、案例

@RequestMapping("/login")
public String login() {
    //转发到一个请求方法(同一个控制器类可以省略/index/)
    return "forward:/user/isLogin";
}
@RequestMapping("/isLogin")
public String isLogin() {
    //重定向到一个请求方法
    return "redirect:/user/isRegister";
}
@RequestMapping("/isRegister")
public String isRegister() {
    //转发到一个视图
    return "register";
}

4、注意事项

​ 在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如

return "forward:/html/my.html";

则需要在 springmvc-servlet.xml 配置

<mvc:resources location="/html/" mapping="/html/**" />

七、@Autowired和@Service

​ 将依赖注入到 Spring MVC 控制器时需要用到 @Autowired 和 @Service 注解。

1、介绍

​ @Autowired 注解属于 org.springframework.beans.factory. annotation 包,可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作;

​ @Service 注解属于 org.springframework.stereotype 包,会将标注类自动注册到 Spring 容器中。

2、案例

  1. springmvc-servlet.xml

在配置文件springmvc-servlet.xml中需要添加 <component-scan/> 元素来扫描依赖基本包

<context:component-scan base-package="com.hqyj.cl.service"/>
  1. 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private String pwd;
}
  1. 创建UserService接口
public interface UserService {
    boolean login(User user);
    boolean register(User user);
}
  1. 创建 UserServiceImpl 类,实现 UserService 接口
@Service
public class UserServiceImpl implements UserService {
    @Override
    public boolean login(User user) {
        if ("admin".equals(user.getName()) && "111".equals(user.getPwd())) {
            return true;
        }
        return false;
    }
    @Override
    public boolean register(User user) {
        if ("admin".equals(user.getName()) && "111".equals(user.getPwd())) {
            return true;
        }
        return false;
    }
}

注意事项:加@Service注解的作用是使类能被 Spring 扫描到

  1. 创建 UserController 类
@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public String getLogin(Model model) {
        User user = new User();
        user.setName("admin");
        user.setPwd("111");
        userService.login(user);
        model.addAttribute("user", user);
        return "login";
    }
    @RequestMapping("/register")
    public String getRegister(Model model) {
        User user = new User();
        user.setName("admin");
        user.setPwd("111");
        userService.login(user);
        model.addAttribute("user", user);
        return "register";
    }
}

注意事项:在 UserService 上添加 @Autowired 注解会使 UserService 的一个实例被注入到 UserController 实例中

  1. springmvc-servlet.xml
<!-- 配置包扫描路径,作用自动检测标注Bean -->
<context:component-scan base-package="com.hqyj.cl" />
<mvc:annotation-driven />
<bean id="viewResolver"
      class="org.springframework.web.servlet.view.InternalResourceView">
    <!--前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/" />
    <!--后缀 -->
    <property name="suffix" value=".jsp" />
</bean>
  1. web.xml
<!-- 部署 DispatcherServlet -->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 表示容器再启动时立即加载servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!-- 处理所有URL -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head lang="en">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
<body>
<h2>Hello World!</h2>
未注册的用户,请
<a href="${pageContext.request.contextPath}/user/register"> 注册</a>!
<br /> 已注册的用户,去
<a href="${pageContext.request.contextPath}/user/login"> 登录</a>!
</body>
</html>
  1. login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
    登录页面! 欢迎 ${user.name} 登录
</body>
</html>
  1. register.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
<body>
    注册页面!
    注册账号成功,用户名为: ${user.name }
</body>
</html>

八、@ModelAttribute(了解)

​ 在 Spring MVC 中非常重要的注解 @ModelAttribute,用来将请求参数绑定到 Model 对象。模型对象要先于 controller 方法之前创建,所以被 @ModelAttribute 注解的方法会在 Controller 每个方法执行之前都执行。尽量少用

1、用途

  • 应用在方法上
  • 应用在方法的参数上
  • 应用在方法上,并且方法也使用了 @RequestMapping

1.1 应用在方法上

1.1.1 应用在无返回值的方法

  1. ModelAttributeController
@Controller
@RequestMapping("/model")
public class ModelAttributeController {
    // 方法无返回值
    @ModelAttribute
    public void myModel(@RequestParam(required = false) String name, Model model) {
        model.addAttribute("name", name);
    }
    @RequestMapping("/index")
    public String model() {
        return "index";
    }
    
    // 上述代码合二为一
    /*    @RequestMapping("/index")
    public String model(@RequestParam(required = false) String name, Model model) {
        model.addAttribute("name", name);
        return "index";
    }*/
}
  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
    ${name}
</body>
</html>

访问:

​ 使用http://localhost:8080/model/index?name=%E5%BC%A0%E4%B8%89访问,页面会显示出name对应的值。提示:name后面一串字符串不是乱码,是字符串转换问题,这里的意思就是张三。

1.1.2 应用在有返回值的方法

​ 使用这种方式,返回值对象 name 会被默认放到隐含的 Model 中,在 Model 中 key 为返回值类型首字母小写,value 为返回的值。等同于 model.addAttribute("string", name);

  1. ModelAttributeController
@Controller
@RequestMapping("/model")
public class ModelAttributeController {
     // 方法有返回值
    @ModelAttribute()
    public String myModel(@RequestParam(required = false) String name) {
        return name;
    }
    @RequestMapping("/index")
    public String model() {
        return "index";
    }
}
  1. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
    ${string}
</body>
</html>

​ 正常情况下,程序中尽量不要出现 key 为 string、int、float 等这样数据类型的返回值。使用 @ModelAttribute 注解 value 属性可以自定义 key,示例代码

@ModelAttribute("name")
public String myModel(@RequestParam(required = false) String name) {
    return name;
}

等价于

model.addAttribute("name", name);

1.2 应用在方法的参数上

​ @ModelAttribute 注解在方法的参数上,调用方法时,模型的值会被注入。这在实际使用时非常简单,常用于将表单属性映射到**模型对象。**

  1. ModelAttributeController
@Controller
@RequestMapping("/model")
public class ModelAttributeController {
    @RequestMapping("/register")
    public String register(@ModelAttribute("user") User user) {
        if ("admin".equals(user.getName()) && "111".equals(user.getPwd())) {
            return "login";
        } else {
            return "register";
        }
    }
}
  1. login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
<body>
登录页面! 欢迎 ${user.name} 登录
</body>
</html>
  1. index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
<body>
注册页面!
注册账号成功,用户名为: ${user.name }
</body>
</html>

1.2.1 说明

@ModelAttribute(“user”) User user语句的功能有两个:

  • 将请求参数的输入封装到 user 对象中
  • 创建 User 实例

​ 以“user”为键值存储在 Model 对象中,和“model.addAttribute(“user”,user)”语句的功能一样。如果没有指定键值,即“@ModelAttribute User user”,那么在创建 User 实例时以“user”为键值存储在 Model 对象中,和“model.addAtttribute(“user”, user)”语句的功能一样。

1.3 ModelAttribute+RequestMapping

  1. ModelAttributeController
@Controller
public class ModelAttributeController {
    // @ModelAttribute和@RequestMapping同时放在方法上 方法直接返回页面名称 会把类路径拼接到url中
    @RequestMapping("/index")
    @ModelAttribute("name")
    public String model(@RequestParam(required = false) String name) {
        return name;
    }
}
  1. index.jsp

    请求路径为http://localhost:8080/j230101SpringMVC_war_exploded/index?name=admin name参数有值

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
    ${name}
</body>
</html

1.3.1 说明

@ModelAttribute 和 @RequestMapping 注解同时应用在方法上时,有以下作用:

  1. 方法的返回值会存入到 Model 对象中,key 为 ModelAttribute 的 value 属性值;
  2. 方法的返回值不再是方法的访问路径,访问路径会变为 @RequestMapping 的 value 值,例如:@RequestMapping(value = “/index”) 跳转的页面是 index.jsp 页面;
  3. 使用这种方式时,不要加类路径,否则可能导致路径出错问题。

1.4 Model和ModelAndView的区别

  • Model:每次请求中都存在的默认参数,利用其 addAttribute() 方法即可将服务器的值传递到客户端页面中。
  • ModelAndView:包含 model 和 view 两部分,使用时需要自己实例化,利用 Model 来传值,也可以设置 view 的名称。

1.5 练习

@ModelAttribute 注解的方法会在每次调用该控制器类的请求处理方法前被调用。这种特性可以用来控制登录权限。

  1. BaseController
public class BaseController {
    @ModelAttribute
    public void isLogin(HttpSession session) throws Exception {
        if (session.getAttribute("user") == null) {
            throw new Exception("没有权限");
        }
    }
}
  1. UserController
@RequestMapping("/user")
public class UserController extends BaseController {
    @RequestMapping("/selectAllUser")
    public String selectAllUser() {
        return "userList";
    }
}

九、Spring MVC JSON数据交互

​ Spring MVC 在数据绑定的过程中需要对传递数据的格式和类型进行转换,它既可以转换 String 等类型的数据,也可以转换 JSON 等其他类型的数据。我们采用阿里巴巴的FastJson技术实现 Spring MVC 处理 JSON 格式数据。页面要引入jquery。注意资源过滤(第一种方式)。spring版本4.0.2 (4.3.13其他包版本过低,只是4.0不支持requestmaping的name属性)

1、案例

  1. pom.xml文件中导入依赖(之前已导入)
<!-- 格式化对象,方便输出日志 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.1.41</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.6</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.6</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
</dependency>
  1. JSONController 方法可以返回值设置为Map集合
@Controller
@RequestMapping("/json")
public class JSONController {
    @RequestMapping("/index")
    public String index() {
        return "index";
    }
    @RequestMapping("/testJson")
    @ResponseBody
    public User testJson(User user) {
        // 打印接收的 JSON数据
        System.out.println("username=" + user.getUsername() + ",password=" + user.getPassword());
        // 返回JSON格式的响应
        return user;
    }
}
  1. index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>测试JSON交互</title>
    <script type="text/javaScript" src="../static/js/jquery-2.1.1.min.js"></script>
</head>
<body>
<form>
    用户名:<input type="text" name="username" id="username"/>
    <br>
    密码:<input type="password" name="password" id="password"/>
    <br>
    <input type="button" value="login" οnclick="login()"/>
</form>
</body>
<script type="text/javaScript">
    function login() {
        $.ajax({
            //请求路径
            url: "../json/testJson",
            //请求类型
            type: "post",
            //定义回调响应的数据格式为JSON字符串,该属性可以省略
            dataType: "json",
            //data表示发送的数据
            data: {
                username: $("#username").val(),
                password: $("#password").val(),
            },
            contentType: "application/x-www-form-urlencoded", //设置请求参数的格式
            processData: true, //设置ajax内部是否自动处理请求参数,默认为true,可省略,如果data给的是个js对象,要么不设置,要么设为true
            //成功响应的结果
            success: function (data) {
                if (data != null) {
                    alert("输入的用户名:" + data.username + ",密码:" + data.password);
                }
            },error:function () {
                alert("服务器错误");
            }
        });
    }
</script>
</html>

2、contentType

​ 常见的contentType类型有以下三种:

// 普通表单提交的数据的格式为application/x-www-form-urlencoded
application/x-www-form-urlencoded
// 发送以ContentType为application/json格式的请求参数,需要把data的内容转为json格式,使用JSON.stringify(param)
application/json
// 上传文件时,请求消息将以multipart/form-data格式封装请求参数
multipart/form-data

// 发送以ContentType为application/json格式的请求参数,需要把data的内容转为json格式,使用JSON.stringify(param)
application/json

ajax中可以定义变量

let user = JSON.stringify({

​ username:$(“username”).val(),

​ password:$(“password”).val(),

});

后面的data:可以修改为:

data:user; 传递到后端需要在后端方法参数中使用@requestBody注解获取 public void login(@requestBodyUser user)

十、Spring MVC拦截器(Interceptor)

​ Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

1、定义拦截器

​ 定义一个拦截器,我们采用实现 HandlerInterceptor 接口来实现

1.1 案例

  1. 实现HandlerInterceptor 接口
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 获取请求的URL
        String url = request.getRequestURI();
        // login.jsp或登录请求放行,不拦截
        if (url.indexOf("/goLogin") >= 0 || url.indexOf("/login") >= 0) {
            return true;
        }
        // 获取 session
        HttpSession session = request.getSession();
        Object obj = session.getAttribute("user");
        if (obj != null)
            return true;
        // 没有登录且不是登录页面,转发到登录页面,并给出提示错误信息
        request.setAttribute("msg", "还没登录,请先登录!");
        request.getRequestDispatcher("login").forward(request, response);
        return false;
    }
    @Override
    public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
    }
    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception {
    }
}

拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,解释如下

  • preHandle( ):该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
  • postHandle( ):该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion( ):该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
  1. springmvc-servlet.xml配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <bean class="com.hqyj.cl.utils.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>
  1. UserController
@Controller
@RequestMapping("/user")
public class UserController {
    // 登录页面初始化
    @RequestMapping("/goLogin")
    public String initLogin() {
        return "login";
    }

    // 处理登录功能
    @RequestMapping("/login")
    public String login(User user, HttpSession session) {
        if ("admin".equals(user.getUsername()) && "111".equals(user.getPassword())) {
            // 登录成功,将用户信息保存到session对象中
            session.setAttribute("user", user);
            // 重定向到主页面的跳转方法
            return "redirect:main";
        }
        return "login";
    }

    // 跳转到主页面
    @RequestMapping("/main")
    public String goMain() {
        return "main";
    }
    
    // 退出登录
    @RequestMapping("/logout")
    public String logout(HttpSession session) {
        // 清除 session
        session.invalidate();
        return "login";
    }
}
  1. login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
<body>
${ msg }
<form action="../user/login" method="post">
    用户名:<input type="text" name="username" /><br>
    密码:<input type="password" name="password" /><br>
    <input type="submit" value="登录" />
</form>
</body>
</html>
  1. main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
欢迎 ${ sessionScope.get("user").username },登录!<br />
<a href="../user/logout">退出</a>
</body>
</html>

十一、文件上传和下载

1、文件上传

  1. pom.xml 文件中添加以下依赖
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.2.2</version>
</dependency>
  1. springmvc-servlet.xml
<!-- 配置MultipartResolver,用于上传文件,使用spring的CommonsMultipartResolver -->
<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="5000000" />
    <property name="defaultEncoding" value="UTF-8" />
</bean>
  • defaultEncoding:请求的编码格式,默认为 ISO-8859-1,此处设置为 UTF-8(注:defaultEncoding 必须和 JSP 中的 pageEncoding 一致,以便正确读取表单的内容)。
  • maxUploadSize:上传文件大小上限,单位为字节。
  1. fileUpload.jsp 表单提交
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/file/upload"
        method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="file"><br> 
        <input type="submit" value="提交">	
    </form>
</body>
</html>
  1. FileController
@Controller
@RequestMapping("/file")
public class FileController {
    @RequestMapping("/upload")
    @ResponseBody
    public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
        // 获取上传文件名
        String originalFileName = file.getOriginalFilename();
        // 得到当前的classpath的绝对路径的URI表示法
        String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        int index = rootPath.indexOf("target");
        String path = rootPath.substring(1,index) + "src/main/webapp/static/images/book";
        // 新文件
        File newFile = new File(path,originalFileName);
        // 判断目标文件所在目录是否存在
        if( !newFile.getParentFile().exists()) {
            // 如果目标文件所在的目录不存在,则创建父目录
            newFile.getParentFile().mkdirs();
        }
        // 将内存中的数据写入磁盘
        file.transferTo(newFile);
        return  "success";
    }
}

1.1 扩展

使用ajax实现文件上传

    <form id="uploadForm">
        <input type="file" name="file">
        <br>
        <input type="button" value="button" οnclick="upload()">
    </form>
    <script>
        function upload() {
            var formData = new FormData($("#uploadForm")[0]);
            // 发送ajax请求
            $.ajax({
                url:'/file/upload',
                type:"post",
                data:formData,
                processData : false,  //必须false才会避开jQuery对 formdata 的默认处理
                contentType : false,  //必须false才会自动加上正确的Content-Type
                success:function (data) {
                    console.log(data);
                },error:function () {
                    alert("服务器错误");
                }
            })
        }
    </script>

2、文件下载

  1. fileDown.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>文件下载</title>
</head>
<body>
<form action="/file/down" method="get">
    <input type="submit" value="下载">
</form>
</body>
</html>
  1. FileController
@RequestMapping("/down")
@ResponseBody
public String down(HttpServletResponse response) throws Exception{
    // 下载文件的路径
    String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
	// 截取到需要的文件存放路径
    int index = rootPath.indexOf("target");
    String path = rootPath.substring(1,index) + "src/main/webapp/static/images/book";
    // 下载文件的名字,假设为banner_1.jpg
    String fileName = "banner_1.jpg";
    //获取输入流
    InputStream is = new BufferedInputStream(new FileInputStream(new File(path,fileName)));
    //转码,免得文件名中文乱码
    String filename = URLEncoder.encode(fileName,"UTF-8");
    //设置文件下载头
    response.addHeader("Content-Disposition", "attachment;filename=" + filename);
    //1.设置文件ContentType类型,这样设置,会自动判断下载文件类型
    response.setContentType("multipart/form-data");
    BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    int len = 0;
    while((len = is.read()) != -1){
        out.write(len);
        out.flush();
    }
    out.close();
    return "success";
}

十二、SpringMVC资源过滤问题

**问题描述:**在web.xml文件中,配置了以下代码后,会把所有请求URL都拦截了,包括js,css等静态资源,这样导致了我们无法使用这些静态资源。

<!-- 部署 DispatcherServlet -->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 表示容器再启动时立即加载servlet -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!-- 处理所有URL -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

**解决办法之一:**在springmvc-servlet.xml文件中,添加以下代码

<!-- 默认的注解映射,必须加,不然没办法兼顾二者 -->
<mvc:annotation-driven />
<!--配置静态资源放行 如果web.xml中servlet-name定义为default,则default-servlet-name="default"可以不写-->
<mvc:default-servlet-handler default-servlet-name="default"/>

在web.xml文件中,添加以下代码

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <!-- 处理所有URL -->
    <url-pattern>/static/*</url-pattern>
</servlet-mapping>

**解决办法之二:**在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"
       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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!-- 包扫描-->
    <context:component-scan base-package="com.hqyj.cl.controller"/>
    <!-- 默认的注解映射,必须加,不然没办法兼顾二者 -->
    <mvc:annotation-driven />
    <mvc:resources mapping="/static/**" location="/static/"/>
	<!-- interceptors -->  
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!-- 不拦截的请求 -->
            <mvc:exclude-mapping path="/static/**"/>
            <bean class="com.hqyj.cl.utils.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    <!--配置视图解析器之InternalResourceViewResolver-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--指定viewclass 必须指定-->
        <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
        <!--前缀-->
        <property name="prefix" value="/jsp/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
  • location:指location指定的目录不要拦截,直接请求,这里指在根目录下的resources文件下的所有文件
  • mapping:指在resources文件下的所有文件(**代表所有文件)