SpringMVC笔记-以及SSM整合思路

SpringMVC

Spring MVC介绍

Spring MVC是框架Spring的一部分,是基于Java实现的一个轻量级web框架。

SpringMVC的核心

学习Spring MVC框架最核心的就是中央调度器(DispatcherServlet)的设计,掌握好Dispatcher Servlet是掌握SpringMVC的核心关键。

Spring MVC是管理控制器对象,原来没有SpringMVC之前使用的是Servlet作为控制器对象使用。现在通过SpringMVC容器创建一种叫做控制器的对象,代替Servlet行使控制器的角色,功能。

导入依赖

注意:在Tomcat10.x以后,要导入点不是以前的javax.servlet-api了。而是需要换成下面的依赖。web使用的是jakarta.servlet的依赖。

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <scope>provided</scope>
</dependency>
<!--    springmvc依赖-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.5</version>
        </dependency>
中央调度器的配置
<?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中央调度器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--spring配置文件位置-->
        <!--
        配置文件默认是 /WEB-INF/springmvc-servlet.xml  如果不配置文件路径,则默认找这个地方的文件
                文件默认的名称是我们配置的DispatcherServlet(中央调度器的名称)加上 -servlet.xml 组成  <servlet-name>-servlet.xml
                springmvc 加载配置文件的时机如果不配置,则是在每次访问配置的虚拟映射路径的时候进行加载,
                WebApplicationContext ctx = new ClassPathXmlApplicationContext("springmvc-servlet.xml");
                创建web容器对象ctx需要读取配置文件,这个配置文件和Spring配置文件的配置一致
        -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <!--web容器启动时加载,配置文件只加载一次-->
        <!--
            web容器Tomcat创建容器的顺序,数值越小,创建时机越早(大于0的整数值)(大于0表示启动时就会创建),数值0表示访问的时候创建
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--拦截(捕获)除了jsp文件以外的所有请求-->
        <!--
        把一些请求交给servlet进行处理
        中央调度器的常用格式:
        1. 使用拓展名   *.xx  xx是自定义拓展名  *.do *.test
            但是要注意: 不能使用 *.jsp 因为以jsp结尾到底是访问这个页面视图,还是要将以jsp结尾的请求交给中央调度器处理呢,有歧义性
        2. 使用 / 稍后进行介绍
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
<!--注解扫描器-->
    <context:component-scan base-package="com.mao.web.controller"/>
<!--视图解析器,帮助解析视图-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--前缀,指定视图文件的路径-->
        <property name="prefix" value="/WEB-INF/view/"/>
<!--后缀,指定视图文件的拓展名-->
        <property name="suffix" value=".jsp"/>
    </bean>
package com.mao.web.controller;

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

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/06/01/周二 1:48 下午
 */
@Controller
public class HelloMVC {
    // RequestMapping注解,
    // 位置:可以放在方法上面,也可以放在类上面(可选)
    // 属性:value是一个数组,可以包含多个请求列表,也就是说一个方法可以处理多个请求,数组的值必须唯一,以 / 开头
    // 作用:把指定的请求,交给指定的方法进行处理,等同于 url-pattern
    @RequestMapping(value= {"/some.do"})
    public ModelAndView hello(){
        ModelAndView mv = new ModelAndView();
        // 向请求域中添加对象,相当于 request.setAttribute("","")
        mv.addObject("username","mao");
        // 指定视图,参数是视图完整路径
        // 但是当配置了视图解析器以后(配置文件中进行配置),参数为视图逻辑名称
        // 使用了逻辑名称,框架使用配置文件中视图解析器的前缀和后缀,拼接为完整路径
        // 视图执行的请求转发操作。。。
        mv.setViewName("hello");
        return mv;
    }

    /**
     * 访问other.do,请求转发给other页面
     * @return
     */
    @RequestMapping(value= {"/test/other.do"})
    public ModelAndView doOther(){
        ModelAndView mv = new ModelAndView();
        // 向请求域中添加对象,相当于 request.setAttribute("","")
        mv.addObject("username","other");
        mv.setViewName("other");
        return mv;
    }
}

请求的处理过程

一般情况:

用户发起请求 —》 Tomcat接收到请求。—〉 DispatcherServlet ---- 分派 ---- 》 具体执行的控制器controller — 〉 返回处理结果

SpringMVC的MVC组件

2. springMVC注解式开发

2.1 @RequestMapping注解的使用

属性:value 请求的url地址(虚拟映射)

位置:1. 在方法上,必须的属性 2. 在类的上面作为模块名称

属性method请求的方式,使用RequestMethod类的枚举值,表示请求的方式。

2.1.1 指定模块名称
// RequestMapping注解,
// 位置:可以放在方法上面,也可以放在类上面(可选)
// 属性:value是一个数组,可以包含多个请求列表,也就是说一个方法可以处理多个请求,数组的值必须唯一,以 / 开头
// 作用:把指定的请求,交给指定的方法进行处理,等同于 url-pattern
@RequestMapping(value={"/some.do"})
@Controller
@RequestMapping(value = "/test") // value属性:表示所有请求地址的公共前缀,相当于是模块名称,位置在类的声明上面
public class MyController {
    // 两个请求地址都有 /test 前缀,所以可以将其提取出来
//    @RequestMapping(value = {"/test/some.do"})
    @RequestMapping(value = {"/some.do"})
    public ModelAndView doSome() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("username", "mao");
        mv.setViewName("hello");
        return mv;
    }

    //    @RequestMapping(value = {"/test/other.do"})
    @RequestMapping(value = {"/other.do"})
    public ModelAndView doOther() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("username", "jun");
        mv.setViewName("other");
        return mv;
    }
}
2.1.2 对请求提交方式的定义

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

