[Java]SpringMVC 学习笔记(动力节点王鹤王妈妈2020)

文章汇总归纳于:https://www.yuque.com/u27599042/un32ge
SpringMVC 学习笔记


资料
1、javaWeb 链接:https://pan.baidu.com/s/1T3ouoZuZCMCAwPRv1Tahdg?pwd=5u25 提取码:5u25
2、主流框架 链接:https://pan.baidu.com/s/10HGe7wP1aed2HUCihc3-yQ?pwd=afjd 提取码:afjd
3、微服务架构 链接:https://pan.baidu.com/s/14RCkZXWyRP5hlOpDpp_ESQ?pwd=g0at 提取码:g0at
4、互联网生态 链接:https://pan.baidu.com/s/1IlM4LAU2gQqUMeN_B48t8w?pwd=egl7 提取码:egl7
6、架构师必会 链接:https://pan.baidu.com/s/10fPzIzSskuecnSxs4F4FRQ?pwd=s8tg 提取码:s8tg


SpringMVC 概述

  • SpringMVC 也叫 Spring Web MVC
  • SpringMVC 是基于 spring 的一个框架, 实际上就是 spring 的一个模块, 是专门做 web 开发的,SpringMVC 也可以理解为是 servlet 的升级版
    • web 开发的底层是 servlet,框架是在 servlet 的基础上加入了一些功能,让你做 web 开发更加方便。
    • SpringMVC其实就是Spring,只不过用在web开发方面
  • SpringMVC基于MVC架构
  • 由于 SpringMVC 是基于 Spring 的,所以SpringMVC也可以认为是一个Spring,由于 Spring 是一个容器,可以通过<bean>@Component@Repository@Service@Controller 创建对象,并且可以通过 IoC 来管理对象,因此SpringMVC也能够创建对象,并且将对象放在容器中(SpringMVC的容器),在SpringMVC容器中放的是控制器对象
    • 在SpringMVC中使用 @Controller 来标注创建控制器对象,并将控制器对象放在SpringMVC容器中,这些对象作为控制器使用,这些对象能够接收用户的请求、显示处理的结果(当做 servlet 使用,但不是 servlet)。
    • 使用 @Controller 标注创建的对象就是一个普通的Java对象,不是servlet,但是SpringMVC赋予了这些对象额外的功能,使其能够像servlet一样接收处理用户的请求,具有控制器的能力。
  • 由于使用 @Controller 标注创建的对象是一个普通的对象,所以不能直接接收用户的请求,但是SpringMVC中有一个对象DispatherServlet(中央调度器),该对象是一个Servlet,该对象负责接收用户的所有请求,然后将用户的请求转发给相应的使用 @Controller 标注创建的对象,由使用 @Controller 标注创建的对象来处理用户的请求。

SpringMVC 处理用户请求的过程

  1. 用户发送请求
  2. 由DispatherServlet(中央调度器)接收所有的用户请求
  3. DispatherServlet将用户请求转发/分配给相应的控制器对象(@Controller标注的对象)
  4. 控制器对象处理用户请求
  5. 用户请求处理结果返回给DispatherServlet
  6. DispatherServlet再将用户请求的处理结果返回给用户

第一个 SpringMVC 程序

创建项目

image.png

  • 添加缺少的目录

image.png

  • 将java目录标记为源代码的根目录

image.png
image.png

引入依赖

  • 引入 spring-webmvc 依赖,由于 springmvc 是基于 spring 的,所以引入 spring-webmvc 依赖,会简洁引入 spring 的其他依赖
  • servlet 依赖,因为底层还是 servlet
<?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.cw</groupId>
	<artifactId>springmvc-001</artifactId>
	<version>1.0-SNAPSHOT</version>
	<!-- web 项目的打包方式 -->
	<packaging>war</packaging>
	
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<!-- JDK 17 -->
		<maven.compiler.source>17</maven.compiler.source>
		<maven.compiler.target>17</maven.compiler.target>
	</properties>
	
	<dependencies>
		<!-- springmvc 依赖 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>5.2.5.RELEASE</version>
		</dependency>
		<!-- servlet 依赖 -->
		<!-- 我使用的 tomcat 为 8.x -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	
	<build>
	
	</build>
</project>

注册 DispatherServlet 对象

  • 修改 web.xml 的版本为 4.0

image.png
image.png
image.png
image.png

  • 在 web.xml 文件中注册 springmvc 的核心对象 DispatherServlet(中央调度器)
    • DispatherServlet 本身是一个 Servlet,它的父类继承 HttpServlet
    • DispatherServlet 也叫前端控制器(front controller),负责接收用户提交的请求,调用其他控制器对象,并把请求的处理结果返回给用户
    • springmvc 的项目必须有 DispatherServlet 对象
<?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 的核心对象 DispatherServlet -->
	<!-- 需要在tomcat服务器启动后,创建DispatcherServlet对象的实例。 -->
	<!-- 为什么要创建DispatcherServlet对象的实例呢? -->
	<!--
		因为DispatcherServlet在他的创建过程中, 会同时创建springmvc容器对象,
		读取springmvc的配置文件,把这个配置文件中的对象都创建好, 当用户发起
		请求时就可以直接使用对象了。
	-->
	<!--
		servlet的初始化会执行init()方法。
		DispatcherServlet在init()中会执行
		{
       //创建springmvc容器,读取配置文件
       WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc配置文件.xml");
       //把容器对象放入到ServletContext中
       getServletContext().setAttribute(key, ctx);
    }
	-->
	<servlet>
		<!--
			启动tomcat时实例化DispatcherServlet对象,会读取springmvc的配置文件
			springmvc的配置文件路径默认为:/WEB-INF/<servlet-name>-servlet.xml
			对于springmvc的配置文件路径我们也可以进行自定义
		-->
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 自定义springmvc读取配置文件路径 -->
		<init-param>
			<!-- contextConfigLocation:指定springmvc配置文件位置的属性 -->
			<param-name>contextConfigLocation</param-name>
			<!-- 指定springmvc配置文件的路径 -->
			<!-- 类路径/springmvc.xml -->
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<!--
			在tomcat启动后,创建Servlet对象
            load-on-startup:
            表示tomcat启动后创建对象的顺序。
            它的值是整数,数值越小,tomcat创建对象的时间越早。
            大于等于0的整数。
		-->
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<!-- 
			使用框架的时候, url-pattern可以使用两种值
            1. 使用扩展名方式, 语法 *.xxxx , xxxx是自定义的扩展名。 
               常用的方式 *.do, *.action, *.mvc等等
               不能使用 *.jsp
               http://localhost:8080/myweb/some.do
               http://localhost:8080/myweb/other.do
               表示以 .do 结尾的请求都交给 springmvc 这个 servlet 处理
            2.使用斜杠 "/" 
		-->
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
</web-app>

image.png

创建发起请求的页面 index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    <p><a href="test.do">发起请求</a></p>
  </body>
</html>

扫描二维码关注公众号,回复: 15677313 查看本文章

创建控制器类

  • 对于控制器类,需要使用 @Controller 注解标注,会创建控制器对象,并放到 springmvc 容器中
  • 在类中的方法上添加 @RequestMapping 注解
    • 使用该注解修饰的方法叫做处理器方法或控制器方法
    • 使用该注解修饰的方法可以处理请求,类似servlet中的doGet、doPost方法
  • 声明视图解析器,用于帮助处理视图
package cw.springmvc.controller;

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

/**
 * ClassName: MyController
 * Package: cw.springmvc.controller
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-14 21:40
 * @Version 1.0
 */
// 能处理请求的都是控制器(处理器),MyController能处理请求
// 是控制器(处理器),也叫后端控制器(back controller)
@Controller
public class MyController {
    
    
    
    /**
     * springmvc中使用方法来处理用户提交的请求
     * 方法是自定义的,可以有多种返回值、多种参数,方法名自定义
     * 使用该方法处理 test.do 的请求
     * 处理请求的方法,使用 @RequestMapping 标注,
     * 作用:把请求地址和方法绑定,一个请求指定一个方法处理
     *
     * @return ModelAndView:本次请求处理的结果
     * Model:请求处理完成后,要显示给用户的数据
     * View:视图
     * @RequestMapping: 属性:
     * value:String[] 类型,请求的uri地址,value值必须唯一,推荐以“/”开头
     * 使用位置:
     * 1. 方法上(常用)
     * 2. 类上
     */
    @RequestMapping({
    
    "/test.do"}) // / 表示该web项目的根地址
    public ModelAndView doSome() {
    
    
        ModelAndView modelAndView = new ModelAndView();
        // 添加数据, 框架在请求的最后把数据放入到request作用域。
        // request.setAttribute("msg","欢迎使用springmvc做web开发");        
        modelAndView.addObject("msg", "hello world");
        // 指定视图, 指定视图的完整路径
        // 后面框架会对视图执行的forward操作, request.getRequestDispather("/show.jsp").forward(...)
        modelAndView.setViewName("/show.jsp");
        return modelAndView;
    }
}

创建显示结果的页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
  <h1>show.jsp从request作用域中获取数据</h1>
  <h2>${msg}</h2>
</body>
</html>

创建 springmvc 的配置文件

  • springmvc 的配置文件和 spring 的配置文件一样
  • 声明组件扫描器,指定 @Controller 注解所在的包名
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
						   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="cw.springmvc.controller"/>
	
</beans>

请求处理过程分析

  1. 用户发起请求 test.do
  2. tomcat 接收用户的请求
    1. tomcat 读取web项目的xml文件,通过servlet配置的url-pattern得知 *.do 的请求要交给DispatcherServlet对象处理
  3. DispatcherServlet对象接收到tomcat转发过来的请求
    1. DispatcherServlet读取springmvc.xml配置文件,得知 test.do 请求由MyController中的doSome方法进行处理
  4. DispatcherServlet对象将请求交给MyController的doSome方法
  5. doSome方法处理请求,并将处理结果放在ModelAndView对象上,然后将ModelAndView对象返回
  6. doSome方法将请求以及请求处理结果转发给 show.jsp
  7. show.jsp 将请求处理结果返回展示给用户

