Java:111-SpringMVC的底层原理(中篇)

这里续写上一章博客(110章博客):
现在我们来学习一下高级的技术,前面的mvc知识,我们基本可以在67章博客及其后面相关的博客可以学习到,现在开始学习精髓:
Spring MVC 高级技术:
拦截器(Inteceptor)使用:
监听器、过滤器和拦截器对比(前面两个在53章博客可以学习到,后面只是名称上的解释,如果可以,那么前面两个也可以说成是拦截器,所以存在很多框架的拦截器):
Servlet:处理Request请求和Response响应
过滤器(Filter):对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进行过滤处理
监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随Web应用的启动⽽启动,只初始化一次,然后会一直运行监视,随Web应用的停⽌⽽销毁
监听器的作用与过滤器虽然都有拦截的意思,但是偏重不同
监听器可以选择对一些数据或者说数据变化进行监听以及拦截,而过滤则是对过来的请求直接拦截,而不是更加里面的数据拦截,所以一般情况下,过滤器通常是在监听器之前进行拦截的
那么说监听器一般有如下的作用:
作用一:做一些初始化工作,web应用中spring容器启动ContextLoaderListener
作用⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁,变量的创建、 销毁和修改等,可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤HttpSessionLisener等
拦截器(Interceptor):是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler),一般来说,这个拦截在一定程度上使用了过滤器以及监听器,因为需要确定拦截的数据,通常需要先获得,所以mvc的拦截器的实现方式通常在于过滤器或者说监听器(注意,只是因为他需要对应的数据,所以他才会在于其他的器,如过滤和监听,否则拦截器一般只是拦截指定数据的处理,而不是在于什么)
根据上面的说明,其实从配置的⻆度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是 配置在表现层框架自己的配置⽂件中的,所以Interceptor一般是框架自己的
根据前面的说明,可以知道如下:
拦截器会在如下的情况可能会发生拦截:
Handler业务逻辑执行之前拦截一次(操作url)
在Handler逻辑执行完毕但未跳转⻚⾯之前拦截一次(操作转发,如果没有,那么一般操作响应数据,如果也没有,那么虽然拦截,但并未做什么)
在跳转⻚⾯之后拦截一次(比如对应的json的处理,或者并未处理)

在这里插入图片描述

前面我们知道了这个图:

在这里插入图片描述

以及他的说明:
流程说明:
第一步:用户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器(一般是map保存的)
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器,可以根据xml配置、注解进行查找,因为查找,所以是映射),⽣成处理器对象及处理器拦截器(如果有则⽣成)一并返回DispatcherServlet,他负责创建
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler(controller的方法,生成对象了,这里相当于调用前面的handle01方法,他负责调用)
第六步:Handler执行完成给处理器适配器返回ModelAndView,即处理器适配器得到返回的ModelAndView,这也是为什么前面我们操作方法时,是可以直接操作他并返回的,而返回给的人就是处理器适配器,就算你不返回,那么处理器适配器或者在之前,即他们两个中间,可能会进行其他的处理,来设置ModelAndView,并给处理器适配器
第七步:处理器适配器向前端控制器返回 ModelAndView(因为适配或者返回数据,所以是适配),ModelAndView 是SpringMVC 框架的一个 底层对 象,包括 Model 和 View
第⼋步:前端控制器请求视图解析器去进行视图解析,根据逻辑视图名来解析真正的视图(加上前后的补充,即前面的配置视图解析器)
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进行视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域,改变了servlet,最终操作servlet来进行返回
第⼗一步:前端控制器向用户响应结果(jsp的)
即可以理解:请求找路径并返回(1,2,3),给路径让其判断路径并返回且获得对应对象(4,5,6,7),变成参数解析(如拼接) 进行转发(8,9),然后到jsp(10),最后渲染(11)
所以说:
第一次拦截:在1和5中间处理
第二次拦截:在6到8中间处理
第三次拦截:在9到11中间处理
其实通过图片,我们应该知道,第一次拦截应该是在3和4中在前端控制器旁边处理,而7和8就是第二次拦截,10到11则是第三次拦截(这里也可能是9到10)
所以可以知道,其实mvc自带的有一些拦截,这也是对应注解,比如@RequestBody或者@ResponseBody可以操作的原因
当然,我们也可以进行添加拦截,这在后面会说明的
为了更加的知道拦截器的处理,我们直接来进行实战:
注意:mvc的拦截器是里面的,也就是说,servlet原本的过滤器必然先处理或者后处理
实际上我们学习源码很大程度是必须要有实战的,因为一个框架的源码我们基本是不可能全部读完的,这取决于一个框架是由很长时间的迭代,以及很多人一起开发完成的,当然,如果你的框架够小,那么可以是单独完成,在这种情况下,学习框架中,用阅读源码来学习,我们只能知道他的一点实现方式,所以学习框架通常需要直接的实战来进行学习,来直接的确定他的作用,而不是单独看源码来确定作用(你怎么知道他有没有其他关联,并且要知道这个关联需要看更多的源码),也就是说,实际上源码的解析大多数是让你知道他的实现方式,而不是具体细节(比如,他为什么这样定义变量等等),当然,除了实现方式有时候也需要学习设计模式,这个在以后会单独给一个博客来进行处理的,先了解一些框架的设计模式再说
那么,既然要实战,我们首先需要操作一个项目,项目如下:

