SpringMVC学习笔记

1.SpringMVC概述

1.1SpringMVC简介

在MVC三层架构中:

  1. 界面层:SpringMVC,用于接收用户请求,显示处理结果
  2. 业务层:Spring,用于处理各种业务,创建service、dao和工具类等对象
  3. 持久层:MyBatis,用于访问数据库,对数据进行增删改查

SpringMVC也叫Spring web mvc。是Spring框架的一部分,是在Spring3.0之后发布的,专门用于web开发。

  1. SpringMVC就是servelt的一个升级版

    • web开发底层就是servlet
    • SpringMVC框架就是在servlet基础上,加入一些功能,使得web开发更为简便
  2. SpringMVC是Spring的一部分,能使用Spring的基本功能

    • Spring可以使用IoC和AOP

      • 可以使用IoC管理对象:通过<bean>@Component,@Service,@Repository,@Controller来创建对象 ,并将创建的对象放入Spirng容器中
      • 可以使用AOP对指定方法织入切面
    • SpringMVC也可以使用IoC和AOP

      • 可以使用IoC管理处理器对象:使用@Controller注解或<bean>来创建处理器对象,并将处理器对象放入SpringMVC容器中
      • 可以使用AOP赋予处理器对象额外的功能
      • SpirngMVC容器中放的是处理器对象,用于处理用户请求

使用@Controller注解创建的对象本质上还是普通类的对象,不是servlet。只不过被SpringMVC赋予了一些额外的功能,使得可以接收用户的请求,显示处理结果,可以被当作是一个servlet来使用。

那么SpringMVC是怎么实现让这些普通类对象也能接收用户请求的呢?

SpringMVC中有一个核心servlet:DispatcherServlet【中央调度器】

  1. 中央调度器负责接收所有的用户请求,然后将接收到的请求转发给SpringMVC容器中处理相应请求的处理器对象
  2. 处理器对象处理完请求后,将处理结果返回给中央调度器
  3. 由中央调度器将请求结果写入相应响应协议包中

1.2SpringMVC的优点

  1. 基于MVC架构:功能分工明确,耦合度低
  2. 上手快,使用简单:只使用一个注解就可以开发SpringMVC项目,是轻量级的,jar很小,不依赖特定的接口和类。
  3. 兼具Spring的功能:作为Spring的一部分,可以使用IoC和AOP,方便整合MyBatis等其他框架
  4. SpringMVC强化注解的使用:在控制器,service,dao中都可以使用注解,方便灵活。
    1. 使用@Controller创建处理器对象
    2. 使用@Service创建业务对象
    3. 使用@Autowired或@Resource在处理器类中自动注入Service,在Service类中注入Dao

1.3SpringMVC实例入门

需求:用户在页面通过一个链接发起一个请求,在页面上显示处理结果。

1.3.1步骤分析

1.新建web类型的maven项目

2.添加依赖

​ 1)添加SpringMVC依赖:spring-webmvc

​ 2)添加servlet依赖:java.servlet-api

3.注册中央调度器【重点】

在web.xml中注册SpringMVC框架的核心对象:DispatcherServlet【中央调度器】

​ 1)DispatcherServlet叫做中央调度器,是一个servlet,其父类继承HttpServlet。

​ 2)DispatcherServelt又叫做前端控制器(front controller)。DispatcherServlet负责接收所有用户提交的请求,分配给其他处理器对象,同时接收处理器对象返回的处理结果,最后将处理结果显示给用户

4.创建发起请求页面:index.jsp

5.使用注解创建处理器类

​ 1)在java类上加入@Controller注解,可以创建处理器对象,并放入到SpringMVC容器中

​ 2)在java类中的方法上面加入@RequestMapping注解,用于接收特定请求

@Controller注解修饰的类叫处理器(控制器),又叫做后端控制器(back controller)

@RequestMapping注解修饰的方法叫处理方法,用于处理用户请求

6.创建响应页面:result.jsp

7.创建SpringMVC的配置文件(同Spring配置文件)

​ 1)声明组件扫描器,指定@Controller注解所在类的包名

​ 2)声明视图解析器,帮助处理视图

1.3.2新建web项目

新建一个web类型的maven项目:01-hello-springmvc,使用模板:maven-archetype-webapp

补全maven项目结构

1.3.3添加依赖

在pom.xml文件中添加SpringMVC和Servlet依赖。

SpringMVC基于Spring框架,加入SpringMVC依赖后,会自动加载Spring相关jar包

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.tsccg</groupId>
  <artifactId>01-hello-springmvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--servlet依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!--SpringMVC依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>
</project>

1.3.4注册中央调度器【重点】

在web.xml中注册SpringMVC框架的核心对象:DispatcherServlet【中央调度器】

​ 1)DispatcherServlet叫做中央调度器,是一个servlet,其父类继承HttpServlet

​ 2)DispatcherServlet又叫做前端控制器(front controller),负责接收所有用户提交的请求,分配给其他处理器对象,同时接收处理器对象返回的处理结果,最后将处理结果显示给用户

1)注册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">
<!--注册SpringMVC的核心对象:DispatcherServlet-->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

2)设置为开启服务器时创建

我们需要在开启服务器时,就创建一个DispatcherServelt对象。为什么呢?

因为当DispatcherServlet对象创建时,会同时创建一个SpringMVC容器对象,并把容器对象放入全局作用域中。

将其设置为开启服务器时创建:

<!--在tomcat启动后,创建servlet对象
	load-on-startup:表示tomcat启动后创建对象的顺序。
		值为大于等于0整数,数值越小,tomcat创建该对象的优先级就越高。
		默认为0,表示不创建对象
-->
<load-on-startup>1</load-on-startup>

3)重新指定读取配置文件的路径

开启服务器:

发现报异常,读取不到/WEB-INF/springmvc-servlet.xml,也就是SpringMVC配置文件

SpringMVC创建容器对象时,读取的配置文件默认路径为:

/WEB-INF/<servlet-name>标签里的值 + -servelt.xml

我们可以重新指定配置文件的读取路径:

<!--重新指定SpringMVC读取配置文件的位置-->
<init-param>
    <!--指定springmvc配置文件的位置的属性-->
    <param-name>contextConfigLocation</param-name>
    <!--指定为类路径下-->
    <param-value>classpath:springmvc.xml</param-value>
</init-param>

同时,在resources目录下新建一个SpringMVC配置文件springmvc.xml,就可以成功创建DispatcherServlet对象了。

springmvc.xml:

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

4)<url-pattern/>

此外,还需要设置<servlet-mapping>

其中,url-pattern可以使用两种值:

  1. 使用拓展名方式,语法:*.xxx xxx是自定义的拓展名,表示当发送过来的请求是以xxx为结尾时,使用该servelt来处理。常用的拓展名:*.do、*.action、*.mvc
  2. 使用斜杠"/",处理所有请求(不包含jsp请求);使用/*,处理所有请求(包含jsp请求)。
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!--*.do表示以.do结尾的请求都交给DispatcherServlet来处理
		http://localhost:8080/MyWeb/some.do
		http://localhost:8080/MyWeb/other.do
	-->
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

完整配置:web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--注册SpringMVC的核心对象:DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--重新指定SpringMVC读取配置文件的位置-->
        <init-param>
            <!--指定springmvc配置文件的位置的属性-->
            <param-name>contextConfigLocation</param-name>
            <!--指定为类路径下-->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--在tomcat启动后,创建servlet对象
	        load-on-startup:表示tomcat启动后创建对象的顺序。
		        值为大于等于0整数,数值越小,tomcat创建该对象的优先级就越高。
		        默认为0,表示不创建对象
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--*.do表示以.do结尾的请求都交给DispatcherServlet来处理
            http://localhost:8080/MyWeb/some.do
            http://localhost:8080/MyWeb/other.do
        -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

1.3.5创建发起请求页面:index.jsp

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <a href="/MyWeb/some.do">发送一个.do的请求</a>
</body>
</html>

1.3.6创建处理器类【重要】

1.在com.tsccg.controller包下新建一个普通java类:MyController

public class MyController {

}

2.在java类上加入@Controller注解,代表这是一个处理器类,又叫后端控制器(back controller)

使用@Controller注解修饰方法后可以创建其处理器对象,并放入到SpringMVC容器中。

//@Controller:创建处理器对象,将对象放在SpringMVC容器中
@Controller
public class MyController {

}

3.在SpringMVC中,是用处理器的方法来处理用户提交的请求的

  1. 处理器方法是自定义的,可以有多种返回值,多种参数,方法名称自定义。
  2. 使用@RequestMapping注解将指定的请求交给指定的处理器方法

4.@RequestMapping注解:请求映射,作用是把一个请求地址与一个方法绑定在一起

  1. 属性:value,String类型,表示请求的uri地址(/some.do)。value的值必须是唯一的,不能重复
  2. 位置:
    1. 处理器类的上面,作用后面再讲
    2. 处理器类的方法上面,使用@RequestMapping注解修饰的方法叫做处理器方法,可以处理请求,类似servelt中的doGet和doPost方法

5.处理器方法的返回值:ModelAndView,表示本次请求的处理结果

  1. Model:数据,请求处理完毕后,要显示给用户的数据
  2. View:视图,比如jsp文件

完整处理器类:

//@Controller:创建处理器对象,将对象放在SpringMVC容器中
@Controller
public class MyController {
    /**
     * @RequestMapping:请求映射,把指定的请求交给指定的方法处理
     */
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        //Model:添加数据,框架在请求的最后把数据放入到请求作用域对象中
        //request.setAttribute("fun","执行的是doSome方法");
        mv.addObject("fun","执行的是doSome方法");
        mv.addObject("msg","Hello SpringMVC!");
        //View:指定视图,value属性指定视图的路径
        //框架对视图执行的是请求转发操作:request.getRequestDispatcher("/result.jsp").forword(req,res);
        mv.setViewName("/result.jsp");
        //返回处理结果
        return mv;
    }
}

1.3.7创建响应页面:result.jsp

result.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<center>
    <h3>从请求作用域中获取数据</h3>
    <h3>${requestScope.msg}</h3>
    <h3>${requestScope.fun}</h3>
</center>
</body>
</html>

1.3.8声明组件扫描器

在SpringMVC配置文件里声明组件扫描器,指定@Controller注解所修饰的类所在的包

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--声明组件扫描器-->
    <context:component-scan base-package="com.tsccg.controller"/>
</beans>

1.3.9开启服务器

发布网站,设置网站别名为:MyWeb

开启服务器:

1.4分析SpringMVC处理请求流程

  1. 通过浏览器页面发起请求some.do给tomcat服务器
  2. tomcat读取web.xml创建DispatcherServlet对象,由于其url-pattern的值为*.do,所以将所有.do结尾的请求交给其处理。
  3. DispatcherServlet对象被创建时,其init()方法创建容器对象,读取SpringMVC配置文件,根据其中的组件扫描器创建处理器对象,并放入容器中,然后将容器对象放入全局作用域中。
  4. DispatcherServlet对象根据SpringMVC配置文件得知处理器处理特定请求的方法:(some.do-->doSome()),于是将some.do的请求分派给MyController.doSome()方法
  5. 框架执行doSome()方法,把得到的处理结果(ModelAndView mv)返回给DispatcherServlet对象,DispatcherServlet对象根据View所指定的视图(result.jsp),请求转发调用。