springmvc.png

DispatcherServlet 源码分析

init()

  • DispatcherServlet 的父类为 HttpServlet,所以 DispatcherServlet 是一个 Servlet,创建 DispatcherServlet 对象时会执行 init() 方法。
init() {
    
    
    // 创建springmvc容器,读取配置文件
    // 读取springmvc配置文件时会进行对象的创建,并将创建出来的对象放到springmvc容器中
    WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc配置文件.xml");
    // 把容器对象放入到ServletContext中
    getServletContext().setAttribute(key, ctx);
}
  • 创建 DispatcherServlet 对象时,执行的 init() 方法在 DispatcherServlet 的父类 FrameworkServlet 的父类 HttpServletBean 中
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    
    
	public final void init() throws ServletException {
    
    
        ......
    	// HttpServletBean 的子类 FrameworkServlet 对该方法有重写,
        // DispatcherServlet 没有对该方法进行重写
        // 所以调用的为 FrameworkServlet 的 initServletBean()
        this.initServletBean(); 
    }

    protected void initServletBean() throws ServletException {
    
    
    }
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    
    
    protected final void initServletBean() throws ServletException {
    
    

        ......

        try {
    
    
            // 创建springmvc容器
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
    
    
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }
    
        ......
    
    }

    // 创建springmvc容器
    protected WebApplicationContext initWebApplicationContext() {
    
    
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null; // springmvc容器对象变量
        // springmvc容器是否已经存在
        if (this.webApplicationContext != null) {
    
    
            wac = this.webApplicationContext;
            ...
        }
    	
        if (wac == null) {
    
    
            // 找springmvc容器
            wac = this.findWebApplicationContext();
        }

        if (wac == null) {
    
    
            // 找不到springmvc容器就创建springmvc容器
            // 创建springmvc容器过程中会读取springmvc的配置文件
            wac = this.createWebApplicationContext(rootContext);
        }

        ...

        if (this.publishContext) {
    
    
            String attrName = this.getServletContextAttributeName();
            // 将springmvc容器放到web应用级作用域中(本web项目的全局作用域)
            this.getServletContext().setAttribute(attrName, wac);
        }

        // 返回springmvc容器
        return wac;
    }
}

doDispath()

  • DispatcherServlet 对象是一个 Servlet,当用户发送请求并且该请求后面将会交给 DispatcherServlet 对象处理,tomcate 会调用 DispatcherServlet 对象的 service 方法接收处理请求
  • 在 DispatcherServlet 中没有重写 service 方法,在 HttpServletBean 中也没有,service 方法在 FrameworkServlet 中有进行重写,所以会调用 FrameworkServlet 中的 service 方法
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    
    
	......
    
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
    
    
            super.service(request, response);
        } else {
    
    
            // 执行该方法,处理请求
            this.processRequest(request, response);
        }

    }

    protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        ...

        try {
    
    
            // 执行该方法
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
    
    
            ...
        } catch (Throwable var17) {
    
    
            ...
        } finally {
    
    
            ...
        }

    }

    // 由于 FrameworkServlet 中的 doService 方法为抽象方法
    // 所以会调用子类 DispatcherServlet 中重写实现的 doService 方法
    protected abstract void doService(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
    
    ......
}
public class DispatcherServlet extends FrameworkServlet {
    
    
    ......
    
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        ...

        try {
    
    
            // 调用执行该方法
            this.doDispatch(request, response);
        } finally {
    
    
            ...
        }

    }

    // DispatcherServlet 处理请求的核心方法
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
    
    
            try {
    
    
                // 模型和视图
                ModelAndView mv = null;
                ...
                
                    // 会根据请求调用相应的处理器 Controller 对象中相应的用于处理该请求的方法
                    /*
                    如果用户发送 test.do 请求
                    @RequestMapping({"/test.do"})
                    public ModelAndView doSome() {
                        ModelAndView modelAndView = new ModelAndView();
                        modelAndView.addObject("msg", "hello world");
                        modelAndView.setViewName("/show.jsp");
                        return modelAndView;
                    }
                    */
                	// mv 接收返回的 ModelAndView 对象
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                
                ...
            } catch (Exception var22) {
    
    
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
    
    
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
    
    
            ...
        }
    }

    ......
}

配置视图解析器

  • 对于不想让用户可以直接通过浏览器的url地址访问的页面(视图),可以将其放到 WEB-INF 目录下,让其受到保护,不能通过浏览器的url直接访问,此时要让视图使用时使用的路径和原来一样(即不用写 /WEB-INF/views/... ),就需要配置视图解析器
  • 视图解析器的配置写在springmvc的配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
						   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 组件扫描器 -->
	<context:component-scan base-package="cw.springmvc.controller"/>
	
	<!-- 声明springmvc框架中的视图解析器,帮助设置视图文件的路径 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 前缀:视图文件所在的目录 -->
		<!-- InternalResourceViewResolver 的 prefix 属性为String类型 -->
		<!-- 最前面的 / 表示本web资源项目的根路径,即webapp -->
		<!-- 最后的 / 表示路径 -->
		<property name="prefix" value="/WEB-INF/views/"/>
		<!-- 后缀:视图文件的扩展名 -->
		<!-- InternalResourceViewResolver 的 suffix 属性为String类型 -->
		<property name="suffix" value=".jsp"/>
	</bean>
	
</beans>

image.png

package cw.springmvc.controller;

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

/**
 * ClassName: MyController
 * Package: cw.springmvc.controller
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-14 21:40
 * @Version 1.0
 */
@Controller
public class MyController {
    
    
    @RequestMapping({
    
    "/test.do"}) 
    public ModelAndView doSome() {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "hello world");
        // 配置完后使用视图文件只需指定文件名即可
        // 框架会使用视图解析器中配置的 前缀 + 文件名 + 后缀 组成完整的视图文件路径
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

类上的 RequestMapping 注解

  • 将 RequestMapping 注解写到类上,可以把请求uri中的公共前缀部分进行抽取,相当于对路由进行分组
package cw.springmvc.controller;

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

/**
 * ClassName: MyController
 * Package: cw.springmvc.controller
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-14 21:40
 * @Version 1.0
 */
@Controller
@RequestMapping({
    
    "/study"}) // 请求uri前缀为/study匹配到该控制类
public class MyController {
    
    
    @RequestMapping({
    
    "/test.do"}) // 除去前缀后的uri为/test.do匹配到该方法处理请求
    public ModelAndView doSome() {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "hello world");
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

设置方法处理请求的类型

  • RequestMapping 注解中具有 method 属性,该属性可以用于指定 RequestMapping 标注的方法用于处理什么类型的请求,该属性的值为 RequestMethod 枚举类的枚举值
  • 如果没有指定方法处理的请求类型,则该方法可以处理任何类型的请求
@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    // 该方法用于处理请求地址为/study/test.do的GET请求
    @RequestMapping(value = {
    
    "/test.do"}, method = RequestMethod.GET)
    public ModelAndView doSome() {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "hello world");
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    // 该方法用于处理请求地址为/study/test.do的POST请求
    @RequestMapping(value = {
    
    "/test.do"}, method = RequestMethod.POST)
    public ModelAndView doSome() {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "hello world");
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

处理器方法形参

  • 处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可在方法内直接使用。
    • HttpServletRequest
    • HttpServletResponse
    • HttpSession
    • 请求中所携带的请求参数

HttpServletRequest

@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(HttpServletRequest request,
                               HttpServletResponse response,
                               HttpSession session) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", "name = " + request.getParameter("name"));
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

请求中所携带的请求参数

逐个接收

  • 要求处理器方法的形参名和请求中参数名必须一致,同名的请求参数赋值给同名的形参
  • 框架接收请求参数依旧是使用request对象,框架先通过request.getParameter()方法获取参数,然后在调用处理器方法时,将同名的请求参数赋值给同名的形参
    • 在将同名的请求参数赋值给同名的形参的过程中,对于可以进行类型转换的参数,框架会将字符串类型转换相应的参数数据类型,如果不能进行类型转换,会报相应的错误
  • 对于该种方法接收参数,get请求中文不会乱码,post请求中文会出现乱码
@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(String name, int age) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", name);
        modelAndView.addObject("age", age);
        modelAndView.setViewName("show");
        return modelAndView;
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>show.jsp</h1>
<h2>${name}</h2>
<h2>${age}</h2>
</body>
</html>

image.png

过滤器解决乱码问题

  • 过滤器可以自定义,也可以使用框架提供的过滤器 CharacterEncodingFilter
  • 在web.xml文件中,注册声明过滤器,解决post请求乱码问题
<!-- 注册声明过滤器,解决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>
  <!-- 强制请求对象(HttpServletRequest)使用encoding设置的字符编码 -->
  <init-param>
    <param-name>forceRequestEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
  <!-- 强制响应对象(HttpServletResponse)使用encoding设置的字符编码 -->
  <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>

image.png

RequestParam 注解

  • 当请求中的参数名和处理器方法中的形参名不一致时,可以使用 RequestParam 注解
  • RequestParam 注解的属性:
    • value - 请求中的参数名
    • required - 值为true,则请求参数不能为空;值为false,请求参数可以为空
  • RequestParam 注解的使用位置:处理器方法的形参前面
@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(@RequestParam("username") String name, 
                               @RequestParam("userage") int age) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", name);
        modelAndView.addObject("age", age);
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(@RequestParam("username") String name,
                               @RequestParam(value = "userage", required = false) Integer age) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", name);
        modelAndView.addObject("age", age);
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

对象接收

