一、什么是拦截器:
- 什么是拦截器:在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略
- 为什么需要拦截器:在做身份认证或者是进行日志的记录时,我们需要通过拦截器达到我们的目的。最常用的登录拦截、或是权限校验、或是防重复提交、或是根据业务像12306去校验购票时间,总之可以去做很多的事情
- 如何用拦截器:在spring中用拦截器需要实现HandlerInterceptor接口或者它的实现子类:HandlerInterceptorAdapter,同时在applicationContext.xml文件中配置拦截器。
拦截器可以做什么? -
用拦截器做很多事情:
日志记录:记录请求信息的日志,以便进行信息监控、信息统计等;
权限检查:如登录校验,在处理器处理之前先判断是否已经登录;
性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。
通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用。还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的都可以用拦截器来实现。
二、 拦截器实现方法:
spring中拦截器主要分两种,一个是HandlerInterceptor,一个是MethodInterceptor。
2.1 . HandlerInterceptor拦截器
HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。
1.实现拦截器类
@Slf4j
public class LoginCheckHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("execute LoginCheckHandlerInterceptor's preHandle method");
String userId = request.getHeader("userId");
if (StringUtils.isEmpty(userId)) {
response.setStatus(401);
response.getWriter().print("please login");
response.getWriter().flush();
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("execute LoginCheckHandlerInterceptor's postHandle method");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("execute LoginCheckHandlerInterceptor's afterCompletion method");
}
}
实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。
下面就是HandlerInterceptorAdapter的代码,可以看到一个方法只是默认返回true,另外两个是空方法:
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
- preHandle
用来拦截处理器的执行,preHandle方法将在Controller处理之前调用的。SpringMVC里可以同时存在多个interceptor,它们基于链式方式调用,每个interceptor都根据其声明顺序依次执行。这种链式结构可以中断,当方法返回false时整个请求就结束了。
方法的返回值是布尔类型,方法如果返回false,那后面的interceptor和Controller都不会执行(通常都会响应一个自定义的 Http 错误码给客户端)。如果返回值为true,则接着调用下一个interceptor的preHandle方法。如果当前是最后一个interceptor,接下来就会直接调用Controller的处理方法。
- postHandle:
postHandle 会在Controller方法调用之后,但是在DispatcherServlet 渲染视图之前调用。因此我们可以在这个阶段,对将要返回给客户端的ModelAndView进行操作。
- afterCompletion:
afterCompletion在当前interceptor的preHandle方法返回true时才执行。该方法会在整个请求处理完成后被调用,就是DispatcherServlet渲染视图完成以后,主要是用来进行资源清理工作。需要注意的是,afterCompletion在interceptor链式结构中以相反的顺序执行,也就是说先申明的interceptor返回会后调用。
拦截器内方法的执行顺序依次是:preHandle ---> postHandle ---> afterCompletion。
2、在配置类定义拦截路径
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
@Autowired
private EasyLogControllerInterceptor easyLogControllerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//addPathPatterns用于添加拦截路径
//excludePathPatterns用于添加不拦截的路径
registry.addInterceptor(LoginCheckHandlerInterceptor ).addPathPatterns("/hello");
}
//此方法用于配置静态资源路径
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/my/");
}
}
3、定义测试controller类
@RestController
public class TestController {
@GetMapping("/hello")
public Map<String,String> hello(){
Map<String,String> response=new HashMap<>();
response.put("msg","hello");
return response;
}
}
配置完后可以运行程序,访问/hello ,控制台将打印对应的请求日志
拦截器调用顺序
在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。
2.2. MethodInterceptor拦截器
MethodInterceptor是AOP项目中的拦截器(注:不是动态代理拦截器),区别与HandlerInterceptor拦截目标时请求,它拦截的目标是方法。
实现MethodInterceptor拦截器大致也分为两种:
(1)MethodInterceptor接口;
(2)利用AspectJ的注解配置;
spring框架的AOP非常强大,可以实现前置增强、后置增强、最终增强、环绕增强等处理,另外还可以对方法入参进行控制过滤,而影响目标方法的执行中的状态。AOP都是基于(Cglib)代理模式实现的,其中的关键点在于实现MethodInterceptor接口,在其“public Object invoke(MethodInvocation mi)”方法中制定代理方法的生成策略,而从此方法的MethodInvocation类型参数mi中可以获得目标对象、方法的参数、目标方法等信息,根据这些信息可以精确地控制增强效果。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
public static class UserMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
String methodName = mi.getMethod().getName();// 方法名
Object returnVal = null;
/*
* 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut
* expression="execution( xxxmethodName)"/>,以此定义切入点。
*
* spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点
* 为了简单化,此处我只用方法名的前缀来定义切入点。
*
*/
if (methodName.startsWith("add")) {
returnVal = beforeEnhance(mi);
} else if (methodName.startsWith("delete")) {
returnVal = afterThrowingEnhance(mi);
} else {
returnVal = mi.proceed();
}
return returnVal;
}
/*
* 前置增强策略
*/
private Object beforeEnhance(MethodInvocation mi) throws Throwable {
/*
* spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。
* 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,
* 增强方法就是"public void before(MethodInvocation mi)"
*
*/
new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式
Object returnVal = mi.proceed(); // 执行目标方法
return returnVal;
}
/*
* 异常处理策略
*/
private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();// 执行目标方法
} catch (Throwable e) {
new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式
throw e;
}
}
}
自定义一个拦截器(实际上是一个拦截器)
这个Advice通知实现了 MethodInterceptor接口,而MethodInterceptor接口继承了Interceptor接口,Intercepto接口又继承了Advice接口,因此我个将这拦截器称为一个通知。
spring框架中,当读到配置文件的"<aop:before>"标签,就安排一个AspectJMethodBeforeAdvice类(继承于AbstractAspectAdive抽象类,此抽象类实现了Advice接口)去处理前置增强;当读到配置文件的"<aop:after-throwing>"标签,则会安排一AspectJAfterThrowingAdvice类(实现了MethodInterceptor接口)去做异常增强处理。spring框架切实做到了遵循”单一职责“原则,专门组织一个类去处理一种类型(前置、后置或最终等)的增强。