项目背景:采用Maven分模块开发,父模块Parent,其子模块Common、Backend、Mobile,其中Backend、Mobile模块引入Common模块,此时Common模块相当于是第三方jar包,Backend、Mobile模块引入了这个jar包,只不过这所谓的第三方jar包我是可以修改的。我在项目中需要使用Http调用其他服务的接口,我是用OpenFeign组件进行Http调用,OpenFeign的使用在此不做过多解释,我在Backend模块定义一个OrderServiceApi接口,作为调用中间接口。
OrderServiceApi
@FeignClient(name = "orderServiceApi", url = "http://127.0.0.1:8000/order-service/")
public interface OrderServiceApi{
@PostMapping(value = "/innerapi/order/get", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
BaseResponse getOrderById(String id, @RequestHeader("SecToken") String token);
}
在开发过程中我发现在Mobile模块中也同样需要调用order-service服务的接口获取数据,因此也需要上述的OrderServiceApi接口,但是在Mobile模块再定义这个接口会造成冗余,不符合通用原则,因此我想到将这个接口放到Common模块中,这样Backend和Mobile模块都可以使用。
我将OrderServiceApi接口抽到Common模块下的com.fkp.api包下,内容不变,在Backend模块下引入这个接口后使用@Resource从容器中获取对象,因为是接口实际注入的是代理对象,因为启动类在Backend模块下,因此我想通过Common模块下的spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fkp.api.OrderServiceApi将其代理对象注入容器,但启动时报错,因为是接口不能注入容器。
定义创建FeignClient接口的代理对象的AutoConfiguration对象
@Configuration
public class FeignClientConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean(name = "orderServiceApi")
@ConditionalOnMissingBean
public OrderServiceApi orderServiceApi(){
FeignClientBuilder builder = new FeignClientBuilder(applicationContext);
FeignClient annotation = OrderServiceApi.class.getAnnotation(FeignClient.class);
return builder.forType(OrderServiceApi.class, annotation.name()).url(annotation.url()).build();
}
}
通过spring.factories文件将其加载到容器中,并自动将该类中符合条件的bean注入容器
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.kms.common.core.operation.config.FeignClientConfiguration
此时在Backend、Mobile模块中就可以正常的从容器中拿到该对象。
原来通过方法一解决了该问题,此事告一段落,但心中一直有一个疑问,就是为什么在Backend或Mobile模块下定义了api接口直接就可以通过@Resource注解从容器中获取其代理对象,而放到Common模块下就无法自动创建代理对象并注入容器了呢。
通过阅读部分源码得知在Backend或Mobile模块的启动类上有@EnableFeignClients注解,而springboot项目启动时因为OpenFeign的AutoConfiguration中会自动加载相关组件,在将api接口生成代理对象并注入容器的过程中默认会扫描启动类所在包及其子包下的带有@FeignClient注解的类并生成器代理对象加入到容器中,这也就是为什么api接口在Backend或Mobile模块下无需配置就可以从容器中获取该接口的代理对象。
然而在Common中定义该接口,@EnableFeignClients注解默认不会扫描Common模块下的com.fkp.api包,也就是api接口所在的包,进而无访将接口生成代理对象注入容器,通过阅读源码,找到其在扫描符合类型(也就是加有@FeignClient注解)的类时,会读取@EnableFeignClients注解上的basePackages属性,此属性是一个数组,代表扫描的包路径,在加载组件时会遍历这个数组,扫描该包下带有@FeignClient注解的类并加到带注册的集合中。这样就解决了问题。
@EnableFeignClients注解还有一个属性时clients,该属性和basePackages属性是互斥的,因为在寻找符合条件的类时,首先判断clients是否为空,若不为空则只将clients指定的类加入到带注册的集合,若为空才会根据basePackages配置的包去扫描符合条件的类。