1.5视图解析器

当开启服务器后,如果在浏览器里直接访问result.jsp,显示的结果是不正确的,也是非法的。

为了保护静态资源文件,我们可以将静态资源文件放到WEB-INF目录下,该目录中的文件不会被非法访问到。

在WEB-INF目录下新建一个文件夹view,将result.jsp移进去

当然,我们也必须在处理方法中重新指定视图所在路径才能让程序正常执行

我们一般在/WEB-INF/view/目录下存放所有视图文件,当在处理方法中指定视图路径时,一般都为:

/WEB-INF/view/one.jsp
/WEB-INF/view/two.jsp
/WEB-INF/view/xxx.jsp

可以看出,路径的前缀和后缀是重复的

  • 前缀:/WEB-INF/view/
  • 后缀:.jsp

为了方便设置视图路径,我们可以将前缀和后缀提取出来,作为公用模板,使用时只需要写视图文件的名称即可。

如何实现呢?可以使用视图解析器:InternalResourceViewResolver来提取视图路径的前缀和后缀

使用方式:

在SpringMVC配置文件springmvc.xml里声明视图解析器,输入前缀和后缀

<!--声明视图解析器,方便开发人员指定视图路径 不需要指定id-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/view/"/><!--前后都要加斜杠-->
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>

当配置了视图解析器后,就可以使用逻辑名称(文件名)来指定视图了。

框架会使用字符连接操作,将视图解析器的前缀 + 逻辑名称 + 视图解析器的后缀连接起来,组成完整路径。

2.SpringMVC注解式开发

2.1@RequestMapping定义请求规则

2.1.1处理多种请求

1.使用一个处理方法处理多种请求

可以在一个处理方法上指定处理多种请求。

指定方式:在@RequestMapping注解中使用数组

@RequestMapping(value={"请求1","请求2"})

@Controller
public class MyController {
    //指定处理some.do和other.do请求
	@RequestMapping({value="/some.do","/other.do"})
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doSome方法");
        mv.addObject("msg","Hello SpringMVC!");
        //使用视图解析器后,只需要输入视图的逻辑名称
        mv.setViewName("result");
        //返回处理结果
        return mv;
    }
}

在index.jsp文件里添加一个other.do的请求

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
        }
    </style>
</head>
<body>
<center>
    <a href="/MyWeb/some.do">发送some.do请求</a>
    <br/>
    <a href="/MyWeb/other.do">发送other.do请求</a>
</center>
</body>
</html>

打开服务器:

2.使用多个处理方法处理多种请求

当只使用Servlet时,一个Servelt接口实现类只能处理一个请求,实现增删查改就需要写四个Servlet类。

而在SpringMVC中,一个处理方法处理一个请求,在一个控制器类中,使用四个处理方法就能实现增删查改。

演示使用多个处理方法处理多种请求:

1.在index.jsp中添加两个不同的请求链接:

http://localhost:8080/MyWeb/add.do

http://localhost:8080/MyWeb/remove.do

2.创建响应页面

在/WEB-INF/view/目录下新建add.jsp和remove.jsp

add.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<center>
    <h3>/WEB-INF/view/add.jsp从请求作用域中获取数据</h3>
    <h3>${requestScope.msg}</h3>
    <h3>${requestScope.fun}</h3>
</center>
</body>
</html>

remove.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<center>
    <h3>/WEB-INF/view/remove.jsp从请求作用域中获取数据</h3>
    <h3>${requestScope.msg}</h3>
    <h3>${requestScope.fun}</h3>
</center>
</body>
</html>

3.在处理器类中编写对应处理方法

@Controller
public class MyController {
    /**
     * @RequestMapping:请求映射,把指定的请求交给指定的方法处理
     */
    @RequestMapping("/some.do")
    public ModelAndView doSome(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doSome方法");
        mv.addObject("msg","Hello SpringMVC!");
        mv.setViewName("result");
        return mv;
    }
    @RequestMapping("/add.do")
    public ModelAndView doAdd(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doAdd方法");
        mv.addObject("msg","处理add.do请求");
        mv.setViewName("add");
        return mv;
    }
    @RequestMapping("/remove.do")
    public ModelAndView doRemove(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doRemove方法");
        mv.addObject("msg","处理remove.do请求");
        mv.setViewName("remove");
        return mv;
    }
}

4.开启服务器

2.1.2在类上使用@RequestMapping

当发送的多个请求有相同的前缀时,这个前缀就叫该类请求的模块名称。

如:

相同前缀为/test,那么这个/test就是模块名称

当不做任何处理时,我们需要在每个处理方法上的@RequestMapping注解中都写上模块名称

为了减少重复代码,我们可以把模块名称抽取出来。

如何实现呢?在类上使用@RequestMapping注解,将模块名称写在里面即可实现抽离。

一般我们在开发中,都会在处理器类上用@RequestMapping指明一个模块名称,表示这个处理器是处理哪部分请求的。

2.1.3指定请求方式

@RequestMapping有一个method属性,可以指定处理方法处理什么类型的请求(get/post)

格式:

  1. 指定处理方法处理GET方式的请求:@RequestMapping(value = "GET方式请求",method = RequestMethod.GET)
  2. 指定处理方法处理POST方式的请求:@RequestMapping(value = "POST方式请求",method = RequestMethod.POST)
  3. 指定处理方法处理任何方式的请求:@RequestMapping(value = "任何方式的请求")

演示使用:

1.在index.jsp中分别添加GET方式的请求和POST方式的请求:

2.在各自处理方法上的@RequestMapping注解中指定处理get/post方式的请求

@Controller
@RequestMapping(value = "/test")
public class MyController {
    //指定处理get方式的请求
    @RequestMapping(value = "/get.do",method = RequestMethod.GET)
    public ModelAndView doGet(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doGet方法");
        mv.addObject("msg","处理get.do请求");
        mv.setViewName("result");
        return mv;
    }
    //指定处理post方式的请求
    @RequestMapping(value = "/post.do",method = RequestMethod.POST)
    public ModelAndView doPost(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doPost方法");
        mv.addObject("msg","处理post.do请求");
        mv.setViewName("result");
        return mv;
    }
    //指定处理任意方式的请求
    @RequestMapping(value = "/any.do")
    public ModelAndView doAny(){
        ModelAndView mv = new ModelAndView();
        mv.addObject("fun","执行的是doAny方法");
        mv.addObject("msg","处理any.do请求");
        mv.setViewName("result");
        return mv;
    }
}

3.开启服务器

4.当请求的请求方式与处理方法指定的请求方式不同时,会报405错误

使用get方式发送一条请求给处理post请求的doPost方法

<a href="/MyWeb/test/post.do">使用GET方式发送post.do请求</a>

2.2处理器方法的参数

处理器方法中可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,也就是说可以在处理器方法内直接使用

  1. HttpServletRequest request
  2. HttpServletResponse response
  3. HttpSession session
  4. 用户发送的请求中所携带的请求参数(如:name,age,email等)

2.2.1处理器方法中使用请求对象

创建一个index2.jsp文件,在里面通过表单发送请求,携带name和age两个参数

index2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
        }
    </style>
</head>
<body>
<center>
    <form action="/MyWeb/receive/getParam.do" method="get">
        姓名:<input type="text" name = "name"/>
        <br/>
        年龄:<input type="text" name = "age">
        <br/>
        <input type="submit" value="提交参数"/>
    </form>
</center>
</body>
</html>

在com.tsccg.controller包下新建处理器类:MyController2,在类上使用@RequestMapping注解抽取模块名称:/receive

处理器方法doGetParam()形参为:HttpServletRequest request

在方法内调用请求对象request获取请求参数,并将请求参数放入当前请求作用域中,然后指定视图:show.jsp。

MyController2:

@Controller
@RequestMapping(value = "/receive")
public class MyController2 {
    @RequestMapping("/getParam.do")
    public ModelAndView doGetParam(HttpServletRequest request) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("userName",request.getParameter("name"));
        mv.addObject("userAge",request.getParameter("age"));
        mv.setViewName("show");
        return mv;
    }
}

在/WEB-INF/view/下新建show.jsp文件

show.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
            color: red;
        }
    </style>
</head>
<body>
    <center>
        用户名:${userName}
        <br/>
        年龄:${userAge}
    </center>
</body>
</html>

打开服务器:

2.2.2逐个接收请求参数

当我们直接在处理器方法上添加普通java类型形参时,SpringMVC会根据形参名自动获取对应的请求参数,然后将请求参数赋给形参。

注意:需要保证请求参数名与处理器方法的形参名相同,形参顺序不影响

1.演示

1)修改index2.jsp页面:

以get方式发送请求,携带name和age两个参数

2)添加处理方法:

在方法上添加String类型的name和int类型的age两个形参

3)打开服务器:

2.过程分析

1)使用请求对象获取请求参数

String strName = request.getParameter("name");
String strAge = request.getParameter("age");

2)SpringMVC框架通过中央处理器(DispatcherServelt)调用处理器MyController2的方法doParam()

调用方法时,把相同名称的请求参数赋值给它的形参。

在赋值时,会自动进行类型转换,将String类型的请求参数age转换为Integer类型,再赋给int类型形参age

doParam(String name = strName,int age = Integer.parseInt(strAge)){
    ...
}
3.缺点分析

当我们在发送请求时,如果没有输入年龄值,会报400错误

400状态码是客户端错误,表示因发送的请求语法错误,服务器无法正常读取。

这里报400状态码是因为:在处理器方法doParam(String name,int age)中,需要将String类型的请求参数age转换为Integer类型,再赋给int类型的方法形参age:

String strAge = request.getParameter("age");
int age = Integer.parseInt(strAge);

当发送过来的请求中age的值为空字符串""时,Integer.parseInt(strAge)的执行结果为null,而null不能赋值给int类型的参数,所以出现了错误。

解决方法就是使用Integer类型或者String类型的age形参:

使用Integer类型只能处理数字和null两种请求参数,当请求参数为非数字类型时,还是会报400错误。

比如输入“abc”:

但是也不必非使用String类型,虽然使用后浏览器不会报400错误了,但是还需要我们在处理器方法中再次转换类型,此外当请求参数出现问题时仍然会执行处理器方法,不利于我们程序的安全。

当使用Integer类型报400状态码时,不会继续执行处理器方法,也便于我们查找到错误:

[org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: "abc"]

2.2.3解决请求参数乱码问题

当我们以GET方式提交中文参数时,不会出现乱码

而当我们以POST方式发送中文参数时,处理器方法获取到的中文参数会出现乱码问题。

1.演示

1)以POST方式发送请求:

2)打开服务器,在页面中输入中文参数,出现乱码问题:

2.解决方法

1)Servlet解决乱码问题的方法

我们在使用Servlet时,会在doPost方法中设置用utf-8字符集重新编辑请求参数:

public void doPost(HttpServletRequest request,HttpServletResponse response) {
    request.setCharacterEncoding("utf-8");
}

因为不想在所有Servlet中都设置一遍,我们将这一步移到过滤器中实现:

过滤器类:OneFilter

public class OneFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("utf-8");//增强
        filterChain.doFilter(servletRequest,servletResponse);//归还
    }
}

web.xml配置:

<filter>
    <filter-name>OneFilter</filter-name>
    <filter-class>com.tsccg.filter.OneFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>OneFilter</filter-name>
    <!-- 通知Tomcat在调用所有资源文件之前,都需要调用OneFilter进行拦截 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