method属性的取值为 RequestMethod枚举常量,常见的是RequestMethod.GET 和 RequestMethod.POST 方式。

如果不指定该属性,则请求方式任意,任何请求都可以。可以处理所有方式的该请求。

/**
 * @RequestMapping: 属性method: 请求的方式:使用RequestMethod枚举类的常量进行赋值,如果不指定该属性,则请求方式任意,任何请求都可以
 * @return
 */
@RequestMapping(value = {"/show.do"},method = RequestMethod.GET)
public ModelAndView doShow() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("username", "jun");
    mv.setViewName("other");
    return mv;
}
2.1.3 处理器方法的参数

参数可以包含四种类型,这些参数会在系统调用时由系统自动进行赋值,程序员在方法内部直接进行调用就好了。

  1. HttpServletRequest
  2. HttpServletResponse
  3. httpSession
  4. 请求中所携带的参数
    @RequestMapping(value = {"/request.do"},method = RequestMethod.GET)
    public ModelAndView doRequest(HttpServletRequest request, HttpServletResponse response) {
        ModelAndView mv = new ModelAndView();
        // 获取请求参数,使用request对象进行获取
        System.out.println(request.getRequestURI());
        mv.addObject("username", request.getParameter("username"));
        mv.setViewName("post");
        return mv;
    }
2.1.4 接收请求的参数

错误: 状态码 400 表示客户端异常。主要是发生在用户提交参数的过程中。

1. 参数逐个接收

只要保证请求参数名于该请求处理方法的参数名相同就可。

  1. 参数的接收最好使用包装类型。例如Integer等。能够接收空值等情况,接收的是null

  2. 框架可以使用String到int,float等类型的自动转换。

  3. post请求中有乱码的问题,使用字符集过滤器

/**
     * 按照名称接收请求参数,请求参数名和控制器方法名的行参名一样。按照名称对象映射接收请求参数
     * @param username 用户名
     * @param password 密码
     * @param age age必须是一个数字
     *            参数接收:框架使用request对象,接收参数
     *                 String username = request.getParameter("");
     *            在中央调度器的内部调用 doProperParam方法时,按名称对象传递参数
     *                 doProperParam(username,password,Integer.valueOf(age))
     *                 框架可以实现请求参数 String 到 int,long,float等类型的转化。
     *            如果客户端传递过来的参数有null,或者这个字段没有赋值,建议方法的接收参数类型都使用包装类来接收空值的参数   Integer
     *            
     * @return
     */
    @RequestMapping(value = {"/param.do"})
    public ModelAndView doParam(String username,String password,Integer age) {
        ModelAndView mv = new ModelAndView();
        // 获取请求参数,使用request对象进行获取
        mv.addObject("username", username);
        mv.addObject("password", password);
        mv.addObject("age", age);
        mv.setViewName("param");
        return mv;
    }

springmvc解决中文乱码问题的过滤器

springmvc默认提供好的有一个解决乱码的过滤器。 CharavterEncodingFilter

    <!--声明框架提供好的过滤器:框架提供的,解决post请求中文乱码问题-->
    <filter>
        <filter-name>filter</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>
<!--            强制请求对象(request)对象使用encoding的编码格式-->
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
<!--            强制响应对象使用encoding的编码格式-->
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>filter</filter-name>
<!--强制所有请求先经过过滤器处理-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
请求参数名和行参名不一致(@RequestParam)

使用@RequestParam注解解决参数名不一致的情况

/**
     * 请求参数和行参不一致
     *      当请求当参数名和行参名不一致当时候,我们使用注解 @RequestParam 注解来进行解决
     *      属性: value:请求中当参数名称
     *             required: boolean类型,默认是true
     *             表示该请求中必须有此参数,没有则会报错,有此参数不代表该参数必须有值,但是必须将该参数传递过来
     *      位置: 在行参定义前
     * @param name
     * @param password
     * @param age
     * @return
     */
    @RequestMapping(value = {"/param2.do"})
    public ModelAndView doParam2(@RequestParam("name") String name,@RequestParam("pwd") String password,@RequestParam("age") Integer age) {
        ModelAndView mv = new ModelAndView();
        // 获取请求参数,使用request对象进行获取
        mv.addObject("username", name);
        mv.addObject("password", password);
        mv.addObject("age", age);
        mv.setViewName("param");
        return mv;
    }
}
2. 对象接收

多个请求参数时,建议使用对象形式接收。

对象接收:在控制器方法的行参是java对象,使用java对象的属性接收请求参数值。

要求:Java对象的属性名和请求中参数名一样。(这种方式最简单)

/**
 * 使用对象接收请求中的参数,
 * 要求:这里的请求参数名和对象的属性名一致
 *      java类有一个无参构造方法,属性有set方法
 *  框架的处理:
 *      1。 调用user的无参构造方法,创建对象
 *      2。 调用对象的set方法,同名的参数,调用对应的set方法
 *      参数是username,调用属性username的set方法
 * @return
 */
@RequestMapping(value = {"/obj.do"})
public ModelAndView doObj(User user) {
    ModelAndView mv = new ModelAndView();
    // 获取请求参数,使用request对象进行获取
    mv.addObject("username", user.getUsername());
    mv.addObject("password", user.getPassword());
    mv.addObject("age", user.getAge());
    mv.setViewName("obj");
    return mv;
}

package com.mao.bean;

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/06/03/周四 11:27 上午
 */