  • 处理器方法的形参可以使用一个对象参数,一次性接收所有的请求参数,但是需要保证请求参数名和对象的属性同名
  • 框架会创建形参对应的对象,然后给该对象的属性赋值,如果请求中的参数名为name,框架会调用对象的setName()方法(set注入)
  • 在该方式中不能使用 RequestParam 注解
public class User {
    
    
    private String name;
    private Integer age;
    
    public User() {
    
    
    }
    
    public String getName() {
    
    
        return name;
    }
    
    public void setName(String name) {
    
    
        this.name = name;
    }
    
    public Integer getAge() {
    
    
        return age;
    }
    
    public void setAge(Integer age) {
    
    
        this.age = age;
    }
}
@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(User user) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", user.getName());
        modelAndView.addObject("age", user.getAge());
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

image.png

处理器方法的返回值

ModelAndView

  • ModelAndView 包含数据部分和视图部分,其中 Model 是数据,最终数据会放到request作用域中,View 是视图,对于视图框架采用的是 forward 转发
  • 若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时处理器方法返回 ModelAndView 比较好。
  • 若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView 对象。
@Controller
@RequestMapping({
    
    "/study"})
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public ModelAndView doSome(User user) {
    
    
        ModelAndView modelAndView = new ModelAndView();
        // 向 ModelAndView 对象上放数据
        modelAndView.addObject("name", user.getName());
        modelAndView.addObject("age", user.getAge());
        // 设置视图
        modelAndView.setViewName("show");
        return modelAndView;
    }
}

String(视图)

  • 如果处理器方法返回的是字符串,则这个字符串表示的是视图,这个字符串可以是视图的逻辑名称,也可以是视图的完整路径,通过配置视图解析器解析可以将其转换为物理视图地址
  • 若要跳转的资源为内部资源,则视图解析器可以使用 InternalResourceViewResolver 内部资源视图解析器,此时处理器方法返回的字符串就是要跳转页面的文件名去掉文件扩展名后的部分,这个字符串与视图解析器中的 prefix、suffix 相结合,即可形成要访问的 URI
  • 如果返回的字符串为视图的完整路径,则项目中不能配置视图解析器,如果配置了视图解析,则返回的视图完整路径会被作为视图的逻辑名称与视图解析器中的 prefix、suffix 相结合,形成要访问的 URI
  • 对于视图,框架执行 forward 转发操作
  • 在springmvc.xml文件中配置视图解析器
<!-- 声明springmvc框架中的视图解析器,帮助设置视图文件的路径 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <!-- 前缀:视图文件所在的目录 -->
  <!-- InternalResourceViewResolver 的 prefix 属性为String类型 -->
  <!-- 最前面的 / 表示本web资源项目的根路径,即webapp -->
  <!-- 最后的 / 表示路径 -->
  <property name="prefix" value="/WEB-INF/views/"/>
  <!-- 后缀:视图文件的扩展名 -->
  <!-- InternalResourceViewResolver 的 suffix 属性为String类型 -->
  <property name="suffix" value=".jsp"/>
</bean>
@Controller
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public String doSome(HttpServletRequest request, String name, Integer age) {
    
    
        System.out.println("name: " + name + " " + "age: " + age);
        // 手动将数据添加到request作用域中
        request.setAttribute("name", name);
        request.setAttribute("age", age);
        return "show";
    }
}

image.png

void

  • 处理器方法返回 void 的应用场景,AJAX 响应
  • 处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void
  • 通过response对象向浏览器响应数据
  • 处理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>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript" src="js/jquery-3.4.1.js"></script>
    <script>
        $(function () {
      
      
            $("#btn").click(function () {
      
      
                $.ajax({
      
      
                    url: "test.do",
                    data: {
      
      
                        name: "李四",
                        age: 22
                    },
                    type: "post",
                    dataType: "json",
                    success: function (res) {
      
      
                        console.log(res)
                    }
                })
            })
        })

    </script>
</head>
<body>
    <h1>index</h1>
    <button id="btn">发起AJAX请求</button>
</body>
</html>
@Controller
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    public void doSome(HttpServletResponse response, String name, Integer age) {
    
    
        System.out.println("name: " + name + " " + "age: " + age);
        User user = new User();
        user.setName(name);
        user.setAge(age);
        // 把结果对象转化为JSON格式,使用jackson
        ObjectMapper objectMapper = new ObjectMapper();
        String res = null;
        try {
    
    
            res = objectMapper.writeValueAsString(user);
        } catch (JsonProcessingException e) {
    
    
            e.printStackTrace();
        }
        // 设置响应的文本类型和字符集
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = null;
        try {
    
    
            // 获取输出对象
            out = response.getWriter();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        out.println(res);
        out.flush();
        out.close();
    }
}

image.png

Object

  • 处理器方法也可以返回 Object 对象,这个 Object 可以是 Integer,String,自定义对象,Map,List 等。
  • 返回的对象是数据。
  • 返回对象,可以响应AJAX请求
  • 使用步骤:
    • 加入处理JSON的工具库依赖,springmvc默认使用Jackson
    • 在springmvc配置文件中,加入 <mvc:annotation-driven> 注解驱动
      • <mvc:annotation-driven> 用于实现对象向JSON格式字符串、xml、text、二进制等数据格式的转换
      • java对象转换为JSON格式字符串、xml、text、二进制等数据格式的方法由HttpMessageConverter(消息转换器)接口定义,该接口的实现类实现了java对象向JSON格式字符串、xml、text、二进制等数据格式的转换
public interface HttpMessageConverter<T> {
    
    
    boolean canRead(Class<?> var1, @Nullable MediaType var2);
	// 检查处理器方法的返回值是否可以转化为var2表示的数据格式
    boolean canWrite(Class<?> var1, @Nullable MediaType var2);

    List<MediaType> getSupportedMediaTypes();

    T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
	// 将处理器方法的返回值对象,调用jackson中的ObjectMapper对象转化为JSON字符串
    void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
  - 实现类
     - MappingJackson2HttpMessageConverter
        - 负责读取和写入 json 格式的数据。
        - 利用Jackson 的 ObjectMapper 将java对象转换为JSON
     - StringHttpMessageConverter
        - 负责读取字符串格式的数据和写出字符串格式的数据
  - `<mvc:annotation-driven>` 该标签加入springmvc配置文件后,会自动创建HttpMessageConverter接口的七个实现类对象,包含MappingJackson2HttpMessageConverter
     - ![image.png](https://cdn.nlark.com/yuque/0/2023/png/28006470/1684321584048-f97e5d63-2e0a-4b8b-b98e-05a4987db989.png#averageHue=%23f1edeb&clientId=ufce7901c-eb44-4&from=paste&height=540&id=ud85d03f9&originHeight=675&originWidth=1028&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=239157&status=done&style=none&taskId=uaee66f25-5caf-4598-9d95-9569700bff7&title=&width=822.4)
  • 在处理方法上使用 @ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。
    • 通过response对象输出数据,响应AJAX请求
<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>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  
       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 https://www.springframework.org/schema/mvc/spring-mvc.xsd">

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

</beans>
@Controller
public class MyController {
    
    
    @RequestMapping(value = {
    
    "/test.do"})
    @ResponseBody
    public User doSome(String name, Integer age) {
    
    
        System.out.println("name: " + name + " " + "age: " + age);
        User user = new User();
        user.setName(name);
        user.setAge(age);
        // 框架会将该对象转化为JSON格式,并将转换后的 JSON 数据放入到响应体中
        return user;
    }
}

image.png

  • 返回对象框架的处理流程:
    • 框架会把返回User类型,调用框架的中ArrayList中每个类的canWrite()方法检查那个HttpMessageConverter接口的实现类能处理User类型的数据–MappingJackson2HttpMessageConverter
    • 框架会调用实现类的write(), MappingJackson2HttpMessageConverter的write()方法把User对象转为json, 调用Jackson的ObjectMapper实现转为json
      • contentType: application/json;charset=utf-8
    • 框架会调用@ResponseBody把转化后的结果数据输出到浏览器, ajax请求处理完成

List

  • 向浏览器客户端返回List集合类型的数据,会被转化为JSON类型的数组
@Controller
public class MyController {
    
    
    
    @RequestMapping(value = {
    
    "/list"})
    @ResponseBody
    public List<User> getList() {
    
    
        List<User> users = new ArrayList<>();
        users.add(new User("张三", 23));
        users.add(new User("李四", 33));
        return users;
    }
    
}

image.png

  • 返回List集合框架的处理流程:
    • 框架会把返回的List集合,调用框架的中ArrayList中每个类的canWrite()方法检查那个HttpMessageConverter接口的实现类能处理User类型的数据–MappingJackson2HttpMessageConverter
    • 框架会调用实现类的write(), MappingJackson2HttpMessageConverter的write()方法把List集合转为json形式的数组, 调用Jackson的ObjectMapper实现转为json形式的数组
      • contentType: application/json;charset=utf-8
    • 框架会调用@ResponseBody把转化后的结果数据输出到浏览器, ajax请求处理完成

String(数据)

  • 处理器方法的返回值是String,如果处理器方法有使用ResponseBody注解标记,则返回的String为数据,否知返回的String为视图
@RequestMapping(value = {
    
    "/string"})
@ResponseBody
public String returnString() {
    
    
    return "向浏览器响应 String 类型的数据";
}

image.png
image.png

处理响应文本类型和编码问题

  • 在RequestMapping注解中添加produces属性,来设置响应文本的类型和编码
// 响应文本类型为普通文本,文本的编码为utf-8
@RequestMapping(value = {
    
    "/string"}, produces = {
    
    "text/plain;charset=utf-8"})
@ResponseBody
public String returnString() {
    
    
    return "向浏览器响应 String 类型的数据";
}

image.png
image.png

处理流程分析

  • 返回String类型数据框架的处理流程:
    • 框架会把返回的String类型数据,调用框架的中ArrayList中每个类的canWrite()方法检查那个HttpMessageConverter接口的实现类能处理User类型的数据–StringHttpMessageConverter
    • 框架会调用实现类的write(), StringHttpMessageConverter的write()方法把String类型数据按照指定的编码和文本类型处理
      • text/html;charset=ISO-8859-1
    • 框架会调用@ResponseBody把转化后的结果数据输出到浏览器, ajax请求处理完成
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
    
    
    public static final Charset DEFAULT_CHARSET; // 默认字符集 ISO-8859-1

    public StringHttpMessageConverter(Charset defaultCharset) {
    
    
        // MediaType.TEXT_PLAIN  "text/plain"
        super(defaultCharset, new MediaType[]{
    
    MediaType.TEXT_PLAIN, MediaType.ALL});
        this.writeAcceptCharset = false;
    }
    
	...
}

中央调度器 url-pattern 为 / 的问题

Tomcat 的 DefaultServlet

