前言
我们知道在web开发中,一般有三大板块:Servlet(服务连接器) 、Listener(监听器) 和Filter(过滤器),而今天我们要学习的拦截器可以算是一个精致的过滤器"法宝","法宝"用的好不好,也间接关系到你的Servelt项目是否足够安全,是否足够稳定。
拦截器的概念
在学习Servlet的时候,我们曾使用自定义类实现不同Filter接口的方式,做过拦截的操作,比如说登录拦截,编码拦截等,在讲解SpringMVC的拦截器之前,我们来回顾下之前的一些拦截案例吧~
就以编码拦截为例子
public class EncodingFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRqst = (HttpServletRequest)request;
HttpServletResponse httpResp = (HttpServletResponse)response;
httpRqst.setCharacterEncoding("UTF-8");
httpResp.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
大家可以看到,我们通过实现Filter接口对所有能够匹配上/*
的url操作做了拦截处理后,再集中设置请求和响应的编码,最终通过doFilter()方法放行,xml配置如下
<filter>
<filter-name>characterEncoding</filter-name>
<filter-class>com.marco.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
而在SpringMVC中,我们可以通过拦截器来实现拦截的功能,并且SpringMVC为我们提供了很多自带的拦截共呢个,比方说上述的编码拦截转换功能我们只需在web.xml中对CharacterEncodingFilter配置即可
<!-- 配置字符编码过滤器 -->
<filter>
<filter-name>characterEncoding</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>characterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
拦截器原理其实和过滤器差不多,只不过在过滤器的基础上更加的细化而已,但是需要注意的是SpringMVC的拦截器只能拦截经过前端控制器DispatcherServlet的请求
其实说白了拦截器就是过滤器的升级版,对所有被拦截内容加以修饰,然后放行,但是这里的拦截的内容仅限于控制器方法执行前后的操作,这种编程方式也贴合了我们之前讲到的AOP的编程思想了吧
SpringMVC拦截器的使用
登录拦截器
相信大家大致已经知道拦截器的作用和概念了,那么我们接下来演示一个登录拦截的栗子吧~
在SpringMVC的web jar中为我们提供了一个控制器拦截器的接口HandlerInterceptor,我们需要重写里面的三个方法,分别是afterCompletion()
、postHandle()
和preHandle()
,那么这三个方法分别在什么时候被调用呢?
我们接下往下看。
首先我们编写一个Login控制器LoginController
package com.marco.controller;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.marco.domain.User;
import com.marco.service.UserService;
import com.marco.util.WebUtil;
import com.marco.vo.UserVo;
@Controller
@RequestMapping("login")
public class LoginController {
@Autowired
UserService userService;
@RequestMapping("toLogin")
public String toLogin() throws Exception {
return "login";
}
@RequestMapping("login")
public String login(UserVo userVo, Model model) throws Exception {
User user = userService.login(userVo.getUloginname(),userVo.getUpsw());
if(user!= null) {
HttpSession httpSession = WebUtil.getHttpSession();
httpSession.setAttribute("currentUser", user);
return "index";
} else {
model.addAttribute("error", "用户名或者密码不正确");
return "login";
}
}
}
接下来编写登录拦截器LoginInterceptor
package com.marco.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.marco.domain.User;
public class LoginInterceptor implements HandlerInterceptor{
/**
* 在postHandle执行完成这这后回调的方法
* @param handler 和postHandle里面的handler一样
*/
@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
System.out.println("####@###############afterCompletion#################");
}
/**
* 当preHandle 返回true时会调用的方法
* @param handler就是用户调用的自定义controller的方法及方法所在的对象的封装 handler{Contoller bean,Method method}
@param modelAndView 就是自定义Controller的返回值最终封装的对象
*
*/
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
throws Exception {
System.out.println("####@###############postHandle#################");
}
/**
* 确定是否放行的方法
* @return true 代表放行
* false 代表拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
System.out.println("####@###############preHandle#################");
User user = (User) request.getSession().getAttribute("currentUser");
if(null != user) {
return true;
} else {
response.sendRedirect(request.getContextPath() + "/index.jsp");
return false;
}
}
}
通过上面的代码不难看出我们主要是在preHandle()
这个方法中做文章,是不是和doFilter()
很相似呢?
只不过在preHandle()
,使用true和false来决定是否放行或者拦截。
配置springmvc.xml! 配置springmvc.xml ! 配置springmvc.xml!
重要的事情说三遍,这里的配置和我们之前的配置有些出入,请看
<mvc:interceptors>
<mvc:interceptor>
<!--代表拦截所有经过DispatcherServlet的请求-->
<mvc:mapping path="/**"/>
<!--做登录拦截要记得将login.action或者login.jsp放行,否则会造成登录不成功的问题-->
<mvc:exclude-mapping path="/login/login*"/>
<bean class="com.marco.interceptor.LoginInterceptor" ></bean>
</mvc:interceptor>
</mvc:interceptors>
那么我们测试看看这三个方法的执行顺序
可以看到这三个方法的执行先后顺序为preHandle()
、postHandle()
、afterCompletion()
准确上来说preHandle()
是进行处理器拦截用的,在Controller处理之前进行调用,因为Interceptor拦截器本质上就是Filter,因此它也遵循链式规则,可以有多个Interceptor同时存在,然后SpringMVC会根据Interceptor声明的前后顺序来一一调用处理,所有的Interceptor中的preHandle方法都会在Controller方法调用之前调用,当返回值为false时,则不会继续请求下去
postHandle()
只会在当前这个Interceptor的preHandle方法返回值为true的时候才会执行,反言之,结果为false时该方法时不执行的,因此他是在处理器进行处理之后进行调用的,但是它在时间轴上的位置会在DispatcherServlet视图的渲染之前,因此我们通过此方法是可以对ModelAndView进行操作的
afterCompletion()
同理,被调用的条件是preHandle方法返回值为true,该方法是在DispatcherServlet视图的渲染之后执行的,它主要是用于清理资源的。
敏感词汇拦截器
如果说你对登录拦截不太熟悉,那么敏感词汇拦截你总该碰到过吧?
比如说你在玩LOL的时候,遇到一个菜鸡队友,想骂它两句,结果留言台下面就变成了
***** 你个 ******, 怎么这么**,*
这个时候你就更气了,但是为了平台的和谐文明,建议不要这样骂人,可以委婉一点。
咳咳… 我什么都没说!好了,那么我们来模拟一下这个"讨厌嫌"的拦截器
首先来个破旧的消息发送台
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="renderer" content="webkit">
<title>LOL消息发送台</title>
<link rel="stylesheet" href="/dms/css/pintuer.css">
<link rel="stylesheet" href="/dms/css/admin.css">
<script src="/dms/js/jquery.js"></script>
</head>
<body>
<form action="test.action">
<input type="text" name="message"/>
<input type="submit" name="send"/>
</form>
</body>
</html>
在编写我们的Controller
package com.marco.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.marco.domain.User;
import com.marco.service.UserService;
import com.marco.util.WebUtil;
import com.marco.vo.UserVo;
@Controller
public class TestController {
@RequestMapping("test")
public String test(HttpServletRequest request) throws Exception {
System.out.println("####@##############我被拦截了################");
String message = request.getParameter("message");
System.out.println("玩家输入:" + message);
request.setAttribute("message", message);
return "response";
}
}
上面都没啥好说的,接着编写拦截器,大家注意看!这次我没有在preHandle()
中做文章,因为需要方法被执行之后并在视图被解析之前对敏感文字做处理,因此,我们的操作应该写在postHandle()
方法中!
我们这里通过替换的方式过滤掉敏感词汇。
package com.marco.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.marco.domain.User;
public class SensitiveWordInterceptor implements HandlerInterceptor{
/**
* 在postHandle执行完成这这后回调的方法
* @param handler 和postHandle里面的handler一样
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
System.out.println("####@###############afterCompletion#################");
}
/**
* 当preHandle 返回true时会调用的方法
* @param handler就是用户调用的自定义controller的方法及方法所在的对象的封装 handler{Contoller bean,Method method}
@param modelAndView 就是自定义Controller的返回值最终封装的对象
*
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView modelAndView)
throws Exception {
System.out.println("####@###############postHandle#################");
String message = request.getAttribute("message").toString();
if(message.contains("垃圾")) {
message = message.replace("垃圾", "***");
}
request.setAttribute("message", message);
}
/**
* 确定是否放行的方法
* @return true 代表放行
* false 代表拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
System.out.println("####@###############preHandle#################");
return true;
}
}
接下来重中之重啦,记得在springmvc.xml中配置我们的静态放行文件
<mvc:interceptors>
<mvc:interceptor>
<!-- 代表拦截所有经过DispatcherServlet的请求 -->
<mvc:mapping path="/**"/>
<bean class="com.marco.interceptor.SensitiveWordInterceptor" ></bean>
</mvc:interceptor>
</mvc:interceptors>
好了,咱们来看看效果,首先输入一串不文明的话,我们跟着debug走
首先debug小虫子进入preHandle(),执行打印语句
当preHandle()返回true时,不会被return中断,而是执行handle方法
跟着我们执行handle方法进入到我们的控制器中并执行test()方法,这里我返回的路径是一个return.jsp页面
上面的步骤执行完,我们跟着debug走,发现一个有点熟悉的名词PostHandle
果不出所料!我们进入了postHandle()
方法中,接下来就是处理我们的敏感词汇了
等待postHandle()
执行完后,返回结果给DispatcherServlet的路上,我们再执行afterCompletion()
方法
好,接下来我们看看页面上显示的结果是不是被修改了
发现这句话变成了我们熟悉的样子,哈哈,那今天的SpringMVC的内容就讲到这里啦!