2)SpringMVC中解决乱码问题的方法

现在使用的是SpringMVC,我们同样可以在过滤器中处理乱码问题。

过滤器可以自定义,也可以使用框架中提供的专门用于处理乱码问题的字符集过滤器:CharacterEncodingFilter

该过滤器位于spring-web-5.2.5.RELEASE.jar包中。

使用方式:

在web.xml中注册字符集过滤器(CharacterEncodingFilter),并进行相关配置

<!--注册字符集过滤器,解决post中文乱码问题-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--设置项目中使用的字符编码-->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <!--强迫请求对象(request)使用encoding属性指定的字符编码-->
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <!--强迫响应对象(response)使用encoding属性指定的字符编码-->
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <!-- 通知Tomcat在调用所有资源文件之前,都需要调用过滤器进行拦截处理 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

查看效果

3)查看字符集过滤器(CharacterEncodingFilter)核心源码

2.2.4校对请求参数名@RequestParam

当发送的请求中携带的请求参数名称与处理方法的形参名称不一致时,形参就无法拿到请求参数的值。

请求页面:

处理方法:

打开服务器,发送请求:

处理方法的形参获取不到值

如何解决呢?我们可以使用@RequestParam注解

@RequestParam:在逐个接收请求参数中,解决请求中携带的请求参数名与处理方法形参名不一致问题

  1. 属性:
    1. value:指定请求参数名称
    2. required:boolean类型,默认为true,表示请求中必须包含value所指定参数,不然报错
  2. 使用位置:处理方法的形参定义前

使用:

在处理方法的形参前,添加注解:@RequestParam("请求参数名"),指定请求参数名

重新发送请求:

此时,如果我们发送一条不包含任何参数的url,会报400错误:

这是因为@RequestParam的required属性默认为true,要求从浏览器发送过来的请求必须携带@RequestParam所指定的参数myName、myAge,不然就报错。

当然,可以设置required属性为false,就不要求url必须携带指定参数了。

2.2.5用对象接收请求参数

当url中携带的请求参数过多时,使用逐个接收请求参数的方式就很不方便了,需要更加简便的方式。

我们可以将处理器方法的参数定义为一个对象,只要保证对象的属性名与请求参数名一致就可以接收到请求参数值。

定义参数类:

com.tsccg.param.Student

public class Student {
    private String name;
    private Integer age;

    public Student() {
        System.out.println("调用无参构造");
    }

    public Student(String name, Integer age) {
        System.out.println("调用有参构造");
        this.name = name;
        this.age = age;
    }
    //getter and setter
    //toString()
}

在处理器方法中使用对象作为形参,接收请求参数:

打开服务器,发送请求:

2.3处理器方法的返回值

处理器方法的返回值有四种常用类型:

  1. ModelAndView:有数据和视图,将数据放入请求作用域,对视图执行forward
  2. String:表示视图,可以用逻辑名称,也可以用完整视图路径
  3. void:即不表示视图,又不表示数据,可用于处理Ajax请求
  4. 自定义类型对象:用于通过框架处理Ajax请求

根据不同情况使用不同的返回值。

2.3.1返回值类型为ModelAndView

​ 如果处理器方法处理完请求后,需要跳转到视图等其他资源,且同时需要在跳转的资源间传递数据,那么处理器方法的返回值类型用ModelAndView比较好。当然,需要在处理器方法中定义ModelAndView对象。

​ 如果处理器方法处理完请求后,只需要跳转而不需要传递数据,或只需要传递数据而不需要跳转,那么此时若返回ModelAndView,则总是会有一部分多余:Model或者View,也就是说此时使用ModelAndView不合适。

2.3.2返回值类型为String

在处理器方法中,使用String类型的返回值,返回的字符串可以指定视图的逻辑名称,也可以指定视图的物理名称。

当然,使用逻辑名称需要提前配好视图解析器:

<!--声明视图解析器,方便开发人员指定视图路径 不需要指定id-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/view/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>

发送请求后,只会跳转到视图页面,在处理器方法中接收到的数据不会在资源文件间传递。

如果想在返回值为String类型的情况下,实现数据传递,可以在处理器方法中加上一个HttpServletRequest类型的参数,也就是请求对象,调用请求对象将接收到的请求参数写入到请求作用域中,实现资源间数据传递。

此外,当指定的是视图的物理名称时,就不能使用视图解析器了,不然会发生冲突

发送请求:

可见,报404错误:[/WEB-INF/view/WEB-INF/view/show.jsp.jsp] 未找到

当在处理器方法中返回视图的物理名称:/WEB-INF/view/show.jsp后,视图解析器为其添上前缀/WEB-INF/view/和后缀.jsp

最后请求转发的路径为:/WEB-INF/view/WEB-INF/view/show.jsp.jsp

所以,当指定视图的物理名称时,不要配置视图解析器。

2.3.3返回值为void

当处理器方法返回值为void时,不能表示数据,也不能表示视图,适合处理Ajax请求。

Ajax只需要服务器端返回数据,和视图无关。

演示用返回值为void的处理器方法处理Ajax请求:

1.在maven中添加jackson依赖

由于本例中服务端向浏览器传回的是json数据,为了方便将普通字符串和对象转换为json格式字符串,使用了json工具包,所以需要导入json工具包的依赖。

在pom.xml中添加jackson工具包依赖:

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

2.前端页面:发送Ajax请求,接收处理结果,写入页面中的div中

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
        }
    </style>
    <!--引入jQuery库文件-->
    <script src="https://www.jq22.com/jquery/jquery-2.1.1.js"></script>
    <!--Ajax-->
    <script type="text/javascript">
        $(function() {
            $("#btn1").click(function() {
                // alert("Hello");//test
                $.ajax({
                    url:"return/ajax.do",
                    data:{
                        "name":$("#myName").val(),
                        "age":$("#myAge").val()
                    },
                    type:"post",
                    dataType:"json",
                    success:function(resp){
                        // alert(resp);//test
                        if(resp.name !== undefined && resp.age !== undefined) {
                            $("#show").text(resp.name + "今年" +  resp.age + "岁")
                        } else {
                            alert("请输入姓名和年龄!")
                        }

                    }
                })
            })
        })
    </script>
</head>
<body>
<center>
    姓名:<input type="text" id="myName"/>
    <br/>
    年龄:<input type="text" id="myAge">
    <br/>
    <input type="button" value="提交" id="btn1"/>
    <div id="show"></div>
</center>
</body>
</html>

3.处理器方法:返回值为void,处理Ajax请求

@Controller
@RequestMapping(value = "/return")
public class MyController3 {
    /**
     * 返回值为void时,适合处理Ajax请求
     */
    @RequestMapping(value = "/ajax.do")
    public void returnVoid(HttpServletResponse response,String name,Integer age) throws IOException {
        //定义json格式字符串
        String json = "{}";
        if (name != null && age != null) {
            //1.处理Ajax请求
            //service调用完毕后,使用student表示处理结果
            Student student = new Student(name,age);
            //2.将student对象转换为json格式字符串
            ObjectMapper mapper = new ObjectMapper();
            json = mapper.writeValueAsString(student);
            System.out.println("json格式字符串:" + json);
        }
        //3.输出数据,将json格式字符串发送回异步对象
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print(json);
        out.flush();
        out.close();
    }
}

4.开启服务器,发送Ajax请求

5.分析处理器方法处理Ajax请求的步骤

可以发现,除了处理Ajax请求得到处理结果的部分外,将对象转换为json格式字符串和输出数据部分的实现步骤都是固定的,也就是说,每次实现都是做重复的步骤。

既然存在重复步骤,那么就意味着可以通过框架来封装这些步骤,我们只需要专心写业务逻辑代码即可。

那么如何使用框架响应Ajax请求呢,这就涉及到了返回值为Object对象的情况。

2.3.4返回值为Object对象

处理器方法的返回值可以为Object对象。这个Object可以是Integer、String、自定义对象、List、Map等。

返回的对象有属性,属性就是数据,所以Object表示数据,和视图无关。

因此,我们可以使用对象表示数据,通过框架处理得到json格式数据响应Ajax请求。

1.通过框架处理Ajax请求的实现步骤分析

1. 添加jackson工具库依赖:处理Ajax请求使用的是json数据格式,需要加入处理json的工具库的依赖,SpringMVC框架默认使用的是jackson
2. 在SpringMVC的配置文件中加入:<mvc:annotation-driven>注解驱动
3. 在处理器方法上添加@ResponseBody注解

2.通过框架处理Ajax请求的内部原理:【了解】

1.<mvc:annotation-driven>注解驱动

​ 1)注解驱动实现的功能是:完成java对象到json、xml、text、二进制等数据格式的转换。

​ 2)使用的接口:HttpMessageConveter,消息转换器。该接口定义了java转为json、xml等数据格式的方法。这个接口有很多实现类。这些实现类完成了java对象到json,到xml,到二进制等数据格式的转换。

​ 3)下面两个方法是控制器类把结果输出给浏览器时使用的:

//判断处理器方法返回的对象能否转换为var2指定的数据格式;
//var2:response.setContentType("application/json;charset=utf-8");
boolean canWrite(Class<?> var1,@Nullable MediaType var2);
//调用jackson中的ObjectMapper把处理器方法的返回值对象转换为json格式字符串
void write(T var1,@Nullable MediaType var2,HttpOutputMessage var3);

​ 4)当<mvc:annotation-driven>加入到SpringMVC配置文件后,会自动创建HttpMessageConverter接口的七个实现类

2.@ResponseBody注解

放在处理器方法上,通过HttpServletResponse对象【响应对象】输出数据,响应Ajax请求。

效果同如下代码:

PrintWriter out = response.getWriter();
out.print(json);
out.flush();
out.close();
3.实例演示-返回自定义对象

1.加入jackson依赖

<!--json工具包:jackson-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>

2.在SpringMVC的配置文件中声明注解驱动

注意:加入的是以mvc为结尾的

完整SpringMVC配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--声明组件扫描器-->
    <context:component-scan base-package="com.tsccg.controller"/>
    <!--声明视图解析器,方便开发人员指定视图路径 不需要指定id-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/view/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--声明注解驱动,创建HttpMessageConverter接口的七个实现类,
    用于实现java对象到json、xml、text、二进制等数据格式的转换。-->
    <mvc:annotation-driven/>
</beans>

3.编写处理器方法

返回值为自定义对象Student

在方法上添加@ResponseBody注解

@Controller
@RequestMapping(value = "/return")
public class MyController3 {
    /**
     * 处理器方法返回一个Student对象,通过框架将其转换为json格式字符串,响应Ajax请求
     */
    @RequestMapping("/object.do")
    @ResponseBody//通过响应对象将转换后的json格式字符串写入响应包中,响应Ajax请求。
    public Student returnObject(String name,Integer age) {
        //调用service,获取处理结果:student
        Student student = null;
        if (name != null && age != null) {
            student = new Student(name,age);
        }
        return student;
    }
}

4.修改页面发送的url

5.开启服务器,发送Ajax请求

4.实例演示-返回List集合

在实际开发中,可能需要同时返回多个对象,这时就需要用List集合存放多个对象,然后返回List集合对象

1.处理器方法:返回List集合对象

