SpringMVC框架

一、SpringMVC 概述

1.1 SpringMVC 简介

SpringMVC 也叫 Spring web mvc。它是基于MVC开发模式的框架,用来优化控制器。

SpringMVC是 Spring 框架的一部分,是在 Spring3.0 后发布的。

什么是MVC?
它是一种开发模式,它是模型视图控制器的简称。所有的web应用都是基于MVC开发。

  • M:模型层。包含实体类,业务逻辑层,数据访问层
  • V:视图层。html,javaScript,vue等都是视图层,用来显现数据
  • C:控制器。它是用来接收客户端的请求,并返回响应到客户端的组件,Servlet就是组件

image-20221019113253517

1.2 SpringMVC 优点

1.基于 MVC 架构
基于 MVC 架构,功能分工明确。解耦合,

2.容易理解,上手快;使用简单。
就可以开发一个注解的 SpringMVC 项目,SpringMVC 也是轻量级的,jar很小。不依赖的特定的接口和类。

3.作为Spring 框架一部分 , 能够使用 Spring 的 IoC和 Aop 。 方便整合Strtus,MyBatis,Hiberate,JPA 等其他框架。

4.SpringMVC强化注解的使用,在控制器,Service,Dao 都可以使用注解。方便灵活。
使用@Controller 创建处理器对象,@Service 创建业务对象,@Autowired 或者@Resource在控制器类中注入 Service, Service 类中注入 Dao。

1.3 基于注解的SpringMVC 程序

所谓 SpringMVC 的注解式开发是指,在代码中通过对类与方法的注解,便可完成处理器在 springmvc 容器的注册。注解式开发是重点。
项目:primary-annotation
完成功能:用户提交一个请求,服务端处理器在接收到这个请求后,给出一条欢迎信息,在响应页面中显示该信息。

新建 maven web 项目

img

pom.xml

在创建好 web 项目后,加入 Servlet 依赖,SpringMVC 依赖
依赖:

 <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>
    <!--springmvc依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- 编码和编译和JDK版本 -->
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

注册中央调度器

在web.xml文件中注册SpringMvc框架。因为web的请求都是由Servlet来进行处理的,而SpringMVC的核心处理器就是一个DispatcherServlet,它负责接收客户端的请求,并根据请求的路径分派给对应的action(控制器)进行处理,处理结束后依然由核心处理器DispatcherServlet进行响应返回

 <servlet>
        <servlet-name>myweb</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启动后创建对象的顺序。它的值是整数,数值越小,
                            tomcat创建对象的时间越早。 大于等于0的整数。
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>myweb</servlet-name>
        <!--
            使用框架的时候, url-pattern可以使用两种值
            1. 使用扩展名方式, 语法 *.xxxx , xxxx是自定义的扩展名。 常用的方式 *.do, *.action, *.mvc等等
               不能使用 *.jsp
               http://localhost:8080/myweb/some.do
               http://localhost:8080/myweb/other.do

            2.使用斜杠 "/"
        -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

(1) 全限定性类名

该中央调度器为一个 Servlet,名称为 DispatcherServlet。中央调度器的全限定性类名在导入的 Jar 文件 spring-webmvc-5.2.5.RELEASE.jar 的第一个包 org.springframework.web.servlet下可找到。

(2)<load-on-startup/>

在<servlet/>中添加<load-on-startup/>的作用是,标记是否在Web服务器(这里是Tomcat)启动时会创建这个 Servlet 实例,即是否在 Web 服务器启动时调用执行该 Servlet 的 init()方法,而不是在真正访问时才创建。
它的值必须是一个整数。

➢ 当值大于等于 0 时,表示容器在启动时就加载并初始化这个 servlet,数值越小,该 Servlet的优先级就越高,其被创建的也就越早;

➢ 当值小于 0 或者没有指定时,则表示该 Servlet 在真正被使用时才会去创建。

➢ 当值相同时,容器会自己选择创建顺序。

(3) <url-pattern/>

对于<url-pattern/>,可以写为 / ,建议写为*.do 的形式。

指定拦截以.do结尾的请求,交给核心处理器DispatcherServlet处理。

(4) 配置文件位置与名称

注册完毕后,可直接在服务器上发布运行。此时,访问浏览器页面,控制台均会抛出FileNotFoundException 异常。即默认要从项目根下的 WEB-INF 目录下找名称为 Servlet 名称-servlet.xml 的配置文件。这里的“Servlet 名称”指的是注册中央调度器<servlet-name/>标签中指定的 Servlet 的 name 值。本例配置文件名为 springmvc-servlet.xml。

img

而一般情况下,配置文件是放在类路径下,即 resources 目录下。所以,在注册中央调度器时,还需要为中央调度器设置查找 SpringMVC 配置文件路径,及文件名。

img

打开 DispatcherServlet 的源码,其继承自 FrameworkServlet,而该类中有一个属性contextConfigLocation,用于设置 SpringMVC 配置文件的路径及文件名。该初始化参数的属性就来自于这里。

img

img

img

创建 SpringMVC 配置文件

在工程的类路径即 src 目录下创建 SpringMVC 的配置文件 springmvc.xml。该文件名可以任意命名。1)。推荐使用springmvc.xml

img

创建处理器

在类上与方法上添加相应注解即可。
@Controller:表示当前类为处理器
@RequestMapping:表示当前方法为处理器方法。该方法要对 value 属性所指定的 URI进行处理与响应。被注解的方法的方法名可以随意。

@Controller
public class MyController {
    /*
       处理用户提交的请求,springmvc中是使用方法来处理的。
       方法是自定义的, 可以有多种返回值, 多种参数,方法名称自定义
     */

    /**
     * 准备使用doSome方法处理some.do请求。
     * @RequestMapping: 请求映射,作用是把一个请求地址和一个方法绑定在一起。
     *                  一个请求指定一个方法处理。
     *       属性: 1. value 是一个String,表示请求的uri地址的(some.do)。
     *                value的值必须是唯一的, 不能重复。 在使用时,推荐地址以“/”
     *       位置:1.在方法的上面,常用的。
     *            2.在类的上面
     *  说明: 使用RequestMapping修饰的方法叫做处理器方法或者控制器方法。
     *  使用@RequestMapping修饰的方法可以处理请求的,类似Servlet中的doGet, doPost
     *
     *  返回值:ModelAndView 表示本次请求的处理结果
     *   Model: 数据,请求处理完成后,要显示给用户的数据
     *   View: 视图, 比如jsp等等。
     */
    @RequestMapping(value = {"/some.do","/first.do"})
    public ModelAndView doSome(){  // doGet()--service请求处理
        //处理some.do请求了。 相当于service调用处理完成了。
        ModelAndView mv  = new ModelAndView();
        //添加数据, 框架在请求的最后把数据放入到request作用域。
        //request.setAttribute("msg","欢迎使用springmvc做web开发");
        mv.addObject("msg","欢迎使用springmvc做web开发");
        mv.addObject("fun","执行的是doSome方法");

        //指定视图, 指定视图的完整路径
        //框架对视图执行的forward操作, request.getRequestDispather("/show.jsp).forward(...)
        mv.setViewName("/show.jsp");
        //mv.setViewName("/WEB-INF/view/show.jsp");
        //mv.setViewName("/WEB-INF/view/other.jsp");


        //当配置了视图解析器后,可以使用逻辑名称(文件名),指定视图
        //框架会使用视图解析器的前缀 + 逻辑名称 + 后缀 组成完成路径, 这里就是字符连接操作
        ///WEB-INF/view/ + show + .jsp
        //mv.setViewName("show");

        //mv.setView( new RedirectView("/a.jsp"));

        //返回mv
        return mv;
    }


    @RequestMapping(value = {"/other.do","/second.do"})
    public ModelAndView doOther(){
        ModelAndView mv  = new ModelAndView();
        mv.addObject("msg","====欢迎使用springmvc做web开发====");
        mv.addObject("fun","执行的是doOther方法");
        mv.setViewName("other");
        return mv;
    }
}

若有多个请求路径均可匹配该处理器方法的执行,则@RequestMapping 的 value 属性中可以写上一个数组。

ModelAndView 类中的 addObject()方法用于向其 Model 中添加数据。Model 的底层为一个 HashMap。

