Spring Interceptor 自动注入导致循环依赖

3,972 阅读4分钟

1,bug现场还原

循环依赖

1.1,在拦截器配置类,通过构造器方式,依赖拦截器,重写addInterceptors函数把拦截器注入到拦截器链中。


1.2,而拦截器中也是通过构造器方式,依赖一个thirdAuthClient


1.3,thirdAuthClient(一个feign客户端接口)


项目启动报错,显示这三个类循环依赖

2,bug分析

2.1假说

看报错,前两个依赖能理解,但是ThirdAuthClient依赖ThirdInterceptorConfig无法理解,这只是一个@FeignClient,应该是在spring启动之后加载。看类图关系也看不出结果,初步怀疑是跟spring bean加载顺序有关系,虽然看到的是循环依赖,但实际可能是用错了加载方式。


2.2梳理

首先关注Spring MVC Interceptor的注入方式


这是官方文档中拦截器的注册方式,直接new出来的。那么关注点就变成了:

为什么Interceptor不可以通过@Autowired方式或者构造器进行注入?

搜了一下,看到这样的说法:

1,SpringBoot项目的Bean装配默认规则是根据Application类所在的包( com.boot.app )位置从上往下扫描

2, 拦截器执行在自动bean初始化之前

这个说法和假说一致,继续思考:拦截器是否确实执行在自动bean初始化之前

在我的使用场景里,我觉得正确的顺序应该是

  1. 拦截器的所有依赖(比如ThirdAuthClient)被初始化bean并注入Spring容器

  2. 执行拦截器注入

  3. 初始化拦截器bean对象,此时需要1中已经存在的bean对象作为拦截器的依赖项

  4. 拦截器注入成功

如果这样想,那么说法2和这个想法不一致,或者说,官方文档中定义的拦截器是常规自定义,不需要依赖,直接new就行了,而我自定义的拦截器通过构造器,显示地声明了依赖,可能是这种区别导致了循环依赖。

又找了一下构造器的其他注入方式,如下


可以通过@Bean注入的方式,那么造成拦截器循环依赖的ThirdAuthClient通过@Autowired注入,其他类通过构造器注入

这样在执行我想法中的顺序2的时候,顺序1所需要的bean就准备好了。

3,分析

首先根据Spring加载及实例化Bean的顺序,如下


循环引用报错应该是Spring在递归解析的时候检测出来的,并且跟我写的构造器方式初始化bean有关系。在网上看到的拦截器的注入方式基本都是手动创建的。

那么这里明确的疑问是:

1,拦截器是否只能手动创建(new出来)?不可以让Spring去管理对象并创建吗?

分析:这个加载过程涉及到了@ConfigurationProperties@Component@FeignClient@Configuration@Component这些注解

@ConfigurationProperties注解属于Spring配置类注解,最先被加载

@FeignClient注解,由FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar,通过registerBeanDefinitions方法向容器中注入 @FeignClient注解的接口

@Configuration@Component,属于配置类,Spring自动加载

根据Spring加载Bean的逻辑,这些注解都在Spring的可见范围内,是可以通过递归解析来分析这些实例的依赖关系,并在合适的时机实例化。

这么分析的话,问题可能是出在构造器上,构造器的显示声明,替代了Spring在解析这些实例的时候所依据的递归解析方式,造成了这些实例不能被依赖解析所解决,如果不使用构造器,那么我觉得这些注解所声明的类是可以放在Spring的依赖解析来处理的。

4,实现

1,手动创建拦截器实例实现

拦截器不加任何自动配置注解,防止Spring自动装配和初始化,并通过构造器显示声明依赖


拦截器配置类通过构造器和@Autowired方式,准备好拦截器需要的bean


执行顺序3的时候,通过@Bean方式手动创建拦截器实例,把拦截器的初始化交给Spring。

这是没更改构造器方式去注入的,实践可行。

2,让Spring容器管理拦截器创建



这是去除了构造器,用注解来替代手动创建拦截器实例,实践可行。

5,结论

拦截器创建可以不通过手动创建,而是交给Spring的容器管理来管理。

参考文档

www.cnblogs.com/shamo89/p/8…

stackoverflow.com/questions/2…