@Controller
@RequestMapping(value = "/return")
public class MyController3 {
    /**
     * 返回List集合对象:List<Student>
     * List集合转换为json数组:[{"name":"张三","age":34},{"name":"李四","age":24}]
     */
    @RequestMapping("/list.do")
    @ResponseBody
    public List<Student> returnList(String name,Integer age) {
        //调用service,获取处理结果:studentList
        List<Student> studentList = new ArrayList<>();
        Student student1 = new Student(name,age);
        Student student2 = new Student("李四",24);
        studentList.add(student1);
        studentList.add(student2);
        return studentList;
    }
}

2.修改前端请求页面:

使得将服务器响应的json数组中的数据写入页面表格中

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
        }
        table {
            width: 150px;
        }
        td {
            width: 50%;
        }
    </style>
    <script src="https://www.jq22.com/jquery/jquery-2.1.1.js"></script>
    <script type="text/javascript">
        $(function() {
            $("#btn1").click(function() {
                // alert("Hello");//test
                $.ajax({
                    url:"return/list.do",
                    data:{
                        "name":$("#myName").val(),
                        "age":$("#myAge").val()
                    },
                    type:"post",
                    dataType:"json",
                    success:function(resp){
                        // alert(resp);
                        $("#show").empty()//每次添加新表格行前,清除旧表格行
                        //遍历json数组[{"name":"张三","age":34},{"name":"李四","age":24}]
                        $.each(resp,function(k,v) {
                            //添加新的表格行
                            $("#show").append("<tr><td>"+v.name+"</td><td>"+v.age+"</td></tr>")
                        })
                    }
                })
            })
        })
    </script>
</head>
<body>
<center>
    姓名:<input type="text" id="myName"/>
    <br/>
    年龄:<input type="text" id="myAge">
    <br/>
    <input type="button" value="提交" id="btn1"/>
    <table border="1" cellpadding="0" cellspacing="0">
        <tr>
            <td>姓名</td>
            <td>年龄</td>
        </tr>
    </table>
    <table id="show" border="1" cellpadding="0" cellspacing="0">
    </table>
</center>
</body>
</html>

3.开启服务器,发送Ajax请求

5.实例演示-返回String对象

当处理器方法返回值类型为String时:

  1. 如果没有在方法上添加@ResponseBody,则表示视图
  2. 如果方法上有@ResponseBody,则表示文本数据

1.处理器方法:

需要注意的是,当返回String对象时,默认的contentType为:text/plain;charset=ISO-8859-1,会导致浏览器解析响应数据时发生中文乱码问题。

解决方法:使用@RequestMapping注解的produces属性,重新指定contentType为:text/plain;charset=utf-8

@Controller
@RequestMapping(value = "/return")
public class MyController3 {
    /**
     * 返回String对象
     * 当返回值为String类型时,如果方法上有@ResponseBody,则代表文本数据
     *                      如果没有,则代表视图
     * produces属性:重新指定contentType
     */
    @RequestMapping(value = "/string.do",produces = "text/plain;charset=utf-8")
    @ResponseBody
    public String returnString(String name,Integer age) {
        return name + "今年" + age + "岁";
    }
}

2.修改请求页面:

3.开启服务器,发送Ajax请求

2.4解读<url-pattern>

2.4.1“/”的作用

前面说过,当SpringMVC的中央调度器DispatcherServlet的<url-pattern/>所指定的值为*.do时,表示Tomcat服务器将所有以.do结尾的请求都交给中央调度器来处理。

那么,当<url-pattern/>所指定的值为“/”时,会有什么作用呢?

我们先在项目中添加一些静态资源:.html、.js、.jpg

在/webapp目录下新建html、js、images三个目录,分别在三个目录中加入相对应的静态资源文件:

1)html

welcome.html

2)js

放入jquery库文件

3)images

放入一张图片dog.jpg

文件结构:

在Index.jsp页面中引入这些静态资源文件

index.jsp文件:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <style type="text/css">
        * {
            font-size: 20px;
        }
    </style>
    <!--引入js文件-->
    <script type="text/javascript" src="js/jquery-3.4.1.js"></script>
</head>
<body>
<center>
    <form action="/MyWeb/path/some.do" method="post">
        姓名:<input type="text" name="name" />
        <br/>
        年龄:<input type="text" name="age" />
        <br/>
        <input type="submit" value="提交"/>
    </form>
    <!--引入图片文件-->
    <img src="images/dog.jpg" alt="无法访问dog.jpg" width="300px"/>
    <br/>
    <!--引入html文件-->
    <a href="html/welcome.html">欢迎界面</a>
</center>
</body>
</html>

开启服务器:发现在打开页面的同时,静态资源也一并加载出来了

我们点击F12,查看控制台:

查看浏览器发送的请求:

http://localhost:8080/MyWeb/index.jsp:动态资源文件,由tomcat处理(jsp会转为servlet)

http://localhost:8080/MyWeb/images/dog.jpg:静态资源文件,tomcat

http://localhost:8080/MyWeb/js/jquery-3.4.1.js:静态资源文件,tomcat

http://localhost:8080/MyWeb/html/welcome.html:静态资源文件,tomcat

http://localhost:8080/MyWeb/path/some.do:动态资源文件,由SpringMVC框架的中央处理器处理(DispatcherServlet)

tomcat本身能处理资源文件的访问,使用的是其自带的默认servlet:DefaultServlet(可查看tomcat的conf/web.xml)

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <!--开启服务器时,创建该servlet的对象-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <!--url-pattern的值为"/"-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

<url-pattern>的值决定了服务器将哪些请求交给当前servlet处理。如果我们搞明白了DefaultServlet的作用,那么就能搞明白“/”的作用。

我们可以查看官方说明DefaultServlet作用的注释:

The default servlet for all web applications, that serves static resources.  
It processes all requests that are not mapped to other servlets with servlet mappings (defined either here or in your own web.xml file). 

意思是说,DefaultServlet可以:

  1. 处理静态资源的请求
  2. 处理未映射到其它servlet的请求,也就是缺省匹配:如果一个请求与除当前servlet外的其他所有servelt都无法匹配,那么就由当前servlet处理该请求

由此,“/”的作用就可想而知了。

2.4.2在中央处理器中设置“/”出现的问题

将我们当前项目的中央处理器的<url-pattern>设置为“/”

重启服务器:

发现所有的静态资源文件都访问不到了,而动态资源index.jsp和some.do仍然可以访问到:

1)动态资源index.jsp;静态资源dog.jpg和jquery-3.4.1.js

2)静态资源welcome.html

3)动态资源some.so