public class User {
    private String username;
    private String password;
    private Integer age;

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

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

2.2 DispatcherServlet作用
  1. 在init()中创建springmvc的容器对象。 WebApplicationContext wcp = new ClassPathXmlApplicationContext();

创建springmvc配置文件里面所有的java对象。java对象就是controller 对象

  1. DispatcherServlet本身就是一个servlet,但是不是我们写的,它负责接收请求。
2.3 配置文件说明
  1. web.xml 部署描述符文件。是给服务器用的(Tomcat),文件的作用是,在tomcat启动的时候,tomcat读取web.xml文件,根据文件的声明创建各种对象,根据文件中的声明,知道请求和servlet等对象的映射关系啊等等。
  2. 框架的配置文件。springmvc的配置文件。作用:声明框架创建项目中的各种对象,主要是创建Controller对象的。
配置文件加载的顺序和功能
  1. tomcat服务器启动,读取web.xml,根据文件的说明,创建对象。

    <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>*.do</url-pattern>
        </servlet-mapping>
    

    创建DispatcherServlet这个类 对象,会执行init()方法。在init方法中会执行springmvc容器对象创建

    WebApplicationContext ctx = new ClassPathApplicationContext(’“classpath:springmvc.xml”);

  2. springmvc读取配置文件的时候,

    <!--注解扫描器-->
        <context:component-scan base-package="com.mao.web.controller"/>
    <!--视图解析器,帮助解析视图-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--前缀,指定视图文件的路径-->
            <property name="prefix" value="/WEB-INF/view/"/>
    <!--后缀,指定视图文件的拓展名-->
            <property name="suffix" value=".jsp"/>
        </bean>
    

    使用组件扫描器,遍历controller包中所有的类,找到类上声明的注解@Controller,以及注解@RequestMapping,然后就可以创建相应的对象。就知道那个请求是执行那个方法进行处理请求了。

  3. 用户发起请求 ----》 中央调度器 ----〉

    中央调度器 里面有WebApplicationContext,WebApplicationContext里面有控制器对象。中央调度器就知道请求的执行方法是哪一个了。

2.4 控制器方法的返回值

控制器方法的返回值表示本次请求的处理结果,返回值有 ModelAndView,String,void,Object。四大类

请求的处理结果包含:数据和视图。

2.4.1 ModelAndView 数据和视图

请求的响应结果有数据和视图,使用ModelAndView最方便

数据:存放在request作用域

视图:执行请求转发操作

2.4.2 String 视图

控制器方法的返回值是String,表示执行forward转发操作。响应的结果就是一个视图。

视图可以是完整的视图路径,或者是视图的逻辑名称。

当前还没解决视图解析器和完整视图路径的冲突问题。

/**
     * 控制器方法返回String,表示逻辑视图名称。需要项目中配置视图解析器
     *
     * @return
     */
    @RequestMapping(value = {"/returnString.do"})
    public String doReturnString(HttpServletRequest request, String username, String password) {
        System.out.println(username);
        System.out.println(password);
        // 处理请求数据,需要自己手动处理了
        // 在行参上加上HttpServletRequest参数
        request.setAttribute("username", username);
        request.setAttribute("password", password);
        // 请求转发到 return/returnString.jsp页面
        return "return/returnString";
    }

    /**
     * 完整视图路径,项目中不能配置视图解析器
     *
     * @param request
     * @param username
     * @param password
     * @return
     */
    @RequestMapping(value = {"/returnString2.do"})
    public String doReturnString2(HttpServletRequest request, String username, String password) {
        System.out.println(username);
        System.out.println(password);
        // 处理请求数据,需要自己手动处理了
        // 在行参上加上HttpServletRequest参数
        request.setAttribute("username", username);
        request.setAttribute("password", password);
        // 请求转发到 return/returnString.jsp页面
        // 当前配置的有视图解析器,写完整路径的话肯定会报错的。所以完整路径和视图解析器发生了冲突,这个在后面可以进行解决
        return "return/returnString";
    }
}
2.4.3 没有数据和视图

空返回值void,经常用来作为响应ajax请求的。使用响应对象HttpServletResponse对象输出数据,响应ajax请求。

/**
 * 控制器方法返回的是void,响应ajax请求,使用HttpServletResponse输出数据
 * @param name
 * @param age
 */
@RequestMapping("/returnVoid-ajax.do")
public void doAjax(HttpServletResponse response,String name, Integer age) throws IOException {
    System.out.println(name+"---"+age);
    // 处理数据
    // 响应数据
    User user = new User();
    user.setAge(age);
    user.setUsername(name);
    user.setPassword("111");
    // 把对象转化为json类型
    ObjectMapper om = new ObjectMapper();
    String json = om.writeValueAsString(user);
    System.out.println(json);
    // 响应ajax
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write(json);
}
2.4.4 object 对象

返回 Student 表示的是 数据。

所以一般控制器方法返回的是对象Object,大部分都是用来处理Ajax请求。

返回对象Object,可以是List,Student,Map,String,Integer,。。。都是数据(对象)。ajax请求需要的就是数据。

在ajax请求中,一般需要从服务器返回的是json格式的数据,经常需要处理java对象到json数据的转换。而且还需要输出数据,来响应ajax到请求。

框架提供了处理java对象到json数据的转化,以及数据的输出工作。

1. 消息转换器 HttpMessageConverter

这是一个接口。

作用:

  1. 实现请求的数据转化为java对象
  2. 把控制器方法返回的对象转为json,xml,text,二进制等不同格式的数据。

HttpMessgaeConverter接口源码:

MediaType: 就是请求过来的数据格式,也叫媒体类型。比如json格式的数据。互联网中数据的格式。(application/json,text/html,image/gif,。。。)

public interface HttpMessageConverter<T> {
  	/**
  	* 作用:检查clazz这个类型的对象,能否转为 mediaType表示的数据格式
  			如果能转为mediaType表示的类型,返回true,true调用read方法
  	*/
    boolean canRead(Class<?> var1, @Nullable MediaType var2);
		/*
			将接收请求中的数据,把数据转为 clazz 表示的对象
		*/
  T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
 
 	/*
 		判断能否将 clazz 这种数据类型,转为 mediaType表示的数据格式
 		返回 true 表示能,则调用write方法
 	*/ 
  boolean canWrite(Class<?> var1, @Nullable MediaType var2);

    
		/*
			把t对象,按照 var2(contentType)说明的格式,把对象转为json或者xml
			T 范型 就是控制器方法返回的数据类型
		*/
    void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}

HttpMessageConverter接口的常见实现类:

  1. MappingJackson2HttpMessageConverter:使用jackson工具库的ObjectMapper把对象转为json数据格式
  2. StringHttpMessageConverter: 把字符串类型的数据,进行格式的转换和编码。
2. 使用实现类

框架根据控制器方法的返回值,自动查找使用的实现类。

默认情况下:springmvc使用了HttpMessageConverter接口的四个实现类。包括Http MessageConverter。

需要在springmvc加上注解驱动的标签:<mvc:annotation-driver/>.加上这个标签后,springmvc项目启动后,会创建HttpMessageConverter接口的七个实现类对象,包含了 StringHttpMessageConverter和MappingJackson2HttpMessageConverter。

3. @ResponseBody

@ResponseBody注解的作用就是把User转换后的json通过HttpServletResponse对象输出给浏览器。

4. 控制器方法返回对象转为json的步骤
  1. pom。xml加入jackson依赖,springmvc框架默认处理json就是使用jackson
  2. 在springmvc的配置文件中,加入注解驱动标签
  3. 在控制器方法的上面加入@ResponseBody注解,表示返回值数据,输出到浏览器。
/**
     * 控制器方法返回的对象转化为json格式数据响应客户端
     *
     * @return
     * @ResponseBody: 这个注解会自动  把 对象转化的json输出给浏览器,并设置响应头
     * 框架的处理模式:
     *      1。 框架根据控制器方法的返回值类型,找到HttpMessageConverter接口的实现类。然后找到json的实现类
     *      2。 使用实现类MappingJackson2HttpMessageConverter。执行write方法。把student对象转化为json格式的数据
     *      3。 框架使用ResponseBody注解,把json数据输出给浏览器
     */
    @ResponseBody
    @RequestMapping("/ajaxUser.do")
    public User doAjaxUser(String name, Integer age) {
        User user = new User();
        user.setUsername(name);
        user.setPassword("123");
        user.setAge(age);
        return user;
    }