  • Tomcat 本身可以处理静态资源的访问,如HTML、图片、JS文件等
  • 在Tomcat的配置文件web.xml中可以看到
  • image.png
  • image.png
  • Tomcat的web.xml文件中配置了一个默认的servlet,在Tomcat服务器启动的时候会自动创建该servlet对象
  • image.png
  • 所有web应用程序的默认servlet,用于处理静态资源,以及没有其他servlet处理的请求
  • image.png
  • DefaultServlet 对应的请求地址为 /

中央调度器 url-pattern 为 / 会出现的问题

  • 当我们的项目中存在servlet对应的映射请求地址为 /,则该servlet会去替代Tomcat中的DefaultServlet,因为Tomcat收到请求后,发现 / 由servlet会进行处理,就会把该请求交给该servlet进行处理,就不会调用DefaultServlet处理请求
  • 所以将自己的servlet对应的请求地址设置为 /,会导致静态资源无法进行访问,因为在默认情况下DefaultServlet没有处理静态资源的能力,没有控制器可以处理静态资源的访问
  • 将自己的servlet对应的请求地址设置为 /,动态资源是能够进行访问的,因为由相应的控制器能够处理请求

处理中央调度器 url-pattern 为 / 的问题(静态资源的访问)

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

  • 在springmvc配置文件中声明了 <mvc:default-servlet-handler/> 后,springmvc框架会在容器中创建DefaultServletHttpRequestHandler处理器对象。
  • DefaultServletHttpRequestHandler处理器对象会像一个检查员,对进入DispatcherServlet的 URL 进行筛查,如果发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的Servlet 处理。
    • 一般的服务器都有默认的 Servlet。
    • 在 Tomcat 中,有一个专门用于处理静态资源访问的 Servlet 名叫 DefaultServlet。其为 default。可以处理各种静态资源访问请求。该 Servlet 注册在 Tomcat 服务器的 web.xml 中。在 Tomcat 安装目录/conf/web.xml。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
	
	<!-- 组件扫描 -->
	<context:component-scan base-package="cw.springmvc"/>
	
	<!-- 注解驱动 -->
	<mvc:annotation-driven/>
	
	<!-- 默认servlet处理器 -->
	<mvc:default-servlet-handler/>
</beans>

image.png

public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware {
    
    
	...
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    
        Assert.state(this.servletContext != null, "No ServletContext set");
        // 获取请求转发器对象
        RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
        if (rd == null) {
    
    
            throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName + "'");
        } else {
    
    
            // 请求转发
            rd.forward(request, response);
        }
    }
    ...
}

<mvc:default-servlet-handler/>与 @RequestMapping 的冲突问题
  • <mvc:default-servlet-handler/> 和 @RequestMapping 有冲突问题,需要加入注解驱动 <mvc:annotation-driven/> 解决冲突问题
  • 没有 <mvc:annotation-driven/><mvc:default-servlet-handler/> 会将所有的请求交给 Web 应用服务器默认的Servlet 处理。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
	
	<!-- 组件扫描 -->
	<context:component-scan base-package="cw.springmvc"/>
	
	<!-- 注解驱动 -->
	<!-- <mvc:annotation-driven/> -->
	
	<!-- 默认servlet处理器 -->
	<mvc:default-servlet-handler/>
</beans>

image.png

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
	
	<!-- 组件扫描 -->
	<context:component-scan base-package="cw.springmvc"/>
	
	<!-- 注解驱动 -->
	<mvc:annotation-driven/>
	
	<!-- 默认servlet处理器 -->
	<mvc:default-servlet-handler/>
</beans>

image.png

方式二:使用<mvc:resources/>

  • 在 Spring3.0 版本后,Spring 定义了专门用于处理静态资源访问请求的处理器ResourceHttpRequestHandler。
  • 并且添加了<mvc:resources/>标签,专门用于解决静态资源无法访问问题。
  • 需要在 springmvc 配置文件中添加如下形式的配置:
<!--
  处理静态资源的方式二:
    使用<mvc:resources/>
  该标签加入配置文件后,框架会创建ResourceHttpRequestHandler处理器对象
  让这个对象处理静态资源的访问,不依赖web服务器
-->
<!-- 
  mapping:访问静态资源的URI地址,使用通配符 **
  location:静态资源在项目中的目录位置 
-->
<!-- /image/**:表示项目image目录下的任意文件或目录 -->
<!-- 当前请求资源符合 /image/** 时,到 /image/ 寻找资源,第一个/表示项目webapp目录 -->
<mvc:resources mapping="/image/**" location="/image/"/>
<mvc:resources mapping="/html/**" location="/html/"/>

image.png

<mvc:resources/>与 @RequestMapping 的冲突问题

image.png

  • <mvc:resources/> 和 @RequestMapping 有冲突问题,需要加入注解驱动 <mvc:annotation-driven/> 解决冲突问题

方式三:一条配置处理所有静态资源

  • 将所有的静态资源放到webapp目录下的static目录下
    • image.png
  • springmvc配置文件的配置如下:
<!-- 注解驱动 -->
<!-- 避免冲突问题 -->
<mvc:annotation-driven/>

<!-- 
  当前请求资源的路径符合 /static/** 时,
  到 /static/ 寻找资源,
  第一个 / 表示项目webapp目录 
-->
<mvc:resources mapping="/static/**" location="/static/"/>

image.png

绝对路径和相对路径

  • 绝对路径:带有协议名称、主机IP以及端口号的地址
  • 相对地址:没有协议、主机IP以及端口号开头的地址,相对地址不能独立使用,必须有一个参考地址,通过参考地址+相对地址才能指定资源

对于前端

  • 对于前端,如果地址不以 / 开头,如:1.html,则该请求的相当于 ./1.html,即向服务器请求当前访问目录下的 1.html 资源
    • 相当于相对路径
    • 如当前访问的地址为 http://localhost:8080/springmvc/index.jsp1.html 相当于 ./1.html,等于 http://localhost:8080/springmvc/1.html
  • 对于前端,如果地址以 / 开头,如:/1.html,则该请求的相当于向服务器请求服务器根目录下的 1.html 资源
    • 相当于绝对路径
    • 如当前访问的地址为 http://localhost:8080/springmvc/index.jsp/1.html 相当于http://localhost:8080/1.html

image.png
image.png
image.png
image.png
image.png
image.png

对于后端

  • 对于后端,如果地址以 / 开头,则开头的 / 相当于当前项目的 webapp 目录
    • 相当于绝对路径
    • 如当前服务器所在的地址为 http://localhost:8080/springmvc/list,在服务器要进行请求转发到 /1.html,则转发的地址相当于 http://localhost:8080/wabapp/1.html
    • 项目部署之后 wabapp 目录就相当于项目的根目录,项目的根目录为 springmvc
      • image.png
      • image.png
@RequestMapping(value = {
    
    "/list"})
public ModelAndView list() {
    
    
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/1.html");
    return modelAndView;
}

image.png
image.png

  • 对于后端,如果地址不以 / 开头,则相当于相对路径,相对当前所在目录下寻找资源,与 ./ 效果相同
@RequestMapping(value = {
    
    "/list"})
public ModelAndView list() {
    
    
    ModelAndView modelAndView = new ModelAndView();
    // 请求访问的地址(当前该方法对应的地址)http://localhost:8080/springmvc/list
    // 1.html <=> ./1.html 
    // 当前所在目录为 http://localhost:8080/springmvc/
    // 对于项目部署之后webapp项目就是项目的根目录
    // 当前所在目录相当于 http://localhost:8080/webapp/
    // 所以 http://localhost:8080/springmvc/./1.html 
    // <=> http://localhost:8080/webapp/1.html
    modelAndView.setViewName("1.html");
    return modelAndView;
}

image.png

@RequestMapping(value = {
    
    "/list"})
public ModelAndView list() {
    
    
    ModelAndView modelAndView = new ModelAndView();
    // static/html/hello.html <=> ./static/html/hello.html
    // http://localhost:8080/sprinmvc/list
    // <=> http://localhost:8080/webapp/static/html/hello.html
    modelAndView.setViewName("static/html/hello.html");
    return modelAndView;
}

image.png
image.png

@RequestMapping(value = {
    
    "/user/aaa/list"})
public ModelAndView list() {
    
    
    ModelAndView modelAndView = new ModelAndView();
    // http://localhost:8080/springmvc/user/aaa/list
    // ../user.html 回退一层目录
    // http://localhost:8080/springmvc/user/aaa/../user.html
    // <=> http://localhost:8080/springmvc/user/aaa/user.html
    // <=> http://localhost:8080/webapp/user/aaa/user.html
    modelAndView.setViewName("../user.html");
    return modelAndView;
}

image.png
image.png

项目路径访问

动态获取web项目名 HttpServletRequest.getContextPath()

<!-- ${pageContext.request.contextPath} 动态获取web项目名 -->
<a href="${pageContext.request.contextPath}/static/html/hello.html">
    ${pageContext.request.contextPath}/static/html/hello.html"
</a>

image.png
image.png

base 标签设置 html 页面访问地址的基地址

  • base 标签设置的是所有 html 页面访问地址的基地址
  • 使用了base标签之后,html页面中没有以/开头的地址,都会以base标签中设置的地址为参考地址
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-05-24
  Time: 12:36
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <base href="http://localhost:8080/${pageContext.request.contextPath}/">
</head>
<body>
<h2>Hello World!</h2>
<a href="1.html">跳转链接:1.html</a><br/>
<a href="2.html">跳转链接:2.html</a><br/>
<a href="./3.html">跳转链接:./3.html</a><br/>
<a href="4.html">跳转链接:4.html</a><br/>
<a href="static/html/hello.html">
    static/html/hello.html"
</a>
</body>
</html>

image.png
image.png

动态获取请求基地址

<%
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() +
            request.getContextPath() + "/";
%>
// request.getScheme() 获取请求的协议
// request.getServerName() 获取服务器IP
// request.getServerPort() 获取端口号
// request.getContextPath() 获取web项目名

SSM 整合

整合开发思路

  • SSM:SpringMVC + Spring + MyBatis
    • SpringMVC:视图层,界面层,负责接收请求,显示处理结果
    • Spring:业务层,负责管理Service、Dao、工具类等对象
    • MyBatis:持久层,负责数据库的访问
  • 请求的处理过程:用户发起请求,由SpringMVC接收用户请求,调用Spring中的Service对象进行相应的业务处理,如果需要涉及数据库,则由Service调用MyBatis进行数据库数据访问,MyBatis将查询的数据交给Service进行业务处理,Service处理完成后将处理结果交给SpringMVC,最后由SpringMVC将结果返回给用户
  • SSM也叫SSI,因为IBatis是MyBatis的前身
  • 整合中有两个容器
    • SpringMVC:负责管理Controller控制器对象
    • Spring:负责管理Service、Dao、工具类等对象
  • 我们进行整合,就需要将使用的对象交给合适的容器进行管理,Controller控制器对象和web相关的对象交给SpringMVC,Service、Dao、工具类等对象交给Spring
  • Spring容器与SpringMVC容器之间,有已经确定好的关系,SpringMVC容器是Spring容器的子容器,子容器可以访问父容器,在子容器中的Controller可以访问父容器中的Service对象
    • 所以我们不需要构建两个容器之间的关系,我们只需要将使用的对象放到相应的容器中即可

数据库准备

create database ssm;

use ssm;

create table t_student(
    id int primary key auto_increment,
    name varchar(30),
    age int
);

创建项目

  • 使用Maven骨架创建web项目
  • image.png

引入依赖

  • 需要引入的依赖:SSM三个框架的依赖,MySQL驱动依赖,Druid连接池依赖,Jackson依赖,JSP依赖,Servlet依赖
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  <!-- 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>
  <!-- 事务相关的依赖 -->
  <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 依赖 -->
  <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>
  <!-- Spring 整合 MyBatis 依赖 -->
  <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>
  <!-- 数据库连接驱动依赖 -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
  </dependency>
  <!-- 德鲁伊连接池依赖 -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
  </dependency>
</dependencies>

引入插件

<build>
  <resources>
    <!--配置打包构建时识别资源文件所在的目录,将资源文件一起打包-->
    <resource>
      <directory>src/main/java</directory><!--所在的目录-->
      <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
        <include>**/*.properties</include>
        <include>**/*.xml</include>
      </includes>
      <filtering>false</filtering>
    </resource>
  </resources>
  <plugins>
    <!--编译插件-->
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <!--源码和打包后使用的JDK都为8-->
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

创建 resources 和 java 目录

image.png

创建配置文件

  • 在resources目录下新建conf目录,用于存放配置文件
  • image.png

SpringMVC 配置文件

image.png

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

  <!-- springmvc 配置文件,用于声明 controller 和其他 web 相关的对象 -->

</beans>

Spring 配置文件

image.png

<?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">
<!-- spring 配置文件,创建Service、Dao等对象 -->
</beans>

数据库属性配置文件

image.png

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/dbtest
jdbc.username=root
jdbc.password=123123

MyBatis 配置文件

image.png

<?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>
	<!-- 指定别名 -->
	<typeAliases>
		<!-- 实体类的别名,指定包名,自动为包下的所有实体类取别名 -->
		<package name="cw.springmvc.study.pojo"/>
	</typeAliases>

	<mappers>
		<!-- 指定SqlMapper文件所在的包,SqlMapper文件的文件名和所在的包必须和接口一样 -->
		<package name="cw.springmvc.study.dao"/>
	</mappers>
</configuration>

配置文件 web.xml

配置注册 DispatchServlet

  • 创建SpringMVC容器对象,读取SpringMVC配置文件,创建Controller对象
  • 创建 DispatchServlet,接收用户请求
<!-- 注册中央调度器 -->
<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- 配置 SpringMVC 配置文件 -->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/dispatcherServlet.xml</param-value>
  </init-param>
  <!-- tomcat 启动时就创建该对象 -->
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcherServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

配置注册 Spring 监听器 ContextLoaderListener

  • 创建Spring容器对象,读取Spring配置文件,创建Service、Dao等对象
<!-- 注册 spring 监听器 -->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <!--spring配置文件-->
  <param-value>classpath:conf/applicationContext.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • 监听容器的创建等事件,SpringMVC容器为Spring容器的子容器,SpringMVC容器Spring容器创建会触发监听器读取Spring配置文件,创建Service、Dao对象放置Spring容器中

配置注册字符集过滤器

  • 配置字符集过滤器,设置请求和响应数据的编码
  • 解决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>
  <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>

创建包 package

  • controller
  • service
  • dao 或 mapper
  • pojo 或 bean 或 domain

image.png

编写 SpringMVC 配置文件

controller 组件扫描器

<!-- controller 组件扫描器 -->
<context:component-scan base-package="cw.springmvc.study.controller"/>

视图解析器

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

注解驱动

<!-- 注解驱动 -->
<!--
  1. 使处理方法的返回值响应给客户端,处理ajax请求
  2. 解决静态资源与@RequestMapping冲突问题
-->
<mvc:annotation-driven/>

编写 Spring 配置文件

  • 整合之后,Dao相关类的对象放在Spring容器中管理,所以MyBatis数据库等相关配置写在Spring配置文件中

声明数据源

<!-- 从类根路径下加载连接数据库配置属性 -->
<context:property-placeholder location="classpath:conf/jdbc.properties"/>

<!-- 数据源 druid -->
<!-- init-method 初始化方法;destroy-method 销毁方法 -->
<bean 
    id="dataSource" 
    class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init"
    destroy-method="close"
>
  <property name="driverClassName" value="${jdbc.driver}"/>
  <property name="url" value="${jdbc.url}"/>
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
</bean>

配置 SqlSessionFactoryBean

  • 使用MyBatis操作数据库需要通过SqlSessionFactory对象来获取与数据库之间的会话对象
  • 配置SqlSessionFactoryBean,其实就是在进行MyBatis的相关配置
<!-- SqlSessionFactoryBean对象 -->
<!-- 在spring容器中注册SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!-- 数据源 -->
  <property name="dataSource" ref="dataSource"/>
  <!-- MyBatis核心配置文件 -->
  <property name="configLocation" value="classpath:conf/mybatis-config.xml"/>
</bean>

MyBatis 组件扫描器

<!-- 声明MyBatis的扫描器 -->
<!-- 
	我们需要使用底层自动生成Mapper接口的代理类对象
	声明MyBatis的Mapper扫描器,会自动扫描Mapper接口创建代理类对象
	并放到Spring容器中
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <!-- SqlSessionFactoryBean对象的id,使用SqlSessionFactoryBean对象是哪个 -->
  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
  <!-- Mapper接口所在的包 -->
  <property name="basePackage" value="cw.springmvc.study.dao"/>
</bean>

service 组件扫描器

<!-- service 组件扫描器 -->
<context:component-scan base-package="cw.springmvc.study.service"/>

事务配置

<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- 数据源 -->
  <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 开启事务的注解驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>

配置 <mvc:resources/> 标签处理中央调度器路径为 /

  • 在 springmvc 配置文件中,配置 <mvc:resources/> 标签
<!-- 配置静态资源访问 -->
<mvc:resources mapping="/static/**" location="/static/"/>
  • 创建静态资源目录
  • image.png

程序代码

pojo

  • 实体类:Student
package cw.springmvc.study.pojo;

/**
 * ClassName: Student
 * Package: cw.springmvc.study.pojo
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 15:54
 * @Version 1.0
 */
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;
    }
    
    @Override
    public String toString() {
    
    
        return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}';
    }
    
    public Integer getId() {
    
    
        return id;
    }
    
    public void setId(Integer id) {
    
    
        this.id = id;
    }
    
    public String getName() {
    
    
        return name;
    }
    
    public void setName(String name) {
    
    
        this.name = name;
    }
    
    public Integer getAge() {
    
    
        return age;
    }
    
    public void setAge(Integer age) {
    
    
        this.age = age;
    }
}

Dao

  • 操作数据库的接口定义:StudentDao
/**
 * ClassName: StudentDao
 * Package: cw.springmvc.study.dao
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 15:57
 * @Version 1.0
 */
public interface StudentDao {
    
    
    
    /**
     * 插入学生信息
     * 
     * @param student 学生信息
     * @return 影响数据库的条数
     */
    int insertStudent(Student student);
    
