SpringBoot2.* 配置拦截器(Interceptor) 并 集成swagger2 3.0版本

2023-11-17

1 Interceptor 简介

拦截器在Web系统中非常常见,对于某些全局统一的操作,我们可以把它提取到拦截器中实现。
在 Spring MVC 中使用接口 HandlerInterceptor 表示,包含三个方法:

- preHandle()	在请求处理之前执行,允许您在请求被传递给控制器之前进行一些预处理操作,
  比如鉴权、日志记录等。如果返回true,则继续执行请求处理链;如果返回false,则中断请求处理。
- postHandle()	在请求处理之后、视图渲染之前执行。您可以在这里对模型和视图进行操作,
  但不能改变视图。通常用于修改响应内容、添加额外的模型数据等。
- afterCompletion()	在整个请求完成后执行,包括视图渲染。
  可以用于清理资源、记录请求处理时间等操作。

三个方法都有的 handler参数 表示处理器,通常情况下可以表示我们使用注解 @Controller 
定义的控制器。

一个请求完整的流程图 及 Interceptor的执行顺序:

注意:

自从前后端分离之后,Spring MVC 中的处理器方法执行后通常不会再返回视图,而是返回表示 json 或 xml 的对象,@Controller 方法返回值类型如果为 ResponseEntity 或标注了 @ResponseBody 注解,此时处理器方法一旦执行结束,Spring 将使用 HandlerMethodReturnValueHandler 对返回值进行处理,会将返回值转换为 json 或 xml,然后写入响应,后续也不会进行视图渲染,这时postHandle 将没有机会修改响应体内容。

如果需要更改响应内容,可以定义一个实现 ResponseBodyAdvice 接口的类,然后将这个类直接定义到 RequestMappingHandlerAdapter 中的 requestResponseBodyAdvice 或通过 @ControllerAdvice 注解添加到 RequestMappingHandlerAdapter。

那么拦截器的顺序是如何指定的呢?

对于 xml 配置来说,Spring 将记录 bean 声明的顺序,先声明的拦截器将排在前面。 对于注解配置来说,由于通过反射读取方法无法保证顺序,因此需要在方法上添加@Order注解指定 bean 的声明顺序。 对应API配置来说,拦截器的顺序并非和添加顺序完全保持一致,为了控制先后顺序,需要自定义的拦截器实现Ordered接口。

注解配置指定顺序示例如下:

@Configuration
public class MvcConfig {
	@Order(2)
	@Bean
	public MappedInterceptor loginInterceptor() {
	    return new MappedInterceptor(
							new String[]{"/**"}, new String[]{"/login"}, 
							new LoginInterceptor()
							);
	}
	
	@Order(1)
	@Bean
	public MappedInterceptor logInterceptor() {
	    return new MappedInterceptor(null, new LoginInterceptor());
	}

}

此时虽然登录拦截器写在前面,但因为 @Order 注解指定的值较大,因此将排在日志拦截器的后面。

API配置指定顺序示例如下:

public class LoginInterceptor implements HandlerInterceptor, Ordered {
	@Override
	public boolean preHandle(HttpServletRequest request, 
														HttpServletResponse response, 
														Object handler) throws Exception {
	    System.out.println("已登录");
	    return true;
	}
	
	@Override
	public int getOrder() {
	    return 2;
	}
}

public class LogInterceptor implements HandlerInterceptor, Ordered {
		@Override
		public boolean preHandle(HttpServletRequest request, 
															HttpServletResponse response, 
															Object handler) throws Exception {
		    System.out.println("请求来了");
		    return true;
		}
		
		@Override
		public int getOrder() {
		    return 1;
		}
}

LogInterceptor 指定的排序号较 LoginInterceptor 来说比较小,因此 LogInterceptor 将排在前面。

  

2 Interceptor 应用场景

过滤器应用场景

- 登录验证,判断用户是否登录;

- 权限验证,判断用户是否有权限访问资源,如校验token;

- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量;

- 处理cookie、本地化、国际化、主题等;

- 性能监控,监控请求处理时长等;

- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现)

拦截器应用场景

- 字符编码设置;

- 响应数据压缩;

- URL级别的权限访问控制

- 过滤敏感词汇(防止sql注入)

3 Interceptor 与Filter(过滤器)的区别

过滤器拦截器 都是 AOP的编程思想 的一种体现,用来解决项目中某一类问题的两种接口(工具),都可以对请求做一些增强。
过滤器配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,
看到Filter 接口中定义了三个方法:
- init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。
  **注意:**这个方法必须执行成功,否则过滤器会不起作用。
- doFilter() :容器中的每一次请求都会调用该方法,FilterChain 用来调用下一个过滤器Filter。
- destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,
  在过滤器 Filter 的整个生命周期也只会被调用一次。
