目录
拦截器
1. 拦截器的介绍
2. 拦截器的三个抽象方法
3. 拦截器的使用
4. 多个拦截器的执行顺序
Java核心技术大会
文末福利(Java核心技术卷)
拦截器
拦截器能拦截请求,前面学习的过滤器也能拦截请求,那两者有什么区别呢?
过滤器:过滤器是过滤从浏览器发送的所有请求,所以过滤器就是作用在浏览器----》前端控制器DispatcherServlet之间!
拦截器:前端控制器DispatcherServlet接收到请求后进行处理,去与Controller的RequestMapping请求映射进行匹配,所以拦截器就是作用在控制器Controller执行的前后!
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()方法!
processDispatcherResult处理ModelAndView,有一个render方法用来渲染视图的
渲染完视图,执行拦截器的最终处理
3. 拦截器的使用
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springmvc-thymeleaf007</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springmvc-thymeleaf007 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
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">
<!--注册过滤器:解决post请求乱码问题-->
<filter>
<filter-name>encode</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>
<!--强制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>
<!--发送put、delete请求方式的过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--注册SpringMVC框架-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置springMVC位置文件的位置和名称-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--将前端控制器DispatcherServlet的初始化时间提前到服务器启动时-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--指定拦截什么样的请求
例如:http://localhost:8080/demo.action
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.zl.controller"/>
<!--视图控制器,需要搭配注解驱动使用-->
<mvc:view-controller path="/" view-name="index"/>
<!--专门处理ajax请求,ajax请求不需要视图解析器InternalResourceViewResolver-->
<!--但是需要添加注解驱动,专门用来解析@ResponseBody注解的-->
<!--注入date类型时,需要使用@DateTimeFormat注解,也要搭配这个使用-->
<mvc:annotation-driven/>
<!--开放对静态资源的访问,需要搭配注解驱动使用-->
<mvc:default-servlet-handler/>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index</h1>
<a th:href="@{/testInterceptor}">测试</a>
</body>
</html>
success.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
success
</body>
</html>
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提交表单
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>index</h1>
<!--<a th:href="@{/testInterceptor}">测试</a>-->
<form th:action="@{/testInterceptor}">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="pwd"><br>
<input type="submit" th:value="提交">
</form>
</body>
<h1 th:text="${msg}"></h1>
</html>
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中注册拦截器
注:拦截器主要放行两个页面,登录页面和登录验证的页面!
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--映射要拦截的请求-->
<mvc:mapping path="/**"/>
<!--配置要放行的请求-->
<mvc:exclude-mapping path="/"/><!--登录的页面-->
<mvc:exclude-mapping path="/testInterceptor"/><!--登录验证的页面-->
<!--配置具体的拦截器实现功能的类-->
<bean class="com.zl.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
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中注册拦截器
<!--注册拦截器,此时是对所有请求都拦截-->
<mvc:interceptors>
<!--方式一-->
<bean class="com.zl.interceptor.FirstInterceptor"/>
<!--方式二:拦截器类要写上Component注解,表示纳入Spring容器管理-->
<ref bean="secondInterceptor"/>
</mvc:interceptors>
执行结果如下:preHandle按照配置的顺序输出,而postHandle和afterComplation按照配置相反的顺序输出
源码分析:
①首先在控制器controller方法上打一个断点
②进行访问时,会进入DispatcherServlet的方法栈,找到doDispatcher方法
ha实际就是HandlerAdapter适配器控制器,调用handle方法,就相当于执行控制器上的方法
③此时需要打四个断点:preHandle、执行控制器方法、postHandle、afterCompletion
执行流程:
(1)先执行前处理器preHandel,
(2)然后去执行控制器方法handle,此时就会去找控制器controller上面的方法去执行,
(3)执行后处理器方法postHandle,
(4)这些执行完毕后会调用processDispatcherResult方法中的render方法去渲染视图,渲染完毕后;最终会执行afterCimpletion方法!
跳转到preHandel
// if中的参数是false,就会直接执行return结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mappedHandler实际上是一个执行链,这个执行链存放的是:当前的控制器方法、和处理控制器方法的拦截器!
注:此时我们可以看到有3个拦截器,我们只定义了2个,另一个实际上是SpringMVC自己创建的!
点开+号查看内部的结构如下:
主要包含三个部分:控制器方法、拦截器集合、拦截器的索引!
进入到 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()返回了false,preHandle()返回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核心技术大会
大会简介
人工智能在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抽奖方式:利用程序进行抽奖。
参与方式:关注博主(只限粉丝福利哦)、点赞、收藏,评论区随机抽取,最多三条评论!