在这里插入图片描述

对应的依赖,在前面我们已经给过多次了,这里我们继续给出吧
 <packaging>war</packaging>
    <dependencies>
        <dependency>
            <!--mvc需要的依赖,即有前端控制器DispatcherServlet-->
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!--servlet坐标,若不使用对应的类,如HttpServletRequest的话,可以不加-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
            <!--这个必须要,后面的可以不写(后面两个),但最好写上,防止其他情况-->
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>
如果需要补充,自行补充吧
对应的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">

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

</web-app>
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<button id="btn">ajax提交</button>
<script>
    $("#btn").click(function () {
        let url = 'test/in';
        let data = '[{"id":1,"username":"张三"},{"id":2,"username":"李四"}]';

        $.ajax({
            type: 'POST',//大小写可以忽略
            url: url,
            data: data,
            contentType: 'application/json;charset=utf-8',
            success: function (data) {
                console.log(data);
                alert(data)
            }
        })
    })
</script>
</body>
</html>

springmvc.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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
         http://www.springframework.org/schema/mvc
         http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.controller"/>
    <mvc:annotation-driven></mvc:annotation-driven>
</beans>
entity里面的User类:
package com.entity;

public class User {
   
   
    String id;
    String username;

    public String getId() {
   
   
        return id;
    }

    public void setId(String id) {
   
   
        this.id = id;
    }

    public String getUsername() {
   
   
        return username;
    }

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

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

test类:
package com.controller;

import com.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/test")
public class test {
   
   
    @RequestMapping("in")
    @ResponseBody
    public List<User> ajax(@RequestBody List<User> list) {
   
   
        System.out.println(list);
        return list;
    }
}

自行配置tomcat,启动,运行看看结果,那么我们基础操作搭建完毕,现在我们来操作一下拦截:
在com包下创建Interceptor包,然后创建MyInterceptor类:
package com.Interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

public class MyInterceptor implements HandlerInterceptor {
   
   
}

其中HandlerInterceptor接口如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
   
   
    //默认的不要求强制被实现
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
   
        return true; //默认是true的
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
   
   
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
   
   
    }
}

这三个代表了三个地方,前面的三个拦截,比如:
preHandle是第一个拦截,postHandle是第二个拦截,afterCompletion是第三个拦截,实际上一个框架的编写,除了给出一些功能外,还需要存在扩展功能,有的话最好,而拦截的处理基本就是框架基本的扩展了(所以在spring中也存在多个拦截的处理,包括mybatis,当然,他们可能并没有特别的说明是拦截,但是你也或多或少可以知道,可以操作一些类来实现在中间进行处理的方式,这其实也算是一种拦截,因为是我们自行处理的,所以拦截器在某些方面可以是这样的认为:框架自身给出可以扩展的方式都可以称为拦截器)
实际上通过前面我们也明白,前端控制器底层基本上也是操作get和post,而servlet也是,但是mvc是建立在servlet上的,所以前端控制器通常也是生成了servlet,在前面我们学习了,前端控制器只是生成一个servlet(一般也可以是他自己),其中只是操作了拦截进行的处理,这个拦截或多或少使用了过滤或者监听,所以说,具体的是否监听或者过滤的处理,可能也是对应的配置导致进行的某些配置再处理(因为你删除了配置前端控制器的,拦截也就不复存在了),那么在这种情况下,我们的三次拦截,也只是其中多个拦截的扩展,那么如果这个时候,你操作了传统的拦截,那么就需要看配置的先后顺序
我们继续修改MyInterceptor类的内容:
package com.Interceptor;

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

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