Model 中的数据存储在 request 作用域中,SringMVC 默认采用转发的方式跳转到视图,本次请求结束,模型中的数据被销毁。

声明组件扫描器

在 springmvc.xml 中注册组件扫描器

<!--声明组件扫描器-->
<context:component-scan base-package="com.bjpowernode.controller" />

定义目标页面

在 webapp 目录下新建一个子目录 jsp,在其中新建一个 jsp 页面 show.jsp。

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

修改视图解析器的注册

SpringMVC 框架为了避免对于请求资源路径与扩展名上的冗余,在视图解析器InternalResouceViewResolver 中引入了请求的前辍与后辍。

而 ModelAndView 中只需给出要跳转页面的文件名即可,对于具体的文件路径与文件扩展名,视图解析器会自动完成拼接。

 <!--声明 springmvc框架中的视图解析器, 帮助开发人员设置视图文件的路径-->
    <bean  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀:视图文件的路径-->
        <property name="prefix" value="/WEB-INF/view/" />
        <!--后缀:视图文件的扩展名-->
        <property name="suffix" value=".jsp" />
    </bean>

把 show.jsp 文件放到 /WEB-INF/jsp/路径中

修改处理器

img

使用逻辑视图名称,show 是逻辑视图名称。

使用 SpringMVC 框架 web 请求处理顺序

img

web开发底层是servlet, springmvc中有一个对象是Servlet : DispatherServlet(中央调度器)

DispatherServlet: 负责接收用户的所有请求, 用户把请求给了DispatherServlet, 之后DispatherServlet把请求转发给我们的Controller对象, 最后是Controller对象处理请求。

index.jsp-----DispatherServlet(Servlet)----转发,分配给---Controller对象(@Controller注解创建的对象)
main.jsp<--------------->DispatcherServlet<-------------------> MainController
addUser.jsp<--------------->DispatcherServlet<------------------->UserController

springmvc请求的处理流程

1)发起some.do
2)tomcat(web.xml--url-pattern知道 *.do的请求给DispatcherServlet)
3)DispatcherServlet(根据springmvc.xml配置知道 some.do---doSome())
4)DispatcherServlet把some.do转发个MyController.doSome()方法
5)框架执行doSome()把得到ModelAndView进行处理, 转发到show.jsp

上面的过程简化的方式
some.do---DispatcherServlet---MyController

1.4 SpringMVC 的 MVC 组件

img

1.5 SpringMVC 执行流程(理解)

1.5.1 流程图

img

springmvc执行过程源代码分析

1.tomcat启动,创建容器的过程

通过load-on-start标签指定的1,创建DisaptcherServlet对象,DisaptcherServlet它的父类是继承HttpServlet的, 它是一个serlvet, 在被创建时,会执行init()方法。

在init()方法中:

//创建容器,读取配置文件
WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc.xml");
//把容器对象放入到ServletContext中
getServletContext().setAttribute(key, ctx);

上面创建容器作用: 创建@controller注解所在的类的对象, 创建MyController对象,这个对象放入到 springmvc的容器中, 容器是map , 类似 map.put("myController",MyController对象)

2.请求的处理过程
1)执行servlet的service()

protected void service(HttpServletRequest request, HttpServletResponse response)
protected void doService(HttpServletRequest request, HttpServletResponse response)
DispatcherServlet.doDispatch(request, response){
   调用MyController的.doSome()方法
   }

doDispatch:springmvc中DispatcherServlet的核心方法, 所有的请求都在这个方法中完成的。

1.5.2 执行流程简单分析

