Spring处理器拦截器都实现了HandlerInterceptor接口,HandlerInterceptor接口主要有以下三个方法:
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
boolean preHandle():预处理回调方法,实现处理器的预处理(如登录检查、日志记录等)。在处理器拦截器执行链中按顺序调用拦截器的该方法。
返回值
true:
继续调用下一个拦截器的
preHandle()
方法,
返回值false:中断之后的拦截器处理器执行链和处理器的执行,此时需要通过Response产生响应。(如登录检查失败后,Response重定向到登录界面)。
void postHandle():后处理回调方法,调用时机是处理器执行之后(在渲染视图之前),可以对ModelAndView中的数据进行相应处理。对于拦截器链中的该方法,调用顺序是逆序调用。
void afterCompletion():整个请求处理完毕回调方法(如资源回收等)。该方法调用的时机是请求处理过程完成,在渲染视图之后调用,此时可以调用任何处理器执行的结果,也因此可以进行适当的资源清理工作。注意该方法仅调用preHandle()方法返回值为true的拦截器的afterCompletion()方法,并且如同postHandle()方法一样逆序调用。类似于try-catch-finally 中的finally()方法。
HandlerInterceptor接口必需要实现其中的3个方法(有的方法在实际中不需要),Spring mvc 提供了一个抽象类HandlerInterceptorAdapter(已实现HandlerInterceptor接口)处理器拦截器适配器,只需继承该适配器抽象类,重写自己需要回调的方法即可。
以下是拦截器的Demo。
1.登录检查
拦截器配置如下:
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<ref bean="loginInterceptor"/>
</list>
</property>
</bean>
<bean id="loginInterceptor" class = "cn.cet.web.interceptor.LoginHanderInterceptor"></bean>
登录拦截器代码实现:
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import cn.cet.entity.User;
public class LoginHanderInterceptor extends HandlerInterceptorAdapter{
//不需要过滤的url
private static Set<String> urls = new HashSet<>();
static {
//放过,不拦截
//包括登录页面,退出请求,注册页面请求,
urls.add("/admin/loginUI.action");
urls.add("/user/loginUI.action");
urls.add("/user/login.action");
urls.add("/admin/login.action");
urls.add("/admin/quit.action");
urls.add("/user/quit.action");
urls.add("/admin/registerUI.action");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String url = request.getServletPath();
System.out.println("url = "+url);
if(urls.contains(url)) {
return true;
}
//执行拦截操作;
HttpSession session = request.getSession();
User userInfo = (User) session.getAttribute(attrName);
if(userInfo!=null) {
return true;
}
//非法请求,重定向到登录页面
response.sendRedirect(request.getContextPath()+"/user/loginUI.action");
return false;
}
}
2.处理器性能检测
思路:在处理器执行之前(preHandle()方法中)记录开始时间;在整个请求处理完成结束(afterCompletion()方法中)记录结束时间;然后结束和开始时间的差值作为处理器执行时间。
问题:preHandle()方法和afterCompletion()方法触发的时机不同,如何保存开始时间,使得再次触发afterCompletion()方法时可以取得进行计算?这肯定得需要一个变量进行存储开始时间,那么问题来了,这个变量是共享的还是独立的,即线程安全和不安全?
由于处理器拦截器是单例状态,即在整个程序运行期间,不管多少用户请求,只有一个该拦截器的实例对象应对处理,即是共享的、线程不安全的。在此我们引入线程绑定变量ThreadLocal,它支持维护变量,为每一个使用该变量线程提供一个独立的该变量的副本,所以不同线程可以自由的改变该线程拥有变量副本的值,不会影响其他线程所拥有的变量副本的值。
以下是代码实现:
拦截器配置信息:
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<ref bean="loginInterceptor"/>
<ref bean = "performInterceptor"/>
</list>
</property>
</bean>
<bean id="performInterceptor" class="cn.cet.web.interceptor.PerformHandlerInterceptor"></bean>
拦截器实现:
package cn.cet.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class PerformHandlerInterceptor extends HandlerInterceptorAdapter{
private final static ThreadLocal<Long> timeThreadLocals = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
timeThreadLocals.set(startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
long startTime = timeThreadLocals.get();
long endtTime = System.currentTimeMillis();
System.out.println("endtTime - startTime:"+ (endtTime - startTime));
}
}
3.简单的拦截器实现日志记录
目前只对登录系统后进行日志记录。
配置文件:
<!--注解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<ref bean="loginInterceptor"/>
<ref bean = "performInterceptor"/>
<ref bean = "logInfoInterceptor"/>
</list>
</property>
</bean>
<bean id="logInfoInterceptor" class="cn.cet.web.interceptor.LogInfoHandlerInterceptor"></bean>
拦截器实现:
package cn.cet.web.interceptor;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import cn.cet.entity.DayLog;
import cn.cet.entity.User;
import cn.cet.service.LogInfoService;
public class LogInfoHandlerInterceptor extends HandlerInterceptorAdapter{
//不需要过滤的url
private static Set<String> urls = new HashSet<>();
static {
//放过,不拦截
//包括登录页面,退出请求,注册页面请求,不需要登录即可
urls.add("/admin/loginUI.action");
urls.add("/user/loginUI.action");
urls.add("/user/login.action");
urls.add("/admin/login.action");
urls.add("/admin/quit.action");
urls.add("/user/quit.action");
urls.add("/admin/registerUI.action");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
//目前只对登录系统后的操作进行日志记录
if(urls.contains(request.getServletPath())) {
//不需要进行日志记录
return true;
}
//得到spring 容器中的 logInfoService bean
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
//该方法如果未找到返回null
LogInfoService logInfoService = (LogInfoService) context.getBean("logInfoService");
DayLog dayLog = new DayLog();
//在登录拦截器之后执行
User userInfo=(User) request.getSession().getAttribute("ADMININFO");
HandlerMethod handlerMethod = (HandlerMethod) handler;
dayLog.setAccount(userInfo.getAccount());
dayLog.setRequestUrl(request.getServletPath());
dayLog.setRequestMethod(handlerMethod.getMethod().getName());
dayLog.setOperateTime(new Date());
if(logInfoService==null) {
//记录日志失败
//作相应的失败处理跳转。。。
return false;
}
System.out.println(dayLog);
logInfoService.save(dayLog);
return true;
}
}
数据库运行之后结果: