【SpringMVC】| 拦截器(含源码分析)

目录

拦截器

1.  拦截器的介绍

2. 拦截器的三个抽象方法

3. 拦截器的使用

4. 多个拦截器的执行顺序

Java核心技术大会

文末福利(Java核心技术卷) 


拦截器

拦截器能拦截请求,前面学习的过滤器也能拦截请求,那两者有什么区别呢?

过滤器:过滤器是过滤从浏览器发送的所有请求,所以过滤器就是作用在浏览器----》前端控制器DispatcherServlet之间

拦截器:前端控制器DispatcherServlet接收到请求后进行处理,去与Controller的RequestMapping请求映射进行匹配,所以拦截器就是作用在控制器Controller执行的前后

【SpringMVC】| 拦截器(含源码分析)_第1张图片

1.  拦截器的介绍

(1)SpringMVC中的拦截器用于拦截控制器方法的执行!

(2)SpringMVC中的拦截器需要实现HandlerInterceptor或者继承HandlerInterceptorAdapter!

(3)SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置。

拦截器的执行原理

preHandle():在请求被处理之前进行操作;预处理
postHandle():在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果;后处理
afterCompletion:所有的请求响应结束后执行善后工作,清理对象、关闭资源 ;最终处理

拦截器实现的两种方式

继承HandlerInterceptorAdapter【处理程序拦截适配器】的父类。
实现HandlerInterceptor【处理程序拦截器】接口,推荐使用实现接口的方式,因为继承是单继承的。

2. 拦截器的三个抽象方法

SpringMVC中的拦截器有三个抽象方法:

(1)preHandle:控制器方法【controller】执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法。

(2)postHandle:控制器方法【controller】执行之后执行postHandle()。

(3)afterComplation:处理完视图和模型数据(ModelAndView),渲染视图完毕之后执行afterComplation()。

通过源码分析执行的顺序:首先通过前端控制器DispatcherServlet源码找到控制器方法的调用,返回的其实是一个mv(ModelAndView),在控制器方法之前执行preHandle()方法、在控制器方法之后执行postHandle()方法。 之后会去调用processDispatcherResult进行视图渲染;最后去调用afetrCompletion()方法!

【SpringMVC】| 拦截器(含源码分析)_第2张图片

processDispatcherResult处理ModelAndView,有一个render方法用来渲染视图的

【SpringMVC】| 拦截器(含源码分析)_第3张图片

渲染完视图,执行拦截器的最终处理

【SpringMVC】| 拦截器(含源码分析)_第4张图片

3. 拦截器的使用

pom.xml




  4.0.0

  org.example
  springmvc-thymeleaf007
  1.0-SNAPSHOT
  war

  springmvc-thymeleaf007 Maven Webapp
  
  http://www.example.com

  
    UTF-8
    1.8
    1.8
  

  
    
      org.springframework
      spring-webmvc
      5.2.5.RELEASE
    
    
      org.thymeleaf
      thymeleaf-spring5
      3.0.10.RELEASE
    
    
      javax.servlet
      javax.servlet-api
      3.1.0
    
    
      ch.qos.logback
      logback-core
      1.2.3
    
    
      com.fasterxml.jackson.core
      jackson-databind
      2.14.2
    
    
      commons-fileupload
      commons-fileupload
      1.3.1
    
  

  
    
      
        src/main/java
        
          **/*.xml
          **/*.properties
        
        false
      
      
        src/main/resources
        
          **/*.xml
          **/*.properties
        
        false
      
    
  


web.xml



    
    
        encode
        org.springframework.web.filter.CharacterEncodingFilter
        
        
            encoding
            utf-8
        
        
        
            forceRequestEncoding
            true
        
        
        
            forceResponseEncoding
            true
        
    
    
    
        encode
        /*
    

    
    
        HiddenHttpMethodFilter
        org.springframework.web.filter.HiddenHttpMethodFilter
    
    
        HiddenHttpMethodFilter
        /*
    

    
    
        springmvc
        org.springframework.web.servlet.DispatcherServlet
        
        
            contextConfigLocation
            classpath:springmvc.xml
        
        
        1
    
    
        springmvc
        
        /
    

springmvc.xml



    
    
    
    
    
    
    
    
    
    

    
    
        
        
        
            
                
                    
                        
                        
                        
                        
                        
                        
                    
                
            
        
    

index.html




    
    Title


index

测试

success.html




    
    Title


success

controller

注:此时实现的功能时通过访问index.xml,通过controller进行处理去访问success.html页面;但是此时有一个问题,如果我们知道了success.html的路径地址,就可以略过index.html直接进行访问!

package com.zl.controller;

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

@Controller
public class TestController {
    @RequestMapping("/testInterceptor")
    public String testInterceptor(){
        return "success";
    }
}

怎么解决这个问题呢?通过拦截器!

index.html提交表单




    
    Title


index

用户名:
密码:

controller拿到数据,并放到session中去

package com.zl.controller;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;


@Controller
public class TestController {

    // 直接进行访问
    @RequestMapping("/success")
    public String success(){
        return "success";
    }

    @RequestMapping("/testInterceptor")
    public String testInterceptor(HttpServletRequest request,String username,String pwd){
        if ("root".equalsIgnoreCase(username) && "123".equalsIgnoreCase(pwd)){
            // 登录成功,存储用户名到session中去
            HttpSession session = request.getSession();
            session.setAttribute("username",username);
            return "success";
        }else {
            request.setAttribute("msg","账户或密码错误");
            return "index";
        }
    }
}

定义拦截器,进行拦截

package com.zl.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 LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 拿到存进session中的username
        if(request.getSession().getAttribute("username") == null){
            // 表示没有登陆过,跳转到index.xml中去登录
            request.setAttribute("msg","请先去登录");
            request.getRequestDispatcher("/WEB-INF/templates/index.html").forward(request,response);
            return false;
        }
        // 表示登录过,放行
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

 在springmvc.xml中注册拦截器

注:拦截器主要放行两个页面,登录页面和登录验证的页面!

    
    
        
            
            
            
            
            
            
            
        
    

4. 多个拦截器的执行顺序

(1)若每个拦截器的preHandle()都返回true,此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关preHandle()会按照配置的顺序执行,而postHandle()afterComplation()按照配置的反序执行

定义两个拦截器

FirstInterceptor

package com.zl.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 FirstInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("FirstInterceptor--->preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

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

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("FirstInterceptor--->afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

SecondInterceptor

package com.zl.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 SecondInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("SecondInterceptor--->preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

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

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("SecondInterceptor--->afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

}

在springmvc.xml中注册拦截器 

    
    
        
        
        
        
    

执行结果如下:preHandle按照配置的顺序输出,而postHandle和afterComplation按照配置相反的顺序输出

【SpringMVC】| 拦截器(含源码分析)_第5张图片

源码分析: 

①首先在控制器controller方法上打一个断点

【SpringMVC】| 拦截器(含源码分析)_第6张图片

②进行访问时,会进入DispatcherServlet的方法栈,找到doDispatcher方法

ha实际就是HandlerAdapter适配器控制器,调用handle方法,就相当于执行控制器上的方法

【SpringMVC】| 拦截器(含源码分析)_第7张图片

③此时需要打四个断点:preHandle、执行控制器方法、postHandle、afterCompletion

执行流程:

(1)先执行前处理器preHandel,

(2)然后去执行控制器方法handle,此时就会去找控制器controller上面的方法去执行,

(3)执行后处理器方法postHandle,

(4)这些执行完毕后会调用processDispatcherResult方法中的render方法去渲染视图,渲染完毕后;最终会执行afterCimpletion方法!

【SpringMVC】| 拦截器(含源码分析)_第8张图片

跳转到preHandel 

// if中的参数是false,就会直接执行return结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}

mappedHandler实际上是一个执行链,这个执行链存放的是:当前的控制器方法、和处理控制器方法的拦截器

注:此时我们可以看到有3个拦截器,我们只定义了2个,另一个实际上是SpringMVC自己创建的!

 

点开+号查看内部的结构如下:

主要包含三个部分:控制器方法拦截器集合拦截器的索引

【SpringMVC】| 拦截器(含源码分析)_第9张图片

进入到 applyPreHandle方法中进行源码分析:首先是遍历这个拦截器集合,并且是i++(就是按照配置的顺序执行)。

注:只要拦截器的preHandler全是true,那么当前的拦截器索引interceptorIndex就是当前最大的索引值;一旦出现了false,此时的拦截器索引interceptorIndex就是前一个拦截器的索引值!

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
                // 根据下标拿到每个拦截器
				HandlerInterceptor interceptor = interceptors[i];
                // 判断当前的拦截的preHandle是true还是false
				if (!interceptor.preHandle(request, response, this.handler)) {
                    // 是false就直接执行agterCompletion,然后结束
                    // 注:此时只能输出当前拦截器所有前面的拦截器的afterCimpletion方法
					triggerAfterCompletion(request, response, null);
					return false;
				}
                // 是true,就修改拦截器的索引下标 
				this.interceptorIndex = i;
			}
		}
		return true;
	}

跳转到postHandle

也是遍历拦截器集合,此时的i初始值也是当前的拦截器集合的个数相关联,但是是逆序(i--)打印的!

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = interceptors.length - 1; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				interceptor.postHandle(request, response, this.handler, mv);
			}
		}
	}

跳转到afterCompletion

也是遍历拦截器集合,此时的i开始值是与拦截器索引interceptorIndex相关联的,也是逆序(i--)打印的!所以对于preHandle是按照配置的顺序打印的;而postHandle和afterCompletion是逆序打印的!

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}

(2)假如现在SecondInterceptor拦截器的preHandle()返回了falsepreHandle()返回false和它之前配置的拦截器的preHandle()都会执行postHandle()都不会执行返回false的拦截器之前的拦截器的afterComplation()会执行

再次查看preHandle的源码:

在for循环的if语句可以看出,当preHandle为false时才会去执行,此时并不会执行postHandle了,会执行afterCompletion,然后返回false直接结束当前方法!并且前面我们已经分析了afterCompletion遍历的结果是与拦截器索引interceptorIndex相关联,而这个值的大小又是当前拦截器为false时的前一个拦截器的索引值(相对于preHandle会少打印一个,少打印当前的)!

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				// preHandle为false要执行的语句
                if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

例如:当前有5个拦截器,one、two、three、four、five,此时three的preHandle返回了false;此时one、two、three的preHandle会执行、postHandle都不会执行、one、two的afterComplation会执行! 

Java核心技术大会

【SpringMVC】| 拦截器(含源码分析)_第10张图片

 

大会简介

人工智能在22年、23年的再次爆发让Python成为编程语言里最大的赢家;云原生的持续普及令Go、Rust等新生的语言有了进一步叫板传统技术体系的资本与底气。我们必须承认在近几年里,Java阵营的确受到了前所未有的挑战,出现了更多更强大的竞争者。

但是,迄今Java仍然有着非常庞大的开发者生态,仍是使用人数最多的编程语言,仍是服务端应用、大数据应用、企业级产品的首选。

本届技术大会由国内Java技术传播领军机构机械工业出版社华章分社发起,周志明、李三红、杨晓峰三位大会主席,与近30位国内外顶级专家将从Java语言、平台和趋势,Java应用开发和系统架构,以及Java性能优化等方面带来8大专场24场主题分享。2023年6月25日-7月1日,让我们相约「 Java核心技术大会 」

PART 1 特邀启动专场

PART 2 Java语言、平台和趋势专场

 

PART 3 Java应用开发专场

PART 4 Java应用与系统架构专场

PART 5 Java应用性能优化专场

PART 6 大数据与数据库专场

PART 7 云原生与Serverless专场

 

PART 8 AI驱动的Java编程专场

「Java核心技术大会 2023」

Core Java Week
2023年6月25日-7月1日
邀您相约
共同深入探讨 Java 生态!
直播预约:视频号“IT阅读排行榜

现场参与更有

  • 赢取Java核心技术 纸书&视频课
  • 带走CoreJava限量周边
  • 锁定购物袋超秒福利
  • 加入交流群,向专家请教、学习
  • 第一时间获取PPT等增值资源

在这里插入图片描述

 

文末福利(Java核心技术卷) 

《Java核心技术卷Ⅰ》和《Java核心技术卷Ⅱ》任选其一免费包邮送出!

本次送书 2 本! 
活动时间:截止到 2023-06-29 00:00:00

抽奖方式:利用程序进行抽奖。

参与方式:关注博主(只限粉丝福利哦)、点赞、收藏,评论区随机抽取,最多三条评论!

你可能感兴趣的:(第四步:SSM框架,SSM,SpringMVC,java)