5.使用Spring MVC开发RESTful API(三)

1.7 Restful API拦截

咱们对全部的api都须要作一个全局的处理:好比:记录全局处理时间、全局处理日志,就须要用到拦截机制:常见拦截机制分为以下几种:java

  • 过滤器(Filter)
  • 拦截器(Interceptor)
  • 切片(Aspect)

应用场景和实现机制。 咱们以记录全局全部时间为例:web

1.7.1 过滤器(Filter)

新建过滤器:TimeFilterspring

@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后端

服务后台结果:api

time filter doFilter-->start
====DetailInfo=====
time filter 耗时:274
time filter doFilter-->end

有时候咱们使用过滤器时候是使用第三方的过滤器;咱们无法修改里面的代码.这个时候咱们如何去将第三方的过滤器注入到Spring里面(添加不了@Component//注入到spring);app

传统开发模式下,会有一个web.xml文件,咱们将第三方的过滤器添加到此web.xml文件下便可。在SpringBoot下添加没有注解:@Component的类到spring下:框架

咱们定义一个注解修饰的类:此注解修饰的类,可当作 web.xml文件相似(此时咱们须要注释掉TimeFilter的注解:@Component):async

@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拦截。ide

过滤器说明: 1.Filter接受到的request请求 究竟是哪一个控制器的哪一个方法去处理,在Filter里面是不知道的,由于filter是javax.servlet的J2EE规范。J2EE规范里面其实不了解跟Spring相关的任何东西,而UserController是Spring MVC本身定义的东西。post

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

相关文章
相关标签/搜索