一、SpringBoot常用拦截器

下面3种拦截器,都是http拦截器,在处理业务逻辑之前对http请求信息进行处理,比如获取请求头,请求参数,设置请求头,请求参数等等

思路清晰,先说jar包:

  • HandlerInterceptor—>spring-webmvc项目,org.springframework.web.servlet.HandlerInterceptor
  • ClientHttpRequestInterceptor—>spring-web项目,org.springframework.http.client.ClientHttpRequestInterceptor
  • RequestInterceptor—>feign-core项目,feign.RequestInterceptor

一目了然,从项目名称和包路径可以看出,3个拦截器分别属于3个不同的项目,所以他们之前的作用也有区别,在这里我大概讲一下3个拦截器的基本应用和区别:

3个拦截器的共同点,都是对http请求进行拦截,但是http请求的来源不同:

  • HandlerInterceptor是最常规的,其拦截的http请求是来自于客户端浏览器之类的,是最常见的http请求拦截器;
  • ClientHttpRequestInterceptor是对RestTemplate的请求进行拦截的,在项目中直接使用restTemplate.getForObject的时候,会对这种请求进行拦截,经常被称为:RestTempalte拦截器或者Ribbon拦截器;
  • RequestInterceptor常被称为是Feign拦截器,由于Feign调用底层实际上还是http调用,因此也是一个http拦截器,在项目中使用Feign调用的时候,可以使用此拦截器;

二、使用及说明

2.1 HandlerInterceptor

从包路径可以看出,这个是处理客户端http servlet请求的,此项目spring-webmvc与spring-mvc项目关闭密切,HandlerInterceptor可以对请求的各个阶段进行拦截,可以说是非常全面了。这个也是常规项目中用的最多的,对http请求进行拦截

public interface HandlerInterceptor {


    /**前置处理:在业务处理器处理请求之前被调用*/
	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception;

	/**中置处理:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView ,现在这个很少使用了*/
	void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception;

    /**后置处理:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等*/
	void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception;

注意:只有perHandle方法返回true的时候,postHandle 和 afterCompletion 方法才会执行,如果业务不需要,就不用重写postHandle 和 afterCompletion 方法了

preHandle

调用时间:Controller方法处理之前

执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序一个接一个执行

若返回false,则中断执行,注意:不会进入afterCompletion

postHandle

调用前提:preHandle返回true

调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作

执行顺序:链式Intercepter情况下,Intercepter按照声明的顺序倒着执行。

备注:postHandle虽然post打头,但post、get方法都能处理

afterCompletion

调用前提:preHandle返回true

调用时间:DispatcherServlet进行视图的渲染之后

多用于清理资源

因为HandlerInterceptorAdapter实现了HandlerInterceptor接口,下面是使用示例


public class JWTInterceptor implements HandlerInterceptor {


    // 重写preHandle方法,在请求发生前执行,此处对每个请求的token进行校验
    // 但是登陆的时候没有token就不能对登陆的接口进行拦截,所以要设置自定义的拦截规则

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, Object> map = new HashMap<>();
        String token = request.getHeader("token");  //从request中获取到请求头中的token,进行解析校验
        try {
            jwtUtil.verifyToken(token);//调用token解析的工具类进行解析
            return true;  //请求放行
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "签名不一致异常");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "令牌过期异常");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "算法不匹配异常");
        } catch (InvalidClaimException e) {
            e.printStackTrace();
            map.put("msg", "失效的payload异常");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效");
        }
        //map异常的数据要返回给客户端需要转换成json格式  @ResponseBody 内置了jackson
        String resultJson = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(resultJson);
        return false;  //异常不放行
    }

    // 当preHandle方法返回值为true的时候才会执行。
    // 重写postHandle方法,在请求完成后执行。
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle执行了");
    }

    // 当preHandle方法返回值为true的时候才会执行。
    // 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion执行了");
    }
}


上边定义的将会在每个方法执行的时候都进行拦截,我们可以自定义拦截规格,拦截那些路径,不拦截哪些路径,需要自定义拦截规则:

//配置拦截条件
@Configuration
public class interceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //参数为我们自定义类,实现了HandlerInterceptor接口重写了三个方法
        
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/interceptorVerify/**")   //拦截所有的路径
                .excludePathPatterns("/login/**"); //放行login目录下的,因为生成token拦截就无法生成了
    }
}

参考:https://blog.csdn.net/weixin_46649054/article/details/118355986

2.2 ClientHttpRequestInterceptor

public interface ClientHttpRequestInterceptor {

	/**只有这一个方法,在项目中直接使用 restTemplate.getForObject 的时候,会对这种请求进行拦截*/
   ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
         throws IOException;

使用示例(SESSIONID可以从RequestContextHolder中拿到)

public class RestClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        HttpHeaders headers = request.getHeaders();
        headers.add("Cookie","SESSIONID=b8dd5bd9-9fb7-48cb-a86b-e079cb554fb8");
        log.info("拦截器已添加header");
        return execution.execute(request,body);
    }
}

配置config类(此处注入bean时需要给方法起个名称,注意不要使用restTemplate,否则使用时不会进入该方法中)