(1)浏览器提交请求到中央调度器
(2)中央调度器直接将请求转给处理器映射器。
(3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。
(4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器(适配器模式)。
(5)处理器适配器调用执行处理器。
(6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器。
(7)处理器适配器直接将结果返回给中央调度器。
(8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。
(9)视图解析器将封装了的视图对象返回给中央调度器
(10)中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。
(11)中央调度器响应浏览器。

二、SpringMVC 注解式开发

2.1 @RequestMapping 定义请求规则

2.1.1 指定模块名称

通过@RequestMapping 注解可以定义处理器对于请求的映射规则。该注解可以注解在方法上,也可以注解在类上,但意义是不同的。value 属性值常以“/”开始

@RequestMapping 的 value 属性用于定义所匹配请求的 URI。但对于注解在方法上与类上,其 value 属性所指定的 URI,意义是不同的。

一个@Controller 所注解的类中,可以定义多个处理器方法。当然,不同的处理器方法所匹配的 URI 是不同的。这些不同的 URI 被指定在注解于方法之上的@RequestMapping 的value 属性中。但若这些请求具有相同的 URI 部分,则这些相同的 URI,可以被抽取到注解在类之上的@RequestMapping 的 value 属性中。此时的这个 URI 表示模块的名称。URI 的请求是相对于 Web 的根目录。

换个角度说,要访问处理器的指定方法,必须要在方法指定 URI 之前加上处理器类前定义的模块名称

项目:requestMapping-modelName。在 primary-annotation 基础上进行修改。

Step1:修改处理器类 MyController。

img

Step2:添加视图页面

在/WEB-INF/jsp 目录下添加 some.jsp 与 other.jsp 页面,删除原 welcome.jsp 页面。

img

img

img

2.1.2 对请求提交方式的定义

对于@RequestMapping,其有一个属性 method,用于对被注解方法所处理请求的提交方式进行限制,即只有满足该 method 属性指定的提交方式的请求,才会执行该被注解方法。

Method 属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 POST 提交。

img

以上处理器方法只能处理 POST 方式提交的请求。客户端浏览器常用的请求方式,及其提交方式有以下几种:

img

也就是说,只要指定了处理器方法匹配的请求提交方式为 POST,则相当于指定了请求发送的方式:要么使用表单请求,要么使用 AJAX 请求。其它请求方式被禁用。

当然,若不指定 method 属性,则无论是 GET 还是 POST 提交方式,均可匹配。即对于请求的提交方式无要求。

项目:requestMapping-method。在 requestMapping-modelName 基础上进行修改。

Step1:修改处理器类 MyController

img

Step2:修改 index 页面

img

2.2 五种数据提交的方式

前四种数据注入的方式,会自动进行类型转换。但无法自动转换日期类型。

2.2.1 单个数据注入

在方法中声明一个和表单提交的参数名称相同的参数,由框架按照名称直接注入。

img

页面:  
  <form action="${pageContext.request.contextPath}/one.action">
      姓名:<input name="myname"><br>
      年龄:<input name="age"><br>
      <input type="submit" value="提交">
  </form>
  
action:
  @RequestMapping("/one")
    public String one(String myname,int age){  ===>自动注入,并且类型转换
        System.out.println("myname="+myname+",age="+(age+100));
        return "main";
    }

2.2.2 对象封装注入

在方法中声明一个自定义的实体类参数,框架调用实体类中相应的setter方法注入属性值,只要保证实体类中成员变量的名称与提交请求的name属性值一致即可。

img

实体类:
public class Users {
    private String name;
    private int age;
}

页面:
	<form action="${pageContext.request.contextPath}/two.action" method="post">
	    姓名:<input name="name"><br>
	    年龄:<input name="age"><br>
	    <input type="submit" value="提交">
	</form>
   
action:
    @RequestMapping("/two")
    public String two(Users u){
        System.out.println(u);
        return "main";
    }

2.2.3 动态占位符提交(仅用于超链接)

使用框架提供的一个注解@PathVariable,将请求url中的值作为参数进行提取,只能是超链接。restful风格下的数据提取方式。restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

img

<a href="${pageContext.request.contextPath}/three/张三/22.action">动态提交</a>    
    
    @RequestMapping("/three/{uname}/{uage}")
    public String three(
            @PathVariable("uname")  ===>用来解析路径中的请求参数
            String name,
            @PathVariable("uage")
            int age){
        System.out.println("name="+name+",age="+(age+100));
        return "main";
    }

2.2.4 请求参数名称与形参名称不一致

请求与形参中的名字不对应,可以使用

@RequestParam(value="name1",required=true)

String namea来进行参数名称绑定。

![img](file:///C:\Users\10325\AppData\Local\Temp\ksohtml8128\wps4.jpg)

   提交请求参数与action方法的形参的名称不一致,使用注解@RequestParam来解析
    /**
     *  姓名:<input name="name"><br>
     *  年龄:<input name="age"><br>
     */
     
    @RequestMapping("/four")
    public String four(
            @RequestParam("name")  ===>专门用来解决名称不一致的问题
            String uname,
            @RequestParam("age")
            int uage){
        System.out.println("uname="+uname+",uage="+(uage+100));
        return "main";
    }

2.2.5 使用HttpServletRequest对象提取

在方法参数中声明一个request对象,使用request的getParameter()获取表单提交的数据,这样得到的数据还要手工进行数据类型的转换。

  /**
     *  姓名:<input name="name"><br>
     *  年龄:<input name="age"><br>
     */
  @RequestMapping("/five")
    public String five(HttpServletRequest request){
        String name = request.getParameter("name");
        int age = Integer.parseInt(request.getParameter("age"));
        System.out.println("name="+name+",age="+(age+100));
        return "man";
    }

2.3 请求参数中文乱码问题

对于前面所接收的请求参数,若含有中文,则会出现中文乱码问题。Spring 对于请求参数中的中文乱码问题,给出了专门的字符集过滤器:spring-web-5.2.5.RELEASE.jar 的
org.springframework.web.filter 包下的 CharacterEncodingFilter 类。

img

(1) 解决方案

在 web.xml 中注册字符集过滤器,即可解决 Spring 的请求参数的中文乱码问题。不过,最好将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。

 <filter>
        <filter-name>encode</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--
          配置参数
            private String encoding;
            private boolean forceRequestEncoding;
            private boolean forceResponseEncoding;
        -->
        <!--指定字符集encoding-->
        <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>encode</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping> 

(2) 源码分析

字符集设置核心方法:

img

2.4 处理器方法的返回值

使用@Controller 注解的处理器的处理器方法,其返回值常用的有四种类型:
➢ 第一种:ModelAndView
➢ 第二种:String
➢ 第三种:无返回值 void
➢ 第四种:返回自定义类型对象
根据不同的情况,使用不同的返回值。

2.4.1 返回 ModelAndView

若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时处理器方法返回ModelAndView 比较好。当然,若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView 对象。

在使用时,若该处理器方法只是进行跳转而不传递数据,或只是传递数据而并不向任何资源跳转(如对页面的 Ajax 异步响应),此时若返回 ModelAndView,则将总是有一部分多余:要么 Model 多余,要么 View 多余。即此时返回 ModelAndView 将不合适。

现在用的很少。

2.4.2 返回 String

处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址

返回内部资源逻辑视图名
若要跳转的资源为内部资源,则视图解析器可以使用 InternalResourceViewResolver 内部资源视图解析器。此时处理器方法返回的字符串就是要跳转页面的文件名去掉文件扩展名后的部分。这个字符串与视图解析器中的 prefix、suffix 相结合,即可形成要访问的 URI。

img

项目:returnString-viewName。在 receiveParameters-object 基础上修改。

直接修改处理器类 MyController

img

当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配置前辍与后辍了。

img

2.4.3 返回 void

对于处理器方法返回 void 的应用场景,AJAX 响应。
若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void。

例如,对于 AJAX 的异步请求的响应。

2.4.4 返回对象 Object

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

但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。一般用于ajax请求。
返回对象,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。

环境搭建

A、 maven pom.xml
由于返回 Object 数据,一般都是将数据转化为了 JSON 对象后传递给浏览器页面的。而这个由 Object 转换为 JSON,是由 Jackson 工具完成的。所以需要导入 Jackson 的相关 Jar 包。
依赖:

  <!--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>

B、 声明注解驱动

将 Object 数据转化为 JSON 数据,需要由消息转换器 HttpMessageConverter 完成。而转换器的开启,需要由<mvc:annotation-driven/>来完成。

SpringMVC 使用消息转换器实现请求数据和对象,处理器方法返回对象和响应输出之间的自动转换

当 Spring 容器进行初始化过程中,在<mvc:annotation-driven/>处创建注解驱动时,默认创建了七个 HttpMessageConverter 对象。也就是说,我们注册<mvc:annotation-driven/>,就是为了让容器为我们创建 HttpMessageConverter 对象。

img

在springmvc.xml文件中添加注解驱动<mvc:annotationdriven/>,它用来解析@ResponseBody注解

<mvc:annotation-driven></mvc:annotation-driven>

HttpMessageConverter 接口 : HttpMessageConverter<T>是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息

HttpMessageConverter<T>接口定义的方法:

boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象 ,同时指定支持 MIME 类型(text/html,applaiction/json 等)

boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在 MediaType 中定义。

LIst<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。

T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。

void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType

加入注解驱动<mvc:annotation-driven/>后适配器类的 messageConverters 属性值

img

img

返回自定义类型对象

返回自定义类型对象时,不能以对象的形式直接返回给客户端浏览器,而是将对象转换为 JSON 格式的数据发送给浏览器的。
由于转换器底层使用了Jackson转换方式将对象转换为JSON数据,所以需要导入Jackson的相关 Jar 包。
Step1:定义数据类

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

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

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

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }
}

返回 List 集合

@Controller
public class AjaxAction {

    //处理ajax请求,一定要加@ResponseBody
    @ResponseBody
    @RequestMapping("/ajax")
    public List<Student> ajax(){
        Student stu1 = new Student("张三",22);
        Student stu2 = new Student("李四",24);
        Student stu3 = new Student("王五",23);
       List<Student> list = new ArrayList<>();
       list.add(stu1);
       list.add(stu2);
       list.add(stu3);
        //调用json转换工具ObjectMapper进行转换
        return list;  //===>springmvc负责转换成json
    }
}

修改 index 页面

<script type="text/javascript">
    function show() {
        $.ajax({
            url:"${pageContext.request.contextPath}/ajax.action",
            dataType:"json",
            type:"get",
            success:function (list) {
              //  alert(list);
                var s="";
                $.each(list,function (i,stu) {
                  //  alert(stu);
                    s+=stu.name+"----"+stu.age+"<br>";
                });
                $("#mydiv").html(s);

            }
        });
    }
</script>

2.5 SpringMVC的四种跳转方式

默认的跳转是请求转发,直接跳转到jsp页面展示,还可以使用框架提供的关键字redirect:,进行一个重定向操作,包括重定向页面和重定向action,使用框架提供的关键字forward:,进行服务器内部转发操作,包括转发页面和转发action。当使用redirect:和forward:关键字时,视图解析器中前缀后缀的拼接就无效了。

本质还是两种跳转:

  • 请求转发
  • 重定向

image-20221021185753569

衍生出四种是

  • 请求转发页面
  • 转发action
  • 重定向页面
  • 重定向action

页面部分:

<!--ctrl+d:复制当前行-->
<a href="${pageContext.request.contextPath}/one.action">请求转发页面(默认)</a><br>
<a href="${pageContext.request.contextPath}/two.action">请求转发action</a><br>
<a href="${pageContext.request.contextPath}/three.action">重定向页面</a><br>
<a href="${pageContext.request.contextPath}/four.action">重定向action</a><br>

Controller部分:

@Controller
public class JumpAction {
    @RequestMapping("/one")
    public String one(){
        System.out.println("请求转发页面(默认)");
        //以前的访问方式
        //request.getRequestDispatcher("/admin/main.jsp").forward(request,response);
        //观察地址栏的变化:  http://localhost:8080/one.action
        //return "main"; //默认的访问方式是自动拼接前缀和后缀进行跳转
        return "forward:/fore/user.jsp";//只要使用了forward:就可以屏蔽前缀和后缀的拼接,自己手工构建返回的全部路径+.jsp
    }
    @RequestMapping("/two")
    public String two(){
        System.out.println("请求转发action");
        //观察地址栏的变化:  http://localhost:8080/two.action
        return "forward:/other.action";   //不使用forward:,就会是这样的路径  /admin/other.action/.jsp
    }
    @RequestMapping("/three")
    public String three(){
        System.out.println("重定向页面");
        //观察地址栏的变化  http://localhost:8080/admin/main.jsp
        return "redirect:/admin/main.jsp";//只要使用了redirect:就可以屏蔽前缀和后缀的拼接
    }
    @RequestMapping("/four")
    public String four(){
        System.out.println("重定向action");
        //观察地址栏的变化  http://localhost:8080/other.action
        return "redirect:/other.action";//只要使用了redirect:就可以屏蔽前缀和后缀的拼接
    }
}

2.6 SpringMVC支持的默认参数类型

这些类型只要写在方法参数中就可以使用了,系统调用时由系统自动赋值,即程序员可在方法内直接使用。

1)HttpServletRequest 对象

2)HttpServletResponse 对象

3)HttpSession 对象

