dubbo多接口实现注册方案
背景介绍
在前后端分离情况下服务端被调用项目也分为web端app端等,但是由于项目前期接口量少,不适合做过多的项目拆分,所以才有了这一套的过渡方案,方便日后项目拆分
实现方案
dubbo默认会使用实现类的第一个接口注册成bean并集成到可远程调用接口集,所以我们要做的就是在dubbo注册bean之后集成到可远程调用接口集之前手动将该实现类的其他接口都注册成bean即可
核心方法(在后面的 DubboConfig 类里有全代码)
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
scanner.setBeanNameGenerator(new AnnotationBeanNameGenerator());
scanner.addIncludeFilter(new AnnotationTypeFilter(DubboService.class));
packagesToScan.stream().peek(scanner::scan).map(scanner::findCandidateComponents).flatMap(Set::stream).forEach(this::registerServiceBean);
}
private void registerServiceBean(BeanDefinition beanDefinition) {
Class<?> beanClass = ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), classLoader);
Class<?>[] interfaceClasses = ClassUtils.getAllInterfacesForClass(beanClass);
String beanName = generateServiceBeanName(interfaceClasses[0]);
BeanDefinition bean = registry.getBeanDefinition(beanName);
for (int i = 1; i < interfaceClasses.length; i++) {
// ServiceBean Bean name
registerBeanDefinition(interfaceClasses[i], bean);
}
}
private String generateServiceBeanName(Class<?> interfaceClass) {
return ServiceBeanNameBuilder.create(interfaceClass, environment).build();
}
private void registerBeanDefinition(Class<?> interfaceClass, BeanDefinition bean) {
String replaceParamName = "interface";
AbstractBeanDefinition newBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ServiceBean.class).getBeanDefinition();
bean.getPropertyValues().forEach(newBeanDefinition.getPropertyValues()::addPropertyValue);
newBeanDefinition.getPropertyValues().removePropertyValue(replaceParamName);
newBeanDefinition.getPropertyValues().add(replaceParamName, interfaceClass.getName());
registry.registerBeanDefinition(generateServiceBeanName(interfaceClass), newBeanDefinition);
}
代码全套
- 创建 @EnableMyDubbo 注解用于替换 @EnableDubbo 注解
import org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @EnableDubboConfig @MyDubboComponentScan public @interface EnableMyDubbo { @AliasFor(annotation = MyDubboComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = MyDubboComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple") boolean multipleConfig() default true; }
- 创建 @MyDubboComponentScan 注解用于替换 @DubboComponentScan 注解
import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan; import org.springframework.context.annotation.Import; 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; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @DubboComponentScan @Import(MyDubboComponentScanRegistrar.class) public @interface MyDubboComponentScan { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
- 创建我们自己的dubbo扫描类 MyDubboComponentScanRegistrar
import org.apache.dubbo.config.annotation.Service; 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.context.annotation.DubboComponentScan; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import java.util.stream.Stream; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; public class MyDubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Set<String> packagesToScan = getPackagesToScan(importingClassMetadata); registerServiceAnnotationBeanPostProcessor(packagesToScan, registry); } private Set<String> getPackagesToScan(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(MyDubboComponentScan.class.getName())); String[] basePackages = attributes.getStringArray("basePackages"); Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses"); String[] value = attributes.getStringArray("value"); // Appends value array attributes Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(value)); packagesToScan.addAll(Arrays.asList(basePackages)); Stream.of(basePackageClasses).map(ClassUtils::getPackageName).forEach(packagesToScan::add); return packagesToScan.isEmpty() ? Collections.singleton(ClassUtils.getPackageName(metadata.getClassName())) : packagesToScan; } private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) { BeanDefinitionBuilder builder = rootBeanDefinition(DubboConfig.class); builder.addConstructorArgValue(packagesToScan); builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry); } }
- 再自定义一个多实现接口标识注解 @DubboService
import org.apache.dubbo.config.annotation.Service; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited @Service public @interface DubboService { }
- 核心,注册实现 DubboConfig
import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.spring.ServiceBean; import org.apache.dubbo.config.spring.beans.factory.annotation.ServiceBeanNameBuilder; import org.apache.dubbo.config.spring.context.annotation.DubboClassPathBeanDefinitionScanner; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.util.ClassUtils; import java.util.Set; @Slf4j public class DubboConfig implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware { private Environment environment; private ResourceLoader resourceLoader; private ClassLoader classLoader; private BeanDefinitionRegistry registry; private Set<String> packagesToScan; public DubboConfig(Set<String> packagesToScan) { this.packagesToScan = packagesToScan; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader); scanner.setBeanNameGenerator(new AnnotationBeanNameGenerator()); scanner.addIncludeFilter(new AnnotationTypeFilter(DubboService.class)); packagesToScan.stream().peek(scanner::scan).map(scanner::findCandidateComponents).flatMap(Set::stream).forEach(this::registerServiceBean); } private void registerServiceBean(BeanDefinition beanDefinition) { Class<?> beanClass = ClassUtils.resolveClassName(beanDefinition.getBeanClassName(), classLoader); Class<?>[] interfaceClasses = ClassUtils.getAllInterfacesForClass(beanClass); String beanName = generateServiceBeanName(interfaceClasses[0]); BeanDefinition bean = registry.getBeanDefinition(beanName); for (int i = 1; i < interfaceClasses.length; i++) { // ServiceBean Bean name registerBeanDefinition(interfaceClasses[i], bean); } } private String generateServiceBeanName(Class<?> interfaceClass) { return ServiceBeanNameBuilder.create(interfaceClass, environment).build(); } private void registerBeanDefinition(Class<?> interfaceClass, BeanDefinition bean) { String replaceParamName = "interface"; AbstractBeanDefinition newBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(ServiceBean.class).getBeanDefinition(); bean.getPropertyValues().forEach(newBeanDefinition.getPropertyValues()::addPropertyValue); newBeanDefinition.getPropertyValues().removePropertyValue(replaceParamName); newBeanDefinition.getPropertyValues().add(replaceParamName, interfaceClass.getName()); registry.registerBeanDefinition(generateServiceBeanName(interfaceClass), newBeanDefinition); } }
使用方式
-
编写app端接口
public interface AppService { String appService(); }
-
编写web端接口
public interface WebService { String webService(); }
-
启动类上由 替换掉 @ 注解
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableMyDubbo @SpringBootApplication public class DubboServiceApplication { public static void main(String[] args) { SpringApplication.run(DubboServiceApplication.class, args); } }
-
编写服务提供者
@DubboService public class ServiceImpl implements WebService, AppService { @Override public String webService() { return "webService"; } @Override public String appService() { return "appService"; } }
-
编写app端消费者
import org.apache.dubbo.config.annotation.Reference; public class AppController { @Reference private WebService webService; public String test() { return webService.webService(); } }
-
编写web端消费者
import org.apache.dubbo.config.annotation.Reference; public class WebController { @Reference private AppService appService; public String test() { return appService.appService(); } }
注意点
- 当前使用的dubbo版本是 2.7.4
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.5</version> </dependency>
遗憾的是并不兼容2.7.4以上版本
2.7.4版本
2.7.5版本 EnableDubbo 注解上增加了 @EnableDubboLifecycle 注解
2.7.6版本虽然和2.7.4一样,但是在 EnableDubboConfig 和 DubboComponentScan 注解里的导入配置类都增加了一行 registerCommonBeans(registry); 导致spring会报由 DubboBootstrapApplicationListener 引起的重复bean注册错误
2.7.7版本直接计划废弃 Service 和 Reference 注解
总结
- 直至2022年2月现在已经3.0.5版本了,所以仅供参考