SpringMVC 入门教程

SpringMVC 入门教程

1、参考资料

2020年IDEA版黑马Java就业班-进阶篇(Mybatis、Spring、SpringMVC、Maven、springboot和项目等等

项目地址:Oneby / springmvc-learn

2、Spring 集成 Web 环境

2.1、Web 环境搭建

1、搭建 Spring 框架基础环境

1、项目整体结构

image-20210306205417611

2、引入依赖

注意:Web 项目的打包方式一定要选择为【war】,否则会出错,通过 <packaging>war</packaging> 设置项目的打包方式为 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">
    <parent>
        <artifactId>springmvc-learn</artifactId>
        <groupId>com.oneby</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springmvc-getting-start</artifactId>
    <packaging>war</packaging>

    <dependencies>
        <!-- spring-beans -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!-- spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!-- spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!-- spring-expression -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!-- commons-logging -->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>

        <!-- jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!-- spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

3、Dao 层

UserDao 接口

/**
 * @ClassName UserDao
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:10
 * @Version 1.0
 */
public interface UserDao {

    public void save();

}

UserDaoImpl 实现类

/**
 * @ClassName UserDaoImpl
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:12
 * @Version 1.0
 */
public class UserDaoImpl implements UserDao {

    public void save() {
        System.out.println("save running....");
    }

}

4、Service 层

UserService 接口

/**
 * @ClassName UserService
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:11
 * @Version 1.0
 */
public interface UserService {

    public void save();

}

UserServiceImpl 实现类

/**
 * @ClassName UserServiceImpl
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:12
 * @Version 1.0
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void save() {
        userDao.save();
    }

}

5、Spring 配置文件

创建 userDao 对象和 userService 对象,并在 userService 中注入 userDao 对象

<?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">

    <!--配置 userDao-->
    <bean id="userDao" class="com.oneby.dao.impl.UserDaoImpl"></bean>

    <!--配置 userService-->
    <bean id="userService" class="com.oneby.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

</beans>

2、在 Spring 框架的基础上集成 Web 环境

1、创建 webapp 文件夹:让该工程成为一个 Web 工程

在【Project Structure】中,选中相应的 Module,我这里选择的是刚才创建的【springmvc-getting-start】工程(Module),点击【+】号,选择【Web】添加 Web 环境

image-20210306210312420

【Path】:配置 web.xml 的存放路径;【Web Resource Directory】:配置 webapp 文件夹存放的路径(webapp 这个文件夹老是要被 IDEA 放到 src/main 目录下,以后 webapp 文件夹就都放在 src/main 目录下吧~)

image-20210306220034960

设置完成后,会在项目指定位置生成 webapp 文件夹,里面包含 web.xml 配置文件

image-20210306210555085


2、创建 Artifact

在【Artifacts】页面中点击【+】号,选择【Web Application: Exploded】–>【From Modules】

image-20210306220345220

选择相应的 Module 添加其 Artifact,Web 项目对应的后缀为【:war exploded】,打包方式为 war 包

image-20210306220517327


3、创建 index.jsp 页面

在 webapp 目录下创建 index.jsp 页面,作为项目的首页

<%--
  Created by IntelliJ IDEA.
  User: Heygo
  Date: 2021/3/6
  Time: 16:14
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>Hello World!</h2>
</body>
</html>

4、创建 UserServlet 类

创建 UserServlet 类,继承自 HttpServlet 类,并重写其中的 doGet() 方法。在该方法中使用 new ClassPathXmlApplicationContext("applicationContext.xml") 语句创建 ApplicationContext 对象,并获取容器中的 bean 实例

/**
 * @ClassName UserServlet
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:17
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //创建 ApplicationContext 对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 从容器中获取 userService 实例
        UserService userService = context.getBean(UserService.class);

        // 调用 userService 对象中的方法
        userService.save();

    }

}

5、在 web.xml 中配置 UserServlet 的映射路径

在 web.xml 配置文件中创建 UserServlet 对象,并配置 UserServlet 的映射路径

<?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">

    <!-- 创建 UserServlet 对象 -->
    <servlet>
        <servlet-name>UserServlet</servlet-name>
        <servlet-class>com.oneby.web.UserServlet</servlet-class>
    </servlet>
    <!-- 配置 Servlet 映射路径 -->
    <servlet-mapping>
        <servlet-name>UserServlet</servlet-name>
        <url-pattern>/userServlet</url-pattern>
    </servlet-mapping>
    
</web-app>

6、集成 Web 环境后项目的整体结构

image-20210306211405620

3、启动项目进行测试

1、配置 Tomcat

在【Run/Debug Configuration】中,点击【+】号选择本地的 Tomcat 服务器

image-20210306214841675

在 【Server】页面中对 Tomcat 服务器参数进行配置

image-20210306214352517

在【Deployment】页面中,点击【+】号添加一个【Artifact】

image-20210306220746244

添加后将【Application Context】设置为 /,表示 web 项目的默认访问路径 /,这里的访问路径需要与上面的 URL 参数对应

image-20210306220816360


2、启动项目进行测试

访问首页 http://localhost:8080/ 的效果

image-20210306220949228

访问 http://localhost:8080/userServlet 后台输出 save running

image-20210306221126026

2.2、ContextLoaderListener

1、自己封装 ContextLoaderListener

上述方式存在的缺点

应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得 Bean 时都要编写 new ClasspathXmlApplicationContext(spring配置文件),这样的弊端是配置文件加载多次,应用上下文对象创建多次。

解决办法

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。


自己封装 ContextLoaderListener 在 Web 项目启动时加载 Spring IOC 容器

为了将字符串 "app" 与程序解耦,我们封装 WebApplicationContextUtils 工具类,专门负责 ApplicationContext 存取,将字符串 "app" 封死在 WebApplicationContextUtils 工具类

/**
 * @ClassName WebApplicationContextUtils
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 22:33
 * @Version 1.0
 */
public class WebApplicationContextUtils {

    // 将 ApplicationContext 对象设置到 ServletContext 域中
    public static void setWebApplicationContext(ServletContext servletContext, ApplicationContext context) {
        servletContext.setAttribute("app", context);
    }

    // 传入 ServletContext 对象,并从 ServletContext 域中取出 ApplicationContext 对象
    public static ApplicationContext getWebApplicationContext(ServletContext servletContext) {
        return (ApplicationContext) servletContext.getAttribute("app");
    }

}

创建 ContextLoaderListener 类,该类实现了 ServletContextListener 接口,重写其中的 contextInitialized() 方法,在 Web 环境启动的时候就创建 Spring IOC容器,并将其放入 ServletContext 域中

/**
 * @ClassName ContextLoaderListener
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 22:33
 * @Version 1.0
 */
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {

        // 获取 ServletContext 对象
        ServletContext servletContext = servletContextEvent.getServletContext();

        // 读取 web.xml 中的全局配置参数
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");

        // 创建 ApplicationContext 对象
        ApplicationContext context = new ClassPathXmlApplicationContext(contextConfigLocation);

        // 将 ApplicationContext 对象存储到 ServletContext 域中
        WebApplicationContextUtils.setWebApplicationContext(servletContext, context);

    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

在 web.xml 配置文件中使用 <listener> 标签创建 ContextLoaderListener 对象,在 Wb 环境启动时会调用 ContextLoaderListener#contextInitialized() 方法,并配置全局初始化参数(为了程序的解耦)

<?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">

    <!-- 创建 UserServlet 对象 -->
    <servlet>
        <servlet-name>UserServlet</servlet-name>
        <servlet-class>com.oneby.web.UserServlet</servlet-class>
    </servlet>
    <!-- 配置 UserServlet 的映射路径 -->
    <servlet-mapping>
        <servlet-name>UserServlet</servlet-name>
        <url-pattern>/userServlet</url-pattern>
    </servlet-mapping>

    <!-- 配置 ContextLoaderListener -->
    <listener>
        <listener-class>com.oneby.listener.ContextLoaderListener</listener-class>
    </listener>

    <!-- 全局初始化参数 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>

</web-app>

UserServlet 中便不需要再手动 new ClassPathXmlApplicationContext("applicationContext.xml"),只需调用 WebApplicationContextUtils.getWebApplicationContext(servletContext) 方法从 ServletContext 域中取出来使用即可

/**
 * @ClassName UserServlet
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:17
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获取 ServletContext 对象
        ServletContext servletContext = this.getServletContext();

        //创建 ApplicationContext 对象
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);

        // 从容器中获取 userService 实例
        UserService userService = context.getBean(UserService.class);

        // 调用 userService 对象中的方法
        userService.save();

    }

}

访问 http://localhost:8080/userServlet 后台输出 save running

image-20210306225526657

2、使用 SpringMVC 自带的 ContextLoaderListener

Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事

①在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)

②使用WebApplicationContextUtils获得应用上下文对象ApplicationContext


配置 SpringMVC ContextLoaderListener

引入 spring-web 的坐标

<!-- spring-web -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

在 web.xml 配置 ContextLoaderListener,并指定 Spring 配置文件路径

<!-- 配置 ContextLoaderListener -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- 全局参数 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

UserServlet 中通过 SpringMVC 提供的 WebApplicationContextUtils 工具类获取 ApplicationContext 对象

/**
 * @ClassName UserServlet
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 15:17
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 创建 ApplicationContext 对象
        // ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取 ServletContext 对象
        ServletContext servletContext = this.getServletContext();

        //创建 ApplicationContext 对象
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);

        // 从容器中获取 userService 实例
        UserService userService = context.getBean(UserService.class);

        // 调用 userService 对象中的方法
        userService.save();

    }

}

访问 http://localhost:8080/userServlet 后台输出 save running

image-20210306231747336

3、SpringMVC 入门案例

3.1、SpringMVC 概述

SpringMVC 入门概述

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。

SpringMVC 已经成为目前最主流的MVC框架之一,并且随着Spring3.0 的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持 RESTful 编程风格的请求。

3.2、SpringMVC 快速入门

SpringMVC 开发步骤

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

开发步骤

①导入SpringMVC相关坐标

②配置SpringMVC核心控制器DispathcerServlet

③创建Controller类和视图页面

④使用注解配置Controller类中业务方法的映射地址

⑤配置SpringMVC核心文件 spring-mvc.xml

⑥客户端发起请求测试

SpringMVC 快速入门案例

导入 spring-web 和 spring-webmvc 的坐标,导入 servlet 和 jsp 的坐标

<!-- spring-web -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

<!-- spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

<!-- javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>

<!-- javax.servlet.jsp-api -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.2.1</version>
    <scope>provided</scope>
</dependency>

创建 spring-mvc.xml 配置文件,开启对 com.oneby.controller 包下的组件扫描

<?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
                http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Controller 的组件扫描 -->
    <context:component-scan base-package="com.oneby.controller"/>

</beans>

在 web.xml 配置文件中创建 DispatcherServlet 对象,通过 <init-param> 标签指定 spring-mvc.xml 配置文件的路径,并配置 DispatcherServlet 的映射地址

<?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">

    <!-- 配置 ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 全局参数 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- 创建 DispatcherServlet -->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定 spring-mvc.xml 配置文件的路径 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <!-- 加载优先级 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- 配置 DispatcherServlet 的映射路径,/ 表示映射所有请求 -->
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

创建 QuickController 类,并在类上使用 @Controller 注解,表示这是一个控制器。使用 @RequestMapping("/quick") 注解配置方法的映射路径,控制器没有配置映射路径则表明该控制器的映射路径为 /。在方法的最后 return "jsp/success.jsp",SpringMVC 会使用默认的视图解析器进行解析,跳转到当前路径下的 jsp/success.jsp 页面

/**
 * @ClassName QuickController
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 23:44
 * @Version 1.0
 */
@Controller
public class QuickController {

    @RequestMapping("/quick")   // 方法映射路径为 /quick
    public String quickMethod() {
        System.out.println("quickMethod running.....");
        // 相当于 "forward:jsp/success.jsp",以相对路径进行跳转,跳转到 /jsp/success.jsp
        return "jsp/success.jsp";
    }

}

在 webapp/jsp 目录下创建 success.jsp 页面

<%--
  Created by IntelliJ IDEA.
  User: Heygo
  Date: 2021/3/6
  Time: 23:46
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2>Success!</h2>
</body>
</html>

访问 http://localhost:8080/quick 后输出 quickMethod running.....,并成功跳转到 success.jsp 页面

image-20210307101706283

SpringMVC 流程图示

客户端通过浏览器发起请求,请求到达 Tomcat 服务器后,Tomcat 会解析请求资源地址,并创建该请求对应的 reqresp 对象。我们在 Web 应用中使用的 reqresp 对象就是 Tomcat 服务器传过来的,SpringMVC 前端控制器会解析请求资源的地址,并将其映射至某一具体控制器,控制器执行后将数据封装在 resp 对象中返回,再经由 http 响应返回至客户端浏览器

image-20210307103212895

3.3、SpringMVC 组件解析

SpringMVC 处理请求的流程

① 用户发送请求至前端控制器DispatcherServlet

DispatcherServlet收到请求调用HandlerMapping处理器映射器。

③ 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet

DispatcherServlet调用HandlerAdapter处理器适配器。

HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

Controller执行完成返回ModelAndView

HandlerAdapterController执行结果ModelAndView返回给DispatcherServlet

DispatcherServletModelAndView传给ViewReslover视图解析器。

ViewReslover解析后返回具体View

DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

image-20210307104843563

SpringMVC 组件解析

1、前端控制器:DispatcherServlet

用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由

它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。

2、处理器映射器:HandlerMapping

HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的

映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3、处理器适配器:HandlerAdapter

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理

器进行执行。

4、处理器:Handler

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由

Handler 对具体的用户请求进行处理。

5、视图解析器:ViewResolver

ViewResolver 负责将处理结果生成 View 视图,ViewResolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

6、视图:View

SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面

3.4、SpringMVC 配置解析

@RequestMapping 注解解析

@RequestMapping

作用:用于建立请求 URL 和处理请求方法之间的对应关系


位置:因为在 public @interface RequestMapping 上标有 @Target({ElementType.METHOD, ElementType.TYPE}),因此 @RequestMapping 即可以用在类上,也可以用在方法上

① 类上,请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录

② 方法上,请求 URL 的第二级访问目录,与类上的使用 @ReqquestMapping 标注的一级目录一起组成访问虚拟路径


属性

value:用于指定请求的 URL。它和 path 属性的作用是一样的,该属性为默认属性。

method:用于指定请求的方式

params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 keyvalue 必须和配置的一模一样。例如:params = {"accountName"} 表示请求参数必须有 accountNameparams = {"moeny!100"} 表示请求参数中 money 不能是 100


@RequestMapping 源码

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

	/**
	 * Assign a name to this mapping.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used on both levels, a combined name is derived by concatenation
	 * with "#" as separator.
	 * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
	 * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
	 */
	String name() default "";

	/**
	 * The primary mapping expressed by this annotation.
	 * <p>This is an alias for {@link #path}. For example
	 * {@code @RequestMapping("/foo")} is equivalent to
	 * {@code @RequestMapping(path="/foo")}.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this primary mapping, narrowing it for a specific handler method.
	 */
	@AliasFor("path")
	String[] value() default {};

	/**
	 * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
	 * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
	 * At the method level, relative paths (e.g. "edit.do") are supported within
	 * the primary mapping expressed at the type level. Path mapping URIs may
	 * contain placeholders (e.g. "/${connect}")
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this primary mapping, narrowing it for a specific handler method.
	 * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
	 * @since 4.2
	 */
	@AliasFor("value")
	String[] path() default {};

	/**
	 * The HTTP request methods to map to, narrowing the primary mapping:
	 * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this HTTP method restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 */
	RequestMethod[] method() default {};

	/**
	 * The parameters of the mapped request, narrowing the primary mapping.
	 * <p>Same format for any environment: a sequence of "myParam=myValue" style
	 * expressions, with a request only mapped if each such parameter is found
	 * to have the given value. Expressions can be negated by using the "!=" operator,
	 * as in "myParam!=myValue". "myParam" style expressions are also supported,
	 * with such parameters having to be present in the request (allowed to have
	 * any value). Finally, "!myParam" style expressions indicate that the
	 * specified parameter is <i>not</i> supposed to be present in the request.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this parameter restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 * <p>Parameter mappings are considered as restrictions that are enforced at
	 * the type level. The primary path mapping (i.e. the specified URI value)
	 * still has to uniquely identify the target handler, with parameter mappings
	 * simply expressing preconditions for invoking the handler.
	 */
	String[] params() default {};

	/**
	 * The headers of the mapped request, narrowing the primary mapping.
	 * <p>Same format for any environment: a sequence of "My-Header=myValue" style
	 * expressions, with a request only mapped if each such header is found
	 * to have the given value. Expressions can be negated by using the "!=" operator,
	 * as in "My-Header!=myValue". "My-Header" style expressions are also supported,
	 * with such headers having to be present in the request (allowed to have
	 * any value). Finally, "!My-Header" style expressions indicate that the
	 * specified header is <i>not</i> supposed to be present in the request.
	 * <p>Also supports media type wildcards (*), for headers such as Accept
	 * and Content-Type. For instance,
	 * <pre class="code">
	 * &#064;RequestMapping(value = "/something", headers = "content-type=text/*")
	 * </pre>
	 * will match requests with a Content-Type of "text/html", "text/plain", etc.
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings inherit
	 * this header restriction (i.e. the type-level restriction
	 * gets checked before the handler method is even resolved).
	 * @see org.springframework.http.MediaType
	 */
	String[] headers() default {};

	/**
	 * The consumable media types of the mapped request, narrowing the primary mapping.
	 * <p>The format is a single media type or a sequence of media types,
	 * with a request only mapped if the {@code Content-Type} matches one of these media types.
	 * Examples:
	 * <pre class="code">
	 * consumes = "text/plain"
	 * consumes = {"text/plain", "application/*"}
	 * </pre>
	 * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
	 * all requests with a {@code Content-Type} other than "text/plain".
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings override
	 * this consumes restriction.
	 * @see org.springframework.http.MediaType
	 * @see javax.servlet.http.HttpServletRequest#getContentType()
	 */
	String[] consumes() default {};

	/**
	 * The producible media types of the mapped request, narrowing the primary mapping.
	 * <p>The format is a single media type or a sequence of media types,
	 * with a request only mapped if the {@code Accept} matches one of these media types.
	 * Examples:
	 * <pre class="code">
	 * produces = "text/plain"
	 * produces = {"text/plain", "application/*"}
	 * produces = "application/json; charset=UTF-8"
	 * </pre>
	 * <p>It affects the actual content type written, for example to produce a JSON response
	 * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} should be used.
	 * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
	 * all requests with a {@code Accept} other than "text/plain".
	 * <p><b>Supported at the type level as well as at the method level!</b>
	 * When used at the type level, all method-level mappings override
	 * this produces restriction.
	 * @see org.springframework.http.MediaType
	 */
	String[] produces() default {};

}

@RequestMapping 举栗说明

UserController 控制器的映射路径为 /userbuy() 方法的映射路径为 /buy,那么执行 buy 操作请求地址为 http://localhost:8080/user/buy,并且提交请求的方式必须为 POST 请求,并且还必须带一个名为 orderId 的参数

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping(value = "/buy", method = RequestMethod.POST, params = {"orderId"})
    public String buy(int orderId) {
        System.out.println("buy something.....");
        return "jsp/success.jsp";
    }

}

SpringMVC XML 配置

xmlns:mvc="http://www.springframework.org/schema/mvc" 这一坨被称为【命名空间】,http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 这一坨被称为【约束地址】

<?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">

SpringMVC 视图解析

SpringMVC 有默认组件配置,默认组件都是 DispatcherServlet.properties 配置文件中配置的,该配置文件在 spring-webmvc.jar 包中,具体地址为 org/springframework/web/servlet/DispatcherServlet.properties ,该文件中配置了默认的视图解析器 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

DispatcherServlet.properties 配置文件的内容如下:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

我们点开 InternalResourceViewResolver 的源码,发现其继承自 UrlBasedViewResolver

public class InternalResourceViewResolver extends UrlBasedViewResolver {

我们点开 UrlBasedViewResolver 的源码,发现了四个重要字段,如下:

REDIRECT_URL_PREFIX = "redirect:" 常量:重定向 URL 前缀;

FORWARD_URL_PREFIX = "forward:" 常量:转发 URL 前缀;

prefix 字段:SpringMVC 构建 URL 时添加的前缀;

suffix 字段:SpringMVC 构建 URL 时添加的后缀;

注意prefix 字段和 suffix 字段都提供了 setter 方法,这说明我们可以通过配置文件定义自己想要的视图解析器

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {

   /**
    * Prefix for special view names that specify a redirect URL (usually
    * to a controller after a form has been submitted and processed).
    * Such view names will not be resolved in the configured default
    * way but rather be treated as special shortcut.
    */
   public static final String REDIRECT_URL_PREFIX = "redirect:";

   /**
    * Prefix for special view names that specify a forward URL (usually
    * to a controller after a form has been submitted and processed).
    * Such view names will not be resolved in the configured default
    * way but rather be treated as special shortcut.
    */
   public static final String FORWARD_URL_PREFIX = "forward:";


   @Nullable
   private Class<?> viewClass;

   private String prefix = "";

   private String suffix = "";
    
    // ...
    
    /**
	 * Set the prefix that gets prepended to view names when building a URL.
	 */
	public void setPrefix(@Nullable String prefix) {
		this.prefix = (prefix != null ? prefix : "");
	}

	/**
	 * Return the prefix that gets prepended to view names when building a URL.
	 */
	protected String getPrefix() {
		return this.prefix;
	}

	/**
	 * Set the suffix that gets appended to view names when building a URL.
	 */
	public void setSuffix(@Nullable String suffix) {
		this.suffix = (suffix != null ? suffix : "");
	}

	/**
	 * Return the suffix that gets appended to view names when building a URL.
	 */
	protected String getSuffix() {
		return this.suffix;
	}

自定义视图解析器

我们可以通过属性注入的方式修改视图的的前后缀

<!--配置内部资源视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

配置视图解析器的前后缀之后,在跳转时 SpringMVC 会自动为我们拼接 URL 的前后缀

/**
 * @ClassName QuickController
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/6 23:44
 * @Version 1.0
 */
@Controller
@RequestMapping()
public class QuickController {

    @RequestMapping("/quick")   // 方法映射路径为 /quick
    public String quickMethod() {
        System.out.println("quickMethod running.....");
        // 相当于 "forward:jsp/success.jsp",以相对路径进行跳转,跳转到 /jsp/success.jsp
        return "success";
    }

}

访问 http://localhost:8080/quick 后输出 quickMethod running.....,并成功跳转到 success.jsp 页面

image-20210307112643363

4、SpringMVC 的请求和响应

4.1、SpringMVC 的数据响应

SpringMVC的数据响应-数据响应方式

1、页面跳转

① 直接返回字符串

② 通过ModelAndView对象返回


2、回写数据

① 直接返回字符串

② 返回对象或集合

4.1.1、页面跳转

1、页面跳转-返回字符串形式

直接返回字符串:此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转

image-20210308133956791


之前我们配置过自定义视图解析器,前缀名为 jsp,后缀名为 .jsp。因此当我们 return "success" 时,SpringMVC 会为我们拼上前后缀:jsp/success.jsp,然后再进行转发前缀的拼接:forward:jsp/success.jsp

@RequestMapping("/quick")
public String quickMethod() {
    // return "success"; // 自定义视图解析器之后可以这样写
    return "forward:jsp/success.jsp"; // 当然也可以这样写啦
}

2、页面跳转-返回ModelAndView形式

① 在Controller中方法返回ModelAndView对象,并且设置视图名称

@RequestMapping("/quick1")
public ModelAndView quickMethod1() {
    /*
        Model:模型 作用封装数据
        View:视图 作用展示数据
     */
    ModelAndView modelAndView = new ModelAndView();
    // 设置模型数据(向 request 域中存储数据)
    modelAndView.addObject("username", "Oneby");
    // 设置视图名称(视图解析器会为我们拼接前后缀)
    modelAndView.setViewName("success");
    // 返回 modelAndView 对象
    return modelAndView;
}

在 jsp 页面中使用 EL 表达式将模型数据取出

<h2>${username} Success!</h2>

② 在Controller中方法形参上直接声明ModelAndView,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面

@RequestMapping("/quick2")
public ModelAndView quickMethod2(ModelAndView modelAndView) {
    // 设置模型数据(向 request 域中存储数据)
    modelAndView.addObject("username", "Oneby");
    // 设置视图名称(视图解析器会为我们拼接前后缀)
    modelAndView.setViewName("success");
    // 返回 modelAndView 对象
    return modelAndView;
}

3、页面跳转-向request域中存数据

通过SpringMVC框架注入的request对象setAttribute()方法设置

@RequestMapping("/quick3")
public String quickMethod3(HttpServletRequest request) {
    // 向 request 域中存储数据
    request.setAttribute("username", "Oneby");
    // 转发到 success 页面
    return "success";
}
4.1.2、回写数据

1、回写数据-直接返回字符串

Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print("Hello World") 即可,那么在Controller中想直接回写字符串该怎样呢?


① 通过SpringMVC框架注入的response对象,使用response.getWriter().print("Hello World") 回写数据,此时不需要视图跳转,业务方法返回值为void

@RequestMapping("/quick4")
public void quickMethod4(HttpServletResponse response) throws IOException {
    response.getWriter().print("Hello World");
}

② 将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回。@ResponseBody注解的作用是将Controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据

@RequestMapping("/quick5")
@ResponseBody
public String quickMethod5() {
    return "Hello World";
}

2、回写数据-直接回写json格式字符串

① 手动拼接 json 字符串

@RequestMapping("/quick6")
@ResponseBody
public String quickMethod6() {
    return "{\"name\":\"Oneby\",\"age\":21}";
}

② 上述方式手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用web阶段学习过的json转换工具jackson进行转换,导入jackson坐标。

<!-- jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>

<!-- jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>

<!-- jackson-annotations -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>

定义 User 实体类

public class User {

    private String username;
    private int age;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

通过jackson转换json格式字符串,回写字符串。

@RequestMapping("/quick7")
@ResponseBody
public String quickMethod7() throws JsonProcessingException {
    // 创建 User 对象
    User user = new User();
    user.setUsername("Oneby");
    user.setAge(21);
    // 利用 ObjectMapper 将对象转为 json 字符串
    ObjectMapper objectMapper = new ObjectMapper();
    String resStr = objectMapper.writeValueAsString(user);
    // 将 json 字符串返回
    return resStr;
}

3、回写数据-返回对象或集合

通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中配置自定义消息转换器

<!-- 配置自定义消息转换器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

在 Controller 方法中返回具体对象或者集合,MappingJackson2HttpMessageConverter 就会帮我们将对象转换为 json 字符串写回客户端

@RequestMapping("/quick8")
@ResponseBody
public User quickMethod8() {
    // 创建 User 对象
    User user = new User();
    user.setUsername("Oneby");
    user.setAge(21);
    // 返回 User 对象
    return user;
}

更加简洁的配置:<mvc:annotation-driven/>

在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动 <mvc:annotation-driven/> 代替上述配置

在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。使用<mvc:annotation-driven />自动加载 RequestMappingHandlerMapping(处理映射器)和 RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在 spring-mvc.xml 配置文件中使用 <mvc:annotation-driven /> 替代注解处理器和适配器的配置。同时使用<mvc:annotation-driven />默认底层就会集成 jackson 进行对象或集合的 json 格式字符串的转换

<mvc:annotation-driven/>

4.2、SpringMVC 的请求

4.2.1、获取请求参数

获得请求参数-请求参数类型

客户端请求参数的格式是:name=value & name=value……,服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数

  1. 基本类型参数

  2. POJO类型参数

  3. 数组类型参数

  4. 集合类型参数

1、基本类型参数

Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。并且能自动做类型转换;自动的类型转换是指从String向其他类型的转换

@RequestMapping("/quick9")
@ResponseBody
public void quickMethod9(String username, int age) {
    System.out.println(username);
    System.out.println(age);
}

客户端浏览器通过 URL 地址后的 QueryString 传参,比如:http://localhost:8080/quick9?username=Oneby&age=12

2、POJO类型参数

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

@RequestMapping("/quick10")
@ResponseBody
public void quickMethod10(User user) {
    System.out.println(user);
}

User 实体类的定义

public class User {

    private String username;
    private int age;
    
    // setter
    
    // getter

客户端浏览器通过 URL 地址后的 QueryString 传参,比如:http://localhost:8080/quick9?username=Oneby&age=12

3、数组类型参数

Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

@RequestMapping("/quick11")
@ResponseBody
public void quickMethod11(String[] strs) {
    System.out.println(Arrays.asList(strs));
}

客户端浏览器通过 URL 地址后的 QueryString 传参,比如:http://localhost:8080/quick11?strs=111&strs=222&strs=333

4、集合类型参数

比较 low 的方案

通过表单提交 User 对象的集合

<form action="${pageContext.request.contextPath}/quick12" method="post">
    <%-- 表明是第一个 User 对象的 username 和 age 属性 --%>
    <input type="text" name="userList[0].username"><br/>
    <input type="text" name="userList[0].age"><br/>
    <%-- 表明是第二个 User 对象的 username 和 age 属性 --%>
    <input type="text" name="userList[1].username"><br/>
    <input type="text" name="userList[1].age"><br/>
    <input type="submit" value="提交">
</form>

获得集合参数时,要将集合参数包装到一个POJO中才可以。我们将 User 对象的集合封装在 VO 类中

/**
 * @ClassName VO
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/8 18:38
 * @Version 1.0
 */
public class VO {
    
    private List<User> userList;

    public List<User> getUserList() {
        return userList;
    }

    public void setUserList(List<User> userList) {
        this.userList = userList;
    }

    @Override
    public String toString() {
        return "VO{" +
                "userList=" + userList +
                '}';
    }

}

Controller 中直接使用 VO 对象接收 List<User> 即可

@RequestMapping("/quick12")
@ResponseBody
public void quickMethod12(VO vo) {
    System.out.println(vo);
}

比较 high 的方案

当使用 ajax 提交时,可以指定 contentTypejson 形式,那么在方法参数位置使用 @RequestBody 可以直接接收集合数据而无需使用 POJO 进行包装。@RequestBody 主要用来接收前端传递给后端的 json 字符串数据(请求体中的 json 字符串数据)

首先需要在引入 jquery 文件,然后在页面中编写如下 ajax 请求提交 User 对象集合

<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
<script>
    var userList = new Array();
    userList.push({username: "Oneby", age: 21});
    userList.push({username: "Heygo", age: 21});

    $.ajax({
        type: "POST",
        url: "${pageContext.request.contextPath}/quick13",
        data: JSON.stringify(userList),
        contentType: "application/json;charset=utf-8"
    });
</script>

Controller 中直接使用 List<User> 进行接收,并且在方法参数位置使用 @RequestBody 注解标明该参数的数据来自请求体中

@RequestMapping("/quick13")
@ResponseBody
public void quickMethod13(@RequestBody List<User> users) {
    System.out.println(users);
}

访问 http://localhost:8080/ajax.jsp 地址后会发现浏览器发送 GET 请求无法获取 jquery 文件(404 NOT Found),这是因为我们之前配置了 DispatcherServlet 的映射地址为 /,表示映射所有请求,而静态资源的 URL 路径无法对应于某一具体的 Controller 方法,因此会出现 404 NOT Found

image-20210308190023404

4.2.2、静态资源的访问

解决方案一:配置静态资源的映射

在 web.xml 配置文件中配置 DispatcherServlet 的映射地址为 /,表示映射所有请求,但是这样无法访问静态资源。我们可以使用 <mvc:resources> 标签配置静态资源的映射地址

<!-- 配置静态资源的映射 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/img/**" location="/img/"/>

解决方案二:<mvc:default-servlet-handler/>

Spring MVC 在全局配置文件中提供了一个<mvc:default-servlet-handler/>标签。在 WEB 容器启动的时候会在上下文中定义一个 DefaultServletHttpRequestHandler,它会对DispatcherServlet的请求进行处理。

如果该请求已经作了映射,那么会接着交给后台对应的处理程序,如果没有作映射,就交给 WEB 应用服务器默认的 Servlet 处理,从而找到对应的静态资源,只有再找不到资源时才会报错。

<mvc:default-servlet-handler/>
4.2.3、请求数据乱码问题

演示 POST 请求乱码问题

当在输入框中填入中文并提交表单时,后台打印的日志出现乱码情况

image-20210308193753421

解决请求数据乱码问题

当 post 请求时,数据会出现乱码,我们可以在 web.xml 文件中设置一个过滤器来进行编码的过滤

<!-- 创建用于配置编码的 filter -->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!-- 设置编码为 UTF-8 -->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<!-- 配置映射地址:映射所有请求 -->
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

当在输入框中填入中文并提交表单时,后台打印的日志无乱码情况

image-20210308194506584

4.2.4、参数绑定注解

获得请求参数-参数绑定注解@RequestParam

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显示的绑定。

@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam("name") String username) {
    System.out.println(username);
}

客户端浏览器通过 URL 地址后的 QueryString 传参,比如:http://localhost:8080/quick14?name=Oneby


@RequestParam 注解的参数

value:与请求参数名称

required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错

defaultValue:当没有指定请求参数时,则使用指定的默认值赋值

@RequestMapping("/quick15")
@ResponseBody
public void quickMethod15(
        @RequestParam(value = "name", required = false,defaultValue = "Oneby") String username) {
    System.out.println(username);
}
4.2.5、Restful 风格参数

Restful风格的参数的获取

Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

① GET:用于获取资源

② POST:用于新建资源

③ PUT:用于更新资源

④ DELETE:用于删除资源


举个栗子

/user/1    GET :       得到 id = 1 的 user
/user/1    DELETE:     删除 id = 1 的 user
/user/1    PUT:        更新 id = 1 的 user
/user      POST:       新增 user

URL 地址中的参数获取

上述 URL 地址 /user/1 中的 1 就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定。地址 /user/1 可以写成 /user/{id},占位符 {id} 对应的就是 1 的值。在业务方法中我们可以使用 @PathVariable 注解进行占位符的匹配获取工作。

image-20210308201820367

@RequestMapping 的请求地址中使用占位符 {name} 标记地址栏中的变量,并在方法参数上使用 @PathVariable(value="name") 注解获取地址栏中的参数,注意:着两个变量名必须一致

@RequestMapping("/quick16/{name}")
@ResponseBody
public void quickMethod16(@PathVariable(value="name") String username) {
    System.out.println(username);
}

客户端浏览器通过 URL 地址传参,比如:http://localhost:8080/quick16/Oneby

4.2.6、自定义类型转换器

在 SpringMVC 中自定义类型转换器

SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。

但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。


自定义类型转换器的开发步骤

① 定义转换器类实现Converter接口

② 在配置文件中声明转换器

③ 在<annotation-driven>中引用转换器


使用 SpringMVC 自带的日期类型转换器

Controller 的方法中注入 Date 类型的参数,SpringMVC 会尝试着将字符串解析成 Date 对象

@RequestMapping("/quick17")
@ResponseBody
public void quickMethod17(Date date) {
    System.out.println(date);
}

使用 SpringMVC 自带的日期类型转换器时,传入的日期字符串参数必须用 / 分隔,比如:http://localhost:8080/quick17?date=2020/3/8


演示自定义日期类型转换器

创建 DateConverter 类,该类实现了 Converter<String, Date> 接口,泛型表示将 String 类型转换为 Date 类型

/**
 * @ClassName DateConverter
 * @Description Converter<String, Date> 的泛型表示将 String 类型转换为 Date 类型
 * @Author Oneby
 * @Date 2021/3/8 20:54
 * @Version 1.0
 */
public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        // 指定日期的转换格式
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        // 进行转换
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

在 spring-mvc.xml 配置文件中创建自定义转换器,并在 <mvc:annotation-driven> 中引用转换器

<!-- mvc 的注解驱动 -->
<mvc:annotation-driven conversion-service="conversionService"/>

<!-- 创建自定义转换器 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!-- 这是一个 list,可以同时配置多个自定义类型转换器 -->
    <property name="converters">
        <list>
            <bean class="com.oneby.converter.DateConverter"></bean>
        </list>
    </property>
</bean>

当使用自定义日期转换器时,传入的日期字符串参数需要用 - 分隔,比如:http://localhost:8080/quick17?date=2020-3-8

4.2.7、Servlet 相关 API

获取 Servlet 相关 API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象有:HttpServletRequestHttpServletResponseHttpSession

@RequestMapping("/quick18")
@ResponseBody
public void quickMethod18(
        HttpServletRequest request,
        HttpServletResponse response,
        HttpSession session) {
    System.out.println(request);
    System.out.println(response);
    System.out.println(session);
}
4.2.8、获取请求头

@RequestHeader 获取请求头

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)@RequestHeader注解的属性如下:

value:请求头的名称

required:是否必须携带此请求头

@RequestMapping("/quick19")
@ResponseBody
public void quickMethod19(
        @RequestHeader(value = "User-Agent", required = false) String userAgent) {
    System.out.println(userAgent);
}

@CookieValue 获取 Cookie 值

使用@CookieValue可以获得指定Cookie的值,@CookieValue注解的属性如下:

value:指定cookie的名称

required:是否必须携带此cookie

@RequestMapping("/quick20")
@ResponseBody
public void quickMethod20(@CookieValue(value = "JSESSIONID") String jsessionId) {
    System.out.println(jsessionId);
}
4.2.9、文件上传

客户端表单实现

文件上传客户端表单需要满足

  1. 表单项type="file"

  2. 表单的提交方式是 post

  3. 表单的 enctype 属性是多部分表单形式,即enctype="multipart/form-data"

<form action="${pageContext.request.contextPath}/quick21" method="post" enctype="multipart/form-data">
    名称<input type="text" name="username"><br/>
    文件<input type="file" name="uploadFile"><br/>
    <input type="submit" value="提交">
</form>

文件上传的原理

当 form 表单的 enctype="application/x-www-form-urlencoded" 时,form 表单的正文内容格式是:key=value&key=value&key=value,当将表单修改为多部分表单时,使用request.getParameter()将失效。当 form 表单的 enctype 取值为 mutilpart/form-data 时,请求正文内容就变成多部分形式:

image-20210308214738027

单文件上传的代码实现

单文件上传步骤

① 导入 fileupload 和 io 坐标

② 配置文件上传解析器

③ 编写文件上传代码


导入 commons-fileupload 和 commons-io 的坐标

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

<!-- commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.3</version>
</dependency>

在 spring-mvc.xml 文件中配置文件上传解析器

<!-- 创建文件解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--上传文件总大小-->
    <property name="maxUploadSize" value="5242800"/>
    <!--上传单个文件的大小-->
    <property name="maxUploadSizePerFile" value="5242800"/>
    <!--上传文件的编码类型-->
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

编写页面提交文件的表单

<form action="${pageContext.request.contextPath}/quick21" method="post" enctype="multipart/form-data">
    名称<input type="text" name="username"><br/>
    文件<input type="file" name="uploadFile"><br/>
    <input type="submit" value="提交">
</form>

编写文件上传代码

@RequestMapping("/quick21")
@ResponseBody
public void quickMethod21(String username, MultipartFile uploadFile) throws IOException {
    // 获取用户名
    System.out.println("上传文件的用户为:" + username);
    //获得文件名称
    String originalFilename = uploadFile.getOriginalFilename();
    // 保存文件
    uploadFile.transferTo(new File("D:\\upload\\" + originalFilename));
}

多文件上传的代码实现

多文件上传,只需要将页面修改为多个文件上传项,将方法参数MultipartFile类型修改为MultipartFile[]即可


编写页面提交文件的表单

<form action="${pageContext.request.contextPath}/quick22" method="post" enctype="multipart/form-data">
    名称<input type="text" name="username"><br/>
    文件1<input type="file" name="uploadFiles"><br/>
    文件2<input type="file" name="uploadFiles"><br/>
    <input type="submit" value="提交">
</form>

编写文件上传代码

@RequestMapping("/quick22")
@ResponseBody
public void quickMethod22(String username, MultipartFile[] uploadFiles) throws IOException {
    // 获取用户名
    System.out.println("上传文件的用户为:" + username);
    // 遍历保存文件
    for (MultipartFile multipartFile : uploadFiles) {
        String originalFilename = multipartFile.getOriginalFilename();
        multipartFile.transferTo(new File("D:\\upload\\" + originalFilename));
    }
}

5、SpringMVC 拦截器

5.1、拦截器的介绍

拦截器(interceptor)的作用

Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

拦截器(interceptor)与过滤器(filter)的区别

区别过滤器拦截器
使用范围是 servlet 规范中的一部分,任何 Java Web 工程都可以使用是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用
拦截范围在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的

5.2、拦截器方法说明

拦截器接口及其中的方法说明

boolen preHandle():方法将在请求处理之前进行调用,该方法的返回值是布尔值 boolean类型的,当它返回为 false 时,表示请求结束,后续的 InterceptorController 都不会再执行;当返回值为 true 时就会继续调用下一个 InterceptorpreHandle 方法或执行 Controller 中的方法

postHandle():该方法是在当前请求进行处理之后被调用,前提是 preHandle 方法的返回值为 true 时才能被调用,且它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作

afterCompletion():该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行,前提是 preHandle 方法的返回值为 true 时才能被调用

public interface HandlerInterceptor {

   /**
    * Intercept the execution of a handler. Called after HandlerMapping determined
    * an appropriate handler object, but before HandlerAdapter invokes the handler.
    * <p>DispatcherServlet processes a handler in an execution chain, consisting
    * of any number of interceptors, with the handler itself at the end.
    * With this method, each interceptor can decide to abort the execution chain,
    * typically sending a HTTP error or writing a custom response.
    * <p><strong>Note:</strong> special considerations apply for asynchronous
    * request processing. For more details see
    * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
    * <p>The default implementation returns {@code true}.
    * @param request current HTTP request
    * @param response current HTTP response
    * @param handler chosen handler to execute, for type and/or instance evaluation
    * @return {@code true} if the execution chain should proceed with the
    * next interceptor or the handler itself. Else, DispatcherServlet assumes
    * that this interceptor has already dealt with the response itself.
    * @throws Exception in case of errors
    */
   default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

      return true;
   }

   /**
    * Intercept the execution of a handler. Called after HandlerAdapter actually
    * invoked the handler, but before the DispatcherServlet renders the view.
    * Can expose additional model objects to the view via the given ModelAndView.
    * <p>DispatcherServlet processes a handler in an execution chain, consisting
    * of any number of interceptors, with the handler itself at the end.
    * With this method, each interceptor can post-process an execution,
    * getting applied in inverse order of the execution chain.
    * <p><strong>Note:</strong> special considerations apply for asynchronous
    * request processing. For more details see
    * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
    * <p>The default implementation is empty.
    * @param request current HTTP request
    * @param response current HTTP response
    * @param handler handler (or {@link HandlerMethod}) that started asynchronous
    * execution, for type and/or instance examination
    * @param modelAndView the {@code ModelAndView} that the handler returned
    * (can also be {@code null})
    * @throws Exception in case of errors
    */
   default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable ModelAndView modelAndView) throws Exception {
   }

   /**
    * Callback after completion of request processing, that is, after rendering
    * the view. Will be called on any outcome of handler execution, thus allows
    * for proper resource cleanup.
    * <p>Note: Will only be called if this interceptor's {@code preHandle}
    * method has successfully completed and returned {@code true}!
    * <p>As with the {@code postHandle} method, the method will be invoked on each
    * interceptor in the chain in reverse order, so the first interceptor will be
    * the last to be invoked.
    * <p><strong>Note:</strong> special considerations apply for asynchronous
    * request processing. For more details see
    * {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.
    * <p>The default implementation is empty.
    * @param request current HTTP request
    * @param response current HTTP response
    * @param handler handler (or {@link HandlerMethod}) that started asynchronous
    * execution, for type and/or instance examination
    * @param ex exception thrown on handler execution, if any
    * @throws Exception in case of errors
    */
   default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
         @Nullable Exception ex) throws Exception {
   }

}

