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

相關文章
相關標籤/搜索