Spring中的攔截器使用

 主要了解SpringBoot中使用攔截器和過濾器的使用,關於兩者,資料所提及的有:

  • 作用域差異:Filter是Servlet規範中規定的,只能用於WEB中,攔截器既可以用於WEB,也可以用於Application、Swing中(即過濾器是依賴於Servlet容器的,和它類似的還有Servlet中的監聽器同樣依賴該容器,而攔截器則不依賴它);
  • 規範差異:Filter是Servlet規範中定義的,是Servlet容器支持的,而攔截器是Spring容器內的,是Spring框架支持的;
  • 資源差異:攔截器是Spring的一個組件,歸Spring管理配置在Spring的文件中,可以使用Spring內的任何資源、對象(可以粗淺的認爲是IOC容器中的Bean對象),而Filter則不能使用訪問這些資源
  • 深度差異:Filter只在Servlet前後起作用,而攔截器可以深入到方法的前後、異常拋出前後等更深層次的程度作處理(這裏也在一定程度上論證了攔截器是利用java的反射機制實現的),所以在Spring框架中,優先使用攔截器;

1. 關於ApplicationListener

 除此之外,還有監聽器,在項目中遇到一個ApplicationListener,在容器初始化完成後,有一些操作需要處理一下,比如數據的加載、初始化緩存、特定任務的註冊等,此時可以使用這個監聽器,下面是使用方式:

// 1. 定義實現類實現ApplicationListener接口
package com.glodon.tot.listener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author liuwg-a
 * @date 2018/11/26 10:48
 * @description 容器初始化後要做的數據處理
 */
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
    private static final Logger logger = LoggerFactory.getLogger(StartupListener.class);

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try {
            logger.info("get local ip is " + InetAddress.getLocalHost().getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
            logger.error("occur a exception!");
        }
    }
}

// 2. 配置上述實現類,返回Bean實例,類似於在xml中配置<bean>標籤
package com.glodon.tot.config;

import com.glodon.tot.listener.StartupListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author liuwg-a
 * @date 2018/11/26 11:10
 * @description 配置監聽器
 */
@Configuration
public class ListenerConfig {
    // 這裏會直接注入
    @Bean
    public StartupListener startupListener() {
        return new StartupListener();
    }
}

主要就是上述配置,其他和普通SpringBoot項目一樣,啓動項目即可,最初啓動(即不主動調用接口)的效果如下:

2018-11-26 11:23:18.360  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.360  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.372  INFO 18792 --- [           main] .m.m.a.ExceptionHandlerExceptionResolver : Detected @ExceptionHandler methods in globalExceptionHandler
2018-11-26 11:23:18.389  INFO 18792 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-11-26 11:23:18.578  INFO 18792 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
welcome to StartupListener...
your ip is 10.4.37.108
2018-11-26 11:23:18.590  INFO 18792 --- [           main] com.glodon.tot.listener.StartupListener  : get local ip is 10.4.37.108
2018-11-26 11:23:18.822  INFO 18792 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2018-11-26 11:23:18.827  INFO 18792 --- [           main] com.glodon.tot.Application               : Started Application in 4.616 seconds (JVM running for 11.46)

在調用接口後,添加了如下日誌:

2018-11-26 11:25:46.785  INFO 18792 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-26 11:25:46.785  INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-26 11:25:46.803  INFO 18792 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms

2. 關於攔截器

 在SpringBoot使用攔截器,流程如下:

// 1. 定義攔截器
package com.glodon.tot.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author liuwg-a
 * @date 2018/11/26 14:55
 * @description 配置攔截器
 */
public class UrlInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(UrlInterceptor.class);

    private static final String GET_ALL = "getAll";
    private static final String GET_HEADER = "getHeader";

    /**
     * 進入Controller層之前攔截請求,默認是攔截所有請求
     * @param httpServletRequest request
     * @param httpServletResponse response
     * @param o object
     * @return 是否攔截當前請求,true表示攔截當前請求,false表示不攔截當前請求
     * @throws Exception 可能出現的異常
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        logger.info("go into preHandle method ... ");
        String requestURI = httpServletRequest.getRequestURI();
        if (requestURI.contains(GET_ALL)) {
            return true;
        }
        if (requestURI.contains(GET_HEADER)) {
            httpServletResponse.sendRedirect("/user/redirect");
        }
        return true;
    }

    /**
     * 處理完請求後但還未渲染試圖之前進行的操作
     * @param httpServletRequest request
     * @param httpServletResponse response
     * @param o object
     * @param modelAndView mv
     * @throws Exception E
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        logger.info("go into postHandle ... ");
    }

    /**
     * 視圖渲染後但還未返回到客戶端時的操作
     * @param httpServletRequest request
     * @param httpServletResponse response
     * @param o object
     * @param e exception
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        logger.info("go into afterCompletion ... ");
    }
}

// 2. 註冊攔截器
package com.glodon.tot.config;

import com.glodon.tot.interceptor.UrlInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


/**
 * @author liuwg-a
 * @date 2018/11/26 15:30
 * @description 配置MVC
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    /**
     * 註冊配置的攔截器
     * @param registry 攔截器註冊器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 這裏的攔截器是new出來的,在Spring框架中可以交給IOC進行依賴注入,直接使用@Autowired注入
        registry.addInterceptor(new UrlInterceptor());
    }

}

主要就是上述的兩個步驟,需要注意的是preHandle()方法只有返回true,Controller中接口方法才能執行,否則不能執行,直接在preHandle()返回後false結束流程。上述在配置WebMvcConfigurer實現類中註冊攔截器時除了使用registry.addInterceptor(new UrlInterceptor())註冊外,還可以指定哪些URL可以應用這個攔截器,如下:

// 使用自動注入的方式注入攔截器,添加應用、或不應用該攔截器的URI(addPathPatterns/excludePathPatterns)
// addPathPatterns 用於添加攔截的規則,excludePathPatterns 用於排除攔截的規則
registry.addInterceptor(urlInterceptor).addPathPatterns(new UrlInterceptor()).excludePathPatterns("/login");

上述註冊攔截器路徑時(即addPathPatternsexcludePathPatterns的參數),是支持通配符的,寫法如下:

通配符 說明
* 匹配單個字符,如/user/*匹配到/user/a等,又如/user/*/ab匹配到/user/p/ab
** 匹配任意多字符(包括多級路徑),如/user/**匹配到user/a/user/abs/po等;

上述也可以混合使用,如/user/po*/**/user/{userId}/*(pathValue是可以和通配符共存的);

注:

  1. Spring boot 2.0 後WebMvcConfigurerAdapter已經過時,所以這裏並不是繼承它,而是繼承WebMvcConfigurer
  2. 這裏在實操時,使用IDEA工具繼承WebMvcConfigurer接口時,使用快捷鍵Alt+Enter已經無論如何沒有提示,進入查看發現這個接口中所有的方法變成了default方法(JDK8新特性,這個修飾符修飾的方法必須要有方法體,此時接口中允許有具體的方法,在實現該接口時,用戶可以選擇是否重寫該方法,而不是必須重寫了),所以沒有提示,可以手動進入接口中複製對應的方法名(不包括default修飾符)。

對於WebMvcConfigurerAdapter接口中的常用方法有如下使用示例,可以選擇性重寫:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private HandlerInterceptor urlInterceptor;

    private static List<String> myPathPatterns = new ArrayList<>();

    /**
     * 在初始化Servlet服務時(在Servlet構造函數執行之後、init()之前執行),@PostConstruct註解的方法被調用
     */
    @PostConstruct
    void init() {
        System.out.println("Servlet init ... ");
        // 添加匹配的規則, /** 表示匹配所有規則,任意路徑
        myPathPatterns.add("/**");
    }

    /**
     * 在卸載Servlet服務時(在Servlet的destroy()方法之前執行),@PreDestroy註解的方法被調用
     */
    @PreDestroy
    void destroy() {
        System.out.println("Servlet destory ... ");
    }

    /**
     * 註冊配置的攔截器
     * @param registry 攔截器註冊器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用於添加攔截規則
        // excludePathPatterns 用戶排除攔截
        registry.addInterceptor(urlInterceptor).addPathPatterns(myPathPatterns).excludePathPatterns("/user/login");
    }

    // 下面的方法可以選擇性重寫
    /**
     * 添加類型轉換器和格式化器
     * @param registry
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
//        registry.addFormatterForFieldType(LocalDate.class, new USLocalDateFormatter());
    }

    /**
     * 跨域支持
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600 * 24);
    }

    /**
     * 添加靜態資源映射--過濾swagger-api
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //過濾swagger
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");

        registry.addResourceHandler("/swagger-resources/**")
                .addResourceLocations("classpath:/META-INF/resources/swagger-resources/");

        registry.addResourceHandler("/swagger/**")
                .addResourceLocations("classpath:/META-INF/resources/swagger*");

        registry.addResourceHandler("/v2/api-docs/**")
                .addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");

    }

    /**
     * 配置消息轉換器--這裏用的是ali的FastJson
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //1. 定義一個convert轉換消息的對象;
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        //2. 添加fastJson的配置信息,比如:是否要格式化返回的json數據;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteDateUseDateFormat);
        //3處理中文亂碼問題
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //4.在convert中添加配置信息.
        fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        //5.將convert添加到converters當中.
        converters.add(fastJsonHttpMessageConverter);
    }


    /**
     * 訪問頁面需要先創建個Controller控制類,再寫方法跳轉到頁面
     * 這裏的配置可實現直接訪問http://localhost:8080/toLogin就跳轉到login.jsp頁面了
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toLogin").setViewName("login");

    }

    /**
     * 開啓默認攔截器可用並指定一個默認攔截器DefaultServletHttpRequestHandler,比如在webroot目錄下的圖片:xx.png,
     * Servelt規範中web根目錄(webroot)下的文件可以直接訪問的,但DispatcherServlet配置了映射路徑是/ ,
     * 幾乎把所有的請求都攔截了,從而導致xx.png訪問不到,這時註冊一個DefaultServletHttpRequestHandler可以解決這個問題。
     * 其實可以理解爲DispatcherServlet破壞了Servlet的一個特性(根目錄下的文件可以直接訪問),DefaultServletHttpRequestHandler
     * 可以幫助迴歸這個特性的
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        // 這裏可以自己指定默認的攔截器
        configurer.enable("DefaultServletHttpRequestHandler");
    }

    /**
     * 在該方法中可以啓用內容裁決解析器,configureContentNegotiation()方法是專門用來配置內容裁決參數的
     * @param configurer
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 表示是否通過請求的Url的擴展名來決定media type
        configurer.favorPathExtension(true)
                // 忽略Accept請求頭
                .ignoreAcceptHeader(true)
                .parameterName("mediaType")
                // 設置默認的mediaType
                .defaultContentType(MediaType.TEXT_HTML)
                // 以.html結尾的請求會被當成MediaType.TEXT_HTML
                .mediaType("html", MediaType.TEXT_HTML)
                // 以.json結尾的請求會被當成MediaType.APPLICATION_JSON
                .mediaType("json", MediaType.APPLICATION_JSON);
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章