/**
     * 控制器方法返回的是集合,则对应json 会转换为 array
     * @param name
     * @param age
     * @return
     */
    @ResponseBody
    @RequestMapping("/ajaxUserList.do")
    public List<User> doAjaxUserList(String name, Integer age) {
        User user = new User();
        user.setUsername(name);
        user.setPassword("123");
        user.setAge(age);
        User user1 = new User("222","222",222);
        List<User> list = new ArrayList<User>();
        list.add(user);
        list.add(user1);
        return list;
    }

 /**
     * 控制器方法返回值是String,在方法上加上 @ResponseBody注解,返回值不是表示视图,而是表示数据
     *
     * 区分String 返回值是数据还是视图,就看方法上是否有 @ResponseBody 注解
     *
     * 默认响应头: Content-Type: text/plain;charset=ISO-8859-1 发生中文乱码
     * 我们配置的过滤器不会起作用,因为ajax请求默认走的是应答对象
     *
     * 解决中文乱码: 需要使用 @RequestMapping注解的produces属性 ,指定 content-type的值
     * 框架处理String返回值:
     *      1。 框架使用StringMessageConverter
     *      2。 StringHttpMessageConverter 默认使用的是text/plain;charset=ISO-8859-1字符集
     * @param name
     * @param age
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/ajaxUserString.do" ,produces = "text/plain;charset=utf-8")
    public String doAjaxUserString(String name, Integer age) {
        User user = new User();
        user.setUsername(name);
        user.setPassword("123");
        user.setAge(age);
        return user.toString();
    }
2.5 解读映射标签<url-pattern/>
2.5.1 配置详解
1. *.do

在没有特殊要求的情况下,springmvc的中央调度器DisptcherServlet的<url-pattern/>常使用后缀匹配方式,如写为*.do或者 *.action等。

2. /

当我们配置了映射地址为 /,这个中央调度器就变成了tomcat默认的servlet的身份了,可以帮我们处理静态资源以及未映射到其他请求的请求。**发现无论是图片,还是js文件,html静态资源文件等,都无法请求成功了。**动态资源(jsp文件还是tomcat帮我们处理的)以及配置了处理的请求的请求可以正常访问。

静态等资源访问失败的原因就是我们没有配置对应的控制器对象

<!--配置中央调度器-->
    <servlet>
        <servlet-name>servlet</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>servlet</servlet-name>
        <!--映射方式二:使用 / -->
        <!--
            使用 / 导致中央调度器成为了默认的 default servlet
            需要处理静态资源和其他未映射的请求。默认中央调度器没有处理静态资源的控制器对象,所以静态资源的请求都是 404
            如果项目中 配置 映射路径为 / 动态资源可以正常访问,静态资源不能政策访问。需要处理静态资源的访问工作。
            -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
3. 静态资源的处理

实际上:无论是静态页面(html),图片,jsp文件,js文件等,实际上都是Tomcat服务器帮我们处理等。只有 some.do 等请求才是我们的中央调度器帮我们处理的。所以实际上tomcat容器肯定也有一个servlet,帮我们处理一些静态的请求了。

4. tomcat 的默认servlet

tomcat的默认的servlet叫做default,帮我们处理所有未映射到其他请求的请求。

<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

		<servlet-mapping>
				<servlet-name>default</servlet-name>
      	<url-pattern>/</url-pattern>
		</servlet-mapping>

Default 就是默认的servlet,提供了静态资源的处理,以及所有未映射到其他请求的请求。根据tomcat默认的servlet的配置映射路径是 /,而这个default是处理我们没有配置的其他请求,所以我们可以得知 / 的优先级一定是比较低的。

所以当我们配置了 / 动态资源正常访问,静态资源不能了。需要处理静态资源的访问工作。

2.5.2 第一种处理静态资源的方式

在springmvc的配置文件中加入 <mvc:default-servlet-handle>标签,springmvc框架会在项目运行时,加入DefaultServletHttpRequestHandle对象,让这个对象处理静态资源的访问

这个对象会把接收到的静态资源的请求地址转发给tomcat到default对象,让它处理。

优点:解决方式简单

缺点:依赖服务器tomcat到default对象。(该方式现在用的少了)

注意:该注解 <mvc:default-servlet-handle>和@RequestMapping二者有冲突,解决方案就是在配置文件中声明注解驱动,加上注解驱动的标签。

<!--配置注解驱动-->
    <mvc:annotation-driven/>
<!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="suffix" value=".jsp"/>
        <property name="prefix" value="/WEB-INF"/>
    </bean>
<!-- 静态资源处理的第一种方式-->
    <mvc:default-servlet-handler/>
2.5.3 第二种解决静态资源的方式

在springmvc配置文件中加入一个<mvc:resources/>标签,框架会创建ResourcesHttpRequestHandle控制器对象,使用这个对象处理静态资源的访问。不依赖Tomcat服务器。推荐使用。

该标签也会和@Requestmapping发生冲突,因此也需要配置注解驱动标签。

    <!-- 静态资源处理的第二种方式-->
    <!--
    mapping: 访问静态资源的uri地址,可以使用通配符 **
            ** : 表示任意目录和目录下的文件
    location: 静态资源在项目中的位置,不要使用 /WEB-INF 目录
    -->
<!--该标签可以多次使用-->
    <mvc:resources mapping="/html/**" location="html/"/>

Spring MVC的优点

SSM框架整合

1. 整合思路

SSM整合思路: Spring + SpringMVC + Mybatis

SSM整合是使用三个框架的优势功能,三个框架对应的三层架构的三层。视图层 业务层 持久层

ssm整合就是把对象交给容器管理,让容器去创建项目中要使用的java对象。现在有两个容器。

第一个是 Spring容器:Spring容器是管理service和dao等对象的。是业务层对象的容器。

第二个是SpringMVC容器:管理控制器对象的。是视图层对象

SSM整合就是把对象交给容器管理。两个容器共存,各自管理不同的对象。把对象声明到配置文件中,让两个容器创建对象。

2. 容器的创建和使用

Spring容器创建:在web.xml声明监听器ContextLoader Listener,这个功能框架已经写好了。功能是创建Spring的容器对象 WebApplicationContext。在创建webApplicationContext对象时,读取Spring的配置文件,遇到的bean标签或者注解,就能创建service,dao等对象,放到容器中进行管理。

WebApplicationContext spring = new WebApplicationContext(‘配置文件’)

SpringMVC容器:在web.xml声明了中央调度器DispatcherServlet,在这个servlet等init方法中,创建了容器对象WebapplicationContext,在创建该对象时,会读取springmvc的配置文件,读取配置文件的时候,遇到@Controller注解,创建控制器对象,放到容器中。

WebApplicationContext springmvc = new WebApplicationContext(‘配置文件’)

3. 两个容器之间的关系

设计上,SpringMVC容器对象时Spring容器的子容器。可以理解为Java中的继承关系,但是并不是真正的继承。所以子容器可以访问父容器中的对象。

4. 整合步骤

  1. 创建mavenweb项目

  2. 加入依赖坐标(spring,springmvc,myabtis,mybatis-spring,mysql驱动,druid连接池,jackson)

  3. 配置web.xml文件:声明容器对象

    1. 声明Spring监听器ContextLoaderListener:创建Spring的容器对象,创建service和dao层对象
    2. 声明springmvc的中央调度器,创建springmvc容器,创建controller层对象
    3. 声明字符集过滤器(mvc提供的),解决post请求乱码
  4. 写Spring,springmvc,myabtis的配置文件

  5. Java代码,实体类,dao接口,mapper文件,service,controller类,使用注解声明对象和赋值

  6. 创建视图文件,各种jsp

mybatis Config配置文件

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

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <typeAliases>
        <package name="com.mao.bean"/>
    </typeAliases>

    <mappers>
<!--        配置package 则需要保证接口名和配置文件名相同,且在同一个包下-->
        <package name="com.mao.dao"/>
    </mappers>

</configuration>

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--Spring配置文件-->
    <!--    注解扫描器-->
    <context:component-scan base-package="com.mao.service"/>

    <!--jdbc配置文件-->
    <context:property-placeholder location="classpath:conf/jdbc.properties"/>
    <!--    数据源-->
    <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}"/>
        <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>
    <!--    创建sqlSessionFactoryBean -->
    <bean id="factoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--配置文件路径 下面配置类mapper,则不需要书写myabtis的config配置文件了-->
        <property name="configLocation" value="classpath:conf/mybatisConfig.xml"/>
    </bean>

    <!--创建dao对象,也就是mapper实现类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="factoryBean"/>
        <!--dao 层接口-->
        <property name="basePackage" value="com.mao.dao"/>
    </bean>

    <!--    Spring事务的配置-->

</beans>

springmvc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <!--springmvc配置文件-->
    <!--    声明组件扫描器 控制层交给mvc进行管理-->
    <context:component-scan base-package="com.mao.web.controller"/>
    <!--配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--        前缀和后缀-->
        <property name="prefix" value="/WEB-INF/jsp"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--    注解驱动
    1。 创建HttpMessageConverter接口的七个实现类对象,处理Java对象到json格式的转化
    2。 解决静态资源中,动态资源访问失败的问题
    -->
    <mvc:annotation-driven/>
</beans>

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<!--配置Spring启动监听器-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>


<!--    springmvc配置-->
    <!--配置中央调度器-->
    <servlet>
        <servlet-name>servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--配置初始化参数-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:conf/dispatcherServlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>servlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!--配置过滤器-->
    <filter>
        <filter-name>filter</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>filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

5. 路径问题

在项目中,有时候请求地址前面有 /,有时候没有 /,但是有时候都可以请求成功,这有问题呢?

相对路径问题

在页面中,有路径的问题,访问路径有 / 开头,也有没有 / 开头的

<a href='test/some.do'>没有斜杠</a>
<a href='/test/some.do'>有斜杠</a>
<a href='http://www.baidu.com'></a>

地址的区别:

  1. 有协议开头的,是绝对地址,地址是唯一的,可以直接访问。
  2. 没有协议开头的,是相对路径,单独使用的时候不代表某个资源,不能访问。相对地址必须和参考地址在一起,才能正常访问。

参考地址:有斜杠 / ,没有斜杠 /,两个参考地址是不一样的。

没有斜杠时:http://localhost/ssm/index.jsp

当我们成功进入某一个资源后,地址会发生改变的http://localhost/ssm/test/some.do,那么参考地址也就变了,再次点击就会发现地址发生错误了。http://localhost/ssm/test/test/some.do

没有斜杠开头时,就会把参考地址和当前相对地址组合在一起成为最终的访问地址。

解决方式:

可以使用 el表达式 ${pageContext.request.contextPath} 这个可以获取当前项目的虚拟目录。优点就是好理解,缺点就是每个地方都需要使用。

也可以使用 html的 base 标签,该标签的作用就是 为页面上的所有链接规定默认地址或目标,也就是说当前页面的所有地址的相对地址都是base标签指定的地址

<head>
  <base href='http://localhost/ssm/'/>
</head>
<%
 String basePath = request.getScheme() +":" + request.getServerName()+":" + request.getServerPort()+":"+
   request.getContextPath()+"/";
%>
<head>
  <base href='<%=basePath%>'/>
</head>
有斜杠开头(绝对路径)

有斜杠开头的地址,会直接在 http://localhost 加上带斜杠的地址,也就是 直接变成 http://localhost/test/some.do

使用 / 开头的地址,参考地址是服务器地址,也就是协议到端口号的位置。 不会包含项目的虚拟目录/ssm

解决方式就是上面解决方式的第一种,使用 el表达式进行解决。

SpringMVC核心技术

1. 请求重定向和转发

请求转发:

使用 forword进行请求转发,实现原理就是 request.getRequestDispatcher("").forward(req,resp)

@Controller
@RequestMapping("/test")
public class MyController2 {
    /**
     * 控制器方法返回的是ModelAndView,实现请求转发
     * 语法: mv.setViewname("forward:视图的完整路径")
     * forward的特点:不和视图解析器一同工作的,就当作项目中没有配置视图解析器
     * @return
     */
    @GetMapping("/forward.do")
    public ModelAndView doForward(){
        ModelAndView mv = new ModelAndView();
        // 显式的使用 forward 进行转发操作,我们不管加不加 forward,都是进行转发操作,
        // 但是,只有我们显式的加上 forward: 这个关键字,才会忽略视图解析器,
        // 没有该关键字,也就是省略了forward的时候,我们只需要写加上视图解析器以后的名称
        // 意义就是我们的文件可能不在视图解析器所配置的目录中,所以使用该方式可以获取其他目录中的文件视图
        mv.setViewName("forward:/WEB-INF/show/forward.jsp");
        return mv;
    }
}
重定向

