概述
最近在开发项目时引用了外部的接口,接口我是以feign的方式实现的,在moudleA中。但是在其他模块moudleB引用并使用了moudleA的Feign接口。所以就必须要将moudleA的@FeignClient注释的接口加入MoudleB的容器中。一下讲述一下实现方式。
假设moudleA有以下接口:
@FeignClient(name = TestCommon.SERVICE_NAME, path = TestCommon.SERVICE_BASE_BATH)
public interface TestClient{
@GetMapping("/get")
BaseResult<String> get(@RequestParam("id") String id);
}
第一种:
在moudleB的启动类中加入 @EnableFeignClients(basePackages = {"包路径a.*","包路径2.*"})注解:
@EnableFeignClients(basePackages = {"包路径a.*","包路径2.*"})
@SpringBootApplication
public class ServerTestApp {
public static void main( String[] args){
SpringApplication app = new SpringApplication(ServerTestApp .class);
app.run(args);
}
}
第二种;
以上的方式都需要moudleB主动去修改自己的代码。下面这种是,moudleA自己将类注入到是容器中。
@FeignClient(name = "testClient", path = TestCommon.SERVICE_BASE_BATH)
public interface TestClient{
@GetMapping("/get")
BaseResult<String> get(@RequestParam("id") String id);
}
这里的name需要配置成唯一的,不再代表注册中心的服务标识。
@Configuration
public class FeignClientBeanConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean(name = "testClient")
public TestClient testClient(){
FeignClientBuilder builder = new FeignClientBuilder(applicationContext);
FeignClient annotation = TestClient.class.getAnnotation(FeignClient.class);
return builder.forType(TestClient.class, annotation.name()).url(annotation.url()).build();
}
}
这种方法需要在spring扫描时,要将FeignClientBeanConfiguration 所在的包扫描到,这个可以在,@SpringBootApplication 和@ComponentScan配置的。这样就可以将TestClient注入mouldeB的容器中,moudleB可以直接使用@Autowired注解获取。
@Autowired
private TestClient;
但是这个方法有一个问题,如果mouldeB自己配置@EnableFeignClients注解,且能扫描到TestClient这就会报错:
Description:
The bean 'AuthFeign.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
所以我们可以按照以上的配置参数。或者使用@ConditionalOnMissingBean(已有的不再注入)和@Primary注解(指定以哪个为主)
@Configuration
public class FeignClientBeanConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean(name = "testClient")
@ConditionalOnMissingBean(TestClient.class)
public TestClient testClient(){
FeignClientBuilder builder = new FeignClientBuilder(applicationContext);
FeignClient annotation = TestClient.class.getAnnotation(FeignClient.class);
return builder.forType(TestClient.class, annotation.name()).url(annotation.url()).build();
}
}
还有一个问题是,使用这种方式,若我们在这个@FeignClient里配置了configuration参数,这时候这个配置是不生效的。
第三种:
我们还可以构建统一的代理,利用spring的实现创建feign的实现,每次只要传入我们想要的feign的类就能获取到对应的实现。
@Component
public class DynamicFeignClient<T> {
private FeignClientBuilder feignClientBuilder;
public DynamicFeignClient(@Autowired ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
//构建查询实现 FeignClientFactoryBean ,指定要调用的url参数
public T getFeignClient(final Class<T> targetCls, String url) {
boolean hasFeignClientAnnotation = AnnotatedElementUtils.hasAnnotation(targetCls, CoreManageFeignClientAnnotation.class);
boolean feignClientAnnotation = AnnotatedElementUtils.hasAnnotation(targetCls, FeignClient.class);
if(hasFeignClientAnnotation && feignClientAnnotation && StringUtils.isNotEmpty(url)) {
FeignClient feignClient = AnnotatedElementUtils.findMergedAnnotation(targetCls, FeignClient.class);
String serviceId = feignClient.name();
String path = feignClient.path();
Class<? extends T> fallback = (Class<? extends T>)feignClient.fallback();
Class<? extends FallbackFactory<? extends T>> fallbackFactory = (Class<? extends FallbackFactory<? extends T>>)feignClient.fallbackFactory();
return this.feignClientBuilder.forType(targetCls, serviceId)
.url(url).path(path)
.fallback(fallback).fallbackFactory(fallbackFactory)
.build();
} else {
return SpringContextUtils.getBean(targetCls);
}
}
//构建查询实现 FeignClientFactoryBean,这个暂未尝试过
public T getFeignClient(final Class<T> targetCls) {
boolean hasFeignClientAnnotation = AnnotatedElementUtils.hasAnnotation(targetCls, CoreManageFeignClientAnnotation.class);
boolean feignClientAnnotation = AnnotatedElementUtils.hasAnnotation(targetCls, FeignClient.class);
if(hasFeignClientAnnotation && feignClientAnnotation) {
FeignClient feignClient = AnnotatedElementUtils.findMergedAnnotation(targetCls, FeignClient.class);
String serviceId = feignClient.name();
String path = feignClient.path();
String url = feignClient.url();
Class<? extends T> fallback = (Class<? extends T>)feignClient.fallback();
Class<? extends FallbackFactory<? extends T>> fallbackFactory = (Class<? extends FallbackFactory<? extends T>>)feignClient.fallbackFactory();
return this.feignClientBuilder.forType(targetCls, serviceId)
.url(url).path(path)
.fallback(fallback).fallbackFactory(fallbackFactory)
.build();
} else {
return SpringContextUtils.getBean(targetCls);
}
}
}
使用这种方式,若我们在这个@FeignClient里配置了configuration参数,这时候这个配置是不生效的。