这是因为在中央处理器(DispatcherServlet)中设置url-pattern为“/”后,中央处理器就取代了tomcat自带的DefaultServlet,导致现在所有的静态资源的请求和未映射到其他servlet的请求都交给中央处理器处理。

  1. 静态资源请求404:当中央调度器接收到静态资源的请求后,会将其当作一个普通的Controller请求,为其查找相应的处理器,由于当前项目中没有能处理静态资源请求的处理器,所以所有的静态资源请求都会报404错误。
  2. 动态资源index.jsp请求正常:中央调度器的url-pattern设置为“/”,表示缺省匹配,而tomcat的servlet容器有内置的“*.jsp”扩展名匹配器,扩展名匹配的优先级高于缺省匹配,所以中央调度器无法拦截jsp请求。如果想设置中央处理器处理所有的请求,需要设置url-pattern为“/*”
  3. 动态资源some.do请求正常:当中央调度器接收到some.do动态资源的请求后,能在我们程序中找到与其相对应的MyController控制器对象,因而能正常处理该请求。

在中央处理器中设置url-pattern为“/”的好处之一就是:除.jsp以外的所有的请求都归中央调度器处理,不必再使用以”.do“结尾的请求。

2.4.3静态资源请求的处理方式

为了解决静态资源无法访问的问题,我们需要在中央调度器中加入处理静态资源请求的处理器,有两种方式:

1.方式一:使用<mvc:default-servlet-handler/>

在SpringMVC配置文件中声明了<mvc:default-servlet-handler/>后,框架会创建DefaultServletHttpRequestHandler处理器对象,对进入中央调度器的请求进行筛查,将静态资源的请求转发给tomcat默认的DefaultServlet来处理。

注意: <mvc:default-servlet-handler/>@RequestMapping有一定冲突,必须要声明注解驱动:<mvc:annotation-driven/>来解决冲突。

重启服务器:成功处理静态资源请求

2.方式2:使用<mvc:resources/>

在Spring3.0版本后,Spring定义了专门用于处理静态资源访问请求的处理器:ResourceHttpRequestHandler,使得处理静态资源请求时不依赖于服务器。这种方式是我们在开发中常用的。

使用方式:

在SpringMVC配置文件里添加

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

location:静态资源在项目中的目录位置,格式为:/images/。前面的斜杠表示根目录:webapp

mapping:访问该静态资源的uri地址,使用通配符:**

​ 如:/images/**,表示:/images/dog.jpg,/images/logo/dark.gif,/images/history/book/list.png

指定ResourceHttpRequestHandler处理本项目中所有的静态资源请求:

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

当然,这种方式还不够完美。因为我们写了三个<mvc:resources/>标签,当项目中静态资源较多时,会很麻烦。

因此,我们可以将所有的静态资源文件都放进一个目录中,这样只需要写一个标签即可:

在webapp目录下新建一个目录:static,将所有静态资源文件都放进去

在SpringMVC配置文件中声明<mvc:resources/>

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

同样,<mvc:resources/>@RequestMapping有一定冲突,需要声明注解驱动<mvc:annotation-driven/>

springmvc.xml完整配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--声明组件扫描器-->
    <context:component-scan base-package="com.tsccg.controller"/>
    <!--声明视图解析器,方便开发人员指定视图路径 不需要指定id-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/view/"/>
        <!--后缀-->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--声明注解驱动
    1.创建HttpMessageConverter接口的七个实现类,用于实现java对象到json、xml、text、二进制等数据格式的转换。
    2.解决动态资源和静态资源冲突的问题
    -->
    <mvc:annotation-driven/>
    <!--处理静态资源请求-->
    <!--方式1:将静态资源请求转发给tomcat默认的DefaultServlet-->
	<!--<mvc:default-servlet-handler/>-->
    <!--方式2:Spring框架自己处理静态资源请求-->
    <mvc:resources location="/static/" mapping="/static/**" />
</beans>

重启服务器:成功处理静态资源请求

2.5绝对地址和相对地址

2.5.1地址分类

1.绝对地址:带有协议名称的完整地址

​ 例如:http://localhost:8080/MyWeb/index.jsp

https://www.baidu.com/my/index

2.相对地址:以某个地址为参考的不完整地址

​ 例如:images/dog.jpg,/images/dog.jpg

相对地址不能独立使用,必须有一个参考地址,通过【参考地址 + 相对地址】才能指定资源文件。

相对地址之前可以加"/",也可以不加"/",那么以这两种方式发送的请求,参考地址有什么不同呢?

2.5.2相对地址加"/"与不加"/"的区别

我们在webapp根目录下新建一个index.jsp,在页面上添加两个超链接:

  1. 一个为不加"/"的相对地址:【user/some.do】
  2. 一个为加"/"的相对地址:【/user/some.do】

index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<center>
    <a href="user/some.do">发起user/some.do的get请求</a>
    <br/>
    <a href="/user/some.do">发起/user/some.do的get请求</a>
</center>
</body>
</html>

开启服务器,通过浏览器访问index.jsp,浏览器发送的请求地址为:

http://localhost:8080/MyWeb/index.jsp

如下图所示:

点击没有"/"user/some.do超链接,浏览器发送的请求地址变为:

http://localhost:8080/MyWeb/user/some.do

如下图所示:

点击有"/"/user/some.do超链接,浏览器发送的请求地址变为:

http://localhost:8080/user/some.do

如下图所示:

我们分析以上发送的请求地址:

1)访问index.jsp

2)点击user/some.do的超链接

3)点击/user/some.do的超链接

为了探究普适性,我们在webapp/static/html目录下新建一个index.html文件,添加两个超链接,发送和上面同样的请求:

1)开启服务器,访问index.html

2)点击user/some.do的超链接

3)点击/user/some.do的超链接

综上,得出结论:

  1. 当发送的相对地址请求前面没有"/"时,参考地址是发起请求页面的路径:http://localhost:8080/MyWeb/
  2. 当发送的相对地址请求前面有"/"时,参考地址是服务器地址(协议+域名+端口号):http://localhost:8080/

2.5.3解决路径访问问题

1.解决加"/"的路径访问问题

在前面发送的地址中,当相对地址为/user/some.do时,浏览器发送的请求地址缺少项目名,如:

http://localhost:8080/user/some.do,缺少/MyWeb

为了让请求能正常访问资源,我们需要在相对地址前面添上项目名,如:/MyWeb/user/some.do

<a href="/MyWeb/user/some.do">发起/user/some.do的get请求</a>

但这种方式不够灵活,把项目名写死了,当项目名改变时,就需要在页面中挨个修改,不可取。

为了更加灵活,我们可以使用EL表达式:${pageContext.request.contextPath}

该EL表达式可以动态地获取项目名:/MyWeb

<a href="${pageContext.request.contextPath}/user/some.do">发起/user/some.do的get请求</a>

查看浏览器页面源码:

2.解决不加"/"的路径访问问题

我们已经知道,当相对地址不加"/"时,会把发送请求页面的路径当作参考地址

浏览器中,访问index.jsp:

​ 请求地址:http://localhost:8080/MyWeb/index.jsp

​ 路径:http://localhost:8080/MyWeb/

在index.jsp中,访问user/some.do:

​ 参考地址:http://localhost:8080/MyWeb/

​ 请求地址变为:http://localhost:8080/MyWeb/user/some.do

那么如果我们将user/some.do请求的处理结果设置为发起该请求的页面index.jsp,就会出现问题。

我们将项目中的视图解析器去掉,在处理器方法中将视图地址指向index.jsp,也就是发起请求的页面本身:

开启服务器,访问index.jsp:http://localhost:8080/MyWeb/index.jsp

连续两次点击user/some.do的链接:报404错误。

第一次点击user/some.do的链接:

参考地址:http://localhost:8080/MyWeb/

请求地址变为:http://localhost:8080/MyWeb/user/some.do,路径正常

第二次点击user/some.do的链接:

参考地址:http://localhost:8080/MyWeb/user/

请求地址变为:http://localhost:8080/MyWeb/user/user/some.do,路径错误

如何解决这个问题呢?

我们可以使用base标签。<base> 标签为当前页面上的所有链接规定默认地址或默认目标。

也就是说,当前页面中,所有没有"/"的相对地址请求都会把base标签所指定的地址当作参考地址。

注意:<base> 标签必须位于 head 元素内部。

使用:在index.jsp页面中加入base标签

重启服务器:

就可以无限发送请求了

当然,这种方式还是不够灵活,当项目名改变时,我们还是要修改base标签指向的地址。

为此,我们可以在jsp页面中使用java代码,动态地获取当前项目路径:

index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    //动态地获取当前项目路径:http://localhost:8080/MyWeb/
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>Title</title>
<%--    <base href="http://localhost:8080/MyWeb/"/>--%>
    <!--使用获取到的项目路径-->
    <base href="<%=basePath%>"/>
</head>
<body>
<center>
    <a href="user/some.do">发起user/some.do的get请求</a>
    <br/>
    <!-- 使用EL表达式:$ {pageContext.request.contextPath}-->
    <a href="${pageContext.request.contextPath}/user/some.do">发起/user/some.do的get请求</a>
</center>
</body>
</html>

3.SSM整合开发

3.1SSM整合开发思路

SSM编程,即SpringMVC + Spring + MyBatis整合,是当下最流行的JavaEE开发技术架构。

  • SpringMVC:视图层,界面层,负责接收请求,显示处理结果
  • Spring:业务层,负责管理service、dao、工具类对象
  • MyBatis:持久层,数据访问层,负责访问数据库

SSM整合也叫做SSI(MyBatis的前身:IBatis),整合需要创建多个容器:

  1. 第一个容器:SpringMVC容器,用于管理Controller控制器对象
  2. 第二个容器:Spring容器,用于管理Service、Dao和工具类容器对象

我们要做的就是把需要使用的对象交给合适的容器去创建、管理:

  1. 把Controller和web开发相关的对象都交给SpringMVC容器,即把对象写入SpringMVC配置文件中
  2. 把Service、Dao和工具类容器对象都交给Spring容器,即把对象写入Spring配置文件中

在创建好容器、分配好对象后,有一个问题:如何实现跨容器访问对象?

也就是说,位于SpringMVC容器中的对象是如何访问到Spring容器中的Service对象的?

Spring容器和SpringMVC容器其实是父子关系,SpringMVC容器是Spring容器的一部分,子容器SpringMVC中的Controller对象可以访问到父容器Spring中的Service对象。

3.2SSM整合开发步骤分析

1.建表

2.创建maven web项目

3.添加依赖

​ SpringMVC、Spring、MyBatis三个框架的依赖,json工具包依赖,数据库驱动依赖,druid连接池依赖,jsp,servlet依赖。

4.创建包:实体类、dao、service、controller等

5.配置web.xml

​ 1)注册中央调度器:DispatcherServlet,用于创建SpringMVC容器对象,进而创建Controller类对象。同时,用于接收用户请求,因为只有servlet才能接收用户请求。

​ 2)注册Spring监听器:ContextLoaderListener,用于创建Spring容器对象,进而创建Service,Dao等对象。同时,使Spring容器只需要创建一次。

​ 3)注册字符过滤器,解决post请求乱码问题

6.写配置文件【重点】

​ 1)SpringMVC的配置文件

​ 2)MyBatis的配置文件

​ 3)连接数据库的属性配置文件

​ 4)Spring的配置文件

7.创建接口和类

​ 创建实体类,Dao接口及其mapper文件,Service接口及其实现类,Controller类

8.创建前端页面,完善功能

3.3SSM整合开发实例

功能:使用SSM整合实现学生信息管理功能

3.3.1建表

建表语句:

drop table if exists `t_student`;
create table `t_student`(
	id int(11) primary key auto_increment,
    name varchar(255) default null,
    age int(11) default null
)engine = InnoDB default charset=utf8;
insert into `t_student`(id,name,age) values(1001,'张三',20);
commit;
select * from t_student;

3.3.2创建maven项目

新建Module,选择maven,使用webapp模板

补全maven项目基本结构:java、resources

3.3.3添加依赖

在pom.xml文件中添加各种依赖和插件,完整配置如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <!--当前模块坐标-->
  <groupId>com.tsccg</groupId>
  <artifactId>05-ssm</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <!--当前项目编译和运行的jdk版本-->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
<!--添加依赖-->
  <dependencies>
      <!-- servlet依赖 -->
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
      </dependency>
      <!-- jsp依赖 -->
      <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2.1-b03</version>
        <scope>provided</scope>
      </dependency>
      <!-- SpringMVC依赖 -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.5.RELEASE</version>
      </dependency>
      <!-- Spring 事务模块依赖 -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.5.RELEASE</version>
      </dependency>
      <!-- Spring jdbc模块依赖 -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.5.RELEASE</version>
      </dependency>
      <!-- jackson依赖01 -->
      <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.9.0</version>
      </dependency>
      <!-- jackson依赖02 -->
      <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
      </dependency>
      <!-- MyBatis整合Spring依赖 -->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>1.3.1</version>
      </dependency>
      <!-- MyBatis依赖 -->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.1</version>
      </dependency>
      <!-- MySQL数据库驱动依赖 -->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.9</version>
      </dependency>
      <!-- druid连接池依赖 -->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.12</version>
      </dependency>
  </dependencies>
<!--插件-->
  <build>
      <resources>
        <!-- 目的是把src/main/java目录中的mapper文件包含到输出结果中,输出到target/classes目录中 -->
        <resource>
          <directory>src/main/java</directory><!--mapper所在的目录-->
          <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
            <include>**/*.properties</include>
            <include>**/*.xml</include>
          </includes>
          <filtering>false</filtering>
        </resource>
        <!-- 目的是把src/main/resources目录中的xml文件包含到输出结果中,输出到target/classes目录中 -->
        <resource>
          <directory>src/main/resources</directory><!--主配置文件所在的目录-->
          <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
            <include>**/*.properties</include>
            <include>**/*.xml</include>
          </includes>
          <filtering>false</filtering>
        </resource>
      </resources>
  </build>
</project>

3.3.4定义包结构

1)在main/java目录下创建存放各种接口和类的包controller、service、dao、实体类

2)在webapp目录下新建文件夹static,在里面创建存放各种静态资源的文件夹:html、images、js,

同时,在WEB-INF目录下新建一个文件夹view,用来存放作为视图的jsp文件

3.3.5配置web.xml

​ 1)注册中央调度器:DispatcherServlet,用于创建SpringMVC容器对象,进而创建Controller类对象。同时,只有servlet才能接收用户请求。

​ 2)注册Spring监听器:ContextLoaderListener,用于创建Spring容器对象,进而创建Service,Dao等对象。同时,使Spring容器只需要创建一次。

​ 3)注册字符过滤器:CharacterEncodingFilter,解决post请求乱码问题

当然,由于注册中央调度器和Spring监听器需要分别指定SpringMVC配置文件和Spring配置文件,我们在注册前,需要把它们创建出来。

在resources目录下新建一个文件夹conf,在该目录下创建springMVC.xml和spring.xml:

然后对web.xml进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--1.注册中央调度器:DispatcherServlet-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:conf/springMVC.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!--设置为处理除jsp外的所有请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--2.注册Spring监听器:ContextLoaderListener-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/spring.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--3.注册字符集过滤器-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

3.3.6配置SpringMVC配置文件

用于管理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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--1.声明组件扫描器,创建Controller对象-->
    <context:component-scan base-package="com.tsccg.controller"/>
    <!--2.声明视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--
		3.声明注解驱动
			1)响应Ajax请求,返回json
			2)解决静态资源访问冲突问题
	-->
    <mvc:annotation-driven/>
    <!--4.处理静态资源请求-->
    <mvc:resources location="/static/" mapping="/static/**" />
</beans>

3.3.7配置MyBatis配置文件

在resources/conf目录下新建mybatis.xml,指定mapper文件所在位置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 指定mapper文件的位置-->
    <mappers>
        <!-- name:包名,这个包名下所有的mapper.xml文件一次都能加载 -->
        <package name="com.tsccg.dao"/>
    </mappers>
</configuration>

3.3.8配置Spring配置文件

在Spring配置文件中,我们需要声明druid数据源、SqlSessionFactoryBean对象、dao代理对象、Service对象。

由于需要连接数据库,我们先创建一个保存数据库连接信息的属性配置文件。在resources/conf目录下新建jdbc.properties,写入MySQL数据库连接信息。

jdbc.properties:

jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/db_ssm?characterEncoding=utf8&useSSL=false
jdbc.mysql.username=root
jdbc.mysql.password=123456
jdbc.mysql.maxActive=20

在spring.xml中声明各种对象:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--引入属性配置文件-->
    <context:property-placeholder location="classpath:conf/jdbc.properties"/>
    <!-- 声明数据源DataSource -->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${jdbc.mysql.driver}"/>
        <property name="url" value="${jdbc.mysql.url}"/>
        <property name="username" value="${jdbc.mysql.username}"/>
        <property name="password" value="${jdbc.mysql.password}"/>
        <property name="maxActive" value="${jdbc.mysql.maxActive}"/>
    </bean>
    <!--声明SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="configLocation" value="classpath:conf/mybatis.xml"/>
    </bean>
    <!--声明dao代理对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.tsccg.dao"/>
    </bean>
    <!--声明service对象:使用组件扫描器扫描@Service所在包的位置-->
    <context:component-scan base-package="com.tsccg.service" />
    <!--事务配置:注解的配置,aspectJ的配置-->
</beans>

3.3.9创建实体类

在entity包下新建数据库实体类Student:

package com.tsccg.entity;

/**
 * @Author: TSCCG
 * @Date: 2021/10/10 16:12
 */
public class Student {
    private Integer id;
    private String name;
    private Integer age;

    public Student() {
    }

    public Student(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    //getter and setter
    //toString()
}

3.3.10创建Dao接口及其mapper文件

在dao包下新建Dao接口:StudentDao,在其中定义增、删、查三个抽象方法

package com.tsccg.dao;

import com.tsccg.entity.Student;

import java.util.List;

public interface StudentDao {
    /**
     * 查询所有学生信息
     */
    List<Student> selectStudents();
    /**
     * 插入一条学生信息
     */
    int insertStudent(Student student);
    /**
     * 删除一条学生信息
     */
    int deleteStudent(Integer id);
}

在同级包下创建其mapper文件:StudentDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tsccg.dao.StudentDao">
    <!-- 查询所有学生信息 -->
    <select id="selectStudents" resultType="com.tsccg.entity.Student">
        select id,name,age from t_student
    </select>
    <!-- 插入一条学生信息 -->
    <insert id="insertStudent">
        insert into t_student(name,age) values(#{name},#{age})
    </insert>
    <!-- 删除一条学生信息 -->
    <delete id="deleteStudent">
        delete from t_student where id = #{id}
    </delete>
</mapper>

3.3.11创建Service接口及其实现类

在service包下新建Service接口:StudentService,对照dao定义增、删、查三个抽象方法

package com.tsccg.service;

import com.tsccg.entity.Student;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/10/10 16:27
 */
public interface StudentService {
    /**
     * 查询所有学生信息
     */
    List<Student> findStudents();
    /**
     * 插入一条学生信息
     */
    int addStudent(Student student);
    /**
     * 删除一条学生信息
     */
    int removeStudent(Integer id);
}

在同级包下新建一个impl包,在其中创建StudentService接口实现类:StudentServiceImpl

package com.tsccg.service.impl;

import com.tsccg.dao.StudentDao;
import com.tsccg.entity.Student;
import com.tsccg.service.StudentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/10/10 16:29
 */
@Service//使用@Service创建其对象,并放入Spring容器
public class StudentServiceImpl implements StudentService {
    //定义一个dao对象,使用@Resource自动注入赋值
    @Resource
    private StudentDao dao;
	/**
	 * 调用dao对象执行查询操作,返回包含所有学生信息的List集合
	 */
    @Override
    public List<Student> findStudents() {
        return dao.selectStudents();
    }
	/**
	 * 调用dao对象执行插入操作,返回处理结果
	 */
    @Override
    public int addStudent(Student student) {
        return dao.insertStudent(student);
    }
	/**
	 * 调用dao对象执行删除操作,返回处理结果
	 */
    @Override
    public int removeStudent(Integer id) {
        return dao.deleteStudent(id);
    }
}

3.3.12创建前端页面

1.流程图

2.导航页面:index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    //动态地获取当前项目路径:http://localhost:8080/MyWeb/
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>Title</title>
    <!--使用获取到的项目路径-->
    <base href="<%=basePath%>"/>
    <style type="text/css">
        * {
            font-size: 20px;
        }
        td {
            text-align: center;
        }
    </style>
</head>
<body>
<center>
    <img src="static/images/logo.jpeg" width="150px" alt="无法加载logo.jpeg"/>
    <h2 style="font-size: 30px">学生信息管理系统</h2>

    <table border="1" cellspacing="0" cellpadding="0" width="200px">
        <tr>
            <td><a href="student/addStudentPage">注册学生信息</a></td>
        </tr>
        <tr>
            <td><a href="student/showStudentPage">展示所有学生信息</a></td>
        </tr>
    </table>
</center>
</body>
</html>
3.注册页面:addStudent

在WEB-INF/view目录下,新建addStudent.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    //动态地获取当前项目路径:http://localhost:8080/MyWeb/
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>注册页面</title>
    <!--使用获取到的项目路径-->
    <base href="<%=basePath%>"/>
    <style type="text/css">
        * {
            font-size: 20px;
        }
    </style>
</head>
<body>
<center>
    <h2>注册学生信息</h2>
    <form action="student/add" method="post">
        <table>
            <tr>
                <td>姓名:</td>
                <td><input type="text" name="name"/></td>
            </tr>
            <tr>
                <td>年龄:</td>
                <td><input type="text" name="age"/></td>
            </tr>
            <tr>
                <td><input type="submit" value="注册"/></td>
                <td><input type="button" value="返回" onclick="window.open('index.jsp','_self')"/></td>
            </tr>
        </table>
    </form>
</center>
</body>
</html>
4.展示学生列表页面:showStudent.jsp

在WEB-INF/view目录下新建showStudent.jsp

因为需要用到jquery,将jquery-3.4.1.js文件放入/static/js目录下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    //动态地获取当前项目路径:http://localhost:8080/MyWeb/
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>Title</title>
    <!--使用获取到的项目路径-->
    <base href="<%=basePath%>"/>
    <style type="text/css">
        * {
            font-size: 20px;
        }
        table {
            width: 600px;
        }
        td {
            width: 50px;
        }
    </style>
    <script type="text/javascript" src="static/js/jquery-3.4.1.js"></script>
    <script type="text/javascript">
        $(function(){
            loadList();//页面加载时调用一次
            $("#btn1").click(function() {
                loadList();//点击按钮时再调用一次
            })
        })
        //定义加载表格的函数
        const loadList = function() {
            $.ajax({
                url:"student/showList",
                type:"get",
                dataType:"json",
                success:function(resp) {
                    // alert(resp);//test
                    //获取表格的jquery对象
                    const obj = $("#show");
                    //清空表格中原有的元素
                    obj.empty();
                    //为表格添加字段名
                    obj.append("<tr>" +
                        "<td>学生编号</td>" +
                        "<td>学生姓名</td>" +
                        "<td>学生年龄</td>" +
                        "<td>删除</td>" +
                        "</tr>"
                    );
                    //为表格添加数据
                    $.each(resp, function (k, v) {
                        obj.append("<tr>" +
                            "<td>" + v.id + "</td>" +
                            "<td>" + v.name + "</td>" +
                            "<td>" + v.age + "</td>" +
                            "<td><a href='student/delete?id=" + v.id + "'>删除</a></td>" +
                            "</tr>")
                    })
                    //为表格添加按钮
                    obj.append("<tr>" +
                        "<td colspan='4'>" +
                        "<input type='button' id='btn1' value='查询'/>" +
                        "<input type='button' value='返回管理界面' onclick='window.open(\"index.jsp\",\"_self\")'/>" +
                        "</td>" +
                        "</tr>");

                }
            })
        };

    </script>
</head>
<body>
<center>
    <table id="show" border="1" cellpadding="0" cellspacing="0">

    </table>
</center>
</body>
</html>
5.展示处理结果页面:result.jsp

在WEB-INF/view目录下新建result.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    //动态地获取当前项目路径:http://localhost:8080/MyWeb/
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
<html>
<head>
    <title>结果</title>
    <base href="<%=basePath%>"/>
    <style type="text/css">
        * {
            font-size: 20px;
            color: red;
        }
    </style>
</head>
<body>
    <center>
        ${requestScope.result}
        <br/>
        <a href="index.jsp">返回管理页面</a>
    </center>
</body>
</html>

3.3.13创建Controller类

在controller包下新建StudentController,处理前端发送的各种请求。

package com.tsccg.controller;

import com.tsccg.entity.Student;
import com.tsccg.service.StudentService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/10/10 16:36
 */
@Controller
@RequestMapping("/student")
public class StudentController {
    //定义Service对象,使用自动注入为其赋值
    @Resource
    private StudentService service;

    /**
     * 处理转到注册页面的请求
     */
    @RequestMapping("/addStudentPage")
    public String registerStudent() {
        return "addStudent";
    }
    /**
     * 处理转到查询学生信息页面的请求
     */
    @RequestMapping("/showStudentPage")
    public String showStudentPage() {
        return "showStudent";
    }
    /**
     * 处理展示学生信息的Ajax请求
     * @return 返回包含所有学生信息的json数组
     */
    @RequestMapping("/showList")
    @ResponseBody
    public List<Student> findStudents() {
        return service.findStudents();
    }
    /**
     * 处理注册学生信息请求
     */
    @RequestMapping("/add")
    public ModelAndView addStudent(Student student) {
        ModelAndView mv = new ModelAndView();
        int result = service.addStudent(student);
        mv.addObject("result",result > 0 ?
                "注册成功" : "注册失败");
        mv.setViewName("result");
        return mv;
    }
    /**
     * 处理删除学生信息请求
     */
    @RequestMapping("/delete")
    public ModelAndView deleteStudent(Integer id) {
        ModelAndView mv = new ModelAndView();
        int result = service.removeStudent(id);
        mv.addObject("result",result > 0 ?
                "删除成功" : "删除失败");
        mv.setViewName("result");
        return mv;
    }
}

3.3.14开启服务器,演示功能

演示注册功能:

演示删除功能:

4.SpringMVC核心技术

4.1简化请求转发与重定向操作

4.1.1请求转发与重定向

我们知道,服务端资源之间的跳转有两种方式:

  1. 请求转发
  2. 重定向

而根据所要跳转的资源类型,又可分为:

  1. 跳转到页面
  2. 跳转到其他处理器/servlet

注意:

  1. 对于请求转发的请求,是从服务端内部访问的,即可以访问服务端外部无法访问的资源,如:WEB-INF目录下的页面
  2. 对于重定向发送的请求,是从服务端外部访问的,即不可以访问WEB-INF下的页面

4.1.2框架封装操作

SpringMVC框架把原来servlet中的请求转发和重定向操作进行了封装,简化成了两个关键字:forward和redirect

  1. forward:
    1. 表示请求转发
    2. 实现:request.getRequestDispatcher("xxx.jsp").forward(req,res);
    3. 使用方式:mv.setViewName("forword:视图文件完整相对路径");
  2. redirect:
    1. 表示重定向
    2. 实现:response.sendRedirect("xxx.jsp");
    3. 使用方式:mv.setViewName("redirect:视图文件完整相对路径");

forward和redirect有一个共同的特点,就是:不受视图解析器影响。

​ 在项目中,当有页面比如login.jsp不在WEB-INF/view目录下时,如果配置了视图解析器,就无法跳转到login.jsp页面。为了解决这种问题,就可以使用forward或者redirect关键字。

4.1.3演示关键字的作用

1.准备工作

现在使用SpringMVC重新创建一个项目,在SpringMVC配置文件里配置好视图解析器

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀-->
    <property name="prefix" value="/WEB-INF/view/"/>
    <!--后缀-->
    <property name="suffix" value=".jsp"/>
</bean>

发送请求页面:index.jsp

在/WEB-INF/view/外的位置创建一个login.jsp文件,作为视图:

2.演示forward:请求转发

在处理器方法中使用forward关键字指定login.jsp为视图,加上前后缀

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age){
        ModelAndView mv = new ModelAndView();
        mv.addObject("userName",name);
        mv.addObject("userAge",age);
        //使用forward指定视图
        mv.setViewName("forward:/login.jsp");
        return mv;
    }
}

