All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.autoconfigure.feign.annotation.FeignConsumersRegistrar Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.autoconfigure.feign.annotation;

import java.io.IOException;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zodiac.commons.logging.SmartSlf4jLoggerFactory;
import org.zodiac.commons.util.ArrayUtil;
import org.zodiac.commons.util.Classes;
import org.zodiac.commons.util.Colls;
import org.zodiac.commons.util.lang.Numbers;
import org.zodiac.commons.util.lang.Strings;
import org.zodiac.commons.util.spring.Springs;
import org.zodiac.feign.core.annotation.FeignConsumer;
import org.zodiac.feign.core.config.FeignConsumerConfigInfo;
import org.zodiac.feign.core.constants.FeignSystemPropertiesConstants;

public class FeignConsumersRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware,
    ApplicationContextAware {

    private static final Logger LOG = SmartSlf4jLoggerFactory.getLogger(FeignConsumersRegistrar.class);

    public static final BeanNameGenerator DEFAULT_BEAN_GENERATOR = (definition,
        registry) -> AnnotationBeanNameGenerator.INSTANCE.generateBeanName(definition, registry) + ".SpringBootFeignClient";

    private ResourceLoader resourceLoader;
    private Environment environment;

    private ApplicationContext applicationContext;

    protected FeignConsumersRegistrar() {
        super();
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        /*Check enabled configuration.*/
        if (!isEnableConfiguration(environment)) {
            LOG.warn("No enabled Spring Boot/Cloud Feign configuration!");
            return;
        }

        AnnotationAttributes attrs = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(EnableFeignConsumers.class.getName()));
        if (Objects.nonNull(attrs)) {
            final Class[] clients = Objects.isNull(attrs) ? null : attrs.getClassArray("clients");
            if (ArrayUtil.emptyArray(clients)) {
                /*
                 * Notes:这里需手动合并,因为当@EnableFeignConsumers引用了@EnableFeignClients时,
                 * 且没有依赖spring-cloud-openfeign包时,就不能合并属性值?
                 * 如配置了value就只能获取value的值,而无法获取被@AliasFor的basePackages的值,稳妥起见手动合并。
                 */
                Set scanBasePackages = getScanBasePackages(metadata, attrs).stream().filter(pkg -> Strings.notBlank(pkg))
                        .collect(Collectors.toSet());

                /*SpringCloud + Feign*/
                if (hasSpringCloudFeignClass()) {
                    LOG.info("The current classpath contains springcloud feign, "
                            + "which automatically enables the SpringCloud + Feign architecture. "
                            + "SpringBoot + Feign has been ignored");
                }
                /*SpringBoot + Feign*/
                else {
                    registerSpringBootFeignClients(metadata, registry, attrs, scanBasePackages);
                }
            } else {
                for (Class clazz : clients) {
                    AnnotatedGenericBeanDefinition definition = new AnnotatedGenericBeanDefinition(clazz);
                    configurerFeignClientPropertyValues(definition, attrs.getClassArray(EnableFeignConsumers.DEFAULT_CONFIGURATION));
                    registry.registerBeanDefinition(DEFAULT_BEAN_GENERATOR.generateBeanName(definition, registry), definition);
                }
            }
        }
    }

    private void registerSpringBootFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry,
            AnnotationAttributes attrs, Set scanBasePackages) {
        new SpringBootFeignClientScanner(registry, attrs.getClassArray(EnableFeignConsumers.DEFAULT_CONFIGURATION))
                .doScan(StringUtils.toStringArray(scanBasePackages));
    }

    private Set getScanBasePackages(AnnotationMetadata metadata, AnnotationAttributes attrs) {
        Set scanBasePackages = Colls.set();
        for (String pkg : (String[]) attrs.get("value")) {
            if (Strings.hasText(pkg)) {
                scanBasePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attrs.get(EnableFeignConsumers.BASE_PACKAGES)) {
            if (Strings.hasText(pkg)) {
                scanBasePackages.add(pkg);
            }
        }
        for (Class clazz : (Class[]) attrs.get(EnableFeignConsumers.BASE_PACKAGE_CLASSES)) {
            scanBasePackages.add(ClassUtils.getPackageName(clazz));
        }
        if (scanBasePackages.isEmpty()) {
            scanBasePackages.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        return scanBasePackages;
    }

    private void configurerFeignClientPropertyValues(BeanDefinition definition, @Nullable Class[] defaultConfiguration) {
        /*First, find SprinCboot Feign client definition.*/
        MergedAnnotation feignClient = ((AnnotatedBeanDefinition) definition).getMetadata().getAnnotations()
                .get(FeignConsumer.class);
        if (!feignClient.isPresent()) {
            /*Fallback, find SpringCloud Feign.*/
            feignClient = ((ScannedGenericBeanDefinition) definition).getMetadata().getAnnotations().get(FeignClient.class);
            if (!feignClient.isPresent()) {
                return;
            }
        }

        String beanClassName = definition.getBeanClassName();
        //((GenericBeanDefinition) definition).setBeanClass(FeignConsumerFactoryBean.class);
        if (Springs.isServletWeb(applicationContext)) {
            ((GenericBeanDefinition) definition).setBeanClass(DefaultServletFeignConsumerFactoryBean.class);
        } else if (Springs.isReactiveWeb(applicationContext)) {
            ((GenericBeanDefinition) definition).setBeanClass(DefaultReactiveFeignConsumerFactoryBean.class);
        } else {
            throw new FatalBeanException("Application environment that does not support Feign.");
        }
        definition.setPrimary(feignClient.getBoolean("primary"));
        MutablePropertyValues propertyValues = definition.getPropertyValues();
        propertyValues.add("targetClass", beanClassName);
        propertyValues.add("url", environment.resolveRequiredPlaceholders(feignClient.getString("url")));// baseUrl
        propertyValues.add("path", getRequestPath(definition, feignClient));
        propertyValues.add("decode404", feignClient.getBoolean("decode404"));
        propertyValues.add("configuration", feignClient.getClassArray("configuration"));
        propertyValues.add("logLevel", feignClient.getValue("logLevel").orElse(null));
        propertyValues.add("connectTimeout", resolveNullableLong(feignClient, "connectTimeout"));
        propertyValues.add("readTimeout", resolveNullableLong(feignClient, "readTimeout"));
        propertyValues.add("writeTimeout", resolveNullableLong(feignClient, "writeTimeout"));
        propertyValues.add("followRedirects", resolveNullableBoolean(feignClient, "followRedirects"));
        propertyValues.add("defaultConfiguration", defaultConfiguration);
    }

    private Boolean resolveNullableBoolean(MergedAnnotation feignClient, String attributeName) {
        try {
            return Boolean.parseBoolean(environment.resolveRequiredPlaceholders(feignClient.getString(attributeName)));
        } catch (NoSuchElementException e) {
            LOG.debug(String.format("Cannot resolve %s, using default value", attributeName), e.getMessage());
        }
        return null;
    }

    private Long resolveNullableLong(MergedAnnotation feignClient, String attributeName) {
        try {
            return Numbers.toLongObjectOrNull(environment.resolveRequiredPlaceholders(feignClient.getString(attributeName)));
        } catch (NoSuchElementException e) {
            LOG.debug(String.format("Cannot resolve %s, using default value", attributeName), e.getMessage());
        }
        return null;
    }

    private String getRequestPath(BeanDefinition definition, MergedAnnotation feignClient) {
        String path = "";
        /*Notes: SpringMvcContract It will automatically splice to the URL.*/
        MergedAnnotation requestMapping = ((AnnotatedBeanDefinition) definition).getMetadata().getAnnotations()
                .get(RequestMapping.class);
        if (!requestMapping.isPresent()) {
            /*Fallback, find by @FeignClient/@SpringBootFeignClient*/
            path = feignClient.getString("path");
        }
        return environment.resolveRequiredPlaceholders(path);

    }

    public static boolean hasSpringCloudFeignClass() {
        return Classes.isPresent("org.springframework.cloud.openfeign.FeignClientsRegistrar");
    }

    /**
     * Check enabled Spring Boot/Cloud Feign configuration.
     * 
     * @param environment {@link Environment}
     * @return enabled or not
     */
    public static boolean isEnableConfiguration(Environment environment) {
        /*Default by true, If want to run in standalone mode, should set false.*/
        return environment.getProperty(FeignSystemPropertiesConstants.FEIGN_CLIENT_CONSUMER_ENABLED, boolean.class, FeignConsumerConfigInfo.DEFAULT_ENABLED);
    }

    class SpringBootFeignClientScanner extends ClassPathBeanDefinitionScanner {

        @Nullable
        private final Class[] defaultConfiguration;

        public SpringBootFeignClientScanner(BeanDefinitionRegistry registry, Class[] defaultConfiguration) {
            super(registry, true);
            this.defaultConfiguration = defaultConfiguration;
            registerFilters();
            setBeanNameGenerator(DEFAULT_BEAN_GENERATOR);
        }

        @Override
        public Set doScan(String... basePackages) {
            Set beanDefinitions = super.doScan(basePackages);
            if (beanDefinitions.isEmpty()) {
                LOG.warn("No spring boot feign client is found in package '{}'.", Arrays.toString(basePackages));
            }

            for (BeanDefinitionHolder holder : beanDefinitions) {
                ScannedGenericBeanDefinition definition = (ScannedGenericBeanDefinition) holder.getBeanDefinition();
                configurerFeignClientPropertyValues(definition, defaultConfiguration);
            }

            return beanDefinitions;
        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return beanDefinition.getMetadata().isInterface();
        }

        private void registerFilters() {
            /*Include service interfaces.*/
            addIncludeFilter(new AnnotationTypeFilter(FeignConsumer.class, true, true));
            /*For-compatibility*/
            addIncludeFilter(new AnnotationTypeFilter(FeignClient.class, true, true));

            /*Exclude service interfaces.*/
            addExcludeFilter(new TypeFilter() {
                @Override
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                        throws IOException {
                    String className = metadataReader.getClassMetadata().getClassName();
                    return Strings.endsWithAny(className, "package-info", "module-info.java");
                }
            });
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy