文章目录
- 参考
- 用途
- 配置侧代码
- 常用拦截器demo
- 拦截器修改返回结果
参考
用途
在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个),背后是一种责任链的设计模式。
Spring Interceptor是一个非常类似于Servlet Filter 的概念 。
典型用途:
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等;
- 权限检查:如登录检测,进入处理器检测是否登录;
- 性能监控:通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间。(反向代理,如 Apache 也可以自动记录)
- 通用行为:读取 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 个方法:
- preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) 方法在进入Controller层之前被调用,可以在这个方法里进行前置初始化、对当前请求做预处理、进行token或者签名校验。
返回 Boolean 类型。返回 false 时,表示请求结束,直接抛出异常;返回 true 时会继续调用下一个 Interceptor 的 preHandle 方法,如果已经是最后一个 Interceptor 的时候就会调用当前请求的 Controller 方法。 - postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 方法在当前请求处理完成之后,也就是 Controller 方法调用之后执行,会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作。
- 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));
}
}
}