    /**
     * 查询全部学生信息
     * 
     * @return 学生信息
     */
    List<Student> selectStudents();
    
}
  • SQL映射配置文件:StudentDao
<?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="cw.springmvc.study.dao.StudentDao">
	
	<insert id="insertStudent">
		insert into t_student(name, age)
		values (#{name}, #{age});
	</insert>
	
	<select id="selectStudents" resultType="Student">
		select id, name, age from t_student order by id desc;
	</select>
	
</mapper>

Service

  • 业务层接口:StudentService
package cw.springmvc.study.service;

import cw.springmvc.study.pojo.Student;

import java.util.List;

/**
 * ClassName: StudentService
 * Package: cw.springmvc.study.service
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 16:05
 * @Version 1.0
 */
public interface StudentService {
    
    
    
    /**
     * 添加学生信息
     *
     * @param student 学生信息
     * @return
     */
    int addStudent(Student student);
    
    /**
     * 查询所有学生信息
     *
     * @return 学生信息
     */
    List<Student> findStudents();
    
}
  • 业务层接口实现类:StudentServiceImpl
package cw.springmvc.study.service.impl;

import cw.springmvc.study.dao.StudentDao;
import cw.springmvc.study.pojo.Student;
import cw.springmvc.study.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * ClassName: StudentServiceImpl
 * Package: cw.springmvc.study.service.impl
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-28 16:07
 * @Version 1.0
 */
@Service
public class StudentServiceImpl implements StudentService {
    
    
    @Autowired // 根据类型自动装配
    private StudentDao studentDao;
    
    @Override
    public int addStudent(Student student) {
    
    
        return studentDao.insertStudent(student);
    }
    
    @Override
    public List<Student> findStudents() {
    
    
        return studentDao.selectStudents();
    }
}

controller

package cw.springmvc.study.controller;

import cw.springmvc.study.pojo.Student;
import cw.springmvc.study.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.List;

/**
 * ClassName: StudentController
 * Package: cw.springmvc.study.controller
 * Description:
 *
 * @Author tcw
 * @Create 2023-06-05 18:33
 * @Version 1.0
 */
@Controller
@RequestMapping("/student")
public class StudentController {
    
    
    
    @Autowired // 自动装配
    private StudentService studentService;
    
    /**
     * 处理新增学生信息请求
     *
     * @return 视图和数据
     */
    @RequestMapping("/add")
    public ModelAndView addStudent(Student student) {
    
    
        // 调用Service处理新增学生信息业务
        int cnt = studentService.addStudent(student);
        ModelAndView mv = new ModelAndView();
        // 添加响应数据
        if (cnt > 0) {
    
    
            mv.addObject("msg", "学生【" + student.getName() + "】注册成功");
        } else {
    
    
            mv.addObject("msg", "学生【" + student.getName() + "】注册失败");
        }
        // 设置响应视图
        mv.setViewName("result");
        return mv;
    }
    
    
    /**
     * 查询所有的学生信息
     *
     * @return 所有的学生信息
     */
    @RequestMapping("/list")
    @ResponseBody
    public List<Student> selectStudentAll() {
    
    
        // 查询学生信息
        return studentService.findStudents();
    }
    
}

JSP

  • image.png
  • index.jsp
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-06-05
  Time: 18:43
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>
    <script>
        $(function () {
      
      
            $("button").click(function (){
      
      
                // 发请求
                $.ajax({
      
      
                    url: "./student/list",
                    type:"get",
                    dataType:"json",
                    success(data) {
      
      
                        let newData = data.map((d) => {
      
      
                            return d.id + " " + d.name + " " + d.age + "<br/>";
                        })
                        $("div").html(""); // 清除
                        $("div").append(newData); // 追加新数据
                    }
                })
            })
        })
    </script>
</head>
<body>
<p>SSM</p>
<form action="./student/add" method="post">
    姓名:<input name="name" type="text"><br/>
    年龄:<input name="age" type="text"><br/>
    <input value="提交" type="submit">
</form>
<br/>
<button>查询学生信息</button>
<div></div>
</body>
</html>
  • result.jsp
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-06-05
  Time: 18:41
  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>${msg}</h2>
</body>
</html>

测试

image.png
image.png
image.png

请求转发与重定向

  • image.png
  • SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。可以使用简单的方式实现转发和重定向。
    • 使用 forward 表示转发,实现 request.getRequestDispatcher(“xx.jsp”).forward()
    • 使用 redirect 表示重定向,实现 response.sendRedirect(“xxx.jsp”)
  • forward 和 redirect 都是 SpringMVC 框架中的关键字,两个关键字都不和视图解析器一同工作

请求转发

  • 通过处理器方法返回ModelAndView,实现请求转发
  • 语法:
mv.setViewName("forward:视图文件完整路径")
  • 注意:forward 不和视图解析器一同使用,不管有没有配置视图解析器,使用forward进行请求转发都相当于没有视图解析器。
  • 使用 forward 关键字进行请求转发为显示转发,该种方式主要用于当想要进行请求转发但是要转发到的页面没有在视图解析器配置的路径中的情况下,使得请求转发可以不受视图解析器的限制,实现可以转发到视图解析器以外的页面
  • 视图解析器配置如下:
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/"/>
  <property name="suffix" value=".jsp"/>
</bean>
  • 控制器:
@Controller
public class MyController {
    
    
    
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name, String age) {
    
    
        ModelAndView mv = new ModelAndView();
        System.out.println(123);
        mv.addObject("name", name);
        mv.addObject("age", age);
        // 进行请求转发
        mv.setViewName("forward:./WEB-INF/view/show.jsp");
        return mv;
    }
    
}
  • show.jsp
    • image.png
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<p>姓名:${name}</p>
<p>年龄:${age}</p>
</body>
</html>
  • image.png

请求重定向

  • 通过处理器方法返回ModelAndView,实现请求重定向
  • 语法:
mv.setViewName("redirect:视图文件完整路径")
  • 注意:redirect 不和视图解析器一同使用,不管有没有配置视图解析器,使用redirect进行请求重定向都相当于没有视图解析器。
  • 控制器:
@Controller
public class MyController {
    
    
    
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name, String age) {
    
    
        ModelAndView mv = new ModelAndView();
        mv.addObject("name111", name);
        mv.addObject("age111", age);
        System.out.println("name: " + name + " age: " + age);
        // 进行请求重定向
        // http://localhost:8080/springmvc/show222.jsp?name111=%E5%BC%A0%E4%B8%89&age111=23
        mv.setViewName("redirect:./show222.jsp");
        return mv;
    }

}
  • 在进行请求重定向的时候,添加到ModelAndView中的数据,框架会将Model中的简单类型的数据转为String使用,作为向请求重定向地址的请求参数使用,使得在两次请求之间可以传递数据
  • 注意:请求重定向是一次全新的从浏览器发出的请求,WEB-INF下的资源是不能被浏览器请求访问的
  • jsp:
    • image.png
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<!--
    请求重定向,是全新的一次请求,参数在request对象上,
    不在本次的请求作用域中
-->
<!-- param 请求参数集合 -->
<p>姓名:${param.name111}</p>
<p>年龄:<%= request.getParameter("age111") %></p>
</body>
</html>
  • image.png
  • image.png

异常处理

  • 之前对代码可能出现异常的处理方法:
public void fun() {
    
    
	try {
    
    
        
    } catch (Exception e){
    
    
        
    }
}
  • SpringMVC 框架中将处理异常的代码(非业务逻辑代码)抽取出来,在一个统一的位置定义异常的处理,采用面向切面编程的方式,来处理异常
  • springmvc框架采用的是统一,全局的异常处理。把controller中的所有异常处理都集中到一个地方,采用的是aop的思想,把业务逻辑和异常处理代码分开,解耦合。
  • springmvc框架提供了两个注解进行异常的处理:@ExceptionHandler、@ControllerAdvice

自定义异常类

package cw.springmvc.exception;

/**
 * ClassName: MyUserException
 * Package: cw.springmvc.exception
 * Description:
 *
 * @Author tcw
 * @Create 2023-06-05 22:31
 * @Version 1.0
 */
public class MyUserException extends Exception{
    
    
    public MyUserException() {
    
    
        super();
    }
    
    public MyUserException(String message) {
    
    
        super(message);
    }
}
package cw.springmvc.exception;

/**
 * ClassName: AgeException
 * Package: cw.springmvc.exception
 * Description:
 *
 * @Author tcw
 * @Create 2023-06-05 22:32
 * @Version 1.0
 */
public class AgeException extends MyUserException{
    
    
    public AgeException() {
    
    
        super();
    }
    
    public AgeException(String message) {
    
    
        super(message);
    }
}
package cw.springmvc.exception;

/**
 * ClassName: NameException
 * Package: cw.springmvc.exception
 * Description:
 *
 * @Author tcw
 * @Create 2023-06-05 22:32
 * @Version 1.0
 */
public class NameException extends MyUserException{
    
    
    public NameException() {
    
    
        super();
    }
    
    public NameException(String message) {
    
    
        super(message);
    }
}

处理器类

package cw.springmvc.controller;

import cw.springmvc.exception.AgeException;
import cw.springmvc.exception.MyUserException;
import cw.springmvc.exception.NameException;
import cw.springmvc.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.sound.midi.Soundbank;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: MyController
 * Package: cw.springmvc.controller
 * Description:
 *
 * @Author tcw
 * @Create 2023-05-21 20:24
 * @Version 1.0
 */
@Controller
public class MyController {
    
    
    
