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;
	}

}

    数据库运行之后结果:

spring 拦截器 返回true报错 spring 拦截器获取返回的结果_登录检查