@Component
public class MyFilter implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 处理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

        System.out.println("Filter 后置");
    }
}

3.1 实现原理不同


过滤器 是基于 函数回调 实现的;拦截器 是基于Java的 **反射机制(动态代理)**实现的。

重点说下过滤器:

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,
而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 
doFilter() 方法就是回调方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

 

ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行
filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain
的doFilter() 方法,以此循环执行实现函数回调。

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    filterChain.doFilter(servletRequest, servletResponse);
}

3.2 使用范围不同

过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。

 

拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。

3.3 触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图:

 

4 自定义Interceptor 实现方式

4.1 实现 HandlerInterceptor接口

// 最常见实现方式
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
			// ...
}

4.2 继承 HandlerInterceptorAdapter类


HandlerInterceptorAdapter 是 HandlerInterceptor接口 的适配器类,通过继承它可以实现拦截器。

4.3 使用 @Component注解 实现自动注册

@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {
			// ...
}

注: 方式一、方式二均需要手动 将自定义好的拦截器处理类进行注册。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Bean
    public AuthInterceptor authInterceptor() {
        return new AuthInterceptor();
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //定义 放行路径
        String[] swaggerExcludes = new String[]{
        "/login","/swagger**/**","/webjars/**","/v3/**","/doc.html"
        };

        // 多个拦截器组成一个拦截器链
        registry.addInterceptor(authInterceptor())
        .addPathPatterns("/**")// addPathPatterns 添加拦截规则,/**表示拦截所有请求
        .excludePathPatterns(swaggerExcludes);// excludePathPatterns 排除拦截

    }

    //springboot2.x 静态资源在自定义拦截器之后无法访问的解决方案
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
    }
}

 

5 SpringBoot 2. 配置 Interceptor(拦截器) 并 集成 Swagger2 3.0*

5.1 引入依赖

implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'

5.2 Swagger 配置

@Slf4j
@EnableOpenApi
@Configuration
public class SwaggerConfig {
    /**
     *    是否开启swagger,正式环境一般是需要关闭的,可根据springboot的多环境配置进行设置
     */
    @Value("${swagger.enabled:false}")
    Boolean swaggerEnabled;

    @Bean
    public Docket createRestApi() {

        return new Docket(DocumentationType.OAS_30) // V3
                // 是否开启
                .enable(swaggerEnabled)
                .apiInfo(apiInfo())
                .select()
                // 扫描的路径包
                .apis(RequestHandlerSelectors.basePackage("com.baseframe.core.controller"))
                // 指定路径处理PathSelectors.any()代表所有的路径
                .paths(PathSelectors.any())
                .build()
                .pathMapping("/");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("BaseFrame 接口文档")
                .description("BaseFrame Restful 接口")
                .contact(new Contact("zhuzs", "<https://xxx.com>", "XXX@qq.com"))
                .version("1.0.0")
                .build();
    }
}

5.3 自定义拦截器

@Slf4j
public class AuthInterceptor implements HandlerInterceptor, Ordered {
    @Autowired
    private AuthService authService;
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        log.info("开始验证权限...");
        String token = TokenUtil.getRequestToken(request);
        //如果token为空
        if (StringUtils.isBlank(token)) {
            setResponse(response,400,"用户未登录,请先登录");
            return false;
        }
        //1. 根据token,查询用户信息
        UserEntity userEntity = authService.findByToken(token);
        //2. 若用户不存在,
        if (userEntity == null) {
            setResponse(response,400,"用户不存在");
            return false;
        }
        //3. token失效
        if (userEntity.getExpireTime().isBefore(LocalDateTime.now())) {
            setResponse(response,400,"用户登录凭证已失效,请重新登录");
            return false;
        }
 
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
 
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
 
    }
    //返回错误信息
    private static void setResponse(HttpServletResponse response, int status, String msg) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
        //UTF-8编码
        httpResponse.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        Result build = Result.build(status, msg);
        String json = JSON.toJSONString(build);
        httpResponse.getWriter().print(json);
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

5.4 将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Bean
    public AuthInterceptor authInterceptor() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //定义 放行路径
        String[] swaggerExcludes = new String[]{
                "/login",
                "/swagger**/**",
                "/webjars/**",
                "/v3/**",
                "/doc.html"
        };

        // 多个拦截器组成一个拦截器链
        // addPathPatterns 用于添加拦截规则,/**表示拦截所有请求
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(authInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns(swaggerExcludes);

    }

    //springboot2.x 静态资源在自定义拦截器之后无法访问的解决方案
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

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

参考文章:

Spring MVC 系列之拦截器 Interceptor 最全总结_springmvc interceptor_大鹏cool的博客-CSDN博客 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot2.* 配置拦截器(Interceptor) 并 集成swagger2 3.0版本 的相关文章

随机推荐