4)Model/ModelMap 对象

5)Map<String,Object>对象

示例:

index.jsp

<a href="${pageContext.request.contextPath}/data.action?name=zar">访问服务器,进行数据携带跳转</a><br><br>

main.jsp

<body>
<h2>main...........显示数据</h2>
<%--
        request.setAttribute("requestUsers",u);
        session.setAttribute("sessionUsers",u);
        model.addAttribute("modelUsers",u);
        map.put("mapUsers",u);
        modelMap.addAttribute("modelMapUsers",u);
--%>
requestUsers:${requestUsers}<br>
sessionUsers:${sessionUsers}<br>
modelUsers:${modelUsers}<br>
mapUsers:${mapUsers}<br>
modelMapUsers:${modelMapUsers}<br>
从index.jsp页来来的数据${param.name}
</body>

Controller

@Controller
public class ParamAction {
    @RequestMapping("/data")
    public String param(HttpServletRequest request,
                        HttpServletResponse response,
                        HttpSession session,
                        Model model,
                        ModelMap modelMap,
                        Map map){
        //Map ,Model,ModelMap,request都使用请求作用域进行传值,
        //所以必须使用请求转发的方式进行跳转,否则丢失数据
        Student stu = new Student("张三",22);
        request.setAttribute("requestStu",stu);
        session.setAttribute("sessionStu",stu);
        modelMap.addAttribute("modelMapStu",stu);
        model.addAttribute("modelStu",stu);
        map.put("mapStu",stu);
        return "main"; //切记请求转发跳
    }
}

