首先引入依赖
<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中的类,但是由于访问权限的问题,无法访问因此拷贝一份到自己的项目中即可,ReferenceBeanBuilder和AnnotationPropertyValuesAdapter。

9076

被折叠的 条评论
为什么被折叠?