    @RequestMapping(value = "/some.do")
    public ModelAndView doSome(String name, Integer age) throws MyUserException {
    
    
        ModelAndView mv = new ModelAndView();
        // 根据请求参数抛出异常,将异常交给SpringMVC框架处理
        if (!"admin".equals(name)) {
    
    
            throw new NameException("用户名不正确.");
        }
        if (age == null || age < 0) {
    
    
            throw new AgeException("年龄不正确.");
        }
        mv.addObject("name", name);
        mv.addObject("age", age);
        mv.setViewName("show");
        return mv;
    }
    
}

异常处理器

  • 当控制器发生异常时,会停止控制器方法的执行,跳转到异常处理器类中,进行处理异常方法的匹配
  • 异常处理类,也是组件,所以需要配置包扫描,进行异常处理类组件扫描
@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    
    
    @AliasFor("basePackages")
    String[] value() default {
    
    };

    @AliasFor("value")
    String[] basePackages() default {
    
    };

    Class<?>[] basePackageClasses() default {
    
    };

    Class<?>[] assignableTypes() default {
    
    };

    Class<? extends Annotation>[] annotations() default {
    
    };
}
  • @ControllerAdvice:标注该类是异常处理类
  • @ExceptionHandler:标注异常处理方法处理的异常类型,如果没有指定处理的异常类型,那么该方法就是用于处理没有异常处理方法匹配的异常,没有指定处理的异常类型的方法只能有一个
  • 要使用这两个注解,需要在SpringMVC配置文件中配置注解驱动
package cw.springmvc.handler;

import cw.springmvc.exception.AgeException;
import cw.springmvc.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * ClassName: GlobalExceptionHandler
 * Package: cw.springmvc.handler
 * Description:
 * 全局异常处理器
 * ControllerAdvice注解:控制器通知、控制器增强
 * 给控制器增加功能,增加异常处理功能
 * ControllerAdvice注解使用在类上
 * 对于控制器通知组件需要配置组件扫描器
 *
 * @Author tcw
 * @Create 2023-06-05 22:39
 * @Version 1.0
 */
// 标注为控制器异常处理组件
@ControllerAdvice
public class GlobalExceptionHandler {
    
    
    
    /**
     * 定义方法,处理发生的异常
     * 处理异常的方法和控制器方法的定义方式一样,
     * 控制器方法可以怎么定义,处理异常的方法就可以怎么定义
     *
     * 处理NameException的异常
     *
     * @param e 表示Controller中抛出的异常对象
     *          通过这个形参可以获取发生的异常信息
     * @return
     */
    // @ExceptionHandler(异常的class):表示该方法处理异常的类型
    @ExceptionHandler(value = {
    
    NameException.class})
    public ModelAndView doNameException(Exception e) {
    
    
        // 异常的处理逻辑:
        // 1.需要把异常记录下来,记录到数据库或日志文件
        //   记录日志发生的时间、哪个方法发生、异常错误的内容
        // 2.发送通知,把异常的信息通过邮件、短信、微信发送给相关人员
        // 3.给用户友好的提示
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "用户名必须是admin");
        mv.addObject("exception", e);
        mv.setViewName("nameError");
        return mv;
    }
    
    /**
     * 处理年龄异常 AgeException
     *
     * @param e 异常对象
     * @return
     */
    @ExceptionHandler(value = {
    
    AgeException.class})
    public ModelAndView doAgeException(Exception e) {
    
    
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "年龄不能为空,且应该大于0");
        mv.addObject("exception", e);
        mv.setViewName("ageError");
        return mv;
    }
    
    /**
     * 处理其他异常,处理没有相匹配的异常处理方法的异常
     * 会先与有指定处理具体异常的方法进行匹配,都不能处理发生的异常
     * 即没有方法匹配
     * 就使用该方法进行处理,使用没有指定具体处理异常类型的方法处理
     * 只能有一个默认处理异常的
     *
     * @param e 异常对象
     * @return
     */
    @ExceptionHandler
    public ModelAndView doOtherException(Exception e) {
    
    
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "未知异常");
        mv.addObject("exception", e);
        mv.setViewName("defaultError");
        return mv;
    }
    
}

JSP

  • show.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<p>姓名:${name}</p>
<p>年龄:${age}</p>
</body>
</html>
  • ageError.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>年龄异常</h1>
<p>${msg}</p>
<p>${exception.message}</p>
</body>
</html>
  • nameError.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>姓名异常</h1>
<p>${msg}</p>
<p>${exception.message}</p>
</body>
</html>
  • defaultError.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>未知异常</h1>
<p>${msg}</p>
<p>${exception.message}</p>
</body>
</html>
  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="./some.do" method="post">
    name:<input type="text" name="name"><br/>
    age:<input type="text" name="age"><br/>
    <input type="submit" name="submit"><br/>
</form>
</body>
</html>

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

	<!-- 控制器包扫描 -->
	<context:component-scan base-package="cw.springmvc.controller"/>
	
	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/view/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
	<!-- 异常处理组件扫描 -->
	<context:component-scan base-package="cw.springmvc.handler"/>
  
	<!-- 注解驱动器 -->
	<mvc:annotation-driven/>
  
</beans>

测试

  • image.pngimage.png
  • image.pngimage.png
  • image.png
  • image.png

拦截器

拦截器概述

  • 拦截器需要实现框架中的指定接口,HandlerInterceptor,实现这个接口的类都为拦截器
  • 拦截器的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
    • 拦截器与过滤器类似,过滤器是用来过滤器请求参数,设置编码字符集等工作;拦截器是拦截用户的请求,做请求做判断处理的。
  • 拦截器的拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。
  • 拦截器是全局的,可以对多个Controller做拦截。 一个项目中可以有0个或多个拦截器, 他们在一起拦截用户的请求。
  • 拦截器常用在:用户登录处理,权限检查, 记录日志。
  • 拦截器的使用步骤:
    • 1.定义类实现HandlerInterceptor接口
    • 2.在springmvc配置文件中,声明拦截器, 让框架知道拦截器的存在。
  • 拦截器的执行时间:
    • 1)在请求处理之前, 也就是controller类中的方法执行之前先被拦截。
    • 2)在控制器方法执行之后也会执行拦截器。
    • 3)在请求处理完成后也会执行拦截器。

创建拦截器类

  • 自定义拦截器,需要实现HandlerInterceptor接口,然后根据需求实现HandlerInterceptor接口中的三个方法

拦截器类方法讲解

package cw.springmvc.handler;

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

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

/**
 * ClassName: MyInterceptor
 * Package: cw.springmvc.handler
 * Description:
 * 拦截器类,拦截用户的请求
 *
 * @Author tcw
 * @Create 2023-06-05 23:40
 * @Version 1.0
 */
public class MyInterceptor implements HandlerInterceptor{
    
    
    /**
     * preHandle 预处理方法
     * 该方法在控制器方法执行前执行,用户的请求会先到达此方法
     * 在该方法中可以获取请求的信息,验证请求信息是否符合要求
     * 可以验证用户是否登录, 验证用户是否有权限访问某个连接地址(url)。
     * 如果验证失败,可以截断请求,请求不能被处理;如果验证成功,可以放行请求,此时控制器方法才能执行。
     * 
     * 重要:是整个项目的入口,门户。 
     * 当preHandle返回true 请求可以被处理。
     * preHandle返回false,请求到此方法就截止。
     *
     * @param request
     * @param response
     * @param handler 被拦截的控制器对象
     * @return 布尔值
     *         true: 请求是通过了拦截器的验证,可以执行处理器方法。
     *         false: 请求没有通过拦截器的验证,请求到达拦截器就截止了。 请求没有被处理
     *                后面的方法:处理器方法、后处理方法、afterCompletion都不执行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
    
    /**
     * postHandle:后处理方法。
     * 在处理器方法之后执行的(MyController.doSome())
     * 能够获取到处理器方法的返回值ModelAndView,
     * 可以修改ModelAndView中的数据和视图,可以影响到最后的执行结果。
     * 主要是对原来的执行结果做二次修正
     *
     * @param request
     * @param response
     * @param handler 被拦截的处理器对象 Controller
     * @param modelAndView 处理器方法的返回值
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
    
    /**
     * afterCompletion:最后执行的方法
     * 在请求处理完成后执行的。
     * 框架中规定是当你的视图处理完成后,对视图执行了forward。就认为请求处理完成。
     * 一般做资源回收工作的, 程序请求过程中创建了一些对象,在这里可以删除,把占用的内存回收。
     *
     * @param request
     * @param response
     * @param handler 被拦截器的处理器对象
     * @param ex 程序中发生的异常
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

拦截器图示

image.png

SpringMVC 配置文件中配置拦截器

<!-- 配置拦截器,拦截器可以配置0到多个 -->
<mvc:interceptors>
  <!-- 配置第一个拦截器 -->
  <mvc:interceptor>
    <!-- 
      指定拦截器拦截的URI地址
      可以使用通配符 
        ** 表示任意的字符,文件或者多级目录和目录中的文件
        /user/** 以user开头的请求地址都会被该拦截器拦截
        /** 该拦截器会拦截所有的请求
    -->
    <mvc:mapping path="/**"/>
    <!-- 需要进行拦截的地址,对应的拦截器对象 -->
    <bean class="cw.springmvc.handler.MyInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>

拦截器中方法编写

package cw.springmvc.handler;

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

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

/**
 * ClassName: MyInterceptor
 * Package: cw.springmvc.handler
 * Description:
 * 拦截器类,拦截用户的请求
 *
 * @Author tcw
 * @Create 2023-06-05 23:40
 * @Version 1.0
 */
public class MyInterceptor implements HandlerInterceptor{
    
    
    