注意Model,Map,ModelMap都使用的是request请求作用域,意味着只能是请求转发后,页面才可以得到值

image-20221021191033150

2.7 日期处理

2.7.1 日期注入

日期类型不能自动注入到方法的参数中。需要单独做转换处理。使用@DateTimeFormat注解,需要在springmvc.xml文件中添加<mvc:annotation-driven/>标签。

(1)在方法的参数上使用@DateTimeFormat注解

@RequestMapping("/submitone")
public String submitdateone( @DateTimeFormat(pattern="yyyy-MM-dd") Date mydate){
    System.out.println(mydate);
    return "dateShow";
}

(2)在类的成员setXXX()方法上使用@DateTimeFormat注解

@DateTimeFormat(pattern="yyyy-MM-dd")
public void setDate(Date date) {
    this.date = date;
}

但这种解决方案要在每个使用日期类型的地方都去添加使用@DateTimeFormat注解,比较麻烦,我们可以使用@InitBinder注解来进行类中统一日期类型的处理。

(3)@InitBinder注解解决类中日期问题

//注册一个全局的日期处理注解
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
    dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(sf, true));
}

这样在类中出现的所有日期都可以进行转换了。

2.7.2 日期显示

(1)JSON中的日期显示

需要在类中的成员变量的getXXX方法上加注解.

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
public Date getDate() {
    return date;
}

