必备知识:ImportBeanDefinitionRegistrar方式实现Bean的动态装载
在ImportBeanDefinitionRegistrar方式实现Bean的动态装载 一文中,@EnableFeignClients 这个注解的作用是开启feignClient的扫描,注解定义如下:
package org.springframework.cloud.openfeign;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/*** Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of {@code @ComponentScan(basePackages="org.my.pkg")}.* @return the array of 'basePackages'.*/String[] value() default {
};/*** Base packages to scan for annotated components.* @return the array of 'basePackages'.*/String[] basePackages() default {
};/*** Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. * @return the array of 'basePackageClasses'.*/Class<?>[] basePackageClasses() default {
};/*** A custom <code>@Configuration</code> for all feign clients* @return list of default configurations*/Class<?>[] defaultConfiguration() default {
};/*** List of classes annotated with @FeignClient. If not empty, disables classpath scanning.* @return list of FeignClient classes*/Class<?>[] clients() default {
};
}
其中**@Import(FeignClientsRegistrar.class)** 导入了一个配置类FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,那么必然要重写接口中的registerBeanDefinitions 方法:
//AnnotationMetadata metadata:注解中的元数据
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry); // 注册默认的配置信息,registerFeignClients(metadata, registry); // 注册不同包下所有的FeignClient}
这个方法里面又做了两件事:
- 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() {
@Overrideprotected 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 interfaceAnnotatedBeanDefinition 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);}}}}
上面代码中的for循环里遍历了扫描到的每个包,找到加了@FeignClient注解的接口,然后迪用下面的方法去注册FeignClient
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);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);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 = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);// has a default, won't be nullboolean primary = (Boolean) attributes.get("primary");beanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {
alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] {
alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class) 这行代码,使用到了FeignClientFactoryBean, 这是一个工厂Bean (class FeignClientFactoryBean
implements FactoryBean, InitializingBean, ApplicationContextAware )
FactoryBean有个特点,就是如果需要获得这个bean的真正的实例,只有一个方法,就是通过 getObject方法来实现
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass<?> getObjectType();default boolean isSingleton() {
return true;}
}
因此,FeignClientFactoryBean中的getObject()方法一定会被调用到,它的实现如下,最终创建了一个复杂的bean进行返回:
@Overridepublic Object getObject() throws Exception {
return getTarget();}/*** @param <T> the target type of the Feign client* @return a {@link Feign} client created with the specified data and the context information*/<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;}else {
this.url = this.name;}this.url += cleanPath();return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));}if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url, but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url, but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));}
本文示例代码:https://download.csdn.net/download/weixin_41300437/13068346