5.3、自定义拦截器

自定义拦截器步骤

自定义拦截器很简单,只有如下三步:

① 创建拦截器类实现HandlerInterceptor接口

② 配置拦截器

③ 测试拦截器的拦截效果

自定义拦截器的代码实现

创建自定义拦截器类,实现 HandlerInterceptor 接口并重写其中的方法

/**
 * @ClassName MyInterceptor
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 10:09
 * @Version 1.0
 */
public class MyInterceptor implements HandlerInterceptor {

    // 在目标方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle......");
        // return true 表示放行,return false 不放行
        return true;
    }

    // 在目标方法执行之后视图对象返回之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }

    // 在流程都执行完毕后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion......");
    }

}

在 spring-mvc.xml 中配置自定义拦截器,path="/**" 表示对所有控制器方法进行拦截

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有 Controller 资源进行拦截操作 -->
        <mvc:mapping path="/**"/>
        <bean class="com.oneby.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

当访问 TargetControllershow() 方法时,请求会被拦截,并根据 preHandle() 方法的返回值判断是否放行

/**
 * @ClassName TargetController
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 10:39
 * @Version 1.0
 */
@Controller
public class TargetController {

    @RequestMapping("/target")
    public ModelAndView show() {
        System.out.println("目标资源执行......");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", "Oneby");
        modelAndView.setViewName("forward:/index.jsp");
        return modelAndView;
    }

}