    private long btime = 0; // 用于统计请求的处理时间
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("拦截器preHandle预处理方法执行...");
        btime = System.currentTimeMillis(); // 记录请求开始处理时间
        // 返回真,后面的方法才能执行
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("拦截器postHandle后处理方法执行...");
        // 原来的doSome执行结果需要调整,进行modelAndView修改。
        if( modelAndView != null){
    
    
            //修改数据
            // modelAndView.addObject("mydate",new Date());
            //修改视图
            modelAndView.setViewName("other");
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("拦截器afterCompletion:最后执行的方法执行...");
        long etime = System.currentTimeMillis(); // 请求处理结束时间
        System.out.println("计算从preHandle到请求处理完成的时间:"+(etime - btime ));
    }
}

控制器

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

测试

  • image.png
  • image.png
  • image.png

拦截器执行流程图示

image.png
image.png

多个拦截器的执行顺序

  • 拦截器在配置文件中,先声明的会先执行,后声明的后执行,声明的若干个拦截器在框架中是使用ArrayList集合进行保存的,是按照声明的先后顺序放入到ArrayList集合中

多个拦截器代码实现

  • 拦截器1
package cw.springmvc.handler;

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

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

/**
 * ClassName: MyInterceptor
 * Package: cw.springmvc.handler
 * Description:
 * 拦截器类,拦截用户的请求
 *
 * @Author tcw
 * @Create 2023-06-05 23:40
 * @Version 1.0
 */
public class MyInterceptor implements HandlerInterceptor{
    
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("拦截器-1-preHandle预处理方法执行...");
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("拦截器-1-postHandle后处理方法执行...");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("拦截器-1-afterCompletion:最后执行的方法执行...");
    }
    
}
  • 拦截器2
package cw.springmvc.handler;

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

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

/**
 * ClassName: MyInterceptor
 * Package: cw.springmvc.handler
 * Description:
 * 拦截器类,拦截用户的请求
 *
 * @Author tcw
 * @Create 2023-06-05 23:40
 * @Version 1.0
 */
public class MyInterceptor2 implements HandlerInterceptor{
    
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        System.out.println("拦截器-2-preHandle预处理方法执行...");
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
        System.out.println("拦截器-2-postHandle后处理方法执行...");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
        System.out.println("拦截器-2-afterCompletion:最后执行的方法执行...");
    }
}
  • 拦截器配置
<!-- 配置拦截器,拦截器可以配置0到多个 -->
<mvc:interceptors>
  <!-- 配置第一个拦截器 -->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="cw.springmvc.handler.MyInterceptor"/>
  </mvc:interceptor>
  <!-- 配置第二个拦截器 -->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="cw.springmvc.handler.MyInterceptor2"/>
  </mvc:interceptor>
</mvc:interceptors>
  • image.png

多个拦截器执行顺序图示

  • image.png
  • image.png
  • 多个拦截器执行.png
  • image.png

不同拦截器 preHandle 不同返回值分析

  • 只有当拦截器的方法preHandle返回true才可以向后执行,如果其中一个拦截器的preHandle方法返回值为false,那么请求会被拦截,不会被控制器方法处理,即只要有拦截器的preHandle方法返回值为false那么处理器方法就不会执行
  • 拦截器-1-preHandle返回true,拦截器-2-preHandle返回false
  • image.png
  • 拦截器-1-preHandle返回false,拦截器-2-preHandle返回true
  • image.png
  • 只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,其后续的处理器方法与 postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中preHandle()方法返回 true相对应的afterCompletion()方法。最终都会给出响应。

拦截器和过滤器的区别

  1. 过滤器是servlet规范中的对象,拦截器是框架中自己定义的对象,拦截器局限于框架之中,离开了SpringMVC框架,拦截器就不能使用了
  2. 过滤器实现Filter接口的对象, 拦截器是实现HandlerInterceptor
  3. 过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的。拦截器是用来验证请求的,能截断请求。
  4. 过滤器是在拦截器之前先执行的。
  5. 过滤器是tomcat服务器创建的对象。拦截器是springmvc容器中创建的对象
  6. 过滤器是一个执行时间点。拦截器有三个执行时间点
  7. 过滤器可以处理jsp,js,html等等。拦截器是侧重拦截对Controller的请求, 如果你的请求不能被DispatcherServlet接收, 这个请求不会执行拦截器内容
  8. 拦截器拦截普通类方法(控制器方法)执行,过滤器过滤servlet请求响应
    1. 控制器其实是普通的Java类,框架给了控制器能够处理请求的能力

拦截器案例:登录验证

  • 注意,前端控制器DispatcherServlet的路径配置,不同的路径配置将会影响到对jsp页面的请求是否会交给DispatcherServlet处理,/DispatcherServlet不会处理jsp页面的请求,/*DispatcherServlet会处理jsp页面的请求,*.doDispatcherServlet不会处理jsp页面的请求

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="./login" method="post">
    username:<input type="text" name="username"><br/>
    <input type="submit" name="submit"><br/>
</form>
</body>
</html>

controller

@Controller
public class MyController {
    
    
    
    @RequestMapping(value = "/login")
    public ModelAndView doSome(String name, Integer age) throws MyUserException {
    
    
        System.out.println("控制器方法执行...");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("login");
        return mv;
    }
    
}

拦截器

public class MyInterceptor implements HandlerInterceptor{
    
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // 获取登录的用户名
        String username = request.getParameter("username");
        System.out.println(username);
        // 判断会话作用域中是否有用户名,判断是否登录过
        Object username1 = request.getSession().getAttribute("username");
        System.out.println(username1);
        // 验证输入的用户名
        if (username == null || username.length() == 0 || !"admin".equals(username)) {
    
    
            // 验证是否登录过
            if (username1 == null || !"admin".equals(username1)) {
    
    
                // 验证失败
                request.getRequestDispatcher("./tips.jsp").forward(request, response);
                return false;
            }
        }
        // 验证成功
        // 将用户名添加到会话作用域
        request.getSession().setAttribute("username", username);
        return true;
    }
    
}

jsp

  • login.jsp
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-06-06
  Time: 10:00
  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>
<h1>模拟登录</h1>
<p>用户【<%= session.getAttribute("username") %>】登录成功</p>
<a href="./logout.jsp">退出登录</a>
</body>
</html>
  • logout.jsp
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-06-06
  Time: 10:03
  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>
<p>【<%= session.getAttribute("username") %>】退出登录</p>
</body>
</html>
<%
    // 删除信息
    session.removeAttribute("username");
%>
  • tips.jsp
<%--
  Created by IntelliJ IDEA.
  User: cw
  Date: 2023-06-06
  Time: 10:07
  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>
<h1>登录失败</h1>
</body>
</html>

DispatcherServlet 配置

<servlet>
  <servlet-name>springmvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springmvc</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

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

	<!-- 控制器包扫描 -->
	<context:component-scan base-package="cw.springmvc.controller"/>
	
	<!-- 视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
	<!-- 配置拦截器,拦截器可以配置0到多个 -->
	<mvc:interceptors>
		<!-- 配置第一个拦截器 -->
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean class="cw.springmvc.handler.MyInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>
	
</beans>

运行测试

  • image.png
  • image.png
  • image.png
  • image.png
  • image.png
  • 再直接浏览器访问:http://localhost:8080/springmvc/login
  • image.png

SpringMVC 执行流程

image.png

  • 浏览器发送请求,请求会最先被Tomcat接收,Tomcat将和DispatcherServlet对应路径相匹配的请求交给DispatcherServlet进行处理
  • DispatcherServlet调用处理器映射器,处理器映射器根据请求,从SpringMVC容器中获取处理器对象
    • 相当于执行:SpringMVC容器对象.getBean("beanName")
    • 处理器映射器:SpringMVC中的一种对象,框架把实现了HandlerMapping接口的类都叫映射器
    • 处理器映射器对象有多个
    • 处理器映射器接口HandlerMapping有很多子接口和子类,因为在没有注解之前需要通过实现接口来创建控制器,控制器种类一多,接口也就多了
    • image.png
    • HandlerExecutionChain mappedHandler = this.getHandler(processedRequest);
  • 框架会把找到的处理器对象放到处理器执行链的类中进行保存,将处理器执行链给DispatcherServlet
public class HandlerExecutionChain {
    
    
    private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
    private final Object handler; // 保存处理器对象
    private final List<HandlerInterceptor> interceptorList; // 项目中所有的拦截器
    private int interceptorIndex;

}
  • DispatcherServlet将处理器执行链中的处理器对象交给处理器适配器,处理器适配器调用执行处理器的方法,将处理器方法的返回值ModelAndView返回给中央调度器
    • 处理器适配器:SpringMVC框架中的对象,需要实现HandlerAdapter
    • 处理器适配器对象有多个
    • 处理器适配器接口HandlerAdapter有很多子接口和子类,因为适配器的种类多
    • image.png
    • HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    • mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • DispatcherServlet接收到处理器适配器的返回结果ModelAndView,调用视图解析器,将ModelAndView交给视图解析器,根据使用解析器的配置,组成视图的完整路径,并创建View对象,将视图对象交给DispatcherServlet
    • 视图解析器需要实现ViewResoler接口
    • 视图解析器可以存在多个
    • View是一个接口,表示视图,在框架中,jsp、html是使用View和其实现类表示
    • image.png
    • mv.setView(new RedirectView("/a.jsp"))mv.setViewName("a")) 效果差不多,只不过前者是显示创建和使用视图对象
    • InternalResourceView表示jsp文件的视图类,视图解析器会创建InternalResourceView对象,这对象中有相应的jsp文件的路径url
  • DispatcherServlet调用视图View,调用视图View对象的方法将Model中的数据放到request作用域中,视图将数据放到视图页面中,DispatcherServlet最后将视图响应给浏览器,向客户端呈现处理结果
  • 使用中央调度器按照处理流程调用相应的对象在不同阶段进行处理,有利于解耦合,其中某个对象进行了修改,不会影响其他对象的正常执行

猜你喜欢

转载自blog.csdn.net/m0_53022813/article/details/131064706