@FeignClient注解的接口,用@Autowired可能获取不到实例

2022年7月27日09:15:20

背景:

Spring-Boot 2.0.8.RELEASE

Spring-Cloud 2.0.4.RELEASE

OpenFeign 2.0.4.RELEASE

JDK 1.8

启动类:

package com.xxx.tfb;

import java.sql.SQLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.h2.tools.Server;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;

import com.xxx.framework.config.SecondDataSourceConfiguration;

import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableEurekaClient
@EnableFeignClients(basePackages="com.xxx")
@EnableCircuitBreaker
@EnableProcessApplication
@EnableHystrix
@SpringBootApplication
@EnableSwagger2
@MapperScan(basePackages = { "com.xxx.**.mapper" })
@MapperScan(basePackages = { "com.xxx.**.oramapper" })
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {SecondDataSourceConfiguration.class})})
@ComponentScan(basePackages = { "com.xxx.framework","com.xxx.workflow",
		"com.xxx.tradeengine","com.xxx.component",
		"com.xxx.controller", "com.xxx.bizservice",
		"com.xxx.rpc","com.xxx.dto.rpc",
		"com.xxx.fallbackservice","com.xxx.helper" })
@SuppressWarnings("all")
public class TfbOrBizServiceApplication 
{
	private static Log log=LogFactory.getLog(TfbOrBizServiceApplication.class);
	
	public static void main(String[] args) {
		SpringApplication.run(TfbOrBizServiceApplication.class, args);
	}
}

Feign客户端:

package com.xxx.rpc;

import java.util.List;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.xxx.dto.tfb.function.UnitBizRequestDto;
import com.xxx.dto.tfb.function.UnitBizResponseDto;
import com.xxx.framework.dto.PaginationDto;
import com.xxx.framework.web.controller.PageRequestDto;
import com.xxx.framework.web.controller.ResponseDto;

@FeignClient(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app")
public interface CommonBizRpc {
	
	/**
	 * 获取交易日期
	 */
	@RequestMapping(value = "/leftQuery/queryAcctDate", method = RequestMethod.POST)
	public ResponseDto<String> queryAcctDate();
}

使用的地方:

package com.xxx.tfb.component.or;

import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import com.xxx.TradeDto;
import com.xxx.bizenum.tfb.CommonEnum;
import com.xxx.framework.utils.SpringContextUtil;
import com.xxx.rpc.CommonBizRpc;
import com.xxx.tradeengine.dto.DataContext;
import com.xxx.tradeengine.parser.TradeComponent;

@Component
@DependsOn("springContextUtil")
public class OrOpenFxccDataInitComponentImpl implements TradeComponent {
	//公共交易服务
	CommonBizRpc commonBizRpc = SpringContextUtil.getBean("commonBizRpc");
			
	@Override
	public void exec(DataContext dataContext) {
		//交易日期
		orDto.setTxDt(commonBizRpc.queryAcctDate().getData());
	}

}

SpringContextUtil工具类:


@Component
public class SpringContextUtil implements ApplicationContextAware {
	private static ApplicationContext applicationContext; // Spring应用上下文环境

    
	/**
	 * 实现ApplicationContextAware接口的回调方法,设置上下文环境
	 * 
	 * @param applicationContext @
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		SpringContextUtil.applicationContext = applicationContext;
	}

	/**
	 * 获取对象
	 * 
	 * @param name
	 * @return Object 一个以所给名字注册的bean的实例 @
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getBean(String name) {
		return (T) applicationContext.getBean(name);
	}

}

这样的配置,应用启动的时候报错了:

Constructor in com.xxx.tfb.component.or.OrOpenFxccDataInitComponentImpl required a bean named 'commonBizRpc' that could not be found.

Action:

Consider defining a bean named 'commonBizRpc' in your configuration.

报错的意思是不存在name为commonBizRpc的实例。已经在@EnableFeignClients(basePackages="com.xxx")配置了basePackages,而且可以确定该@FeignClient已经被扫描到了。

为什么提示找不到呢?

分析下源码:

@EnableFeignClients 注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

注意上面导入的FeignClientsRegistrar类,该类实现了将@FeignClient注解的接口加入到Spring管理的容器里:

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("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) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// 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);
				}
			}
		}
	}

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		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);
        // className为带有包路径的类名
		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);

        // 默认的别名
        // 这里的name是服务方的名称,本示例为注册到Eureka里的服务名
		String alias = name + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

		beanDefinition.setPrimary(primary);

        // 如果注解里加了qualifier,别名就使用qualifier里的名称
		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

注意上面的registerFeignClients方法,交给Spring容器管理的实例的name默认是className,即带有包路径的name,本次示例为"com.xxx.rpc.CommonBizRpc",默认的别名:
        // 这里的name是服务方的名称,本示例为注册到Eureka里的服务名,tfb-biz-common-service-appFeignClient
        String alias = name + "FeignClient";

        // 如果注解里加了qualifier,别名就使用qualifier里的名称
        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

这样在注入的时候,要么使用的name是com.xxx.rpc.CommonBizRpc而不能是CommonBizRpc,或tfb-biz-common-service-appFeignClient(如果该服务注册多个客户端,可能会报错),或使用CommonBizRpc.class

SpringContextUtil.getBean();

@AutoWired也一样

为了方便使用,建议在每个@FeignClient里配置上qualifier,修改后的代码如下:

package com.xxx.rpc;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;


@FeignClient(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app", qualifier="commonBizRpc")
public interface CommonBizRpc {
	
	/**
	 * 获取交易日期
	 */
	@RequestMapping(value = "/leftQuery/queryAcctDate", method = RequestMethod.POST)
	public ResponseDto<String> queryAcctDate();
}

注意事项:

1.@EnableFeignClients(basePackages="com.xxx") 如果不配置basePackages的话,那么默认只会扫描当前启动类包及其子包的@FeignClient注解

2.@FeignClient里的path最好写上,而且是服务端的contextPath(value = "tfb-biz-common-service-app", path="/tfb-biz-common-service-app", qualifier="commonBizRpc")

  • 作者:lzhfdxhxm
  • 原文链接:https://blog.csdn.net/lzhfdxhxm/article/details/114296387
    更新时间:2022年7月27日09:15:20 ,共 9231 字。