@ FeignClient injection can't find an exception how to resolve it
- 2021-10-11 18:19:23
- OfStack
Preface
The inter-service invocation in Springcloud is made through Feign, and in the caller service we need to define 1 interface class with @ FeignClient annotation. And add @ EnableFeignClients annotation to the startup class.
When the program starts, it will check whether there is an @ EnableFeignClients annotation. If there is an annotation, it will start package scanning and scan the interface with @ FeignClient annotation.
Here, we will learn the startup process of @ EnableFeignClients with you in combination with a problem encountered before.
Problem description
When building a simple demo before, it always reports errors after starting
Field client1Feign in com.aiqinhai.client2.controller.Testrequired a bean of type
'com.aiqinhai.client2.feignclient.Client1Feign' that could not be found.
Action:
Consider defining a bean of type 'com.aiqinhai.client2.feignclient.Client1Feign' in your configuration.
Process finished with exit code 1
After checking for 1 ton, it was found that the @ EnableFeignClients annotation on startup did not specify the basePackages packet scanning path.
Moreover, the Client1Feign interface is not in the same directory as the startup class, so the above error will be reported when starting.
Later, the scan packet path was specified in @ EnableFeignClients.
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.aiqinhai.client2.feignclient"})
public class Client2Application {
public static void main(String[] args) {
SpringApplication.run(Client2Application.class, args);
}
}
The problem is solved, but we still need to know what the @ EnableFeignClients annotation did when the service started.
Plough root @ EnableFeignClients
The best way to understand the function of this annotation is to look at the source code of the annotation. After clicking in, you can see
/**
* Scan annotations @FeignClient Annotated interface
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
// Same as basePackages
String[] value() default {};
// Scanned package.
String[] basePackages() default {};
//feigin client Global configuration, default configuration in FeignClientsConfiguration Class
Class<?>[] defaultConfiguration() default {};
//@FeignClient Annotation, and if this property is specified, the scan is turned off.
Class<?>[] clients() default {};
}
As you can see, FeignClientsRegistrar class is included in import in the annotation. Let's go in and have a look. We can see that the logic of packet scanning is implemented in FeignClientsRegistrar, which scans all. class files, filters out the interface annotated by @ FeignClient, and then generates FeignClientFactoryBean objects through BeanDefinitionBuilder and injects them into IOC containers.
The specific code is as follows
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// Packet scanning method entry
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// Register the default configuration bean To ioc
registerDefaultConfiguration(metadata, registry);
// Registration @FeignClients Annotated interface bean, Generate a dynamic proxy for it
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// Get @EnableFeignClients Annotation attribute
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
// Registration @EnableFeignClients defaultConfiguration Class defined in the bean To ioc
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration")); }
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//spring Scanning tool class
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// Packets to be scanned
Set<String> basePackages;
//@EnableFeignClients Annotation attribute
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//@FeignClient Annotation filter, scan only @FeignClient Interface of annotation annotation
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
// Analyse @EnableFeignClient Attribute clients
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
// If @EnableFeignClient Not specified clients, Turn on package scanning, otherwise turn off scanning, and use clients
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
// From all under the classpath .class Scan in file @FeignClient Interface for annotations
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// Get annotations
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
//@FeignClient The annotation must be applied to the interface, otherwise an exception is thrown.
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// Get FeignClient Annotation attribute
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// Service name
String name = getClientName(attributes);
// Registration configuration Object specified in the bean
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// Generate FeignClient bean, And register to ioc
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// Generate bean, Inject into IOC Container
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// feignclient Class name
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// Register to ioc
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
@ FeignClient class, injection can't find class
Reason:
@ FeignClinet and @ EnableFeignClients are not the same package.
It may be caused by different versions of springboot and springcloud.