1.7 Restful API拦截
我们对所有的api都需要做一个全局的处理:比如:记录全局处理时间、全局处理日志,就需要用到拦截机制:常见拦截机制分为如下几种:
- 过滤器(Filter)
- 拦截器(Interceptor)
切片(Aspect)
应用场景和实现机制。 我们以记录全局所有时间为例:1.7.1 过滤器(Filter)
新建过滤器:TimeFilter
@Component//注入到spring
public class TimeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("time filter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("time filter doFilter-->start");
long start = new Date().getTime();
filterChain.doFilter(servletRequest,servletResponse);
long end = new Date().getTime();
System.out.println("time filter 耗时:"+(end-start));
System.out.println("time filter doFilter-->end");
}
@Override
public void destroy() {
System.out.println("time filter destroy");
}
}
app端调用DetailInfo接口:
服务后台结果:
time filter doFilter-->start
====DetailInfo=====
time filter 耗时:274
time filter doFilter-->end
有时候我们使用过滤器时候是使用第三方的过滤器;我们没法修改里面的代码.这个时候我们如何去将第三方的过滤器注入到Spring里面(添加不了@Component//注入到spring);
传统开发模式下,会有一个web.xml文件,我们将第三方的过滤器添加到此web.xml文件下即可。在SpringBoot下添加没有注解:@Component的类到spring下:
我们定义一个注解修饰的类:此注解修饰的类,可看成 web.xml文件类似(此时我们需要注释掉TimeFilter的注解:@Component):
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean timeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//1.注入bean
TimeFilter timeFilter = new TimeFilter();
registrationBean.setFilter(timeFilter);
//2.定义url
List<String> urls = Arrays.asList("/*");
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
对别使用了@Component注解的过滤器和@Bean注入的过滤器:
1.@Bean可以自定义过滤器过滤前缀 @Component修饰的不可以,相当于全部url拦截。
过滤器说明: 1.Filter接受到的request请求 到底是哪个控制器的哪个方法去处理,在Filter里面是不知道的,因为filter是javax.servlet的J2EE规范。J2EE规范里面其实不了解跟Spring相关的任何东西,而UserController是Spring MVC自己定义的东西。
2.如果需要知道:是哪个控制器的哪个方法去处理信息的话就要使用到了拦截器;拦截器是Spring框架自己提供的
1.7.2 拦截器(Interceptor)
@Component //注意光声明为Component不能让其起作用 还需要在:WebConfig 中继承(extends) WebMvcConfigurerAdapter
public class TimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 1.在控制器方法调用前执行
* 2.Interceptor比Filter的一个优势是具有:handler
* 3.return 返回值决定了后面方法是否要执行
*/
HandlerMethod handlerMethod = (HandlerMethod) handler;
System.out.println(handlerMethod.getBean().getClass().getName());//打印出类名
System.out.println(handlerMethod.getMethod().getName());//打印出方法名
request.setAttribute("startTime",new Date().getTime());
System.out.println("TimeInterceptor-->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
/**
* 在控制器方法调用成功后执行:如果方法抛出了异常将不会执行此方法
*/
System.out.println("TimeInterceptor-->postHandle");
Long start = (Long)request.getAttribute("startTime");
System.out.println("TimeInterceptor 耗时:"+(new Date().getTime()-start));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception {
/**
* 控制器方法不管成功还是失败都会进入到此方法
*/
System.out.println("TimeInterceptor-->afterCompletion");
Long start = (Long)request.getAttribute("startTime");
System.out.println("TimeInterceptor 耗时:"+(new Date().getTime()-start));
System.out.println("TimeInterceptor ex is :"+e);
}
}
拦截器起作用还需要添加到InterceptorRegistry中
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private TimeInterceptor timeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
@Bean
public FilterRegistrationBean timeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//1.注入bean
TimeFilter timeFilter = new TimeFilter();
registrationBean.setFilter(timeFilter);
//2.定义url
List<String> urls = Arrays.asList("/*");
registrationBean.setUrlPatterns(urls);
return registrationBean;
}
}
使用App端测试:
后端打印日志:
我们修改用户详情接口:抛出一个异常
@GetMapping("/{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User DetailInfo(@PathVariable(name = "id") String xxx){
throw new UserNotExistException();
}
App端请求:
后端打印出:
发现afterCompletion里面的ex为null原因是我们全局的异常处理类已经处理了这个异常;说明@ControllerAdvice修饰的GeneralExceptionHandler会在我们拦截器afterCompletion执行之前处理。
拦截器特点:
1.拦截器相比于过滤器Filter,它能够拿到request里面的类和方法,知道是哪个类的哪个方法去执行操作,但是有个问题是:它没法拿到方法上的参数值。 原因:我们查看Spring源码:DispatcherServlet(分发我们请求的)--> doService方法--> this.doDispatch(request, response);
//此时调用我们拦截器的PreHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//真正进行方法参数封装的方法是:handle
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
1.7.2 切面
2.如果除了记录类和方法时候还需要知道具体参数内容,就需要使用切片:
需要满足:切入点 增强
1.在哪些方法上起作用是使用一个表达式表述的:
execution(* com.yxm.security.web.controller.UserController.*(..))
//第一个星-标识任何返回值、第二个星标识任何方法,(..)标识任何参数。
上面的意思是:在UserController类下的任何方法 任何参数 任何返回值都会执行切面
2.在什么时候起作用是使用4个注解来表述的:@After @Before @Around @AfterThrowing
@Around("execution(* com.yxm.security.web.controller.UserController.*(..))")
@Aspect
@Component
public class TimeAspect {
@Around("execution(* com.yxm.security.web.controller.UserController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{
/**
* 切片:pointcut里面包含了所有要执行方法的信息:类名 方法名 方法参数等
*/
System.out.println("time aspect start");
Object[] args = pjp.getArgs();
for (Object arg : args) {
System.out.println("arg is "+arg);
}
long start = new Date().getTime();
Object object = pjp.proceed();//其实就是调用被拦截的方法;类似于Filter中chain.doFilter(request,response)
System.out.println("time aspect 耗时:"+ (new Date().getTime() - start));
System.out.println("time aspect end");
return object;
}
}
App端测试:
后端代码:
执行顺序:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。