拦截测试:当 preHandle() 方法返回 true,拦截器将会放行目标请求

image-20210309105725761

拦截测试:当 preHandle() 方法返回 false,拦截器将会拦截目标请求

image-20210309105836780

利用 postHandle() 方法修改请求域中的数据

postHandle() 方法的参数中提供了 ModelAndView modelAndView) 参数,利用此参数可以修改模型数据或者视图名称

// 在目标方法执行之后视图对象返回之前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    // 修改模型数据
    modelAndView.addObject("name","Heygo");
    System.out.println("postHandle......");
}

程序测试:成功将请求域中 name 字段的值修改为 Heygo

image-20210309110223843

5.4、多拦截器操作

多拦截器的配置与拦截顺序

我们在创建一个拦截器

/**
 * @ClassName MyInterceptor1
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 11:03
 * @Version 1.0
 */
public class MyInterceptor1 implements HandlerInterceptor {

    // 在目标方法执行之前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle1......");
        // return true 表示放行,return false 不放行
        return true;
    }

    // 在目标方法执行之后视图对象返回之前执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 修改模型数据
        modelAndView.addObject("name","Heygo");
        System.out.println("postHandle1......");
    }

    // 在流程都执行完毕后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion1......");
    }

}

在 spring-mvc.xml 文件中配置自定义拦截,注意拦截器声明的顺序,该顺序决定了拦截器执行的顺序

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有 Controller 资源进行拦截操作 -->
        <mvc:mapping path="/**"/>
        <bean class="com.oneby.interceptor.MyInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 对所有 Controller 资源进行拦截操作 -->
        <mvc:mapping path="/**"/>
        <bean class="com.oneby.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

