SpringBoot中自动将包内接口生成Dubbo的ReferenceBean

首先引入依赖

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.7</version>
</dependency>

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-autoconfigure</artifactId>
    <version>2.7.7</version>
</dependency>

定义一个注解

@DubboReferenceScanIgnore
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboReferenceBeanRegistrar.class)
public @interface EnableDubboReferenceScan {

    /**
     * @return dubbo直连服务器地址
     */
    String url() default "";

    /**
     * @return 扫描的包路径
     */
    String[] basePackages();
}

通过这个注解传入指定的包名称

@Import定义了一个实现了ImportBeanDefinitionRegistrar接口的对象,用于扫描指定包路径下的接口,然后根据接口类型生成ReferenceBean并注册到Spring容器当中,然后在使用的地方就像如下的方式可以使用了。

// 开启dubbo的扫描功能
@EnableDubboReferenceScan(basePackages = {"com.xquant.platform.test.trade"})
@SpringBootApplication
public class TradeTestClientStartMain {

    private final Logger logger = LoggerFactory.getLogger(getClass());

	// 像Spring注入一样注入一个Dubbo的Reference引用
    @Autowired
    private FacadeTradeTestService facadeTradeTestService;


    public static void main(String[] args) {
        SpringApplication.run(TradeTestClientStartMain.class);
    }
    
    @Bean
    public ApplicationRunner runner() {
        return args -> {
            facadeTradeTestService.newNormalTrade(TradeParamFactory4Bond.bondIn4CNBD(), null, "2016-05-06", null);
        };
    }
}

由于在ImportBeanDefinitionRegistrar需要使用到ApplicationContext这个Bean对象,但是此时通过ApplicationContextAware是无法引入的(ImportBeanDefinitionRegistrar此时是不会进行Bean实例化的),所以定义下面类

package com.xquant.platform.test.trade.client.env;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

/**
 * Created in 2020/6/21 20:35 by guanglai.zhou
 */
public class ClientSpringApplicationRunListener implements SpringApplicationRunListener {

    private static ApplicationContext applicationContext;
    private static ConfigurableEnvironment configurableEnvironment;
    private SpringApplication application;

    public ClientSpringApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static ConfigurableEnvironment getConfigurableEnvironment() {
        return configurableEnvironment;
    }

    @Override
    public void starting() {

    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        configurableEnvironment = environment;
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        applicationContext = context;
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }
}

并在spring.factories文件中添加配置

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.xquant.platform.test.trade.client.env.ClientSpringApplicationRunListener

这样在Spring容器刷新之前就会将容器对象存到ClientSpringApplicationRunListener类中了
对应的DubboReferenceBeanRegistrar实现如下:

package com.xquant.platform.test.trade.client.anno;

import com.xquant.platform.test.trade.client.env.ClientSpringApplicationRunListener;
import com.xquant.platform.test.trade.client.filter.ReferenceBeanFilter;
import com.xquant.platform.test.trade.client.filter.impl.ReferenceBeanFilterImpl;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.ServiceBean;
import org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor;
import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceAnnotationBeanPostProcessor;
import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static com.alibaba.spring.util.AnnotationUtils.getAttribute;
import static java.lang.reflect.Proxy.newProxyInstance;
import static org.springframework.util.StringUtils.hasText;

/**
 * @author :guanglai.zhou
 * @date :Created in 2020/6/8 9:32
 * @description:注册Dubbo ReferenceBean
 */
public class DubboReferenceBeanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware, ApplicationContextAware, BeanFactoryAware, BeanClassLoaderAware {

    /**
     * The bean name of {@link ReferenceAnnotationBeanPostProcessor}
     */
    public static final String BEAN_NAME = "referenceAnnotationBeanPostProcessor";

    /**
     * Cache size
     */
    private static final int CACHE_SIZE = Integer.getInteger(BEAN_NAME + ".cache.size", 32);
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private static ReferenceBeanFilter referenceBeanFilter = new ReferenceBeanFilterImpl();
    private final ConcurrentMap<String, ReferenceBean<?>> referenceBeanCache =
            new ConcurrentHashMap<>(CACHE_SIZE);
    private final ConcurrentMap<String, ReferencedBeanInvocationHandler> referencedBeanInvocationHandlersCache =
            new ConcurrentHashMap<>();
    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
    private Environment environment;
    private ApplicationContext applicationContext = ClientSpringApplicationRunListener.getApplicationContext();
    private DefaultResourceLoader resourceLoader = new DefaultResourceLoader(ClassUtils.getDefaultClassLoader());
    private ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    private BeanFactory beanFactory;
    private ClassLoader classLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        EnableDubboReferenceScan enableDubboReferenceScan = ((StandardAnnotationMetadata) importingClassMetadata).getIntrospectedClass().getAnnotation(EnableDubboReferenceScan.class);
        if (enableDubboReferenceScan != null) {
            String[] basePackages = enableDubboReferenceScan.basePackages();
            String url = enableDubboReferenceScan.url();
            Set<Class<?>> candidates = scan(basePackages);
            AnnotationAttributes attributes = new AnnotationAttributes();
            for (Class<?> injectedType : candidates) {
                /**
                 * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
                 */
                String referencedBeanName = buildReferencedBeanName(attributes, injectedType);

                /**
                 * The name of bean that is declared by {@link Reference @Reference} annotation injection
                 */
                String referenceBeanName = getReferenceBeanName(attributes, injectedType);

                ReferenceBean referenceBean = null;
                try {
                    referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }

                boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);

                registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);

                getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);

            }
        }
    }

    /**
     * @param attributes           the attributes of {@link Reference @Reference}
     * @param serviceInterfaceType the type of Dubbo's service interface
     * @return The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
     */
    private String buildReferencedBeanName(AnnotationAttributes attributes, Class<?> serviceInterfaceType) {
        ServiceBeanNameBuilder serviceBeanNameBuilder = ServiceBeanNameBuilder.create(attributes, serviceInterfaceType, this.getEnvironment());
        return serviceBeanNameBuilder.build();
    }

    public Environment getEnvironment() {
        return environment;
    }

    @Override
    public void setEnvironment(Environment environment) {

        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);

        this.environment = (ConfigurableEnvironment) environment;

    }

    /**
     * Get the bean name of {@link ReferenceBean} if {@link Reference#id() id attribute} is present,
     * or {@link #generateReferenceBeanName(AnnotationAttributes, Class) generate}.
     *
     * @param attributes     the {@link AnnotationAttributes attributes} of {@link Reference @Reference}
     * @param interfaceClass the {@link Class class} of Service interface
     * @return non-null
     * @since 2.7.3
     */
    private String getReferenceBeanName(AnnotationAttributes attributes, Class<?> interfaceClass) {
        // id attribute appears since 2.7.3
        String beanName = getAttribute(attributes, "id");
        if (!hasText(beanName)) {
            beanName = generateReferenceBeanName(attributes, interfaceClass);
        }
        return beanName;
    }

    /**
     * Build the bean name of {@link ReferenceBean}
     *
     * @param attributes     the {@link AnnotationAttributes attributes} of {@link Reference @Reference}
     * @param interfaceClass the {@link Class class} of Service interface
     * @return
     * @since 2.7.3
     */
    private String generateReferenceBeanName(AnnotationAttributes attributes, Class<?> interfaceClass) {
        StringBuilder beanNameBuilder = new StringBuilder("@Reference");

        if (!attributes.isEmpty()) {
            beanNameBuilder.append('(');
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                beanNameBuilder.append(entry.getKey())
                        .append('=')
                        .append(entry.getValue())
                        .append(',');
            }
            // replace the latest "," to be ")"
            beanNameBuilder.setCharAt(beanNameBuilder.lastIndexOf(","), ')');
        }

        beanNameBuilder.append(" ").append(interfaceClass.getName());

        return beanNameBuilder.toString();
    }

    private ReferenceBean buildReferenceBeanIfAbsent(String referenceBeanName, AnnotationAttributes attributes,
                                                     Class<?> referencedType)
            throws Exception {

        ReferenceBean<?> referenceBean = referenceBeanCache.get(referenceBeanName);

        if (referenceBean == null) {
            ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder
                    .create(attributes, applicationContext)
                    .interfaceClass(referencedType);
            referenceBean = beanBuilder.build();
            referenceBeanCache.put(referenceBeanName, referenceBean);
        } else if (!referencedType.isAssignableFrom(referenceBean.getInterfaceClass())) {
            throw new IllegalArgumentException("reference bean name " + referenceBeanName + " has been duplicated, but interfaceClass " +
                    referenceBean.getInterfaceClass().getName() + " cannot be assigned to " + referencedType.getName());
        }
        return referenceBean;
    }

    /**
     * Is Local Service bean or not?
     *
     * @param referencedBeanName the bean name to the referenced bean
     * @return If the target referenced bean is existed, return <code>true</code>, or <code>false</code>
     * @since 2.7.6
     */
    private boolean isLocalServiceBean(String referencedBeanName, ReferenceBean referenceBean, AnnotationAttributes attributes) {
        return existsServiceBean(referencedBeanName) && !isRemoteReferenceBean(referenceBean, attributes);
    }

    /**
     * Check the {@link ServiceBean} is exited or not
     *
     * @param referencedBeanName the bean name to the referenced bean
     * @return if exists, return <code>true</code>, or <code>false</code>
     * @revised 2.7.6
     */
    private boolean existsServiceBean(String referencedBeanName) {
        return applicationContext.containsBean(referencedBeanName) &&
                applicationContext.isTypeMatch(referencedBeanName, ServiceBean.class);

    }

    private boolean isRemoteReferenceBean(ReferenceBean referenceBean, AnnotationAttributes attributes) {
        boolean remote = Boolean.FALSE.equals(referenceBean.isInjvm()) || Boolean.FALSE.equals(attributes.get("injvm"));
        return remote;
    }

    /**
     * Register an instance of {@link ReferenceBean} as a Spring Bean
     *
     * @param referencedBeanName The name of bean that annotated Dubbo's {@link Service @Service} in the Spring {@link ApplicationContext}
     * @param referenceBean      the instance of {@link ReferenceBean} is about to register into the Spring {@link ApplicationContext}
     * @param attributes         the {@link AnnotationAttributes attributes} of {@link Reference @Reference}
     * @param localServiceBean   Is Local Service bean or not
     * @param interfaceClass     the {@link Class class} of Service interface
     * @since 2.7.3
     */
    private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       boolean localServiceBean, Class<?> interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        if (localServiceBean) {  // If @Service bean is local one
            /**
             * Get  the @Service's BeanDefinition from {@link BeanFactory}
             * Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition}
             */
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
            // The name of bean annotated @Service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            // register Alias rather than a new bean name, in order to reduce duplicated beans
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
    }

    /**
     * Get or Create a proxy of {@link ReferenceBean} for the specified the type of Dubbo service interface
     *
     * @param referencedBeanName   The name of bean that annotated Dubbo's {@link Service @Service} in the Spring {@link ApplicationContext}
     * @param referenceBean        the instance of {@link ReferenceBean}
     * @param localServiceBean     Is Local Service bean or not
     * @param serviceInterfaceType the type of Dubbo service interface
     * @return non-null
     * @since 2.7.4
     */
    private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
                                    Class<?> serviceInterfaceType) {
        if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    newReferencedBeanInvocationHandler(referencedBeanName));
        } else {
            exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
            return referenceBean.get();
        }
    }

    private void exportServiceBeanIfNecessary(String referencedBeanName) {
        if (existsServiceBean(referencedBeanName)) {
            ServiceBean serviceBean = getServiceBean(referencedBeanName);
            if (!serviceBean.isExported()) {
                serviceBean.export();
            }
        }
    }

    private ServiceBean getServiceBean(String referencedBeanName) {
        return applicationContext.getBean(referencedBeanName, ServiceBean.class);
    }

    private InvocationHandler newReferencedBeanInvocationHandler(String referencedBeanName) {
        return referencedBeanInvocationHandlersCache.computeIfAbsent(referencedBeanName,
                ReferencedBeanInvocationHandler::new);
    }

    public ApplicationContext getApplicationContext() {
        if (applicationContext == null) {
            applicationContext = ClientSpringApplicationRunListener.getApplicationContext();
        }
        return applicationContext;
    }

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

    private Set<Class<?>> scan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<Class<?>> beanClasses = new LinkedHashSet<Class<?>>();
        for (String basePackage : basePackages) {
            beanClasses.addAll(doScan(basePackage));
        }
        return beanClasses;
    }

    private Set<Class<?>> doScan(String basePackage) {
        Set<Class<?>> candidates = new LinkedHashSet<Class<?>>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                    + resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        String className = metadataReader.getClassMetadata().getClassName();
                        Class<?> aClass = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
                        // 当目标类是接口并且自动生成Dubbo的ReferenceBean时不被忽略
                        if (referenceBeanFilter.isCandidate(aClass)) {
                            candidates.add(aClass);
                        }
                    } catch (Throwable ex) {
                        throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource,
                                ex);
                    }
                }
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

    /**
     * Resolve the specified base package into a pattern specification for
     * the package search path.
     * <p>The default implementation resolves placeholders against system properties,
     * and converts a "."-based package path to a "/"-based resource path.
     *
     * @param basePackage the base package as specified by the user
     * @return the pattern specification to be used for package searching
     */
    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    private class ReferencedBeanInvocationHandler implements InvocationHandler {

        private final String referencedBeanName;

        private Object bean;

        private ReferencedBeanInvocationHandler(String referencedBeanName) {
            this.referencedBeanName = referencedBeanName;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = null;
            try {
                if (bean == null) {
                    init();
                }
                result = method.invoke(bean, args);
            } catch (InvocationTargetException e) {
                // re-throws the actual Exception.
                throw e.getTargetException();
            }
            return result;
        }

        private void init() {
            ServiceBean serviceBean = getApplicationContext().getBean(referencedBeanName, ServiceBean.class);
            this.bean = serviceBean.getRef();
        }
    }
}

其中可能会使用到一些Dubbo中的类,但是由于访问权限的问题,无法访问因此拷贝一份到自己的项目中即可,ReferenceBeanBuilderAnnotationPropertyValuesAdapter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值