@Slf4j
@Configuration
public class RestTemplateCrsConfig {

    @Bean(name = "restTemplateToken")
    public RestTemplate restTemplate(HeaderRequestInterceptor  headerRequestInterceptor) {
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(headerRequestInterceptor);
        RestTemplate restTemplate = new RestTemplate();
        //统一加token
        restTemplate.setInterceptors(interceptors);

        return restTemplate;
    }
}

引用注入的RestTemplate(此处注意,引用时名称和注入式名称一致,例如注入为restTemplateToken,则类名也为restTemplateToken,如下代码),此时调用时就会将自定义的token值放入header中一并传给接收方,接收方根据token进行鉴权。

	@Autowired
    private RestTemplate restTemplateToken;```
 public void updater(String projectId, String ame) {
     String url = 127.0.0.1:8080/test;
        try {
            restTemplateCrs.setErrorHandler(new RestTemplateErrorHandler());
            ResponseEntity<ResultModel> response = restTemplateCrs.postForEntity(url, name, ResultModel.class);
            ResultModel body = response.getBody();

2.3 RequestInterceptor

public interface RequestInterceptor {

  /**在项目中使用Feign调用的时候,可以使用此拦截器*/
  void apply(RequestTemplate template);
}

使用示例,实现登录用户信息在微服务之间的传递
参考:https://blog.csdn.net/liuerchong/article/details/123765305

实现思路:

1:准备一个ThreadLocal变量,供线程之间共享。

2:每个微服务对所有过来的Feign调用进行过滤,然后从请求头中获取User用户信息,并存在ThreadLocal变量中。

3:每个微服务在使用FeignClient调用别的微服务时,先从ThreadLocal里面取出user信息,并放在request的请求头中。

4:封装为一个注解,在启动类上标记即可。

1:ThreadLocal工具类 :UserInfoContext

public class UserInfoContext {
    private static ThreadLocal<UserInfo> userInfo = new ThreadLocal<UserInfo>();
    public static String KEY_USERINFO_IN_HTTP_HEADER = "X-AUTO-FP-USERINFO";

    public UserInfoContext() {
    }

    public static UserInfo getUser(){
        return (UserInfo)userInfo.get();
    }

    public static void setUser(UserInfo user){
        userInfo.set(user);
    }
}

2:准备承载用户信息的userInfo实体类
编写拦截器 : TransmitUserInfoFeighClientIntercepter

public class TransmitUserInfoFeighClientIntercepter implements RequestInterceptor {

    private static final Logger log = LoggerFactory.getLogger(TransmitUserInfoFeighClientIntercepter.class);
    public TransmitUserInfoFeighClientIntercepter() {
    }

    @Override
    public void apply(RequestTemplate requestTemplate) {
        //从应用上下文中取出user信息,放入Feign的请求头中
        UserInfo user = UserInfoContext.getUser();
        if (user != null) {
            try {
                String userJson = JSON.toJSONString(user);
                requestTemplate.header("KEY_USERINFO_IN_HTTP_HEADER",new String[]{URLDecoder.decode(userJson,"UTF-8")});
            } catch (UnsupportedEncodingException e) {
                log.error("用户信息设置错误",e);
            }
        }
    }
}

4:编写过滤器:TransmitUserInfoFilter

public class TransmitUserInfoFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(TransmitUserInfoFeighClientIntercepter.class);
    public TransmitUserInfoFilter() {
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       this.initUserInfo((HttpServletRequest)request);
       chain.doFilter(request,response);
    }

    private void initUserInfo(HttpServletRequest request){
        String userJson = request.getHeader("KEY_USERINFO_IN_HTTP_HEADER");
        if (StringUtils.isNotBlank(userJson)) {
            try {
                userJson = URLDecoder.decode(userJson,"UTF-8");
                UserInfo userInfo = (UserInfo) JSON.parseObject(userJson,UserInfo.class);
                //将UserInfo放入上下文中
                UserInfoContext.setUser(userInfo);
            } catch (UnsupportedEncodingException e) {
               log.error("init userInfo error",e);
            }
        }
    }

    @Override
    public void destroy() {
    }
}

5:编写注解实现类: EnableUserInfoTransmitterAutoConfiguration

@Configuration
public class EnableUserInfoTransmitterAutoConfiguration {

    public EnableUserInfoTransmitterAutoConfiguration() {
    }

    @Bean
    public TransmitUserInfoFeighClientIntercepter transmitUserInfo2FeighHttpHeader(){
       return new TransmitUserInfoFeighClientIntercepter();
    }

    @Bean
    public TransmitUserInfoFilter transmitUserInfoFromHttpHeader(){
        return new TransmitUserInfoFilter();
    }
}

编写注解 EnableUserInfoTransmitter

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({EnableUserInfoTransmitterAutoConfiguration.class})
public @interface EnableUserInfoTransmitter {
}

在启动类上标记注解即可使用


@SpringBootApplication

@EnableUserInfoTransmitter
public class TestCommonClient {

    public static void main(String[] args){

        SpringApplication.run(TestCommonClient.class,args);
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