(2)JSP页面的日期显示

需要使用国际化标签,先添加依赖

<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

导入国际化的标签库

<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

再使用标签显示日期

<div id="stulistgood">
<c:forEach items="${list}" var="stu">
<p>${stu.name}-------${stu.age}-------<fmt:formatDate value="${stu.date}" pattern="yyyy-MM-dd"></fmt:formatDate></p>
</c:forEach>
</div>

2.8 <mvc:annotation-driven/>标签的使用

<mvc:annotation-driven/>会自动注册两个bean,分别为DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter是springmvc为@controller分发请求所必须的。除了注册了这两个bean,还提供了很多支持。

1)支持使用ConversionService 实例对表单参数进行类型转换;

2)支持使用 @NumberFormat 、@DateTimeFormat;

3)注解完成数据类型的格式化;

4)支持使用 @RequestBody 和 @ResponseBody 注解;

5)静态资源的分流也使用这个标签;

2.9 资源在WEB-INF目录下

很多企业会将动态资源放在WEB-INF目录下,这样可以保证资源的安全性。在WEB-INF目录下的动态资源不可以直接访问,必须要通过请求转发的方式进行访问。这样避免了通过地址栏直接对资源的访问。重定向也无法访问动态资源。

项目案例:

页面结构图:

此目录下的动态资源,不可直接访问,只能通过请求转发的方式进行访问。

