拦截器的概念css
A:“什么是拦截器?”html
B:“拦截器是经过统一拦截从客户端发往服务器的请求来完成功能的加强。”前端
A:(一脸懵逼)java
B:“说得简单点,拦截器就是在客户端向服务器端发出请求的期间,在请求交友服务器处理以前或以后对请求数据作一些修改或者其余相关的操做。”web
A:“能说说具体实现什么功能吗?”spring
B:“拦截器的使用场景是解决一些共性问题。好比乱码问题、权限验证问题。你能够将这些共性的操做从各个不一样业务功能的控制器方法中抽离出来,放在统一的拦截器中执行,这样你的解决乱码问题的代码,或者权限验证的代码不会重复地出如今各个不一样业务功能对应的控制器方法执行体中。”express
A:“我能够理解为拦截器是看门狗吗?这只狗负责看门,监管进门和出门的人;这只狗也能不一样人家的门前作相同的事情——拦截出门或进门的人们。”json
B:“能文雅点吗。。。”spring-mvc
A:“springMVC拦截器的出现却是帮咱们在MVC实现时,营造了一种AOP的效果”tomcat
B:“spring大法万岁!”
A:“。。。”
好,小段子结束,仍是用博客的正常写法来写吧。
这里,也许你还要问——拦截器和过滤器有什么区别?
过滤器Filter依赖于Servlet容器,Filter被Servlet容器所管理;基于回调函数;过滤范围大(请求、资源等)
拦截器Interceptor依赖于springMVC框架容器;基于反射机制;只过滤请求
(本文出自happyBKs的博客:http://my.oschina.net/happyBKs/blog/710833)
springMVC拦截器的原理和使用
咱们仍是先来看一个应用场景:解决乱码问题。
利用SpringMVC的拦截器能够解决乱码问题。可是,你应该会说,我使用Servlet容器中的过滤器也能够完成。是的,如今咱们如今一个springMVC项目中经过配置一个过滤器来解决乱码问题。(实际上springMVC拦截器与Servlet容器中的过滤器在实现解决乱码问题时的原理十分类似)
咱们在前几篇博客文章的例子的基础上改代码吧,顺便说明一下一个springMVC能够有两个互不干扰的前端控制器配置。在web.xml中,咱们在最后追加一个前端控制器viewSpace-dispatcher。能够看到,这个web.xml配置文件中有两个org.springframework.web.servlet.DispatcherServlet。可是拦截的请求是不一样的,一个是/,一个是/test2/*。这里请求会自动匹配更具体的一个,关于各类url通配符优先级,改天我专门弄一篇博客来讲。
关于web.xml的servlet的url-pattern,我只想插入一个注意事项:
在web.xml文件中,如下语法用于定义映射:
l. 以”/’开头和以”/*”结尾的是用来作路径映射的。
2. 之前缀”*.”开头的是用来作扩展映射的。
3. “/” 是用来定义default servlet映射的。
4. 剩下的都是用来定义详细映射的。好比: /aa/bb/cc.action
因此,为何定义”/*.action”这样一个看起来很正常的匹配会错?由于这个匹配即属于路径映射,也属于扩展映射,致使容器没法判断。
言归正传,下面是web.xml,重点看最后的那个viewSpace-dispatcher:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd " version="2.5"> <display-name>Archetype Created Web Application</display-name> <!-- Spring应用上下文, 理解层次化的ApplicationContext --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- DispatcherServlet, Spring MVC的核心 --> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <!-- mvc-dispatcher拦截全部的请求--> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>viewSpace-dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/configs/spring/viewSpace-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>viewSpace-dispatcher</servlet-name> <!-- mvc-dispatcher拦截全部的请求--> <url-pattern>/test2/*</url-pattern> </servlet-mapping> </web-app>
而后咱们编写viewSpace-dispatcher的配置文件:
\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 --> <!-- 启用Spring基于annotation的DI, 使用户能够在Spring MVC中使用Spring的强大功能。 激活 @Required @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 --> <context:annotation-config /> <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其余型的bean, 如@Service --> <context:component-scan base-package="com.happyBKs.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <!-- HandlerMapping, 无需配置, Spring MVC能够默认启动。 DefaultAnnotationHandlerMapping annotation-driven HandlerMapping --> <!-- 扩充了注解驱动,能够将请求参数绑定到控制器参数 --> <mvc:annotation-driven /> <!-- 静态资源处理, css, js, imgs --> <mvc:resources mapping="/resources/**" location="/resources/" /> <!-- 配置ViewResolver。 能够用多个ViewResolver。 使用order属性排序。 InternalResourceViewResolver放在最后。 --> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="order" value="1"/> <!--<property name="mediaTypes">--> <!--<map>--> <!--<entry key="json" value="application/json"/>--> <!--<entry key="xml" value="application/xml"/>--> <!--<entry key="htm" value="text/html"/>--> <!--</map>--> <!--</property>--> <property name="defaultViews"> <list> <!-- JSON View --> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"> </bean> </list> </property> <!--<property name="ignoreAcceptHeader" value="true"/>--> </bean> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsps/" /> <property name="suffix" value=".jsp" /> </bean> <!--200*1024*1024即200M resolveLazily属性启用是为了推迟文件解析,以便捕获文件大小异常 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="209715200"/> <property name="defaultEncoding" value="UTF-8"/> <property name="resolveLazily" value="true"/> </bean> </beans>
以后咱们变量一个控制器TestController2 :
package com.happyBKs.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * Created by sunsun on 2016/7/9. */ @Controller @RequestMapping("/test2") public class TestController2 { @RequestMapping("/login") public String login(){ System.out.println("进入控制器的login方法"); return "login"; } @RequestMapping("/viewAll") public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){ ModelAndView mv = new ModelAndView(); System.out.println("进入控制器的viewAll方法"); System.out.println("name="+name); System.out.println("pwd="+pwd); mv.setViewName("/hello"); return mv; } }
这个控制器的两个方法,一个是分发访问登陆页面的请求,一个是接收登陆请求并将表单数据在控制台输出最终转到hello页面。
登陆页面\WEB-INF\jsps\login.jsp:(本项目是以前博客中的项目例子基础上追加的功能,原项目定义了webapp的url名称为mvc,因此表单action的请求路径请注意!)
<%-- Created by IntelliJ IDEA. User: happyBKs Date: 2016/7/9 Time: 13:22 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登陆</title> </head> <body> <form action="/mvc/test2/viewAll" method="post"> 用户名<input type="text" name="name" id="name"><br/> 密 码<input type="password" name="pwd" id="pwd"><br/> <input type="submit" name="登陆"> </form> </body> </html>
登陆后的hello页面:
<%-- Created by IntelliJ IDEA. User: happyBKs Date: 2016/7/9 Time: 14:42 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>成功登陆</title> </head> <body> 成功登陆 </body> </html>
以后咱们发布项目到tomcat,可是请求http://localhost:8080/mvc/test2/login,却提示404错误。
控制台输出:
19680 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'viewSpace-dispatcher' processing GET request for [/mvc/test2/login] 19680 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'viewSpace-dispatcher' processing GET request for [/mvc/test2/login] 19686 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /login 19686 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /login 19689 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Did not find handler method for [/login] 19689 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Did not find handler method for [/login] 19690 [http-apr-8080-exec-10] WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/mvc/test2/login] in DispatcherServlet with name 'viewSpace-dispatcher' 19690 [http-apr-8080-exec-10] WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/mvc/test2/login] in DispatcherServlet with name 'viewSpace-dispatcher' 19690 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request 19690 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request
这是为何呢?
缘由是,当一个请求的全路径经过servlet映射找到所服务的DispatcherServlet后,DispatcherServlet按照url-pattern路径映射的匹配后剩余的路径进一步交给DispatcherServlet,由DispatcherServlet进一步实现剩下路径在其搜索包空间内的控制器类的请求映射的匹配。
回到这个例子中就是:http://localhost:8080/mvc/test2/login中,首先去除了域名和端口/mvc/test2/login,tomcat中配置了应用上下文Application Context为mvc,因此springMVC项目处理的请求为/test2/login。在web.xml中由viewSpace-dispatcher因url-pattern为/test2/**接收请求/test2/login,而后将匹配剩余部分路径/login进一步交给viewSpace-dispatcher所对应的springMVC容器配置文件中定义的控制器类包搜索空间中搜索可以匹配这个请求/login的控制器及其方法。可是这里咱们本来但愿其可以映射到的控制器类TestController2的@RequestMappping的注解值又配置了一个/test2,本来咱们但愿的目标方法public String login()的@RequestMappping注解值为/login。这时候实际springMVC是将请求/login尝试与/test2/login进行匹配,固然不可能匹配上,因此才会在控制台输出请求匹配不了,整个请求也只能返回404。
你若不信,当咱们这时候请求http://localhost:8080/mvc/test2/test2/login,你会发现尽然正常显示了!!
因此咱们就将控制器类TestController2的映射路径改成“/”,这样就能够了。
package com.happyBKs.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * Created by sunsun on 2016/7/9. */ @Controller @RequestMapping("/") public class TestController2 { @RequestMapping("/login") public String login(){ System.out.println("进入控制器的login方法..."); return "login"; } @RequestMapping("/viewAll") public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){ ModelAndView mv = new ModelAndView(); System.out.println("进入控制器的viewAll方法..."); System.out.println("name="+name); System.out.println("pwd="+pwd); mv.setViewName("/hello"); return mv; } }
咱们来看看运行结果:
请求http://localhost:8080/mvc/test2/login
控制台输出:
提交表单以后,转到http://localhost:8080/mvc/test2/viewAll:
控制台输出:
能够看到中文数据出现了乱码。。。。。。。
springMVC在框架中已经为web的过滤器提供了一个CharacterEncodingFilter类
在web.xml中,增长一个过滤器:
<filter> <filter-name>viewSpace-filter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> </filter> <filter-mapping> <filter-name>viewSpace-filter</filter-name> <url-pattern>/test2/*</url-pattern> </filter-mapping>
这个过滤器指定/test2/*符合该通配符的url请求都会被这个过滤器过滤到,而后交由这个CharacterEncodingFilter来处理,将请求参数中的数据编码转换成utf8。
注意:这个不一样用/test2,而要使用完整的通配符,不然过滤器会觉得你仅仅是要过滤http://localhost:8080/mvc/test2一个url请求,这与前端控制器的url-pattern不一样,须要特别注意。
咱们按照刚才的方法运行请求:
控制台输出发现参数已经不会再出现中文乱码了。
好,过滤器就像一个检票口,符合特定条件的请求进入者能够通过相应一些手续后进入。过滤器和拦截器在功能上能够说是十分类似的,只是在一些细节上有所不一样,下面,咱们来看看拦截器如何实现。
拦截器的实现步骤以下:
1. 首先编写一个拦截器类,需要实现HandlerInterceptor接口:
package com.happyBKs.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * Created by happyBKs on 2016/7/9. */ @Controller @RequestMapping("/") public class TestController2 { @RequestMapping("/login") public String login(){ System.out.println("进入控制器的login方法..."); return "login"; } @RequestMapping("/viewAll") public ModelAndView viewAll(@RequestParam("name") String name,@RequestParam("pwd") String pwd){ ModelAndView mv = new ModelAndView(); System.out.println("进入控制器的viewAll方法..."); System.out.println("name="+name); System.out.println("pwd="+pwd); mv.setViewName("/hello"); return mv; } }
2.将拦截器注册到springMVC框架中:
注意,在这个前端控制器器配置文件中,必须声明
xmlns:mvc="http://www.springframework.org/schema/mvc"
http://www.springframework.org/schema/mvc/spring-mvc.xsd"
应为拦截器需要用到mvc的名称空间:
咱们在\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.xml中追加以下内容:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.happyBKs.interceptor.TestInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
注意:这里的,mvc:mapping的path属性配置的路径必须是“/**”,而不能是“/”或者“/*”。
/**的意思是全部文件夹及里面的子文件夹
/*是全部文件夹,不含子文件夹
/是web项目的根目录
而后能够看到请求http://localhost:8080/mvc/test2 结果控制台输出:拦截器的三个方法与控制器方法的执行顺序能够看见了吧。
执行进入preHandle方法 进入控制器的login方法... 执行进入postHandle方法 11705 [http-apr-8080-exec-10] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name 'login' 11705 [http-apr-8080-exec-10] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name 'login' 11705 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name 'login'; URL [/WEB-INF/jsps/login.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher' 11705 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name 'login'; URL [/WEB-INF/jsps/login.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher' 11714 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to resource [/WEB-INF/jsps/login.jsp] in InternalResourceView 'login' 11714 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to resource [/WEB-INF/jsps/login.jsp] in InternalResourceView 'login' 执行进入afterCompletion方法
拦截器的方法
下面咱们来详细说说拦截器的三个方法:
preHandle:在请求备注里以前进行调用
postHandle:在请求被处理以后进行调用
afterComletion:在请求结束以后才进行调用
preHandle方法与其余两个方法相比比较特殊,它是具备返回值的。这个boolean类型的返回值表示这个请求是否能“活命”。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入preHandle方法"); return true; }
返回值为true表示,在preHandle执行完以后请求继续传递下去;为false,请求被拦截后就终止请求了。
若是咱们把这个方法的返回值改为false,控制台输出结果和页面显示以下:
看到了请求没有被执行下去,连控制器方法都没有被执行,因此这个请求被拦截下懒以后被拦截器给扼杀在摇篮里了。
这三个方法的形参都具备HttpServletRequest request和HttpServletResponse response,前者包含了全部的请求内容,后者包含了全部的响应内容。
preHandle方法里的Object handler表示的是被拦截的请求目标对象。好比,这个例子中请求拦截的目标就是这个TestController2。
postHandle方法里的独有的参数ModelAndView modelAndView:咱们能够经过该参数改变显示的视图,或者修改发往视图的方法。看到了吧,即便你在控制器方法中已经对映射的视图资源作了设定,这里依然能够更改。这个更改既包括这请求映射到那个视图资源,还包括了传递给视图资源的数据等。
这里咱们举个例子,加入咱们在控制器TestController2中增长一个方法:
@RequestMapping("/msg") public ModelAndView msg(){ System.out.println("进入控制器的msg方法..."); ModelAndView mv=new ModelAndView(); mv.setViewName("/InterTest"); mv.addObject("msg","从控制器的方法返回的视图数据"); return mv; }
增长一个jsp视图页面\WEB-INF\jsps\InterTest.jsp:
注意:这里msg数据用的EL表达式的形式${msg}。
<%-- Created by IntelliJ IDEA. User: sunsun Date: 2016/7/12 Time: 20:58 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>拦截器测试</title> </head> <body> msg=${msg} </body> </html>
而后咱们运行请求http://localhost:8080/mvc/test2/msg
好,这时候,咱们在原先的拦截器类TestInterceptor类的postHandle方法上作个修改:
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入postHandle方法"); //经过modelAndView参数改变显示的视图,或者修改发往视图的方法 modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据"); }
咱们再次运行方才的项目,请求:http://localhost:8080/mvc/test2/msg
看到了吧,msg的数据已是被拦截器的postHandle方法修改后的数据了。
而且,控制台输出也代表,执行的各个环节依然还在。
固然,这里的拦截器还能够对请求转发的视图资源作更改。例如,咱们继续更改控制器类的postHandle方法:
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入postHandle方法"); //经过modelAndView参数改变显示的视图,或者修改发往视图的方法 modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据"); modelAndView.setViewName("/hello"); }
将modelAndView的视图资源设置为hello页面,即成功登陆页面\WEB-INF\jsps\hello.jsp
运行结果变为:
看吧,视图资源也能换。
afterCompletion方法有点像C++中的析构函数,afterCompletion方法在请求被响应以后最后执行,用于对一些资源的释放,对咱们来讲不是很经常使用。
下面咱们来讲明另一个问题:若是存在多个拦截器,执行机制是怎么样的?
咱们将定义两个拦截器TestController和TestController2
package com.happyBKs.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by happyBKs on 2016/7/10. */ public class TestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入TestInterceptor的preHandle方法"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入TestInterceptor的postHandle方法"); //经过modelAndView参数改变显示的视图,或者修改发往视图的方法 // modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据"); // modelAndView.setViewName("/hello"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行进入TestInterceptor的afterCompletion方法"); } }
package com.happyBKs.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by happyBKs on 2016/7/10. */ public class TestInterceptor2 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入TestInterceptor2的preHandle方法"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入TestInterceptor2的postHandle方法"); //经过modelAndView参数改变显示的视图,或者修改发往视图的方法 // modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据"); // modelAndView.setViewName("/hello"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行进入TestInterceptor2的afterCompletion方法"); } }
以后在springMVC前端控制器配置文件\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 --> <!-- 启用Spring基于annotation的DI, 使用户能够在Spring MVC中使用Spring的强大功能。 激活 @Required @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 --> <context:annotation-config /> <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其余型的bean, 如@Service --> <context:component-scan base-package="com.happyBKs.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <!-- HandlerMapping, 无需配置, Spring MVC能够默认启动。 DefaultAnnotationHandlerMapping annotation-driven HandlerMapping --> <!-- 扩充了注解驱动,能够将请求参数绑定到控制器参数 --> <mvc:annotation-driven /> <!-- 静态资源处理, css, js, imgs --> <mvc:resources mapping="/resources/**" location="/resources/" /> <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">--> <!--<property name="alwaysUseFullPath" value="true"></property>--> <!--</bean>--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsps/" /> <property name="suffix" value=".jsp" /> </bean> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.happyBKs.interceptor.TestInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.happyBKs.interceptor.TestInterceptor2"></bean> </mvc:interceptor> </mvc:interceptors> </beans>
而后咱们运行,请求http://localhost:8080/mvc/test2/login
控制台输出结果:
执行进入TestInterceptor的preHandle方法
执行进入TestInterceptor2的preHandle方法
进入控制器的login方法...
执行进入TestInterceptor2的postHandle方法
执行进入TestInterceptor的postHandle方法
执行进入TestInterceptor2的afterCompletion方法
执行进入TestInterceptor的afterCompletion方法
是否是有点糊涂,没关系,看看这个执行机制的示意图:
打个比方说:好比你从上海去北京出差,有两个收费口,咱们先通过,收费口1,而后通过2,达到北京,而后回来时先通过2,在通过1,而且,回来的过程当中我告诉收费口把发票寄到上海家里,因而收费口2的发票先寄出,而后收费口1的再寄出。收费口就是拦截器,去北京的路上的收费口执行preHandle方法,到北京执行控制器方法,返程中收费口执行postHandle方法,开发票就是afterCompletion方法。
最后,在拦截器的实现方法上再补充一点,拦截器除了能够经过实现HandleInterceptor接口来完成,还有一种接口也能够——接口WebRequestInterCeptor。可是这种方法的preHandle方法没有返回值,所以不具备终止请求的功能。因此我仍是推荐经过实现HandleInterceptor接口来完成。
SpringMVC拦截器的使用场景
使用原则:处理全部请求中的共性问题
1. 解决乱码问题
咱们将控制器TestInterceptor从新整理一下,对preHandle方法中的request参数设置一下编码。
package com.happyBKs.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by happyBKs on 2016/7/10. */ public class TestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入TestInterceptor的preHandle方法"); request.setCharacterEncoding("utf-8"); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入TestInterceptor的postHandle方法"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行进入TestInterceptor的afterCompletion方法"); } }
原先项目web.xml中的过滤器咱们注释掉。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd " version="2.5"> <display-name>Archetype Created Web Application</display-name> <!-- Spring应用上下文, 理解层次化的ApplicationContext --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/configs/spring/applicationContext*.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!--DispatcherServlet, Spring MVC的核心--> <!--<servlet>--> <!--<servlet-name>mvc-dispatcher</servlet-name>--> <!--<servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>--> <!--<!– DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml--> <!--–>--> <!--<init-param>--> <!--<param-name>contextConfigLocation</param-name>--> <!--<param-value>/WEB-INF/configs/spring/mvc-dispatcher-servlet.xml</param-value>--> <!--</init-param>--> <!--<load-on-startup>1</load-on-startup>--> <!--</servlet>--> <!--<servlet-mapping>--> <!--<servlet-name>mvc-dispatcher</servlet-name>--> <!--<!– mvc-dispatcher拦截全部的请求–>--> <!--<url-pattern>/</url-pattern>--> <!--</servlet-mapping>--> <servlet> <servlet-name>viewSpace-dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/configs/spring/viewSpace-dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>viewSpace-dispatcher</servlet-name> <!-- mvc-dispatcher拦截全部的请求--> <url-pattern>/test2/*</url-pattern> </servlet-mapping> <!-- <filter> <filter-name>viewSpace-filter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf8</param-value> </init-param> </filter> <filter-mapping> <filter-name>viewSpace-filter</filter-name> <url-pattern>/test2/*</url-pattern> </filter-mapping>--> </web-app>
springMVC前端控制器配置文件\WEB-INF\configs\spring\viewSpace-dispatcher-servlet.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 本配置文件是工名为mvc-dispatcher的DispatcherServlet使用, 提供其相关的Spring MVC配置 --> <!-- 启用Spring基于annotation的DI, 使用户能够在Spring MVC中使用Spring的强大功能。 激活 @Required @Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 --> <context:annotation-config /> <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其余型的bean, 如@Service --> <context:component-scan base-package="com.happyBKs.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <!-- HandlerMapping, 无需配置, Spring MVC能够默认启动。 DefaultAnnotationHandlerMapping annotation-driven HandlerMapping --> <!-- 扩充了注解驱动,能够将请求参数绑定到控制器参数 --> <mvc:annotation-driven /> <!-- 静态资源处理, css, js, imgs --> <mvc:resources mapping="/resources/**" location="/resources/" /> <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">--> <!--<property name="alwaysUseFullPath" value="true"></property>--> <!--</bean>--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsps/" /> <property name="suffix" value=".jsp" /> </bean> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.happyBKs.interceptor.TestInterceptor"></bean> </mvc:interceptor> <!-- <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.happyBKs.interceptor.TestInterceptor2"></bean> </mvc:interceptor>--> </mvc:interceptors> </beans>
运行后请求http://localhost:8080/mvc/test2/login:
提交后控制输出:无乱码
执行进入TestInterceptor的afterCompletion方法 197415 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request 197415 [http-apr-8080-exec-10] DEBUG org.springframework.web.servlet.DispatcherServlet - Successfully completed request 206359 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'viewSpace-dispatcher' processing POST request for [/mvc/test2/viewAll] 206359 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'viewSpace-dispatcher' processing POST request for [/mvc/test2/viewAll] 206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /viewAll 206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Looking up handler method for path /viewAll 206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public org.springframework.web.servlet.ModelAndView com.happyBKs.controller.TestController2.viewAll(java.lang.String,java.lang.String)] 206360 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Returning handler method [public org.springframework.web.servlet.ModelAndView com.happyBKs.controller.TestController2.viewAll(java.lang.String,java.lang.String)] 206360 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testController2' 206360 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'testController2' 执行进入TestInterceptor的preHandle方法 206381 [http-apr-8080-exec-1] DEBUG org.springframework.web.cors.DefaultCorsProcessor - Skip CORS processing: request is from same origin 206381 [http-apr-8080-exec-1] DEBUG org.springframework.web.cors.DefaultCorsProcessor - Skip CORS processing: request is from same origin 进入控制器的viewAll方法... name=马云 pwd=123 执行进入TestInterceptor的postHandle方法 206404 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name '/hello' 206404 [http-apr-8080-exec-1] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Invoking afterPropertiesSet() on bean with name '/hello' 206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name '/hello'; URL [/WEB-INF/jsps//hello.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher' 206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Rendering view [org.springframework.web.servlet.view.JstlView: name '/hello'; URL [/WEB-INF/jsps//hello.jsp]] in DispatcherServlet with name 'viewSpace-dispatcher' 206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to resource [/WEB-INF/jsps//hello.jsp] in InternalResourceView '/hello' 206405 [http-apr-8080-exec-1] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to resource [/WEB-INF/jsps//hello.jsp] in InternalResourceView '/hello' 执行进入TestInterceptor的afterCompletion方法
看到了吧,拦截器方法能够对请求的数据作不少设置和修改,一样,也能够对响应的数据的编码等作修改。
2. 解决权限验证问题
好比,咱们如今须要一个拦截器,专门用来作权限验证,拦截器会在preHandle方法检查服务服务器session是否有该用户的会话,若是有则继续执行,若是没有则将响应重定向到登陆页面。
这个部分由于在大部分业务模块中都须要先行完成,若是把这样一个共性的东西添加到各个业务模块,整个系统的代码质量和可维护性可想而知有多糟,这正是拦截器的用武之地和使用原则。
好,咱们就试着写个代码示例,在preHandle方法中:
package com.happyBKs.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by happyBKs on 2016/7/10. */ public class TestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入TestInterceptor的preHandle方法"); request.setCharacterEncoding("utf-8"); //对用户是否登陆进行判断 if(request.getSession().getAttribute("user")==null){ //若是用户没有回话,即没有登陆,就终止请求,并发送到登陆页面 request.getRequestDispatcher("/test2/login").forward(request,response);//发送到登陆页面 return false;//终止请求 } return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行进入TestInterceptor的postHandle方法"); //经过modelAndView参数改变显示的视图,或者修改发往视图的方法 // modelAndView.addObject("msg","被拦截器的postHandle方法修改后的视图数据"); // modelAndView.setViewName("/hello"); } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行进入TestInterceptor的afterCompletion方法"); } }
好,咱们尝试运行,并随便请求一个能给该拦截器拦截的url,如http://localhost:8080/mvc/test2/msg。这时候咱们是没有登陆过的,看看会发生什么。咱们的预想是preHandle方法将验证到咱们sessioin会话为空,而后跳转到登陆页面。然而,恐怖的一幕发生了,页面死住,控制台开始疯狂套异常和死循环。。。
这是为何呢?原来,咱们的登陆页面url /test2/login也在拦截器的拦截返回内,当咱们请求http://localhost:8080/mvc/test2/msg,拦截器preHandle方法检查到了咱们会话为空没有登陆,而后请求被终止的同时转而请求一样在拦截器做用返回内的/test2/login,这时候死循环的故事就开始了,懂了吧。所以,拦截器的使用须要十分留心,拦截器的方法中在对请求进行转发时尤其要注意,请求转发不能是该拦截器,不然就会出现死循环。固然,还有一种状况,就是多个拦截器做用下的多个url相互转发请求,形成多个拦截器之间的死循环。
好,这里咱们增长一个公共jsp页面\loginPub.jsp:页面内容再也不详述
拦截器代码改成:
public class TestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行进入TestInterceptor的preHandle方法"); request.setCharacterEncoding("utf-8"); //对用户是否登陆进行判断 if(request.getSession().getAttribute("user")==null){ //若是用户没有回话,即没有登陆,就终止请求,并发送到登陆页面 //request.getRequestDispatcher("/test2/login").forward(request,response);//发送到登陆页面 request.getRequestDispatcher("/loginPub.jsp").forward(request,response);//发送到登陆页面 return false;//终止请求 } return true; }
运行结果:
请求http://localhost:8080/mvc/test2/msg:
控制台输出显示,执行到了拦截器的preHandle方法以后就没有了,由于拦截器检测到没有登陆,因此讲请求转发到了登陆页面\loginPub.jsp