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接口:
25.png

服务后台结果:

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端测试:
26.png

后端打印日志:
27.png

我们修改用户详情接口:抛出一个异常

@GetMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)
    public User DetailInfo(@PathVariable(name = "id") String xxx){
        throw new UserNotExistException();
    }

App端请求:
28.png

后端打印出:
29.png

发现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.如果除了记录类和方法时候还需要知道具体参数内容,就需要使用切片:
需要满足:切入点 增强
30.png

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端测试:
31.png

后端代码:
32.png

执行顺序:
33.png


startshineye
91 声望26 粉丝

我在规定的时间内,做到了我计划的事情;我自己也变得自信了,对于外界的人跟困难也更加从容了,我已经很强大了。可是如果我在规定时间内,我只有3分钟热度,哎,我不行,我就放弃了,那么这个就是我自己的问题,因为你自己...