需求背景:记录所有Restful API 的处理时间
编写一个Fileter并注入到Spring容器中
@Component
public class TimeFIlter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("timeFilter init !");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("timeFilter start");
long start = System.currentTimeMillis();
chain.doFilter(request,response);
System.out.println("timeFilter耗时:"+(System.currentTimeMillis()-start));
System.out.println("timeFilter stop");
}
@Override
public void destroy() {
System.out.println("timeFilter destory!");
}
}
测试结果如下:
配置第三方的过滤器(即将一个普通的类加载到过滤器链上)
新建一个配置类
@Configuration
public class WebConfig {
/**
* 模拟配置第三方的Filter
* 和在Spring项目的xml中配置filter类似(自行脑补)
* @return
*/
@Bean
public FilterRegistrationBean timeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
TimeFIlter timeFIlter = new TimeFIlter();
registrationBean.setFilter(timeFIlter);
List urls = new ArrayList<>();
//所有的url都拦截
urls.add("/*");
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
filter控制器的弊端:
filter只能获取到HttpServletRequest和HttpSevletResponse对象,至于具体操作是由那一个控制器的哪一个方法执行的,这个是获取不到的。因为Filter接口是由J2EE规范来制定,的J2EE规范是不知道关于Spring框架的具体实现的。Controller控制器是由Spring框架定义的一套组件。如果要获取这些信息的话,需要用到Spring框架提供的组件才行。
拦截器是由Spring框架提供的一套组件。
编写拦截器
@Component
public class TimeInterceptor implements HandlerInterceptor {
/**
* 拦截器调用之前执行
* @param httpServletRequest
* @param httpServletResponse
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object handler) throws Exception {
System.out.println("preHandle");
httpServletRequest.setAttribute("startTime",System.currentTimeMillis());
//获取类名
System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
//获取方法名
System.out.println(((HandlerMethod)handler).getMethod().getName());
return false;
}
/**
* 拦截器调用之后执行
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandler");
Long time = (Long)httpServletRequest.getAttribute("startTime");
System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-time));
}
/**
* 一定会执行的方法
* @param httpServletRequest
* @param httpServletResponse
* @param o
* @param e
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
System.out.println("afterCompletion");
Long time = (Long)httpServletRequest.getAttribute("startTime");
System.out.println("time interceptor 耗时:"+(System.currentTimeMillis()-time));
System.out.println("e is"+e);
}
}
注册拦截器
1)配置类继承自WebMvcConfigurerAdapter
2)重新WebMvcConfigurerAdapter类的addInterceptors()方法
@Autowired
private TimeInterceptor timeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器
registry.addInterceptor(timeInterceptor);
}
运行测试结果:
修改getInfo()方法,让其抛出异常
运行测试结果:
可见,postHandle中的方法未执行,afterCompletion依旧执行,注意在afterCompletion方法中未捕获到抛出的异常,是因为异常已经被带有@ControllerAdvice注解的Controller处理。
抛出一个未被处理的异常,再次进行测试,如下:
结果如下:
拦截器会被拦截所有控制器中的方法调用,不光是自己写的,Spring提供的控制器也会被拦截
interceptor的不足
无法获取到具体执行的控制器中的方法的参数的值,这与Spring本身的设计有关,可以查看DispatcherServlet的源码
在DispatcherServlet的doDispatch()方法中,有如下的描述
如果applyPreHandle()方法,返回false,直接返回,否则继续执行ha.handle()方法。
打开applyPreHandle()方法的源码,如下
如此可见,如果存在interceptor拦截器,dispatcherServlet是不会调用处理器,而Controller中方法参数的拼装是在处理器中完成的(即ha.handle())。所谓参数的拼装就是把请求中的数据,封装成具体Controller中的参数的过程。
综上,如果除了要知道具体是哪个Controller的哪个方法被调用,还要知道具体的方法的参数值,interceptor是做不到的。
Spring AOP简介
编写切片组件
切入点
1)@Before
2)@After
3)@AfterThrow
4) @Around
完全替代了前三种注解,一般直接用@Around
在哪些方法上执行
execution(* com.crsri.secutity.demo.web.controller.UserController.*(..))
其他的表达式可以参考
https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts
切入时执行的逻辑
切片组件的代码如下:
@Aspect
@Component
public class TimeAspect {
/**
* 切片方法
* @param pjp 包含已拦截方法中的所用信息
* @return
*/
@Around("execution(* com.crsri.secutity.demo.web.controller.UserController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("time aspect start");
long start = System.currentTimeMillis();
Object[] args = pjp.getArgs();
for(Object arg : args){
System.out.println("arg is "+arg);
}
Object obj = pjp.proceed();
System.out.println("time aspect 耗时:"+(System.currentTimeMillis()-start));
System.out.println("time aspect stop");
return obj;
}
}
运行结果如下:
综上:要对Restful API的请求进行拦截,不同的业务需求使用不同的组件
请求的拦截及异常的处理层次
请求处理
Filter –> Interceptor –> Aspect —-> Controller
异常处理
Controller –> Aspect –> ControllerAdvice –> Interceptor –> Filter –> tomcat等Web容器
http://qa.blog.163.com/blog/static/190147002201312872938439/