使用 redirect进行重定向,实现原理就是 response.sendRedirect("");

/**
 * 当控制器方法返回的 ModelAndView 实现重定向时,
 * 语法
 * mv.setViewName("redirect:完整路径");
 * 特点:也会忽略视图解析器
 * 框架提供的重定向功能
 * 1。 框架可以实现两次请求之间的数据传递,把第一次请求中的Model里面的简单类型的数据,转为字符串,附加到目标页面的后面,做get参数传递
 * 可以在目标页面中获取参数值使用
 *获取参数的值 可以使用 ${param.xxx} 进行获取参数的值 
 * @return
 */
@GetMapping("/redirect.do")
public ModelAndView doRedirect() {
    ModelAndView mv = new ModelAndView();
    // 重定向 无法访问 WEB-INF 里面的内容哦
    mv.setViewName("redirect:/html/redirect.jsp");
    return mv;
}

2. 异常处理

2.1 springmvc框架处理异常的方式:

使用 @ExceptionHandler注解处理异常。框架处理异常的方式是集中处理。把各个Controller类里面的方法中抛出的异常集中到一个地方处理。处理异常的叫做异常处理器。

  1. @Exceptionhandler:放在方法上面的,表示此方法可处理某个类型的异常。当异常发生时,执行这个方法。
  2. @ControllerAdvice: 放在类上面,表示此类中有异常处理的方法,相当于aop中的@Aspect。此注解可以理解为控制器增强,就是给Controller类增强功能的处理功能。
2.2 @ExceptionHandler

使用该注解可以将一个方法指定为异常处理方法。该注解只有一个可选属性value,为一个Class<?> 数组,用于指定该注解的方法所要处理的类,即所要匹配的异常。

而被注解的方法,其返回值可以说 ModelAndView,String,或者 void,方法名随意,方法参数可以说Exception及其子类,HttpServletRequest,等。

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

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

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

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

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

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

package com.mao.web.handle;

import com.mao.web.exception.AgeException;
import com.mao.web.exception.NameException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * @Description @ControllerAdvice 表示当前类是一个异常处理类
 * @Author 毛毛
 * @CreateDate 2021/06/13/周日 1:35 下午
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    // 定义处理异常的方法 和Controller类中的定义方式相同

    /**
     * Exception 表示Controller类中的方法抛出去的异常对象,被处理的方法抛出什么异常,我们就接收什么异常
     *
     * @param e
     * @return
     * @ExceptionHandler: 表示此方法处理异常
     * 属性 value : 值是异常的类型
     */
    @ExceptionHandler(value = NameException.class) // 表示此方法处理异常
    public ModelAndView doNameException(Exception e) {
        System.out.println(e);
        // 发生了异常以后,我们可以记录异常日志,发送通知给程序员等,还可以给用户友好的提示,等等
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "姓名有误!");
        mv.setViewName("redirect:/fail.jsp");
        return mv;
    }

    @ExceptionHandler(value = AgeException.class) // 表示此方法处理异常
    public ModelAndView doAgeException(Exception e) {
        System.out.println(e);
        // 发生了异常以后,我们可以记录异常日志,发送通知给程序员等,还可以给用户友好的提示,等等
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "年龄有误!");
        mv.setViewName("redirect:/fail.jsp");
        return mv;
    }

    @ExceptionHandler() // 表示此方法处理未知异常,也就是任何类型的异常 优先级最低,
    // 只有错误类型都没有被异常处理方法所捕获,才会将错误方法给该方法执行
    public ModelAndView doOtherException(Exception e) {
        System.out.println(e);
        // 发生了异常以后,我们可以记录异常日志,发送通知给程序员等,还可以给用户友好的提示,等等
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "有误!");
        mv.setViewName("redirect:/fail.jsp");
        return mv;
    }
}

**还是要注意一下,我们框架进行重定向的时候,会把请求域的数据以查询字符串的形式拼接在重定向的地址后面,也就是说发起的说get请求,在新页面我们可以通过el表达式的${param.xxx}**的形式获取参数的值。

3. 拦截器

Spring MVC中的拦截器Interceptor说非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理和后处理。

拦截器:是框架中的一种对象,需要实现接口HandlerInterceptor。拦截用户请求的。准确的说是拦截请求到Controoler方法处理之前的请求。

作用: 可以预先对请求作出相应的处理,然后根据处理的结果来决定是否执行Controller。也可以把多个Controller中共用的功能定义到拦截器。

特点:

  1. 拦截器分为系统拦截器和自定义拦截器。
  2. 一个项目中可以有多个拦截器。
  3. 拦截器侧重拦截用户的请求。
  4. 拦截器的执行时机是在Controller执行相应的请求之前执行的。
3.1 拦截器的定义
  1. 创建拦截器需要让该类实现接口HandlerInterceptor,实现接口中的三个方法。
  2. 在配置文件中(mvc的配置文件)声明拦截器对象,并指定拦截的uri地址

拦截器类的定义;

package com.mao.web.interceptor;

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

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

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/06/13/周日 3:03 下午
 */
public class MyInterceptor implements HandlerInterceptor {
    /**
     * preHandle: 预先处理请求的方法 方法参数:三个
     *
     * @param request
     * @param response
     * @param handler  该参数指被拦截的控制器对象(controller)方法
     * @return 布尔值  true 请求是正确的,表示放行,让controller方法处理,三个拦截器方法都会被执行。
     * <br>           false 请求被拦截,无法被controller方法执行,也只会执行该拦截器方法,其他的两个也不会执行,请求到此截止。
     * 特点:该方法是在控制器方法的执行前执行,可以对请求进行预处理,做登陆检验,权限判断,统计数据等
     * 可以决定请求是否执行
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... 111111111111");
        return true;
    }

    /**
     * postHandle:后处理方法
     *
     * @param request
     * @param response
     * @param handler      被拦截的控制器对象
     * @param modelAndView 控制器方法的返回值,也就是请求的返回值
     *                     特点:在控制器方法后执行
     *                     能够获取到控制器方法的执行结果。可以修改原来的执行结果。可以修改数据和视图。
     *                     作用:可以做对请求的二次处理
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle 22222222");
        // 对数据进行二次处理
        modelAndView.addObject("username", request.getParameter("name") + "mao");
    }

    /**
     * afterCompletion 最后执行的方法
     *
     * @param request
     * @param response
     * @param handler  被拦截的控制器对象(controller)
     * @param ex       异常对象
     *                 特点: 该方法在请求完成之后执行的,请求完成处理的标志是 视图处理完成,对视图执行转发操作
     *                 也就是说用户已经接收到了响应结果。
     *                 该方法一般是程序最后要做的工作,可以释放内存,清理临时变量,例如删除会话域中的某些值
     *                 方法的执行条件:
     *                 当前拦截器的 preHandle()方法必须执行
     *                 而且   preHandle方法的返回值必须为 true
     * @throws Exception
     */
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion ... 33333333");
    }
}

