@ 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.


Related articles: