文章目录

  • 参考
  • 用途
  • 配置侧代码
  • 常用拦截器demo
  • 拦截器修改返回结果


参考


用途

在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个),背后是一种责任链的设计模式。
Spring Interceptor是一个非常类似于Servlet Filter 的概念 。

典型用途:

  1. 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
  2. 权限检查:如登录检测,进入处理器检测是否登录;
  3. 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)
  4. 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现。

配置侧代码

配置侧

@Configuration
public class WebConfigure extends WebMvcConfigurerAdapter {
	@Bean
    public ThreadInfoSetInterceptor tenantInterceptor() {
        return new ThreadInfoSetInterceptor();
    }

    @Bean
    public SafeInterceptor safeInterceptor() {
        return new SafeInterceptor();
    }
    
	@Override
    public void addInterceptors(InterceptorRegistry registry) {
	    // 如下的add顺序就是拦截器执行的顺序
        registry.addInterceptor(new SafeInterceptor());   
		// 设置适用于那些URL不适用于哪些
        registry.addInterceptor(threadInfoSetInterceptor).addPathPatterns(URL_NEED_INFO).excludePathPatterns(URL_NOT_NEED_INFO);
        registry.addInterceptor(signCheckInterceptor);

    }
}

常用拦截器demo

需要自定义 Interceptor 的话必须实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且需要重写下面下面 3 个方法:

  1. preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在进入Controller层之前被调用,可以在这个方法里进行前置初始化、对当前请求做预处理、进行token或者签名校验。
    返回 Boolean 类型。返回 false 时,表示请求结束,直接抛出异常;返回 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。
  2. postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
  3. afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex) ,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。此方法主要用来进行资源清理。

具体实例:LogID注入,用于一个请求涉及到多个微服务调用时,各个微服务记录日志时用这个透传的统一的日志ID

// 设置为执行的最高优先级
@Order(Ordered.HIGHEST_PRECEDENCE)
public class LogIDInjectHandlerInterceptor extends HandlerInterceptorAdapter {

	private static final Logger log = LoggerFactory.getLogger(LogIDInjectHandlerInterceptor.class);
	// 作为唯一标志放入到request中,key即为类名,value为生成的日志ID
	private static final String ATTRIBUTE = LogIDInjectHandlerInterceptor.class.getName();

	@Override
	public boolean preHandle(
			javax.servlet.http.HttpServletRequest request,
			javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {

		if (null != request.getAttribute(ATTRIBUTE)) {
			// 已经注入logID,透传即可
			return true;
		} else {
			// 生成唯一性标志
			String requestID = UniqueIDUtil.generateRequestID();
			UniqueIDUtil.setCurrentRequestID(requestID);
			// logback的map中放入k、v
			MDC.put(MDCKeys.LOG_ID, requestID);
			return true;
		}
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		UniqueIDUtil.setCurrentRequestID(null);
	}
}


// UniqueIDUtil中的生成方法
	private static final String PATTERN_FORMAT = "yyyyMMddHHmmss%sSSS", DEFAULT_IP = "000000000000";
	private static final Random RANDOM = new Random();
	private static final String LOCAL_IP = getLocalIP();
	private static final String REAL_PATTERN = String.format(PATTERN_FORMAT, LOCAL_IP);
	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(REAL_PATTERN);
	private static final ThreadLocal<String> CURRENT_REQUEST_ID = new ThreadLocal<>();
	
	public static String generateRequestID() {
		return LocalDateTime.now().format(FORMATTER) + getRandomHexString();
	}

	public static String getRandomHexString() {
		int rn = RANDOM.nextInt(), h16 = 0xffff0000 & rn, l16 = 0xffffff & rn;
		return Integer.toHexString((0xffff & (h16 ^ l16)) | 0x8000).toUpperCase();
	}

一个请求header方面的配置

public class SafeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse response, Object o) throws Exception {
        //添加跨域CORS
        response.addHeader("Access-Control-Max-Age", "1800");//30 min
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception{

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }


}

线程变量注入拦截器

@Slf4j
public class ThreadInfoSetInterceptor extends HandlerInterceptorAdapter {

    /**
     * header 中语言key
     */
    private static final String HEADER_LANGUAGE = "accept-language";

    @Autowired
    private ResponseUtil responseUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 设置language,便于国际化
        ThreadLocalCache.set(ContextConstant.LANGUAGE, getLanguageEnum(request));
        // 设置logID
        response.setHeader("logId", RequestUtil.getCurrentRequestID());

        String user = request.getHeader(USER);
        if (user == null) {
        	// 通过修改注入的response,最终修改对外的返回结果
            responseUtil.noAuthResponse(response, ResponeCode.PARAM_ILLEGAL,ResponeCode.PARAM_ILLEGAL);
            return false;
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {                                	
        // 移除所有变量
        ThreadLocalCache.remove(USER);
        ThreadLocalCache.remove(EMPLOYEE_ID);
        ThreadLocalCache.clear();
        super.afterCompletion(request, response, handler, ex);
    }
}

限流器拦截器参考另一篇文章

拦截器修改返回结果

使用参考ThreadIfnfoSet拦截器

@Component
public class ResponseUtil {
    public void noAuthResponse(HttpServletResponse response, String msgKey, int code) throws IOException {
        Response httpResult = new Response();
        httpResult.setCode(code);
        // 修改返回信息
        httpResult.setMessage("systemError"));
        if(response instanceof ResponseWrapper){
            HttpServletResponse httpServletResponse=((ResponseWrapper) response).getHttpServletResponseHolder();
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON.toString());
            httpServletResponse.setStatus(HttpServletResponse.SC_OK);
            httpServletResponse.getWriter().write(JSON.toJSONString(httpResult));
        }

    }
}