拦截器在配置文件的声明;

<!--声明拦截器-->
    <mvc:interceptors>
        <!--第一个拦截器-->
        <mvc:interceptor>
            <!--拦截器要拦截的uri地址,可以使用通配符 ** -->
            <!-- 拦截以 /ssm/ 开始的请求-->
            <mvc:mapping path="/ssm/**"/>
            <!--不拦截的地址-->
            <mvc:exclude-mapping path="/test/**"/>
            <!--指定该拦截器使用的bean类-->
            <bean class="com.mao.web.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
3.2 多个拦截器

使用两个拦截器,看拦截器的执行顺序,以及那个方法控制请求的执行。

package com.mao.web.interceptor;

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

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

/**
 * @Description
 * @Author 毛毛
 * @CreateDate 2021/06/13/周日 3:03 下午
 */
public class MyInterceptor2 implements HandlerInterceptor {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("2222222222 preHandle .... ");
        return true;
    }


    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("22222222222222 postHandle ");
        // 对数据进行二次处理
        modelAndView.addObject("username", request.getParameter("name") + "mao22");
    }


    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("2222222222222222 afterCompletion ... ");
    }
}

  1. 两个拦截器的preHandle的返回值都为true时

    11111111111111 preHandle .... 
    2222222222 preHandle .... 
    doSome ..........
    22222222222222 postHandle 
    11111111111111111postHandle 。。。。。
    2222222222222222 afterCompletion ... 
    11111111111111 afterCompletion ...
    
  2. 第一个preHandle为true,第二个为false时

    11111111111111 preHandle .... 
    2222222222 preHandle .... 
    11111111111111 afterCompletion ... 
    

    发现第一个拦截器的afterCompletion方法执行了,这是因为第一个拦截器返回值为true,所以满足了执行的条件。

  3. 第一个preHandle为false,第二个为true或者false

    11111111111111 preHandle .... 
    
3.3 为什么使用多个拦截器
  1. 把验证功能分散到独立的拦截器。每个拦截器做单一的验证处理
  2. 组合多个拦截器。
3.4 总结

多个拦截器,是穿在一个链条上的,多个拦截器和一个控制器对象在一个链条上。框架中使用 HandlerExecutionChain(处理器执行链),表示这个执行链条。

public class HandlerExecutionChain {
    private final Object handler; // 控制器对象
  	private Handlerinterceptor[] interceptors;// 存放多个拦截器对象的。MyInterceptor 
    private final List<HandlerInterceptor> interceptorList;
}

拦截器实现12 21 的执行顺序,是通过遍历数组 Handlerinterceptor[] interceptors 进行实现的

3.5 拦截器和过滤器的对比
  1. 拦截器是mvc框架中的对象。过滤器是servlet中的对象
  2. 拦截器对象是框架容器创建的,过滤器对象是tomcat容器创建的。(过滤器是我们手写,但是对象的创建是tomcat创建的,而且只在服务器启动时创建,服务器销毁时自动销毁)
  3. 拦截器侧重对请求做判断的,处理的,可以截断请求。过滤器是侧重对request,response对象的属性,参数设置值的。例如request.setCharacterEncoding().
  4. 拦截器的执行时机有三个,控制器方法之前,之后,请求完成后。过滤器是在请求之前执行的。(当然过滤器在放行以后也可以做其他的处理)
  5. 拦截器是拦截对controller,动态资源请求的。过滤器可以过滤所有请求,动态和静态的都可以。
  6. 拦截器和过滤器同时存在的话,先执行的是过滤器( 为什么最先执行过滤器,因为在servlet规范中是这样定义的 ),后面是中央调度器(分配请求到指定的controller类中的方法嘛),最后才是拦截器,然后才会执行控制器方法。

4. springmvc内部的执行流程

流程图:

内部处理过程:(忽略tomcat)

  1. 用户发起请求,交给中央调度器(dispatcherServlet)

  2. 中央调度器把请求(request)交给处理器映射器(HandleMapping)。

    处理器映射器:spirngmvc框架中的对象,需要实现接口HandleMapping。实现该接口的都是映射器。

    ​ 作用:从springmvc容器中获取控制器对象,把找到的控制器和拦截器对象都放到处理器执行链对象中,保存并返回给中央调度 器。(ApplicationContext.getBean() )

  3. 中央调度器把获取到的 处理器执行链中的控制器对象,交给了处理器适配器(HandleAdapter)。

    处理器适配器:是springmvc框架中的对象,实现HandleAdapter接口

    适配器作用:执行控制器的方法,也就是执行MyController.doSome()方法。得到结果ModelAndView,并把结果交给中央调度器。

  4. 中央调度器把控制器执行的结果再次交给视图解析器,

    视图解析器:springmvc框架中的对象,需要实现ViewResolver接口。

    视图解析器的作用:处理视图的,组成视图的完整路径,能创建View类型的对象

  5. 中央调度器调用 View类方法,把Model中的数据放到了request作用域。对视图执行forward操作。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尤雨东

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值