拦截测试:先声明的拦截器裹在最外层,其 preHandle() 方法最先执行,postHandle()afterCompletion() 最后执行;后声明的拦截器裹在最内层,其 preHandle() 方法最后执行,postHandle()afterCompletion() 最先执行

image-20210309110653547


测试多拦截器的拦截顺序

在 spring-mvc.xml 文件中颠倒一下两个拦截器声明的顺序

<!-- 配置拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 对所有 Controller 资源进行拦截操作 -->
        <mvc:mapping path="/**"/>
        <bean class="com.oneby.interceptor.MyInterceptor1"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 对所有 Controller 资源进行拦截操作 -->
        <mvc:mapping path="/**"/>
        <bean class="com.oneby.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

拦截测试:拦截顺序和上面的顺序恰好相反

image-20210309111315956

拦截器结论

当拦截器的 preHandle() 方法返回 true 则会执行目标资源,如果返回 false 则不执行目标资源

多个拦截器情况下,配置在前的先执行,配置在后的后执行

拦截器中的方法执行顺序是:preHandler-------目标资源----postHandle---- afterCompletion

5.5、用户登录权限控制

用户登录权限控制的需求

需求:用户没有登录的情况下,不能对后台菜单进行访问操作,点击菜单跳转到登录页面,只有用户登录成功后才能进行后台功能的操作

image-20210309111710074

用户登录权限控制代码实现

① 判断用户是否登录的本质:判断 session 中有没有 user 对象,如果没有登陆则先去登陆,如果已经登陆则直接放行访问目标资源

首先编写拦截器

public class PrivilegeInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        //逻辑:判断用户是否登录  本质:判断session中有没有user
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if(user==null){
            //没有登录
            response.sendRedirect(request.getContextPath()+"/login.jsp");
            return false;
        }
        //放行  访问目标资源
        return true;
    }
}

然后配置该拦截器:找到项目案例的spring-mvc.xml,添加如下配置:

<!--配置权限拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--配置对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="com.itheima.interceptor.PrivilegeInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

② 在登陆页面输入用户名密码,点击登陆,通过用户名密码进行查询,如果登陆成功,则将用户信息实体存入 session,然后跳转到首页,如果登陆失败则继续回到登陆页面

UserController 中编写登陆逻辑

@RequestMapping("/login")
public String login(String username,String password,HttpSession session){
    User user = userService.login(username,password);
    if(user!=null){
        //登录成功  将user存储到session
        session.setAttribute("user",user);
        return "redirect:/index.jsp";
    }
    return "redirect:/login.jsp";
}

service层代码如下:

//service层
public User login(String username, String password) {
    User user = userDao.findByUsernameAndPassword(username,password);
    return user;
}