image-20221021193919855

springmvc.xml

    <!--添加视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

action:

@Controller
public class WebInfAction {
    @RequestMapping("/showIndex")
    public String showIndex(){
        System.out.println("index.............");
        return "index";
    }
    @RequestMapping("/showMain")
    public String showMain(){
        System.out.println("main.............");
        return "main";
    }
    @RequestMapping("/showLogin")
    public String showLogin(){
        System.out.println("login.............");
        return "login";
    }
    // 登录业务判断
    @RequestMapping("/login")
    public String login(String name, String pwd, HttpServletRequest request){
        if("admin".equals(name) && "123".equals(pwd)){
            return "main";
        }
        request.setAttribute("msg","用户名或密码不正确!");
        return "login";
    }
}

三、SpringMVC拦截器

SpringMVC 中的 Interceptor 拦截器,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

3.1 拦截器介绍

3.1.1 拦截器的应用场景

1、日志记录:记录请求信息的日志

2、权限检查,如登录检查

3、性能检测:检测方法的执行时间

3.1.2 拦截器的执行原理

img

3.1.3 拦截器执行的时机

  1. preHandle():在请求被处理之前进行操作

  2. postHandle():在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果

  3. afterCompletion:所有的请求响应结束后执行善后工作,清理对象,关闭资源

3.1.4 拦截器实现的两种方式

  1. 继承HandlerInterceptorAdapter的父类

  2. 实现HandlerInterceptor接口,实现的接口,推荐使用实现接口的方式

3.2 HandlerInterceptor接口分析

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

(1) preHandle

该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。

(2) postHandle

该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。

(3) afterCompletion

当preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据等。

3.3 自定义拦截器实现权限验证

实现一个权限验证拦截器。

  1. 修改web.xml文件中请求路径
<servlet-name>springmvc</servlet-name>
<!--全局匹配-->
<url-pattern>/</url-pattern>
  1. 将所有的页面放入WEB-INF目录下

  2. 开发登录action

@Controller
public class WebInfAction {

    @RequestMapping("/showIndex")
    public String showIndex(){
        System.out.println("访问index.jsp");
        return "index";
    }
    @RequestMapping("/showMain")
    public String showMain(){
        System.out.println("访问main.jsp");
        return "main";
    }

    @RequestMapping("/showLogin")
    public String showLogin(){
        System.out.println("访问login.jsp");
        return "login";
    }

    //登录的业务判断
    @RequestMapping("/login")
    public String login(String name, String pwd, HttpServletRequest request){
        if("zar".equalsIgnoreCase(name) && "123".equalsIgnoreCase(pwd)){
            //在session中存储用户信息,用于进行权限验证
            request.getSession().setAttribute("users",name);
            return "main";
        }else{
            request.setAttribute("msg","用户名或密码不正确!");
            return "login";
        }
    }
}
  1. 开发拦截器
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //是否登录过的判断
        if(request.getSession().getAttribute("users") == null){
            //此时就是没有登录,打回到登录页面,并给出提示
            request.setAttribute("msg","您还没有登录,请先去登录!");
            request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
            return false;
        }
        return true;//放行请求
    }
}

  1. 配置springmvc.xml文件
    <!--注册拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--映射要拦截的请求-->
            <mvc:mapping path="/**"/>
            <!--设置放行的请求-->
            <mvc:exclude-mapping path="/showLogin"></mvc:exclude-mapping>
            <mvc:exclude-mapping path="/login"></mvc:exclude-mapping>
            <!--配置具体的拦截器实现功能的类-->
            <bean class="com.bjpowernode.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
posted @ 2021-08-06 15:07  王陸  阅读(319)  评论(0编辑  收藏  举报