说明:
(1)本篇博客合理性解释:
● 本篇博客介绍Spring MVC中一个高级组件:拦截器;
● 但是,本篇博客的主要目的只是【引入Interceptor拦截器】、【简单的走一遍拦截器的配置过程】;
● 有关拦截器的【实用技巧】和【具体细节】等深入的内容在后面会介绍;即,本篇博客仅仅是拦截器的一个入门;
(2)本篇博客主要内容:
● 拦截器(Interceptor)简介;
● 拦截器(Interceptor)开发流程概述;
● 拦截器(Interceptor)开发流程走了一遍,演示了最初级的使用;
● 介绍了HandlerInterceptor接口的三个方法:preHandle()、postHandle()、afterCompletion();
(3)如有需要:过滤器(filter)可以参考【(5)常用功能与过滤器、监听器、FreeMarker;】专栏中的内容;Spring AOP可以参考【(15)Spring基础:IoC;AOP;JDBC;】专栏中的内容;
目录
2.开发前准备:准备一个【interceptor工程】;(这部分不是本篇博客的重点,快速扫过去就行)
(1)第一步:在pom.xml中引入servlet-api依赖;
(2)第二步:新建一个实现了HandlerInterceptor接口的类;
(3)第三步:在applicationContext.xml中配置过滤地址;
一:拦截器简介;
说明:
(1)拦截器的作用:拦截url,对其进行前置/后置过滤;
(2)【Spring中的拦截器】和【J2EE中的过滤器】,其作用有点类似,都是拦截请求;但是二者底层的实现逻辑是不同的;
● 拦截器(Interceptor)是Spring MVC的标准组件;(Interceptor对象被创建后,是天然的运行在IoC容器中的;)
● 过滤器(Filter)是J2EE的标准组件,是J2EE的标准;过滤器(Filter)的具体实现,是由不同的第三方容器厂商实现的;( 比如,我们在【过滤器一:过滤器简介;创建第一个Filter;】中直接使用的过滤器,就是Tomcat这个第三方厂商,根据J2EE标准实现的;)
(3)我们已经知道,拦截器(Interceptor)的作用是拦截url,对其进行前置/后置处理;
● 我们很容易就能联想到Spring中的【AOP面向切面编程】;
● 其实,拦截器(Interceptor)的底层最基本的实现,就是依赖于Spring AOP理念;(【拦截器(Interceptor)的具体实现】和【AOP中的环绕通知】很相似;)
……………………………………………………
(注)如有需要,过滤器(Filter)的有关内容可以参考【(5)常用功能与过滤器、监听器、FreeMarker;】的内容;Spring AOP的有关内容可以参考【(15)Spring基础:IoC;AOP;JDBC;】;
二:拦截器(Interceptor)开发流程;
1. 拦截器(Interceptor)开发流程:简介;
说明:
(1)拦截器(Interceptor)底层依赖于【最原始的servlet-api,也就是J2EE的javx.servlet】;所以,首先需要引入javax.servlet依赖;(至于为什么要引入Servlet依赖,在【附加:Java简介(Java SE,Java EE,JDK等);【java.servlet.**】和【javax.**】简介;Eclipse和IDEA在使用servlet-api.jar时的区别;】和本篇博客的后续内容中会得到答案;)
(2)新建一个类,然后这个类要实现HandlerInterceptor接口;(这个接口的完整地址是org.springframework.web.servlet.HandlerInterceptor,可以看到,这个接口是Spring定义的)
(3)然后,在编好了拦截器类后,需要在applicationContext.xml中配置过滤地址;
2.开发前准备:准备一个【interceptor工程】;(这部分不是本篇博客的重点,快速扫过去就行)
(1)创建【interceptor】工程;
首先,创建一Maven WebApp项目;
这个过程可以参考【SpringMVC入门与数据绑定2:Spring MVC初体验二:使用IDEA创建【maven + WebApp】项目;】;啰嗦一下吧,其基本过程就是:
第一步:创建一Maven项目;
第二步:将工程设置为Web工程;
● 添加web功能;
● 三个设置项:【设置web.xml文件目录】、【设置web资源保存目录】、【设置artifact发布】;
第三步:给项目配置Tomcat;
● 配置一个本地的Tomcat服务器;
● 在Deployment中配置artifact发布;
● 设置一下Server中的端口号等内容;
……………………………………………………
然后,配置Spring MVC;
这个过程可以参考【SpringMVC入门与数据绑定3:Spring MVC初体验三:Spring MVC环境配置;(通过一个简单案例,走了一遍Spring MVC流程;)】,其基本过程就是:
第一步:在pom.xml中引入依赖;
第二步:在web.xml中配置DispatcherServlet;
第三步:创建并编写applicationContext.xml;
第四步:将新引入依赖添加到Tomcat发布中去;
……………………………………………………
至此,一个Spring MVC项目就创建好了;
(2)【interceptor】工程,初始内容展示;
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>com.imooc</groupId> <artifactId>interceptor</artifactId> <version>1.0-SNAPSHOT</version> <repositories> <repository> <id>aliyun</id> <name>aliyun</name> <url>https://maven.aliyun.com/repository/public</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.9</version> </dependency> </dependencies> </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_3_1.xsd" version="3.1"> <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:applicationContext.xml</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>characterFilter</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> </filter> <filter-mapping> <filter-name>characterFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>formContentFilter</filter-name> <filter-class>org.springframework.web.filter.FormContentFilter</filter-class> </filter> <filter-mapping> <filter-name>formContentFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
……………………………………………………
Persons:
package com.imooc.restful.entity; import com.fasterxml.jackson.annotation.JsonFormat; import java.util.Date; public class Person { private String name; private Integer age; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date birthday; public Person() { } public Person(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
……………………………………………………
RestfulController:
package com.imooc.restful.controller; import com.imooc.restful.entity.Person; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController @RequestMapping("/restful") public class RestfulController { @GetMapping("/persons") public List<Person> findPersons() { List list = new ArrayList(); Person person1 = new Person(); person1.setName("zhang"); person1.setAge(20); person1.setBirthday(new Date()); Person person2 = new Person(); person2.setName("wang"); person2.setAge(21); person2.setBirthday(new Date()); list.add(person1); list.add(person2); return list; } }
……………………………………………………
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <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" xmlns:mv="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 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.imooc.restful"></context:component-scan> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=utf-8</value> <value>application/json;charset=utf-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <mvc:default-servlet-handler/> <mvc:cors> <mvc:mapping path="/restful/**" allowed-origins="http://localhost:8080,http://www.imooc.com" max-age="3600"/> </mvc:cors> </beans>
……………………………………………………
client.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script type="text/javascript" src="jquery-3.5.1.js"></script> <script> $(function () { $("#btnGet").click(function () { $.ajax({ "url" : "/restful/request", "type" : "get", "dataType" : "json", "success" : function (json) { $("#message").text(json.message); }, "error":function () { alert("mmmmmmm"); } }) }) }); $(function () { $("#btnPost").click(function () { $.ajax({ "url" : "/restful/request/103", "type" : "post", "data": "name=lily&age=23", "dataType" : "json", "success" : function (json) { $("#message").text(json.message + ":" +json.id); }, "error":function () { alert("mmmmmmm"); } }) }) }); $(function () { $("#btnPut").click(function () { $.ajax({ "url" : "/restful/request", "type" : "put", "data": "name=lily&age=23", "dataType" : "json", "success" : function (json) { $("#message").text(json.message); }, "error":function () { alert("mmmmmmm"); } }) }) }); $(function () { $("#btnDelete").click(function () { $.ajax({ "url" : "/restful/request", "type" : "Delete", "dataType" : "json", "success" : function (json) { $("#message").text(json.message); }, "error":function () { alert("mmmmmmm"); } }) }) }); $(function () { $("#btnPersons").click(function () { $.ajax({ "url" : "/restful/persons", "type" : "get", "dataType" : "json", "success" : function (json) { for (var i = 0; i < json.length; i++) { var p = json[i]; $("#divPersons").append("<h2>" +p.name + ":" + p.age + ":" + p.birthday+ "</h2>") } } }) }) }); </script> </head> <body> <input type="button" id="btnGet" value="向服务器发送Get请求"> <input type="button" id="btnPost" value="向服务器发送Post请求"> <input type="button" id="btnPut" value="向服务器发送Put请求"> <input type="button" id="btnDelete" value="向服务器发送Delete请求"> <h1 id="message"></h1> <hr/> <input type="button" id="btnPersons" value="查询所有人员"> <div id="divPersons"></div> </body> </html>
其实,【interceptor工程】就是照搬前面的【restful工程】;
3.开发拦截器(Interceptor);(重点)
(1)第一步:在pom.xml中引入servlet-api依赖;
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
说明:
(1)servlet依赖说明:
(2)第一次遇到,基于IDEA,在工程中引入servlet api是在【OA系统六:前期准备四:整合FreeMarker;引入Servlet3.1依赖;顺带设置下logback.xml日志;】;并且,当时也是把servlet的scope设置为了provided;
(3)一个疑问:Eclipse和IDEA在开发的时候,如果需要用到HttpServletRequest或者HttpServletResponse时,一个不需要引入javax.servlet依赖,一个需要引入javax.servlet依赖,这个问题还未解决,等会解决后,再补充;:在【附加:Java简介(Java SE,Java EE,JDK等);【java.servlet.**】和【javax.**】简介;Eclipse和IDEA在使用servlet-api.jar时的区别;】中有详细介绍;
(2)第二步:新建一个实现了HandlerInterceptor接口的类;
MyInterceptor类:
package com.imooc.restful.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 { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取当前的url地址,打印一下示意性的信息; System.out.println(request.getRequestURL() + "请求还未进入Controller之前,执行preHandle()方法。"); //如果返回return true;那么请求就会被送达给【后面的拦截器(如果还有的话)】,或者送给Controller; //如果返回return false;那么当前的请求就会被阻止,直接产生响应,返回给客户端; //即,这个return的结果,决定了当前请求是继续向后传递执行,还是立即结束、返回响应; return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println(request.getRequestURL() + "Controller方法return以后,但还未产生响应文本之前,执行postHandle()方法。"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(request.getRequestURL() + "响应文本产生后,执行afterCompletion()方法。"); } }
说明:
(1)HandlerInterceptor接口的完整地址是org.springframework.web.servlet.HandlerInterceptor,可以看到这个接口是Spring中定义的;
(2)HandlerInterceptor接口中的三个方法说明:这三个方法对应了三个不同的执行时机;
● preHandle():前置执行处理:意思是:一个请求产生后,在请求还未进Controller之前,先要执行preHandle()方法,对这个请求进行预置处理;
● postHandle():目标资源已被Spring MVC框架处理:意思是:【Controller的方法已经处理了请求,Controller方法return以后,但还未产生响应文本之前】,在这个时机执行postHandle()方法;
● afterCompletion():响应文本已经产生:意思是:产生了响应文本以后,afterCompletion()方法就会被自动执行;(如果Controller方法返回的是ModelAndView,那么数据和模板引擎混合后,会产生一个HTML片段,然后afterCompletion()方法就会被执行;;;;如果Controller方法返回的是一个需要被JSON序列化的对象,那么当jackson组件把这个对象序列化为JSON字符串后,afterCompletion()方法就会被执行)
● 这个三个方法在执行时,是按照时间顺序,有序执行的;
(3)preHandle()、postHandle()、afterCompletion()方法的参数,印证了为什么我们在第一步,要先引入servlet依赖;
(4)代码内容说明:
强调一些preHandle()方法的返回值问题:
● 首先,一个前提,一个项目可能有多个拦截器,即同一个请求可能会被多个拦截器处理,然后再传给Controller;
● 如果preHandle()方法,返回true的话:那么请求就会被送给后面的拦截器(如果还有的话),或者送给Controller;
● 如果preHandle()方法,返回false的话:那么当前的请求就会被阻止,直接产生响应,返回给客户端;
● 即,这个return的结果,决定了当前请求是继续向后传递执行,还是立即结束、返回响应;
虽然,上面写了实现了HandlerInterceptor接口的MyInterceptor类;但是,Spring MVC还不认识这个类的;我们需要在applicationContext.xml中对其进行配置,就是第三步的内容;
(3)第三步:在applicationContext.xml中配置过滤地址;
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.imooc.restful.interceptor.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
说明:
(1)内容说明:主要配置两项:【拦截哪些url】,【使用哪个类来处理,拦截到的url】;
其中, 【path="/**"】表示,拦截所有请求;
(2)一个<mvc:interceptor>中,只能配置一个bean节点;
(4)启动Tomcat,观察效果;
可以看到, preHandle()、postHandle()、afterCompletion()这三个方法的执行顺序,符合预期;