Spring boot 让我们的项目配置越来越简单,很多第三方也对spring boot进行来很好的继承。今天将要分析的是dubbo对spring boot的集成——dubbo-spring-boot-starter。
在spring boot项目中我们经常会见到各种各样的starter,那一个starter究竟做了什么,如何注入到我们的项目中去的呢?
项目地址:https://github.com/alibaba/dubbo-spring-boot-starter
关于dubbo starter的使用,可参见项目文档:https://github.com/alibaba/dubbo-spring-boot-starter/blob/master/README.md
dubbo starter的代码比较少,文件结构如下图所示:
.
|-- LICENSE
|-- README.md
|-- README_zh.md
|-- googlestyle-java.xml
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- com
| | `-- alibaba
| | `-- dubbo
| | `-- spring
| | `-- boot
| | |-- DubboAutoConfiguration.java
| | |-- DubboCommonAutoConfiguration.java
| | |-- DubboConsumerAutoConfiguration.java
| | |-- DubboProperties.java
| | |-- DubboProviderAutoConfiguration.java
| | |-- annotation
| | | `-- EnableDubboConfiguration.java
| | |-- bean
| | | |-- ClassIdBean.java
| | | `-- DubboSpringBootStarterConstants.java
| | |-- context
| | | `-- event
| | | `-- DubboBannerApplicationListener.java
| | |-- health
| | | `-- DubboHealthIndicator.java
| | |-- listener
| | | `-- ConsumerSubscribeListener.java
| | `-- server
| | `-- DubboServer.java
| `-- resources
| `-- META-INF
| |-- dubbo
| | `-- com.alibaba.dubbo.rpc.InvokerListener
| |-- spring.factories
| `-- spring.provides
`-- test
`-- java
`-- com
`-- alibaba
`-- dubbo
`-- spring
`-- boot
|-- Mock.java
`-- testcase
`-- ClassIdBeanTest.java
首先我们分析一下dubbo starter做了什么事情?
其实每一个spring boot项目的starter的本质都是一样:在你的spring容器中注入特定的bean来达到特定效果。
dubbo的核心是暴漏服务和代理消费。通过ServiceConfig来暴漏服务,通过ReferenceConfig来代理消费。dubbo starter正是围绕这两个核心来实现的。
dubbo starter入口
确切的说不是dubbo starter的入口,spring boot所有的starter的入口都一样,从spring boot提供的扩展方式spring.factories来切入。
dubbo starter的配置如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration
org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener
上述配置了三个Configration和一个ApplicationListener,DubboBannerApplicationListener我们暂时忽略,从名字就可以看出,只是注入dubbo的banner。我们重点关注三个Configration:
1、DubboAutoConfiguration
@Configuration
@EnableConfigurationProperties(DubboProperties.class)
public class DubboAutoConfiguration {
/**
* Start a non-daemon thread
*
* @return DubboServer
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.dubbo", name = "server", havingValue = "true")
public DubboServer dubboServer() {
final DubboServer dubboServer = new DubboServer();
final CountDownLatch latch = new CountDownLatch(1);
Thread awaitThread = new Thread("dubboServer") {
@Override
public void run() {
latch.countDown();
dubboServer.await();
}
};
awaitThread.setContextClassLoader(this.getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
try {
latch.await();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return dubboServer;
}
@Bean
public DubboHealthIndicator dubboHealthIndicator() {
return new DubboHealthIndicator();
}
}
启动了一个非守护线程,用来hold住dubbo服务。类似我常做的System.in.read。
2、DubboProviderAutoConfiguration
@Configuration
@ConditionalOnClass(Service.class)
@ConditionalOnBean(annotation = EnableDubboConfiguration.class)
@AutoConfigureAfter(DubboAutoConfiguration.class)
@EnableConfigurationProperties(DubboProperties.class)
public class DubboProviderAutoConfiguration extends DubboCommonAutoConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private DubboProperties properties;
@PostConstruct
public void init() throws Exception {
Map<String, Object> beanMap = this.applicationContext.getBeansWithAnnotation(Service.class);
if (beanMap != null && beanMap.size() > 0) {
this.initIdConfigMap(this.properties);
for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
this.initProviderBean(entry.getKey(), entry.getValue());
}
}
}
private void initProviderBean(String beanName, Object bean) throws Exception {
Service service = this.applicationContext.findAnnotationOnBean(beanName, Service.class);
ServiceBean<Object> serviceConfig = new ServiceBean<Object>(service);
if ((service.interfaceClass() == null || service.interfaceClass() == void.class)
&& (service.interfaceName() == null || "".equals(service.interfaceName()))) {
Class<?>[] interfaces = bean.getClass().getInterfaces();
if (interfaces.length > 0) {
serviceConfig.setInterface(interfaces[0]);
}
}
Environment environment = this.applicationContext.getEnvironment();
String application = service.application();
serviceConfig.setApplication(this.parseApplication(application, this.properties, environment,
beanName, "application", application));
String module = service.module();
serviceConfig.setModule(
this.parseModule(module, this.properties, environment, beanName, "module", module));
String[] registries = service.registry();
serviceConfig.setRegistries(
this.parseRegistries(registries, this.properties, environment, beanName, "registry"));
String[] protocols = service.protocol();
serviceConfig.setProtocols(
this.parseProtocols(protocols, this.properties, environment, beanName, "registry"));
String monitor = service.monitor();
serviceConfig.setMonitor(
this.parseMonitor(monitor, this.properties, environment, beanName, "monitor", monitor));
String provider = service.provider();
serviceConfig.setProvider(
this.parseProvider(provider, this.properties, environment, beanName, "provider", provider));
serviceConfig.setApplicationContext(this.applicationContext);
serviceConfig.afterPropertiesSet();
serviceConfig.setRef(bean);
//关键方法 暴漏服务
serviceConfig.export();
}
@Override
protected String buildErrorMsg(String... errors) {
if (errors == null || errors.length != 3) {
return super.buildErrorMsg(errors);
}
return new StringBuilder().append("beanName=").append(errors[0]).append(", ").append(errors[1])
.append("=").append(errors[2]).append(" not found in multi configs").toString();
}
}
DubboProviderAutoConfiguration这个类用来暴漏dubbo服务,它继承了DubboCommonAutoConfiguration,获得一些解析能力。此类最核心的就是initProviderBean的最后一行代码 serviceConfig.export()。它将从spring容器中获取所有实现Service接口的bean,经过解析,最后通过serviceConfig来暴漏服务。
3、DubboConsumerAutoConfiguration
@Configuration
@ConditionalOnClass(Service.class)
@ConditionalOnBean(annotation = EnableDubboConfiguration.class)
@AutoConfigureAfter(DubboAutoConfiguration.class)
@EnableConfigurationProperties(DubboProperties.class)
public class DubboConsumerAutoConfiguration extends DubboCommonAutoConfiguration {
private static final Map<ClassIdBean, Object> DUBBO_REFERENCES_MAP =
new ConcurrentHashMap<ClassIdBean, Object>();
@Autowired
private ApplicationContext applicationContext;
@Autowired
private DubboProperties properties;
public static Object getDubboReference(ClassIdBean classIdBean) {
return DUBBO_REFERENCES_MAP.get(classIdBean);
}
@Bean
public BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
Class<?> objClz;
if (AopUtils.isAopProxy(bean)) {
objClz = AopUtils.getTargetClass(bean);
} else {
objClz = bean.getClass();
}
try {
for (Field field : objClz.getDeclaredFields()) {
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
DubboConsumerAutoConfiguration.this
.initIdConfigMap(DubboConsumerAutoConfiguration.this.properties);
ReferenceBean<?> referenceBean =
DubboConsumerAutoConfiguration.this.getConsumerBean(beanName, field, reference);
Class<?> interfaceClass = referenceBean.getInterfaceClass();
String group = referenceBean.getGroup();
String version = referenceBean.getVersion();
ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
Object dubboReference =
DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
if (dubboReference == null) {
synchronized (this) {
// double check
dubboReference =
DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
if (dubboReference == null) {
referenceBean.afterPropertiesSet();
// dubboReference should not be null, otherwise it will cause
// NullPointerException
dubboReference = referenceBean.getObject();
DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.put(classIdBean,
dubboReference);
}
}
}
field.setAccessible(true);
field.set(bean, dubboReference);
}
}
} catch (Exception e) {
throw new BeanCreationException(beanName, e);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
};
}
/**
* init consumer bean
*
* @param reference reference
* @return ReferenceBean<T>
* @throws BeansException BeansException
*/
private <T> ReferenceBean<T> getConsumerBean(String beanName, Field field, Reference reference)
throws BeansException {
ReferenceBean<T> referenceBean = new ReferenceBean<T>(reference);
if ((reference.interfaceClass() == null || reference.interfaceClass() == void.class)
&& (reference.interfaceName() == null || "".equals(reference.interfaceName()))) {
referenceBean.setInterface(field.getType());
}
Environment environment = this.applicationContext.getEnvironment();
String application = reference.application();
referenceBean.setApplication(this.parseApplication(application, this.properties, environment,
beanName, field.getName(), "application", application));
String module = reference.module();
referenceBean.setModule(this.parseModule(module, this.properties, environment, beanName,
field.getName(), "module", module));
String[] registries = reference.registry();
referenceBean.setRegistries(this.parseRegistries(registries, this.properties, environment,
beanName, field.getName(), "registry"));
String monitor = reference.monitor();
referenceBean.setMonitor(this.parseMonitor(monitor, this.properties, environment, beanName,
field.getName(), "monitor", monitor));
String consumer = reference.consumer();
referenceBean.setConsumer(this.parseConsumer(consumer, this.properties, environment, beanName,
field.getName(), "consumer", consumer));
referenceBean.setApplicationContext(DubboConsumerAutoConfiguration.this.applicationContext);
return referenceBean;
}
@Override
protected String buildErrorMsg(String... errors) {
if (errors == null || errors.length != 4) {
return super.buildErrorMsg(errors);
}
return new StringBuilder().append("beanName=").append(errors[0]).append(", field=")
.append(errors[1]).append(", ").append(errors[2]).append("=").append(errors[3])
.append(" not found in multi configs").toString();
}
}
DubboConsumerAutoConfiguration是消费端的关键类,核心方法是beanPostProcessor(),该方法会创建一个Spring 的 BeanPostProcessor,并实现postProcessBeforeInitialization,在bean初始化完成后实现,判断bean是否是Reference,如果是Reference,则为其生成代理类。
通过上面三个类就完成了dubbo-starter的核心功能。
网友评论