Hi,
This seems to be a bug, given that this is suppose to be supported as of #288.
I am currently using Spring Boot 2.0.0.RC2 with Spring Cloud Finchley.M7.
I have two Feign clients, each with a custom Feign Configuration class adding a custom RequestInterceptor. One RequestInterceptor adds a header for auth and another is an expensive one because it does all the OAuth2 Authorization Code flow.
@FeignClient(name = "gAuthifyApiClient", url = "{gAuthifyUrl}", configuration = GAuthifyApiClient.FeignClientConfiguration.class, decode404 = true)
public interface GAuthifyApiClient {
...
@Configuration("gAuthifyFeignClientConfiguration")
class FeignClientConfiguration {
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(@Value("${gauthify.api.key}") String apiKey) {
return new BasicAuthRequestInterceptor("", apiKey);
}
}
...
}
@FeignClient(name = "profileIdApiClient", url = "{profileIdUrl}", configuration = ProfileIdApiClient.FeignClientConfiguration.class, decode404 = true)
public interface ProfileIdApiClient {
...
@Configuration("profileIdFeignClientConfiguration")
class FeignClientConfiguration {
@Bean
public BearerHeaderAuthRequestInterceptor bearerHeaderAuthRequestInterceptor() {
return new BearerHeaderAuthRequestInterceptor();
}
}
...
class BearerHeaderAuthRequestInterceptor implements RequestInterceptor {
//Expensive OAuth2 flow logic
}
...
}
The problem is that the client that uses the former (header auth) is picking up both interceptors. Hence, when using the first client, both interceptors are triggered.
I did some debugging and it seems it has to do with the hierarchical nature of the FactoryBean used to search for RequestInterceptors for the named factory bean.
More specifically...
FeignClientFactoryBean.configureUsingConfiguration(...) {
...
Map<String, RequestInterceptor> requestInterceptors = **context.getInstances**(this.name, RequestInterceptor.class);
...
}
NamedContextFactory.**getInstances**(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) {
return **BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);**
}
return null;
}
**BeanFactoryUtils.beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type)**
throws BeansException {
Assert.notNull(lbf, "ListableBeanFactory must not be null");
Map<String, T> result = new LinkedHashMap<>(4);
result.putAll(lbf.getBeansOfType(type));
**if (lbf instanceof HierarchicalBeanFactory)** {
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
Map<String, T> parentResult = beansOfTypeIncludingAncestors(
(ListableBeanFactory) hbf.getParentBeanFactory(), type);
parentResult.forEach((beanName, beanType) -> {
if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) {
result.put(beanName, beanType);
}
});
}
}
return result;
}
Any ideas? Is this already supported in the specified version?
Thanks in advance!
Can you try removing the @Configuration annotations? They are not necessary. http://cloud.spring.io/spring-cloud-static/Edgware.SR2/single/spring-cloud.html#spring-cloud-feign-overriding-defaults
Also as an FYI the Feign code has been moved to its own top level project, so future Feign specific issues should be opened in https://github.com/spring-cloud/spring-cloud-openfeign
Thanks @ryanjbaxter. I was just writing a comment about I fixed the issue by removing @Configuration from Feign configuration classes.
Thanks anyway for you promptly reply and for pointing out about where to open issues for this component.
This issue can be deleted or closed.
@ryanjbaxter BTW, looking at the documentation you outlined, it clearly states:
Creating a bean of one of those type and placing it in a @FeignClient configuration (such as FooConfiguration above) allows you to override each one of the beans described. Example:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
The documentation seems to be wrong. That is probably the cause that made me configure it that way.
I re-read it and there is a note about annotating with @Configuration.
FooConfiguration does not need to be annotated with @Configuration. However, if it is, then take care to exclude it from any @ComponentScan that would otherwise include this configuration as it will become the default source for feign.Decoder, feign.Encoder, feign.Contract, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any @ComponentScan or @SpringBootApplication, or it can be explicitly excluded in @ComponentScan.
You were right, I didn't take that into account.
Thanks!
Hello, I am using SpringCloud Finchley.RELEASE with SpringBoot 2.0.3.RELEASE.
I want to custom my Feign Client and add custom RequestInterceptor(ReportExportInterceptor ) . I have read you talk above, and my Feign Client Configuration Class was not annotated with @Configuration. However the interceptor
was not triggered.
When the ReportExportInterceptor annotated with @Configuration , its work.When I add this code , its not work.
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = ReportExportClientService.ReportExportInterceptor.class)})
public class HolderSaasStoreAggregationMerchantApplication {}
So I am not sure what`s wrong with my code. Could you give me some advice? Thank you!
this is my Feign Client code.
`@FeignClient(name = "holder-saas-store-report", configuration = ReportExportClientService.ReportExportInterceptor.class)
public interface ReportExportClientService {
@PostMapping("file/export/{exportType}")
ExportRespDTO export(@PathVariable("exportType") Integer exportType);
/**
* 拦截器为了传递request不确定的content内容
*/
class ReportExportInterceptor {
@Bean("reportInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
String paramString = getParamString(request);
template.body(paramString);
}
public String getParamString(HttpServletRequest request) {
String param = null;
try {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
JSONObject jsonObject = JSONObject.parseObject(responseStrBuilder.toString());
if (jsonObject != null) {
param = jsonObject.toJSONString();
}
} catch (Exception e) {
e.printStackTrace();
}
return param;
}
};
}
}
}
`
Hi.
I can tell you what it worked for me...
My Application
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Notice that SpringBootApplication contains a ComponentScan.excludeFilters.
Feign client
@FeignClient(name = "acmeApiClient", url = "${acme.api.url}", configuration = AcmeApiClient.Configuration.class)
public interface AcmeApiClient {
@GetMapping("/users")
ResponseEntity getUsers();
class Configuration {
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(@Value("${acme.api.key}") String apiKey) {
return new BasicAuthRequestInterceptor("", apiKey);
}
}
I would try removing the @ComponentScan.excludeFilters and adding both annotations, @SpringBootApplication and @EnableFeignClients.
As a side note, I would suggest renaming ReportExportInterceptor to Configuration, as it is a configuration class that configures an RequestInterceptor.
Hope it helps. Please let me know how it went.
Bests
Thank for you reply!
I create a new test project,do as you say.It worked!!!
But I have a great confuse that why it didn`t work on my another project.Any Environmental problems?
Do you have any ideas?
Most helpful comment
Can you try removing the
@Configurationannotations? They are not necessary. http://cloud.spring.io/spring-cloud-static/Edgware.SR2/single/spring-cloud.html#spring-cloud-feign-overriding-defaultsAlso as an FYI the Feign code has been moved to its own top level project, so future Feign specific issues should be opened in https://github.com/spring-cloud/spring-cloud-openfeign