SpringBoot常用拦截器(HandlerInterceptor,ClientHttpRequestInterceptor,RequestInterceptor)
一、SpringBoot常用拦截器下面3种拦截器,都是http拦截器,在处理业务逻辑之前对http请求信息进行处理,比如获取请求头,请求参数,设置请求头,请求参数等等思路清晰,先说jar包:HandlerInterceptor—>spring-webmvc项目,org.springframework.web.servlet.HandlerInterceptorClientHttpReques
一、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);
}
}
更多推荐
所有评论(0)