When using Feign, I implemented an inner controller corresponding to its interface. By separating the Feign interface layer from the business implementation layer, I can ensure that the Feign interface is always available when other services depend on it. Here is the code implementation:
Feign API interface
package cn.shihh.module.notify.api.client;
import cn.shihh.module.common.constant.CommonConstants;
import cn.shihh.module.common.util.R;
import cn.shihh.module.notify.api.pojo.BaseMessage;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(name = "shihh-notify", url = "${shihh.notify.url:swartz-notify}", path = "/notify", contextId = "insideNotifyClient")
public interface InsideNotifyClient {
@PostMapping(CommonConstants.PROVIDER + "/notify/send")
R<Boolean> send(@RequestBody BaseMessage baseMessage);
}
Business implementation
package cn.shihh.module.notify.controller.inner;
import cn.shihh.module.common.util.R;
import cn.shihh.module.notify.api.client.InsideNotifyClient;
import cn.shihh.module.notify.api.pojo.BaseMessage;
import cn.swartz.module.notify.service.NotifyInsideService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class InnerInsideNotifyController implements InsideNotifyClient {
private final NotifyInsideService notifyInsideService;
@Override
public R<Boolean> send(BaseMessage baseMessage) {
return R.ok(notifyInsideService.saveInside(baseMessage));
}
}
This code structure decouples the application and ensures the robustness of the interface. However, it also brings some problems. The business implementation layer depends on the Feign interface implementation, which means that the business service that implements the Feign interface will create an instance of Feign. This is unnecessary for the service and introduces unnecessary complexity.
So, I want to check if the business implementation controller of the Feign interface has already been created when creating the Feign instance. If it has, then the Feign proxy object should not be generated. This way, I can avoid making HTTP requests for internal calls when aggregating services.
Here is the proposed implementation:
CustomerFeignClientsRegistrar.registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
@SneakyThrows
private void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableCustomerFeign.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
ResolvableType resolvableType =
ResolvableType.forRawClass(Class.forName(candidateComponent.getBeanClassName()));
String[] names = ((DefaultListableBeanFactory) registry).getBeanNamesForType(resolvableType);
if (names.length > 0) {
continue;
}
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an " +
"interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
I added the following code to check if the bean class already exists in the Spring context:
ResolvableType resolvableType =
ResolvableType.forRawClass(Class.forName(candidateComponent.getBeanClassName()));
String[] names = ((DefaultListableBeanFactory) registry).getBeanNamesForType(resolvableType);
if (names.length > 0) {
continue;
}
I have tested this approach and confirmed that it works in my project.
However, I need more examples to determine if there are any unknown issues with this approach.
Comment From: OlgaMaciaszek
Hello, @shihaoH. Thanks for creating this issue. As announced during Spring Cloud 2022.0.0 release, we're treating the Spring Cloud OpenFeign project as feature-complete. We are only going to be adding bugfixes and possibly merging some small community feature PRs. We are actively developing Spring Interface Clients instead, so we welcome any new ideas or proposals there (in the Spring Framework repo).