主要内容:SpringMVC概述、SpringMVC注解式开发、SpringMVC核心技术
一、SpringMVC概述
1.1 SpringMVC简介
SpringMVC属于表现层的框架,它是Spring框架的一部分。
1.2 SpringMVC实现流程
1.2.1 导入Jar包
1.2.2 注册中央调度器
1.2.3 创建SpringMVC配置文件
1.2.4 定义处理器
ModelAndView类中的addObject()方法用于向其Model中添加数据,因为该方法的返回值为ModelAndView,所以可以向Model中连续添加多个数据。
1.2.5 注册处理器
在springmvc.xml中注册处理器。需要注意的是处理器的id属性值为一个请求URI,表示当客户端提交该请求时,会访问class指定的这个处理器。
1.2.6 定义目标页面
1.2.7 修改视图解析器的注册
SpringMVC框架为了避免对于请求资源路径与扩展名的冗余,在视图解析器InternalResouceViewResolver中引入了请求的前缀和后缀。
1.2.8 修改处理器
1.3 SpringMVC实现步骤
(1) 浏览器提交请求到中央调度器
(2) 中央调度器直接将请求转给处理器映射器。
(3) 处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。
(4) 中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器。
(5) 处理器适配器调用执行处理器。
(6) 处理器将处理结果及要跳转的视图封装到一个对象ModelAndView中,并将其返回给 处理器适配器。
(7) 处理器适配器直接将结果返回给中央调度器。
(8) 中央调度器调用视图解析器,将ModelAndView中的视图名称封装为视图对象。
1.4 绝对路径与相对路径
1.4.1 访问路径与资源路径
通常的URL资源访问路径由两部分构成:访问路径与资源名称。资源名称指的是要访问资源的直接名称,如show.jsp,或与要访问资源存在映射关系的间接名称,如show.do。而访问路径,则是通过该路径则可以定位到指定的资源,即在URL资源访问路径中除了资 源名称以外的其它部分。一般情况下,在URL访问路径中,最后一部分为资源名称,而其它部分则为访问路径。
根据“访问路径是否可以独立完成资源准确定位”的评判标准,可以将访问路径分为绝对路径和相对路径。
1.4.2 绝对路径
绝对路径是指根据给出的访问路径可以准确定位到资源的路径,对于计算机中Web应用的绝对路径则是指带访问协议的路径。如:http://127.0.0.1:8080/primary/index.jsp
1.4.3 相对路径
相对路径是指根据给出的访问路径无法准确定位到资源的路径,相对路径必须要结合其参照路径才可以组成可以准确定位资源的绝对路径。
在Web应用中,相对路径的写法有两种:一种是以斜杠开头的相对路径,一种是不以 斜杠开头的相对路径。根据相对路径是否以斜杠开头,且出现的文件所处位置的不同,其 默认的参照路径是不同的。这也是相对路径容易出错的地方:确定参照路径。
1.4.3.1 以斜杠开头的相对路径
(1)前台路径
所谓前台路径是指,由浏览器解析执行的代码中所包含的路径。例如,html、css、js中的路径,及jsp中静态部分的路径。像html及jsp中的静态部分中的img src=" “、href=” “、form action=”“等;像 css 中的 background:img(”"}等;像 window.location.href="",都属于前台路径。
前台路径的参照路径是Web服务器的根路径,即:http://127.0.0.1:8080/
(2)后台路径
所谓后台路径是指,由服务器解析执行的代码及文件中所包含的路径。例如,java代码中的路径、jsp文件动态部分(java代码块)中的路径、xml文件中的路径(xml文件是要被 java代码加载入内存,并由java代码解析的)等。
后台路径的参照路径是Web应用的根路径。如:http://127.0.0.1:8080/primary/
(3)后台路径特例
对于后台路径的参照路径有一个特例:当代码中使用 response 的 sendRedirect()方法进行重定向时,其参照路径不是web应用的路径,而是web服务器的根路径。
例如,执行response.sendRedirect("/show.jsp")将会报404错误。因为其参照路径是 Tomcat的根,而非当前项目的根。所以若要使用重定向,要么写为无斜杠的相对路径,要么在路径上添加项目名称。
1.4.3.2 不以斜杠开头的相对路径
不以斜杠开头的相对路径,无论是前台路径,还是后台路径,其参照路径都是当前资源的访问路径,而不是当前资源的保存路径。
二、SpringMVC注解式开发
所谓SpringMVC的注解式开发是指,处理器是基于注解的类的开发。对于每一个定义的处理器,无需在配置文件中逐个注册,只需在代码中通过对类与方法的注解,便可完成注册。
2.1 注解式开发的实现
2.1.1 注册组件扫描器
这里的组件即处理器,需要制定处理器所在的基本包。
2.1.2 定义处理器
此时的处理器类无需继承任何父类,实现任何接口。只需在类上与方法上添加相应注解即可。
@Controller:表示当前类处理器
@RequestMapping:表示当前方法为处理器方法,该方法要对value属性所指定的URL进行处理与响应。被注解的方法的方法名可以随意。
2.2 处理器请求映射规则的定义
2.2.1 对请求URI的命名空间的定义
一个@Controller所注解的类中,可以定义多个处理器方法。当然,不同的处理器方法所匹配的URI是不同的。这些不同的URI被指定在注解于方法之上的@RequestMapping的 value属性中。但若这些请求具有相同的URI部分,则这些相同的URI,可以被抽取到注解在类之上W@RequestMapping的value属性中。此时的这个URI称为命名空间。
2.2.2 请求URI中通配符的应用
(1)资源名称中使用通配符
在请求的资源名称中使用名称配符,表示请求的资源名称中只要包含指定的宇符即可完成匹配。
例如,下面的写法中/some*.do表示的意思是,只要请求的资源名称以some开头即可;/*other.do表示的意思是,只要请求的资源名称以other结尾即可。
(2)资源路径中使用通配符
在资源路径中使用通配符,有两种用法:路径级数的精确匹配,与路径级数的可变匹配。 /xxx/*/show.do:表示在 show.do 的资源名称前面,只能有两级路径,第一级必须是/xxx,而第二级随意。这种称为路径级数的精确匹配。
/xxx/**/show.do:表示在 show.do 的资源名称前面,必须以/xxx 路径开头,而其它级的路径是否包含,若包含又包含几级,各级又叫什么名称,均随意。这种称为路径级数的可变匹配。
2.2.3 对请求提交方式的定义
对于@RequestMapping,其有一个属性 method,用于对被注解方法所处理请求的提交方式进行限制,即只有满足该 method 属性指定的提交方式的请求,才会执行该被注解方法。
Method 属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 POST 提交。
只要指定了处理器方法匹配的请求提交方式为 POST,则相当于指定了请求发送的方式:要么使用表单请求,要么使用 AJAX 请求。其它请求方式被禁用。
当然,若不指定 method 属性,则无论是 GET 还是 POST 提交方式,均可匹配。即对于请求的提交方式无要求。
2.2.4 对请求中携带参数的定义
@RequestMapping 中 params 属性中定义了请求中携带的参数的要求。
2.3 处理器方法的参数
处理器方法可以包含以下五类参数:HttpServletRequest、HttpServletResponse、HttpSession、用于承载数据的 Model、请求中所携带的请求参数。这些参数会在系统调用时由系统自动赋值,即程序员可在方法内直接使用。
这里只举例讲解一下第五类参数:请求中所携带的请求参数。即处理器方法是如何接收请求参数的。
2.3.1 逐个参数接收
只要保证请求参数名与该请求处理方法的参数名相同即可。
2.3.2 校正请求参数名 --@RequestParam
所谓校正请求参数名,是指若请求 URL 所携带的参数名称与处理方法中指定的参数名不相同时,则需在处理方法参数前,添加一个注解@RequestParam(“请求参数名”),指定请求 URL 所携带参数的名称。
2.3.3 整体参数接收
将处理器方法的参数定义为一个对象,只要保证请求参数名与这个对象的属性同名即可。参数名称中不用写为“对象.属性”的形式。
2.3.4 域属性参数的接收
所谓域属性,即对象属性。当请求参数中的数据为某类对象域属性的属性值时,要求请求参数名为“域属性名.属性”。
2.3.5 路径变量
对于处理器方法中所接收的请求参数,可以来自于请求中所携带的参数,也可以来自于
请求的 URI 中所携带的变量,即路径变量。此时,需要借助@PathVariable 注解。
@PathVariable 在不指定参数的情况下,默认其参数名,即路径变量名与用于接收其值的属性名相同。若路径变量名与用于接收其值的属性名不同,则@PathVariable 可通过参数指出路径变量名称。
2.4 处理器方法的返回值
2.4.1 返回ModelAndView
若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时处理器方法返回 ModelAndView 比较好。
2.4.2 返回String
(1)返回内部资源逻辑视图名
若要跳转的资源为内部资源,则视图解析器可以使用 InternalResourceViewResolver 内部资源视图解析器。此时处理器方法返回的字符串就是要跳转页面的文件名去掉文件扩展名后的部分。这个字符串与视图解析器中的 prefix、suffix 相结合,即可形成要访问的 URI。
当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配置前辍与后辍了。
(2)返回 View 对象名
若要跳转的资源为外部资源,则视图解析器可以使用 BeanNameViewResolver,然后在配置文件中再定义一些外部资源视图 View 对象,此时处理器方法返回的字符串就是要跳转资源视图 View 的名称。当然,这些视图 View 对象,可以是内部资源视图 View 对象。
2.4.3 返回void
(1) 通过 ServletAPI 传递数据并完成跳转
通过在处理器方法的参数中放入的 ServletAPI 参数,来完成资源跳转时所要传递的数据及跳转。
一方面,可在方法参数中放入 HttpServletRequest 或 HttpSession,使方法中可以直接将数据放入到 request、session 的域中,也可通过 request.getServletContext()获取到ServletContext,从而将数据放入到 application 的域中。
另一方面,可在方法参数中放入 HttpServletRequest 与 HttpServletResponse,使方法可以完成请求转发与重定向。
(2) AJAX 响应
若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void。例如,对于 AJAX 的异步请求的响应。
处理器对于 AJAX 请求中所提交的参数,可以使用逐个接收的方式,也可以以对象的方式整体接收。只要保证 AJAX 请求参数与接收的对象类型属性同名。
2.4.4 返回Object
处理器方法也可以返回 Object 对象。但返回的这个 Object 对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。
返回 Object 对象,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。
(1)返回数值型对象
(2)返回字符串对象
若要返回非中文字符串,将前面返回数值型数据的返回值直接修改为字符串即可。但若返 回的字符串中带有中文字符 ,需要使用@RequestMapping 的 produces 属性指定字符集。
(3)返回自定义类型对象
返回自定义类型对象时,不能以对象的形式直接返回给客户端浏览器,而是将对象转换为 JSON 格式的数据发送给浏览器的。
(4)返回 Map 集合
(5)返回 List 集合
三、SpringMVC核心技术
3.1 请求转发与重定向
当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重
定向。
注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中页的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资源的。
3.1.1 返回 ModelAndView 时的请求转发
默认情况下,当处理器方法返回 ModelAndView 时,跳转到指定的 View,使用的是请求转发。但也可显式的进行指出。此时,需在 setViewName()指定的视图前添加 forward:,且此时的视图不会再与视图解析器中的前辍与后辍进行拼接,即必须写出相对于项目根的路径。此时的视图解析器不再需要前辍与后辍
(1)请求转发到页面
当通过请求转发跳转到目标资源(页面或 Controller)时,若需要向下传递数据,除了可以使用 request,session 外,还可以将数据存放于 ModelAndView 中的 Model 中。页面通过 EL 表达式可直接访问该数据。
(2)请求转发到其它 Controller
3.1.2 返回 ModelAndView 时的重定向
返回 ModelAndView 时的重定向,需在 setViewName()指定的视图前添加 redirect:,且此时的视图不会再与视图解析器中的前辍与后辍进行拼接。即必须写出相对于项目根的路径。重定向的目标资源中,将无法访问用户提交请求 request 中的数据。
(1)重定向到页面:
在重定向时,请求参数是无法通过 request 的属性向下一级资源中传递的。但可以通过以下方式将请求参数向下传递。
A、通过 ModelAndView 的 Model 携带参数
注意事项:
a、放入到 Model 中的 value,只能是基本数据类型与 String,不能是自定义类型的对象数据。
b、页面可以通过 EL 表达式中的请求参数 param读取。
c、重定向的页面不能是/WEB-INF 中的页面。
Step1:修改处理器类 MyController。
Step2:修改 show 页面位置
由于重定向的请求无法访问/WEB-INF/下的内容,所以此时的 show 页面需要调换位置:将 show 页面放到项目的根路径 WebRoot 下。
Step3:修改 show 页面内容
由于 Model 中的数据是放在 URL 后的参数,所以页面需要使用 param 来接收。
B、使用 HttpSession 携带参数
(2)重定向到其它 Controller:
重定向到其它 Controller 时,若要携带参数,完全可以采用前面的方式。而对于目标Controller 接收这些参数,则各不相同。
3.1.3 返回 String 时的请求转发
当处理器方法返回 String 时,该 String 即为要跳转的视图。在其前面加上前辍 forward:,则可显式的指定跳转方式为请求转发。请求转发的目标资源无论是一个页面,还是一个 Controller,用法相同。
对于处理器方法返回字符串的情况,当处理器接收到请求中的参数后,发现用于接收这些参数的处理器方法形参为包装类对象,则除了会将参数封装为对象传递给形参外,还会存放到 request 域属性中。
3.1.4 返回 String 时的重定向
在处理器方法返回的视图字符串的前面添加前辍 redirect:,则可实现重定向跳转。
当重定向到目标资源时,若需要向下传递参数值,除了可以直接通过请求 URL 携带参数,通过 HttpSession 携带参数外,还可使用其它方式。
(1)重定向到页面:
A、通过 Model 形参携带参数
可以在 Controller 形参中添加 Model 参数,将要传递的数据放入 Model 中进行参数传递。该方式同样也是将参数拼接到了重定向请求的 URL 后,所以放入其中的数据只能是基本类型数据,不能是自定义类型。
B、 通过形参 RedirectAttributes 的 addAttribute()携带参数
RedirectAttributes 是 Spring3.1 版本后新增的功能,专门是用于携带重定向参数的。通过 addAttribute()方法会将参数名及参数值放入该 Map 中,然后视图解析器会将其拼接到请求的 URL 中。所以,这种携带参数的方式,不能携带自定义对象。
(2)重定向到 Controller
A、通过 Model 形参携带参数
B、 通过形参 RedirectAttributes 的 addFlushAttribute()携带参数
RedirectAttributes 形参的 addFlushAttibute()携带方式不会将放入其中的属性值通过请求URL 传递,所以其中可以存放任意对象。
3.1.5 返回 void 时的请求转发
当处理器方法返回 void 时,若要实现请求转发,则需要使用 HttpServletRequest 的请求转发方法。无论下一级资源是页面,还是 Controller,用法相同。
需要注意的是,若有数据需要向下一级资源传递,则需要将数据放入到 request或 session 中。不能将数据放到 Model、RedirectAttributes 中。因为它们中的数据都是通过拼接到处理器方法的返回值中,作为请求的一部分出现向下传递的。这里没有返回值,所以它们中的数据便无法向下传递了。
3.1.6 返回 void 时的重定向
当处理器方法返回 void 时,若要实现重定向,则需要使用 HttpServletResponse 的重定向方法 sendRedirect()。
需要注意的是,若有数据需要向下一级资源传递,则需要将数据放入到 session中。
3.2 异常处理
3.2.1 SimpleMappingExceptionResolver 异常处理器实现过程
该方式只需要在 SpringMVC 配置文件中注册该异常处理器 Bean 即可。该 Bean 比较特殊,没有 id 属性,无需显式调用或被注入给其它<bean/>,当异常发生时会自动执行该类。
(1)自定义异常类
定义三个异常类:NameException、AgeException、StudentException。其中 StudentException是另外两个异常的父类。
(2)修改 Controller
(3)注册异常处理器
(4)定义异常响应页面
3.2.2 自定义异常处理器实现过程
使用 SpringMVC 定义好的 SimpleMappingExceptionResolver 异常处理器,可以实现发生指定异常后的跳转。但若要实现在捕获到指定异常时,执行一些操作的目的,它是完成不了的。此时,就需要自定义异常处理器。
自定义异常处理器,需要实现HandlerExceptionResolver 接口,并且该类需要在 SpringMVC配置文件中进行注册。
(1)定义异常处理器
当一个类实现了 HandlerExceptionResolver 接口后,只要有异常发生,无论什么异常,都会自动执行接口方法 resolveException()。
(2)注册异常处理器
3.2.3 异常处理注解实现过程
使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可选属性 value,为一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。
对于异常处理注解的用法,可以直接将异常处理方法注解于 Controller 之中。不过,一般不这样使用。而是将异常处理方法专门定义在一个 Controller 中,让其它Controller 继承该 Controller 即可。
(但是,这种用法的弊端也很明显:Java 是“单继承多实现” 的,这个 Controller 的继承将这唯一的一个继承机会使用了,使得若再有其它类需要继承,将无法直接实现。)
(1)定义异常处理的 Controller
(2)修改 Controller
3.3 类型转换器
在前面的程序中,表单提交的无论是 int 还是 double 类型的请求参数,用于处理该请求的处理器方法的形参,均可直接接收到相应类型的相应数据,而非接收到 String 再手工转换。那是因为在SpringMVC 框架中,有默认的类型转换器。这些默认的类型转换器,可以将 String类型的数据,自动转换为相应类型的数据。
但默认类型转换器并不是可以将用户提交的 String,转换为所有用户需要的类型。此时,就需要自定义类型转换器了。
例如,在 SpringMVC 的默认类型转换器中,没有日期类型的转换器,因为日期的格式太多。若要使表单中提交的日期字符串,被处理器方法形参直接接收为 java.util.Date,则需要自定义类型转换器了。
3.3.1 自定义类型转换器
若要定义类型转换器,则需要实现 Converter 接口。该 Converter 接口有两个泛型:第一个为待转换的类型,第二个为目标类型。而该接口的方法 convert(),用于完成类型转换。
3.3.2 对类型转换器的配置
类型转换器定义完毕后,需要在 SpringMVC 的配置文件中对类型转换进行配置。首先要注册类型转换器,然后再注册一个转换服务 Bean。将类型转换器注入给该转换服务Bean。最后由处理器适配器来使用该转换服务 Bean。
(1)注册类型转换器
(2)创建转换服务 Bean
对于类型转换器,并不是直接使用,而是通过转换服务 Bean 来调用类型转换器。而转换服务 Bean 的创建,是由转换服务工厂 Bean – ConversionServiceFactoryBean 完成。
该工厂 Bean 有一个 Set 集合属性 converters,用于指定该转换服务可以完成的转换,即可以使用的转换器。从 Set 集合可知,各转换器间无先后顺序。
(3)使用转换服务 Bean
转换服务 Bean 是由处理器适配器直接调用的。采用 mvc 的注解驱动注册方式,可以将转换服务直接注入给处理器适配器。
(4)SpringMVC 配置文件总的配置
3.3.3 接收多种日期格式的类型转换器
3.3.4 数据回显(细节略)
当数据类型转换发生异常后,需要返回到表单页面,让用户重新填写。正常情况下,发生类型转换异常,系统会自动跳转到 400 页面。所以,若要在发生类型转换异常后,跳转到指定页面,则需要将异常捕获,然后通过异常处理器跳转到指定页面。
若仅仅是完成跳转,则使用系统定义好的 SimpleMappingExceptionResolver 就可以。但,当页面返回到表单页面后,还需要将用户原来填写的数据显示出来,让用户更正填错的数据。也就是还需要完成数据回显功能。此时就需要自定义异常处理器了。
3.3.5 自定义类型转换失败后提示信息(细节略)
SpringMVC 并没有专门的用于自定义类型转换失败后提示信息的功能。需要程序员自己实现。
3.4 数据验证
在 Web 应用程序中,为了防止客户端传来的数据引发程序的异常,常常需要对数据进行验证。输入验证分为客户端验证与服务器端验证。客户端验证主要通过 JavaScript 脚本进行,而服务器端验证则主要是通过 Java 代码进行验证。
我们这里所讲的是 SpringMVC 在服务端是如何对数据进行验证的。
3.4.1 修改 springmvc 配置文件
验证器由 SpringMVC 框架的 LocalValidtorFactoryBean 类生成,而真正验证器的提供者则是 HibernateValidator。 在 SpringMVC 配置文件中将验证器注册后,需要将其注入给注解驱动。
3.4.2 在实体属性上添加验证注解
使用的验证器注解均为 javax.validation.constraints 包中的类。
在注解的 message 属性中,可以使用{属性名}的方式来引用指定的注解的属性值。
3.4.3 修改 Controller
由于这里使用的验证器为 Bean 对象验证器,所以对于要验证的参数数据,需要打包后由处理器方法以 Bean 形参类型的方式接收,并使用@Validated 注解标注。注意,不能将@Validated 注解在String 类型与基本类型的形参前。
紧跟着@Validated 所注解的形参的后面,是一个 BindingResult 类型的形参。通过该形参可获取到所有验证异常信息。
只要发生数据验证失败,则需要将页面重新跳转到 index.jsp 表单页面,让用户重填。
3.4.4 页面显示验证异常信息
3.5 文件上传
3.5.1 上传单个文件
(1)导入Jar包
(2)定义上传页面
定义具有文件上传功能的页面 index.jsp,其表单的设置需要注意,method 属性为 POST, enctype 属性为 multipart/form-data。另外,需要注意 file 表单元素的参数名称,Controller中需要使用。
(3)定义处理器
(4)在 SpringMVC 中注册文件上传处理器
(5)设置文件超出大小的异常处理
当上传文件超出指定大小时,会抛出 MaxUploadSizeExceededException 异常。通过在SpringMVC 配置文件中设置 SimpleMappingExceptionResolver,可实现对该异常的处理。
(6)定义上传成功与失败页面
3.5.2 上传多个文件
(1)修改index页面
(2)修改处理器类
3.6 拦截器
SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。
其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。
当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
3.6.1 一个拦截器的执行
(1)自定义拦截器
preHandle(request, response, Object handler):该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
postHandle(request, response, Object handler, modelAndView):该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
afterCompletion(request, response, Object handler, Exception ex):当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。
(2)注册拦截器
<mvc:mapping/>用于指定当前所注册的拦截器可以拦截的请求路径,而/**表示拦截所有请求。
(3)修改处理器
3.6.2 多个拦截器的执行
当有多个拦截器时,形成拦截器链。拦截器链的执行顺序,与其注册顺序一致。需要再次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法。