开启服务器:

可见,成功访问到/WEB-INF/view/以外的视图文件:login.jsp,且实现了实现处理器与login.jsp间的数据共享

3.演示redirect:重定向

在处理器方法中,使用redirect关键字指定视图为:login.jsp,同样加上前后缀

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age){
        ModelAndView mv = new ModelAndView();
        mv.addObject("userName",name);
        mv.addObject("userAge",age);
        //使用redirect指定视图
        mv.setViewName("redirect:/login.jsp");
        return mv;
    }
}

开启服务器:

同样成功访问到login.jsp,但是未能实现处理器与login.jsp间的数据共享,且第二次浏览器发送的请求携带着在处理器方法中写入Model的数据。

我们来分析一下全过程:

1.浏览器第一次发送请求:http://localhost:8080/MyWeb/some.do ,携带参数:name=tom&age=21
2.处理器接收到some.do请求,对请求进行处理:
1)将请求参数写入Model:
mv.addObject("userName","tom");
mv.addObject("userAge","21");
2)重定向到login.jsp:中央调度器将login.jsp的地址和上一步Model里的数据写入响应协议包
mv.setViewName("redirect:/login.jsp");
3.浏览器第二次发送请求:http://localhost:8080/MyWeb/login.jsp?userName=tom&userAge=21
4.服务器定位到login.jsp
1)根据EL表达式${userName}从当前请求作用域中查找数据,但当前请求作用域中没有数据
2)服务器将login.jsp的页面数据响应回浏览器

框架对重定向的操作:框架会把Model中的简单类型数据,转化成String,作为第二次浏览器发送请求的参数使用,目的是实现资源间的数据共享。

那么如何在login.jsp中拿到第二次请求所携带的数据呢?

我们可以使用参数集合对象:${param.key}

重新发送请求:

4.2统一处理异常

4.2.1框架的异常处理思路

在我们之前的学习中,一直都是通过try...catch语句来处理异常的。

这些异常处理代码与业务代码无关,经常在一个页面中多次使用,造成代码的冗余。

public void fun1() {
    try {
        //业务代码
    } catch(Exception e) {
        
    }
}
public void fun2() {
    try {
        //业务代码
    } catch(Exception e) {
        
    }
}
public void fun3() {
    try {
        //业务代码
    } catch(Exception e) {
        
    }
}

为此,SpringMVC框架采用一种统一、全局的异常处理:

​ 把Controller对象的所有异常处理都集中到一个地方,采用AOP的思想,将业务逻辑代码和异常处理代码分离,实现解耦合。

需要使用两个注解:

  1. @ControllerAdvice
  2. @ExceptionHandler

4.2.2异常处理步骤分析

1.准备工作

使用SpringMVC技术新建一个maven项目:

项目包含前端页面:index.jsp,发送携带参数的请求

有一个控制器用于处理此请求

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age){
        ModelAndView mv = new ModelAndView();
        mv.addObject("userName",name);
        mv.addObject("userAge",age);
        mv.setViewName("show");
        return mv;
    }
}

视图页面:show.jsp

2.添加异常处理

现分析为以上项目添加异常处理的步骤:

1.创建三个自定义异常类

​ 1)MyUserException,继承Exception

​ 2)MyNameException,继承MyUserException,用于处理name参数异常

​ 3)MyAgeException,继承MyUserException,用于处理age参数异常

2.修改Controller,抛出自定义异常

3.创建一个普通类,作为全局异常处理类

​ 1)在类的上面添加@ControllerAdvice

​ 2)在类中定义方法,方法上面添加@ExceptionHandler

4.创建处理异常的视图页面

5.配置SpringMVC的配置文件

​ 1)声明第一个组件扫描器,用于扫描@Controller修饰的类

​ 2)声明第二个组件扫描器,用于扫描@ControllerAdvice修饰的类

​ 3)声明注解驱动

4.2.3演示异常处理

1.创建自定义异常类

在com.tsccg.exception包下新建三个自定义异常类MyUserException、MyNameException和MyAgeException

其中,MyNameException和MyAgeException是MyUserException的子类。

1)MyUserException

public class MyUserException extends Exception{
    public MyUserException() {
        super();
    }
    public MyUserException(String message) {
        super(message);
    }
}

2)MyNameException

public class MyNameException extends MyUserException{
    public MyNameException() {
    }

    public MyNameException(String message) {
        super(message);
    }
}

3)MyAgeException

public class MyAgeException extends MyUserException {
    public MyAgeException() {
        super();
    }

    public MyAgeException(String message) {
        super(message);
    }
}
2.修改Controller抛出自定义异常

在控制器方法中抛出MyNameException异常对象,MyAgeException异常对象

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age) throws MyUserException {
        ModelAndView mv = new ModelAndView();
        //判断,当参数不合要求时,抛出自定义异常
        if(!"张三".equals(name)) {
            throw new MyNameException("姓名不对!");
        }
        if(age < 0 || age > 200) {
            throw new MyAgeException("年龄不对!");
        }
        mv.addObject("userName",name);
        mv.addObject("userAge",age);
        mv.setViewName("show");
        return mv;
    }
}
3.创建全局异常处理类

在com.tsccg.handler包下创建一个普通java类:GlobalExceptionHandler,作为全局异常处理类

(1)在类的上面添加@ControllerAdvice注解:

​ 1)控制器增强,给控制器类增加功能--异常处理功能

​ 2)需要在SpringMVC配置文件中使用组件扫描器创建该类对象

(2)在类中定义方法,方法上面添加@ExceptionHandler注解:

​ 1)@ExceptionHandler(value=异常的class):表示当Controller中抛出此类型异常时,由当前方法处理。

​ 2)方法作用:处理Controller中抛出的异常。

​ 3)方法定义:和控制器方法一样,可以有多个参数,可以有ModelAndView,String,void,对象类型的返回值。

​ 4)方法参数:Exception,表示Controller中抛出的异常对象,通过形参可以获取发生的异常信息。

(3)异常处理逻辑:

​ 1)记录异常:记录到数据库,日志文件。记录的内容包括日志发生的事件,由哪个方法发生的,异常错误的内容。

​ 2)发送通知:把异常信息通过邮件、短信、微信、qq等形式发送给相关人员。

​ 3)给用户返回友好而和善的提示: Σ( ° △ °|||!!!)︴

GlobalExceptionHandler:

@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 表示当Controller中抛出MyNameException类型异常时,使用该方法处理异常
     */
    @ExceptionHandler(value = MyNameException.class)
    public ModelAndView doMyNameException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","名字错啦骚年!");
        //将异常对象一并写入请求作用域
        mv.addObject("ex",exception);
        mv.setViewName("nameError");
        return mv;
    }
    /**
     * 表示当Controller中抛出MyAgeException类型异常时,使用该方法处理异常
     */
    @ExceptionHandler(value = MyAgeException.class)
    public ModelAndView doMyAgeException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","年龄错啦骚年!");
        //将异常对象一并写入请求作用域
        mv.addObject("ex",exception);
        mv.setViewName("ageError");
        return mv;
    }
    /**
     * 处理其他类型的异常:当Controller中抛出其他类型异常时,使用该方法处理异常
     */
    @ExceptionHandler
    public ModelAndView doDefaultException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg","发生了异常!");
        //将异常对象一并写入请求作用域
        mv.addObject("ex",exception);
        mv.setViewName("defaultError");
        return mv;
    }
}
4.创建处理异常的视图页面

nameError:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>nameError</title>
    <style type="text/css">
        * {
            font-size: 20px;
            color: red;
        }
    </style>
</head>
<body>
<center>
    nameError.jsp
    <br/>
    提示信息:${requestScope.msg}
    <br/>
    错误信息:${ex.message}
</center>
</body>
</html>

ageError:

defaultError:

5.配置SpringMVC的配置文件
  1. 声明第一个组件扫描器,用于扫描@Controller修饰的类
  2. 声明第二个组件扫描器,用于扫描@ControllerAdvice修饰的类
  3. 声明注解驱动
<!--1.声明组件扫描器,扫描@Controller所在包-->
<context:component-scan base-package="com.tsccg.controller"/>
<!--2.声明组件扫描器,扫描@ControllerHandler所在包-->
<context:component-scan base-package="com.tsccg.handler"/>
<!--3.声明注解驱动-->
<mvc:annotation-driven/>
6.测试处理异常功能

开启服务器,通过浏览器发送请求:

1)符合要求的参数:

2)抛出MyNameException:

3)抛出MyAgeException:

4)抛出其他异常

4.3拦截器

4.3.1拦截器概述

什么是拦截器?

  1. 拦截器是SpringMVC框架中的一种对象,需要实现HandlerInterceptor接口

  2. 拦截器和过滤器类似,但功能方向侧重点不同

    1. 过滤器:用于过滤请求参数,设置字符编码集等工作
    2. 拦截器:用于拦截用户请求,对请求做判断处理
  3. 拦截器是全局的,可以对多个Controller做拦截

    1. 一个项目中可以有0个或多个拦截器,它们一起拦截用户的请求
    2. 拦截器常用于做用户登陆处理、权限检查、记录日志等工作
  4. 拦截器可看作是多个Controller中公用的非业务功能,集中到拦截器统一处理,使用的是AOP思想。

拦截器的使用步骤:

  1. 创建一个普通类,作为拦截器使用
    1. 实现HandlerInterceptor接口
    2. 实现接口中的三个方法
      1. preHandle():在请求处理之前,也就是处理器方法执行之前执行
      2. postHandle():在处理器方法执行之后执行
      3. afterHandle():在请求处理完成之后执行
  2. 在SpringMVC配置文件中,声明拦截器,让框架知道拦截器的存在
    1. 组件扫描器,扫描@Controller注解
    2. 声明拦截器:
      1. 指定拦截的请求uri地址
      2. 声明拦截器对象

4.3.2单个拦截器的使用

1.前提

使用SpringMVC技术新建一个maven项目:有发送请求页面,有处理请求的Controller,有响应页面。

发送请求页面:index.jsp,发送携带参数的请求