dao 层代码如下:

//dao层
public User findByUsernameAndPassword(String username, String password) throws EmptyResultDataAccessException{
    User user = jdbcTemplate.queryForObject("select * from sys_user where username=? and password=?", new BeanPropertyRowMapper<User>(User.class), username, password);
    return user;
}

此时仍然登陆不上,因为我们需要将登陆请求 url 让拦截器放行,添加资源排除的配置

<!--配置权限拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--配置对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <!--配置哪些资源排除拦截操作-->
        <mvc:exclude-mapping path="/user/login"/>
        <bean class="com.itheima.interceptor.PrivilegeInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

JdbcTemplate.queryForObject() 方法如果查询不到数据会抛异常,导致程序无法达到预期效果,如何来解决该问题?

在业务层处理来自 dao 层的异常,如果出现异常 service 层返回 null,而不是将异常抛给controller,因此改造登陆的业务层代码,添加异常的控制。

:我们在下节学习了异常处理之后,就无须再手动进行 try{} catch{},将异常一层一层往上抛,最后再通过异常处理器进行补货,并跳转至指定页面

public User login(String username, String password) {
    try {
        User user = userDao.findByUsernameAndPassword(username,password);
        return user;
    }catch (EmptyResultDataAccessException e){
        return null;
    }
}

6、SpringMVC 异常处理

6.1、异常处理思路与方式

Java 程序的异常

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理,如下图:

image-20210309161434201

异常处理的两种方式

① 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver

② 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

6.2、简单异常处理器

环境搭建

在 Service 层的方法中制造各种异常

/**
 * @ClassName DemoServiceImpl
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 15:38
 * @Version 1.0
 */
@Service
public class DemoServiceImpl implements DemoService {
    
    public void show1() {
        System.out.println("抛出类型转换异常....");
        Object str = "zhangsan";
        Integer num = (Integer) str;
    }

    public void show2() {
        System.out.println("抛出除零异常....");
        int i = 1 / 0;
    }

    public void show3() throws FileNotFoundException {
        System.out.println("文件找不到异常....");
        InputStream in = new FileInputStream("C:/xxx/xxx/xxx.txt");
    }

    public void show4() {
        System.out.println("空指针异常.....");
        String str = null;
        str.length();
    }

    public void show5() throws MyException {
        System.out.println("自定义异常....");
        throw new MyException();
    }
    
}

自定义异常 MyException 类继承自 Exception

/**
 * @ClassName MyException
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 15:39
 * @Version 1.0
 */
public class MyException extends Exception {
    
}

在 Controller 层调用 Service 层方法肯定会抛出异常

/**
 * @ClassName DemoController
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 15:40
 * @Version 1.0
 */
@Controller
public class DemoController {

    @Autowired
    private DemoService demoService;

    @RequestMapping(value = "/show")
    public String show() throws FileNotFoundException, MyException {
        System.out.println("show running......");
        demoService.show1();
        //demoService.show2();
        //demoService.show3();
        //demoService.show4();
        //demoService.show5();
        return "index";
    }

}

异常测试:当访问 http://localhost:8080/show 地址时,将抛出 java.lang.ClassCastException 异常

image-20210309162249915

简单异常处理器 SimpleMappingExceptionResolver

SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

<!-- 配置简单映射异常处理器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <map >
            <entry key="java.lang.ClassCastException" value="error"/>
            <entry key="com.oneby.exception.MyException" value="error"/>
        </map>
    </property>
</bean>

异常处理测试:当访问 http://localhost:8080/show 地址时,Service 等将会抛出 java.lang.ClassCastException 异常,经过 SpringMVC 的 SimpleMappingExceptionResolver 处理后将会跳转到 /jsp/error.jsp 页面(视图解析器会自动为我们拼接前后缀)

image-20210309162409650

6.3、自定义异常处理器

自定义异常处理器的步骤

①创建异常处理器类实现HandlerExceptionResolver

②配置异常处理器

③编写异常页面

④测试异常跳转

自定义异常处理器的代码实现

创建自定义异常类 MyExceptionResolver,该类实现了 HandlerExceptionResolver 接口,并重写了其中的 resolveException() 方法

/**
 * @ClassName MyExceptionResolver
 * @Description TODO
 * @Author Oneby
 * @Date 2021/3/9 16:49
 * @Version 1.0
 */
public class MyExceptionResolver implements HandlerExceptionResolver {
    /*
        参数Exception:异常对象
        返回值ModelAndView:跳转到错误视图信息
     */
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        if (e instanceof MyException) {
            modelAndView.addObject("info", "自定义异常");
        } else if (e instanceof ClassCastException) {
            modelAndView.addObject("info", "类转换异常");
        }
        modelAndView.setViewName("error");
        return modelAndView;
    }
}

在 spring-mvc.xml 文件中配置自定义异常处理器。当程序出现异常后,便会交由此异常处理器进行处理

<!-- 配置自定义异常处理器 -->
<bean class="com.oneby.resolver.MyExceptionResolver"/>

编写 error.jsp 页面

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

异常测试:当访问 http://localhost:8080/show 地址时,Service 等将会抛出 java.lang.MyException 异常,经过自定义的 MyExceptionResolver 处理后将会跳转到 /jsp/error.jsp 页面(视图解析器会自动为我们拼接前后缀)

image-20210309173508384

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值