public class MyInterceptor implements HandlerInterceptor {
   
   
    //第一次(个)
    /*

    Handler业务逻辑执行之前拦截一次

     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
   
        //程序先执⾏preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行
        //也就是说,这个拦截器拥有着结束执行的能力,并不是拦截器只能操作扩展,实际上过滤或者监听也可以称为拦截器的
        System.out.println(handler);
        System.out.println("Handler业务逻辑执行之前拦截一次,我是第一次");
        return true;

        //我们可以看到,handler这个变量,实际上他就是准备执行对应的handler方法的,但是需要看看是否返回true
        //默认情况下HandlerInterceptor里面是返回true的,自己可以看一看就知道了

        //由于他是在执行具体方法之前的拦截,所以一般来说,我们会使用他来完成一些权限的处理
        //实际上你也可以使用过滤器来完成,但是需要配置,所以使用mvc封装的比较方便
    }

    //第二次(个)
    /*
    在Handler逻辑执行完毕但未跳转⻚⾯之前拦截一次
    看参数就知道,除了当前的方法外,还有对应的视图和数据(modelAndView,因为是之后的吗)
    所以在没有进行渲染时,你可以选择针对数据进行某些修改,这个在spring中也存在这样的处理,在一个bean进行生成时,你也可以进行拦截进行某些处理或者修改

     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
   
   
        System.out.println(handler);
        System.out.println(modelAndView);
        System.out.println("在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次");
    }

    //第三次(个)
    /*
    在跳转⻚⾯之后拦截一次
    由于具体的数据和视图都操作完毕,那么这里只能获取对应的方法,以及你需要的异常信息了
    一般来说跳转页面就是将内容响应给前端,所以一般渲染视图中10到11一般是这里了(渲染视图一般是需要根据文件,如jsp进行替换数据的过程,其中对数据的替换使用,这样才能进行响应)
    即jsp是一个视图,那么通过转译编译就是对应的第10步的处理了,10前面是根据视图对象找到该文件,10后面是转译编译,中间考虑拦截(因为中间的处理都在前端控制器,所以或多或少他们的数据在某个情况下是共享的,所以可以替换的处理,一般情况下,是在jsp替换后,准备响应时,进行的拦截,也就是说11步就是最后的响应,比如,如果你在jsp中操作了输出语句,那么这个值输出后,这个方法才会进行处理,其实在68章博客中(一般是最后)也说明了这三个拦截方法,可以选择去看一下)
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
   
   
        System.out.println(handler);
        System.out.println(ex);
        System.out.println("在跳转页面之后拦截一次,我是第三次");
    }
}

我们写好了拦截器,自然需要进行使用,在mvc中使用,是必须需要配置的,并且他是mvc的,所以是需要在mvc的对应的配置文件中进行配置:
对应的springmvc.xml的补充:
 <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!--项目路径下的对应路径,一个*代表一个路径下,但不代表路径下的路径下,两个基本就是所有-->
            <!--对所有的controller类里面的所有方法都进行拦截(因为是/**)-->
            <bean class="com.Interceptor.MyInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

实际上mvc:interceptors代表可以配置多个,在配置多个时,一般需要操作路径,防止都进行拦截的处理(这个时候不写可能报错,具体可以测试),而不是指定的拦截(因为默认是/**),所以存在mvc:interceptors中存在mvc:interceptor,所以如果你只有一个并且需要是/ * *,那么可以这样写:
   <mvc:interceptors>

            <bean class="com.Interceptor.MyInterceptor"></bean>
    </mvc:interceptors>
<!--
其中<mvc:mapping path="/**"/>也可以进行去掉,因为默认是/**
直接省略<mvc:interceptor>,代表只有你这一个的意思,但是由于只有一个,通常情况下,默认是/**的,而不会给你进行路径的设置,所以如果你只有一个并且需要是/**,那么可以这样写
-->
一般我们建议使用这样的方式来进行编写:
 <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!--项目路径下的对应路径,一个*代表一个路径下,但不代表路径下的路径下,两个基本就是所有-->
            <!--对所有的controller类里面的所有方法都进行拦截(因为是/**)-->
            <bean class="com.Interceptor.MyInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
<!--一般来说,/**的/代表从端口开始,这里了解即可,所以大多数访问也会进行拦截处理(但是也要知道,一个端口基本只有一个服务器占用,所以并不会影响其他服务器的,所以不需要考虑其他服务器的影响问题(除非是业务逻辑上的影响,比如文件的操作))
虽然大多数访问都会进行拦截处理,但是要知道,他是在需要执行对应的方法执行进行的拦截,也就是说,如果你并没有需要执行对应的方法,那么就不会执行,而不会也自然不会出现true,这里我们需要注意的是:只有当前面的拦截的返回值或者说,第一个拦截的返回值为true时,后面的两个拦截才会进行处理,这也是为什么如果你访问index.jsp,由于第一个拦截没有执行(他并不会操作对应的方法,自然也就不会经过这个拦截方法了,一般情况下,没有经过这个方法时,对应进行操作的变量是默认为false,但是只要你操作了这个拦截,或者就算你不进行重写,他默认放行,因为这个时候他是默认的方法是true的,前面的HandlerInterceptor接口可以看到返回值的),那么其他的拦截,比如第三个拦截没有起作用的原因,默认情况下,如果你不执行第一个拦截,那么就是false的

这里的true是否看起来相当于过滤器中的放行的意思呢,之所以需要这样的规定,是保证其他的,如index.jsp不会操作拦截的原因(因为是直接的访问,并不需要操作拦截,而减低操作的可能,从而使得服务器可以接收更多请求,或者减少内存损耗)
-->
当然其实还有功能,这是mvc设计出来的功能,他也可以进行去掉,比如他可以选择性的不拦截一个路径的请求:
  <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/demo/**"/> <!--选择不拦截demo对应路径下面的请求-->
            <bean class="com.Interceptor.MyInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
这样就能不拦截demo开头的处理了,一般情况下,servlet或者mvc的普通拦截都是在项目路径下的直接拦截,比如前面的测试的路径test/in,就是放在项目路径后面,而不是端口路径
编写好后,我们执行前面的代码,看看后端的结果:
打印如下:
/*
public java.util.List<com.entity.User> com.controller.test.ajax(java.util.List<com.entity.User>)
Handler业务逻辑执行之前拦截一次,我是第一次

[User{id='1', username='张三'}, User{id='2', username='李四'}]

public java.util.List<com.entity.User> com.controller.test.ajax(java.util.List<com.entity.User>)
null
在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次

public java.util.List<com.entity.User> com.controller.test.ajax(java.util.List<com.entity.User>)
null
在跳转页面之后拦截一次,我是第三次
*/
这个时候,你可以选择将返回值变成false,那么我们看看这个打印结果:
/*
public java.util.List<com.entity.User> com.controller.test.ajax(java.util.List<com.entity.User>)
Handler业务逻辑执行之前拦截一次,我是第一次
*/
我们可以发现,他甚至连对应的方法都不执行了,而方法都不执行,默认情况下,是没有响应体信息的,那么在前端显示的就是空白(这个时候,甚至都不会操作视图,也就是单纯的返回空响应),也就是说,这个true也决定了对应的controller的方法的执行,这也是为什么默认情况下,对应的HandlerInterceptor方法的返回值是true的一个原因
现在我们添加一个前端jsp,test.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    System.out.println(1);
%>
1
</script>
</body>
</html>

上面的这个地方加上代码:
<%
    System.out.println(1);
%>
这个是jsp的语法(在51章博客有说明),在转译和编译的情况下,会进行处理的,最终操作拦截,那么我们在springmvc.xml中加上如下的配置:
<bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
然后再test类中加上这个代码:
 @RequestMapping("ix")
    public String ix() {
   
   
        return "test";
    }
首先还是false的返回值,然后直接的在url中后面加上test/ix访问即可,这个时候我们查看后端的打印:
/*
public java.lang.String com.controller.test.ix()
Handler业务逻辑执行之前拦截一次,我是第一次
*/
因为false不会经过方法,所以在前端是显示空白的
经过这两次的测试,可以发现handler的打印信息包含了,访问权限,返回值,对有包路径的方法及其参数列表等等,我们给ix方法加上两个参数列表,即:
public String ix(String a,Integer b) {
   
   
对应的打印信息如下:
/*
public java.lang.String com.controller.test.ix(java.lang.String,java.lang.Integer)
Handler业务逻辑执行之前拦截一次,我是第一次
*/
即也的确如此,现在我们给false,变成true的返回值,然后看看打印结果:
/*
public java.lang.String com.controller.test.ix(java.lang.String,java.lang.Integer)
Handler业务逻辑执行之前拦截一次,我是第一次

public java.lang.String com.controller.test.ix(java.lang.String,java.lang.Integer)
ModelAndView [view="test"; model={}]
在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次

1

public java.lang.String com.controller.test.ix(java.lang.String,java.lang.Integer)
null
在跳转页面之后拦截一次,我是第三次

*/
也验证了前面注释中的:如果你在jsp中操作了输出语句,那么这个值输出后,这个方法才会进行处理
但是还有一个问题,如果controller对应的方法没有返回值呢,因为没有返回值说明他不会经过视图,而不经过视图,那么第三个拦截是否不会进行了,所以我们修改ix方法:
  @RequestMapping("ix")
    public void ix(String a,Integer b) {
   
   
    }
因为当我们没有给出视图名时,会将请求(参数)进行拼接,一般是@RequestMapping的整个拼接
我们还是true,因为false修改与不修改是一样的,反正都不执行,那么他的修改没有意义,我们看看执行后的打印结果:
/*
public void com.controller.test.ix(java.lang.String,java.lang.Integer) throws java.io.IOException
Handler业务逻辑执行之前拦截一次,我是第一次

public void com.controller.test.ix(java.lang.String,java.lang.Integer) throws java.io.IOException
ModelAndView [view="test/ix"; model={}]
在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次

public void com.controller.test.ix(java.lang.String,java.lang.Integer) throws java.io.IOException
null
在跳转页面之后拦截一次,我是第三次
*/
但是这里还存在一些细节,这是前面并没有说明的,我们看如下:
我们继续修改:
 @RequestMapping("ix")
    public String ix(String a, Integer b) throws IOException {
   
   
        return null;
    }
执行之后,结果与上面的一样,也就证明了,其实对应的视图默认是null(是默认,虽然会根据组件得到路径的视图),所以当你返回类型为void时,他的视图结果与返回null值是一样的,然而,虽然根据路径得到了视图,但是其实在没有视图时也会得到结果,为什么,我们看这个代码:
@RequestMapping("ix")
    public void ix(String a, Integer b, HttpServletResponse mm) throws IOException {
   
   
        mm.setContentType("text/html;charset=utf-8");
        PrintWriter writer = mm.getWriter();
        writer.println("哈哈哈");
    }
后端打印:
/*
public void com.controller.test.ix(java.lang.String,java.lang.Integer,javax.servlet.http.HttpServletResponse) throws java.io.IOException
Handler业务逻辑执行之前拦截一次,我是第一次

public void com.controller.test.ix(java.lang.String,java.lang.Integer,javax.servlet.http.HttpServletResponse) throws java.io.IOException
null
在Handler逻辑执行完毕但未跳转页面之前拦截一次,我是第二次

public void com.controller.test.ix(java.lang.String,java.lang.Integer,javax.servlet.http.HttpServletResponse) throws java.io.IOException
null
在跳转页面之后拦截一次,我是第三次
*/
可以发现没有视图了,没有视图相当于直接的返回的操作(直接操作响应体信息了),但是为什么加上这些就会没有呢,这是因为如果你要自己加上响应信息的话,那么必然会与原来的如jsp而发生冲突(一般来说io并不能覆盖,只是增加),导致对应的jsp信息出现问题,所以他们是需要分开的,所以只要你存在要自行操作响应体信息的,那么视图就会为null,通过测试,只要你在参数列表中加上HttpServletResponse mm,比如:
 @RequestMapping("ix")
    public void ix(String a, Integer b, HttpServletResponse mm) throws IOException {
   
   
//        mm.setContentType("text/html;charset=utf-8");
//        PrintWriter writer = mm.getWriter();
//        writer.println("哈哈哈");
    }
那么视图就为null,即执行的操作响应体信息,如果你没有设置,自然前端显示空白
而打印信息中,第二次和第三次无论是否操作了HttpServletResponse或者无论是否报错,都会出现,也就是说,后面两个并没有互相联系的不能让对方放行的处理,所以他们基本必然都会打印了,但是报错的话,视图是找不到了,那么就不会出现对应的转译编译才对,为什么第三次拦截也会出现呢,在前面我们说过了"10到11则是第三次拦截(这里也可能是9到10)",虽然在第8次报错,但是第9次的操作需要返回报错信息,这个时候是会经过第三次拦截的(简单来说,有返回,就算是空的,他通常也会操作)
至此,我们的细节基本说明完毕
拦截器的执行流程:
在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置⽂件中所定义的拦截器的顺序相关,单个 拦截器,在程序中的执行流程如下图所示:

在这里插入图片描述

1:程序先执⾏preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方 法,否则将不再向下执行
2:在业务处理器(即控制器Controller类)处理完请求后,会执⾏postHandle()方法,然后会通过DispatcherServlet向客户端返回响应
3:在DispatcherServlet处理完请求后,才会执⾏afterCompletion()方法
再结合这个图吧:

在这里插入图片描述

根据前面的说明,他的流程也可以说是对的
总结一下:

在这里插入图片描述

上面是单个拦截器的流程,那么多个拦截器的执行流程呢:
多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置⽂件中, Interceptor1拦截 器配置在前),在程序中的执行流程如下图所示:

在这里插入图片描述

从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置⽂件中拦截器的配置 顺序执行,⽽它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行
我们来看例子:
首先创建两个类在Interceptor包中:
package com.Interceptor;

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

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

public class MyInterceptor1 implements HandlerInterceptor {
   
   
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
            handler) throws Exception {
   
   
        System.out.println("preHandle1....");

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
   
   
        System.out.println("postHandle1....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object
            handler, Exception ex) throws Exception {
   
   

        System.out.println("afterCompletion1....");


    }
}

package com.Interceptor;

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

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

public class MyInterceptor2 implements HandlerInterceptor {
   
   


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
            handler) throws Exception {
   
   
        System.out.println("preHandle2....");

        return true;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
   
   
        System.out.println("postHandle2....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object
            handler, Exception ex) throws Exception {
   
   

        System.out.println("afterCompletion2....");


    }
}
我们进行配置(去掉原来的):
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor2"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

<bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
后端(之前处理的):
@RequestMapping("ix")
    public String ix(String a, Integer b) throws IOException {
   
   
        return "test";
    }
现在我们执行(操作上面的test.jsp)看看打印信息:
/*
preHandle1....
preHandle2....
postHandle2....
postHandle1....
1
afterCompletion2....
afterCompletion1....
*/
也的确是第一个是顺序(后配置的在后面,你修改配置中拦截的顺序即可),后面两个是反序,现在我们来测试,让其中一个
配置放在前面:
 <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor2"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
打印结果:
/*
preHandle2....
preHandle1....
postHandle1....
postHandle2....
1
afterCompletion1....
afterCompletion2....
*/
也的确如此,那么如果其中一个不放行呢,我们将preHandle2…的对应方法的返回值设置为false看看,上面的配置不变,执行后,看看打印结果:
/*
preHandle2....
*/
只有这一个,那么我们将配置顺序改变回来,执行看看结果:
/*
preHandle1....
preHandle2....
afterCompletion1....
*/
感觉到了,如果你的返回值是false,那么你后面的拦截都不能进行处理,包括后面的第一次拦截,但是,如果存在第一次拦截执行完毕,那么允许他执行第三次拦截,虽然是这样说,但是也只是一个结论,我们选择继续添加一个类来进行处理:
package com.Interceptor;

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

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

public class MyInterceptor3 implements HandlerInterceptor {
   
   


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object
            handler) throws Exception {
   
   
        System.out.println("preHandle3....");

        return false;
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
   
   
        System.out.println("postHandle3....");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object
            handler, Exception ex) throws Exception {
   
   

        System.out.println("afterCompletion3....");


    }
}
将preHandle2…的对应方法的返回值设置为true(修改回来),然后配置如下:
 <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor1"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor3"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.Interceptor.MyInterceptor2"></bean>
        </
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值