Controller:

@Controller
public class MyController {
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name,Integer age) throws Exception {
        System.out.println("====执行MyController的doSome方法====");
        ModelAndView mv = new ModelAndView();
        mv.addObject("userName",name);
        mv.addObject("userAge",age);
        mv.setViewName("show");
        return mv;
    }
}

响应页面:show.jsp

2.创建一个普通类,作为拦截器使用
  1. 实现HandlerInterceptor接口
  2. 实现接口中的三个方法
    1. preHandle():在请求处理之前,也就是处理器方法执行之前执行
    2. postHandle():在处理器方法执行之后执行
    3. afterHandle():在请求处理完成之后执行
public class MyInterceptor implements HandlerInterceptor {
    /**
     * preHandle():预处理方法,是整个项目的入口【最重要的】
     * 1.参数:Object handler,被拦截的处理器对象
     * 2.返回值:boolean
     *    1.true:请求可以通过
     *    2.false:请求到此方法就截止
     * 3.执行时机:在处理器方法执行之前执行,用户请求首先到达此方法
     * 4.功能:在这个方法中可以获取请求的信息,验证请求是否符合要求
     *    1.可以用于做用户登录验证
     *    2.可以验证用户是否有权限访问某个链接地址
     *       1.如果验证成功,放行请求,处理器方法正常执行
     *       2.如果验证失败,截断请求,处理器方法不能执行,请求不能被处理
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        System.out.println("====1.执行拦截器MyInterceptor中的preHandle方法====");
        boolean isFlag = false;
        String name = request.getParameter("name");
        if ("张三".equals(name)) {
            isFlag = true;
        }
        return isFlag;
    }

    /**
     * postHandle():后处理方法【了解】
     * 1.参数:
     *    1.Object handler:被拦截的处理器对象
     *    2.ModelAndView mv:处理器方法的返回值
     * 2.执行时机:在处理器方法之后执行
     * 3.功能:能够获取到处理器方法的返回值ModelAndView,修改其中的数据和视图,影响最后的处理结果。主要用于对原来的处理结果进行二次修正。
     */
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("====2.执行拦截器MyInterceptor中的postHandle方法====");

    }

    /**
     * afterCompletion:最后执行的方法【了解】
     * 1.参数:
     *    1.Object handler:被拦截的处理器对象
     *    2.Exception ex:程序中发生的异常
     * 2.执行时机:在请求处理完成后执行的。(在框架中,规定请求处理完成的时机为:当视图处理完毕之后,对视图执行了forward或者redirect)
     * 3.作用:一般做资源回收工作,程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收。
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        System.out.println("====3.执行拦截器MyInterceptor中的afterCompletion方法====");
    }
}

3.配置SpringMVC配置文件

在SpringMVC配置文件中,声明拦截器,让框架知道拦截器的存在

  1. 组件扫描器,扫描@Controller注解
  2. 声明拦截器:
    1. 指定拦截的请求uri地址
    2. 声明拦截器对象
<!--1.声明组件扫描器,扫描@Controller所在包-->
<context:component-scan base-package="com.tsccg.controller"/>
<!--2.声明拦截器,可以有0到多个-->
<mvc:interceptors>
    <!--声明第一个拦截器-->
    <mvc:interceptor>
        <!--1.指定拦截的请求uri地址
			path:使用uri地址,可以使用通配符 **
				例如:/user/**:拦截user开头的请求:http://localhost:8080/MyWeb/user/some.do
					 /**:拦截所有请求
		-->
        <mvc:mapping path="/**"/>
        <!--2.声明拦截器对象-->
        <bean class="com.tsccg.handler.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>
4.测试拦截器功能

开启服务器,通过浏览器发送请求:

1)preHandle()方法返回值为true:

后台输出:

====1.执行拦截器MyInterceptor中的preHandle方法====
====执行MyController的doSome方法====
====2.执行拦截器MyInterceptor中的postHandle方法====
====3.执行拦截器MyInterceptor中的afterCompletion方法====

发送请求后,拦截器验证后放行,处理器正常处理请求。

2)preHandle()方法返回值为false:

后台输出:

====1.执行拦截器MyInterceptor中的preHandle方法====

发送请求后,被拦截器预处理方法截断,处理器方法和其他拦截器方法不会执行。

5.单个拦截器中方法与处理器方法的执行顺序

5.添加拦截反馈页面

拦截器截断请求后,页面没有任何显示是因为拦截器的预处理方法没有给任何反馈。

在预处理方法截断请求时,请求转发一个反馈页面:tip.jsp

@Override
public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {
    boolean isFlag = false;
    String name = request.getParameter("name");
    System.out.println("====1.执行拦截器MyInterceptor中的preHandle方法====");
    if ("张三".equals(name)) {
        isFlag = true;
    } else {
        //请求转发到反馈页面
        request.getRequestDispatcher("/WEB-INF/view/tip.jsp").forward(request,response);
    }
    return isFlag;
}

在/WEB-INF/view文件夹下新建tip.jsp

演示:

6.使用postHandle方法修改处理结果

postHandle方法可以获取到处理器的处理结果,可以对其进行修改。

现修改处理器的处理结果,插入一条日期数据,并指定新的视图:show_02.jsp

@Override
public void postHandle(HttpServletRequest request,
                       HttpServletResponse response,
                       Object handler,
                       ModelAndView modelAndView) throws Exception {
    System.out.println("====2.执行拦截器MyInterceptor中的postHandle方法====");
    //对处理结果进行修改
    if(modelAndView != null) {
        modelAndView.addObject("date",new Date());
        modelAndView.setViewName("show_02");
    }
}

在/WEB-INF/view目录下新建show_02.jsp:

开启服务器,发送请求:

7.计算处理请求所花费时间

1.定义成员变量startTime作为初始时间戳,在preHandle方法中使用System.currentTimeMillis()为其赋值。

2.在afterCompletion方法中定义结束时间戳:endTime,计算总耗时并输出。

开启服务器,查看效果:

4.3.3多个拦截器的使用

1.创建第二个拦截器

在之前的基础上,在handler包下再创建第二个拦截器:MyInterceptor2

设置预处理方法截断条件为年龄。

public class MyInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        boolean isFlag = false;
        Integer age = Integer.parseInt(request.getParameter("age"));
        System.out.println("222====1.执行拦截器MyInterceptor2中的preHandle方法====");
        if (age >=0 && age < 200) {
            isFlag = true;
        } else {
            request.setAttribute("msg","年龄不对!");
            //请求转发到反馈页面
            request.getRequestDispatcher("/WEB-INF/view/tip.jsp").forward(request,response);
        }
        return isFlag;
    }
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("222====2.执行拦截器MyInterceptor2中的postHandle方法====");
    }
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        System.out.println("222====3.执行拦截器MyInterceptor2中的afterCompletion方法====");
    }
}
2.声明第二个拦截器

在SpringMVC配置文件中声明第二个拦截器。

先声明的拦截器先执行:在框架中存放多个拦截器使用的是ArrayList集合,按照声明的先后顺序放入到ArrayList集合中。

<!--3.声明拦截器,可以有0到多个-->
<mvc:interceptors>
    <!--声明第一个拦截器-->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.tsccg.handler.MyInterceptor" />
    </mvc:interceptor>
    <!--声明第二个拦截器-->
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.tsccg.handler.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>
3.测试运行

1)两个拦截器的preHandle方法都返回true:

后台打印结果:

111====1.执行拦截器MyInterceptor中的preHandle方法====
222====1.执行拦截器MyInterceptor2中的preHandle方法====
====执行MyController的doSome方法====
222====2.执行拦截器MyInterceptor2中的postHandle方法====
111====2.执行拦截器MyInterceptor中的postHandle方法====
222====3.执行拦截器MyInterceptor2中的afterCompletion方法====
111====3.执行拦截器MyInterceptor中的afterCompletion方法====

2)第二个拦截器的preHandle方法返回false:

后台打印结果:

111====1.执行拦截器MyInterceptor中的preHandle方法====
222====1.执行拦截器MyInterceptor2中的preHandle方法====
111====3.执行拦截器MyInterceptor中的afterCompletion方法====

3)第一个拦截器不通过:

后台打印结果:

111====1.执行拦截器MyInterceptor中的preHandle方法====
5.多个拦截器方法和处理器方法的执行顺序

当存在多个拦截器时,形成拦截器链。拦截器链的执行顺序与在配置文件中声明的顺序一致。

需要强调的是,当某一个拦截器的preHandle()方法返回true并被执行到时,会向一个专门的方法栈中放入该拦截器的afterCompletion()方法。

多个拦截器中方法与处理器方法的执行顺序如下图所示:

换一种表现形式,也可以这么理解:

4.3.4过滤器与拦截器的区别

  1. 功能侧重不同:
    1. 过滤器用来设置request,response中的参数,侧重于对数据的过滤,比如说设置字符编码集
    2. 拦截器用来验证请求,可以截断请求
  2. 所处位置不同:
    1. 过滤器是servlet中的对象
    2. 拦截器是SpringMVC框架中的对象,如果请求不被中央调度器所接收,那么请求就不会执行拦截器器中的内容
  3. 实现接口不同:
    1. 过滤器实现的是Filter接口
    2. 拦截器实现的是InterceptorHandler接口
  4. 对象的创建者不同:
    1. 过滤器是由Tomcat服务器创建的
    2. 拦截器是由SpringMVC的容器创建的
  5. 执行时机不同:
    1. 过滤器只有一个执行时机
    2. 拦截器有三个执行时机
  6. 处理的对象不同:
    1. 过滤器可以处理jsp,js,html等
    2. 拦截器侧重于处理Controller对象

4.3.5模拟登陆验证

还是使用前面单个拦截器的例子,在其基础上进行修改。

1.index.jsp
2.login.jsp

3.loginOut.jsp

4.拦截器:MyInterceptor
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        System.out.println("====1.执行拦截器MyInterceptor中的preHandle方法====");
        boolean isFlag = false;
        String loginName = "";
        //从会话作用域中获取name属性值
        Object attr = request.getSession().getAttribute("name");
        if (attr != null) {
            loginName = (String)attr;
        }
        if ("张三".equals(loginName)) {
            isFlag = true;
        } else {
            request.setAttribute("msg","只有张三有权限登陆!");
            //请求转发到反馈页面
            request.getRequestDispatcher("/WEB-INF/view/tip.jsp").forward(request,response);
        }
        return isFlag;
    }
}
5.测试验证功能

5.补充

5.1SpringMVC执行流程

执行流程简析:

  1. 浏览器发起请求到中央调度器
  2. 中央调度器直接将请求转交给处理器映射器
  3. 处理器映射器根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器
  4. 中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器
  5. 处理器适配器调用执行处理器
  6. 处理器将处理结果以及将要跳转的视图封装到一个ModelAndView对象里,然后将其返回给处理器适配器
  7. 处理器适配器直接将ModelAndView对象返回给中央调度器
  8. 中央调度器调用视图解析器,将ModelAndView对象中的视图名称封装成视图对象
  9. 视图解析器将封装了的视图对象返回给中央调度器
  10. 中央调度器调用视图对象,让其自己进行渲染,也就是数据填充,生成响应对象
  11. 中央调度器响应浏览器
posted @ 2021-10-13 14:42  TSCCG  阅读(107)  评论(0编辑  收藏  举报