Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
1. spring Schema扩展
spring 提供对自定义配置文件的扩展解析,完成一个自定义配置一般需要以下步骤:
- 设计配置属性和JavaBean
- 编写XSD文件
- 编写NamespaceHandler和BeanDefinitionParser完成解析工作
- 编写spring.handlers和spring.schemas串联起所有部件
- 在Bean文件中应用
2. dubbo spring Schema扩展解析
- 首先來看dubbo spring的简单配置本文的配置(dubbo源码demo项目中的dubbo-demo-provider.xml):
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="demo-provider"/>
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 声明需要暴露的服务接口 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- 和本地bean一样实现服务 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
其中自定义的element标签(dubbo还有其他标签,在此不每个说明):
- dubbo:application
- dubbo:registry
- dubbo:protocol
- dubbo:service
这些标签spring默认是无法识别的,需要dubbo对spring Schema进行扩展,dubbo服务则按照spring的扩展规则进行扩展
上面的自定义标签在dubbo服务中分配有对应的bean实体
- dubbo:application
public class ApplicationConfig extends AbstractConfig {
private static final long serialVersionUID = 5508512956753757169L;
// 应用名称
private String name;
// 模块版本
private String version;
// 应用负责人
private String owner;
// 组织名(BU或部门)
private String organization;
// 分层
private String architecture;
// 环境,如:dev/test/run
private String environment;
// Java代码编译器
private String compiler;
// 日志输出方式
private String logger;
// 注册中心
private List<RegistryConfig> registries;
// 服务监控
private MonitorConfig monitor;
// 是否为缺省
private Boolean isDefault;
- dubbo:registry
public class RegistryConfig extends AbstractConfig {
public static final String NO_AVAILABLE = "N/A";
private static final long serialVersionUID = 5508512956753757169L;
// 注册中心地址
private String address;
// 注册中心登录用户名
private String username;
// 注册中心登录密码
private String password;
// 注册中心缺省端口
private Integer port;
// 注册中心协议
private String protocol;
// 客户端实现
private String transporter;
private String server;
private String client;
private String cluster;
private String group;
private String version;
// 注册中心请求超时时间(毫秒)
private Integer timeout;
// 注册中心会话超时时间(毫秒)
private Integer session;
// 动态注册中心列表存储文件
private String file;
// 停止时等候完成通知时间
private Integer wait;
// 启动时检查注册中心是否存在
private Boolean check;
// 在该注册中心上注册是动态的还是静态的服务
private Boolean dynamic;
// 在该注册中心上服务是否暴露
private Boolean register;
// 在该注册中心上服务是否引用
private Boolean subscribe;
// 自定义参数
private Map<String, String> parameters;
// 是否为缺省
private Boolean isDefault;
- dubbo:protocol
public class ProtocolConfig extends AbstractConfig {
// 服务协议
private String name;
// 服务IP地址(多网卡时使用)
private String host;
// 服务端口
private Integer port;
// 上下文路径
private String contextpath;
// 线程池类型
private String threadpool;
// 线程池大小(固定大小)
private Integer threads;
// IO线程池大小(固定大小)
private Integer iothreads;
// 线程池队列大小
private Integer queues;
// 最大接收连接数
private Integer accepts;
// 协议编码
private String codec;
// 序列化方式
private String serialization;
// 字符集
private String charset;
// 最大请求数据长度
private Integer payload;
// 缓存区大小
private Integer buffer;
// 心跳间隔
private Integer heartbeat;
// 访问日志
private String accesslog;
// 网络传输方式
private String transporter;
// 信息交换方式
private String exchanger;
// 信息线程模型派发方式
private String dispatcher;
// 对称网络组网方式
private String networker;
// 服务器端实现
private String server;
// 客户端实现
private String client;
// 支持的telnet命令,多个命令用逗号分隔
private String telnet;
// 命令行提示符
private String prompt;
// status检查
private String status;
// 是否注册
private Boolean register;
// 参数
private Map<String, String> parameters;
// 是否为缺省
private Boolean isDefault;
- dubbo:service
public abstract class AbstractServiceConfig extends AbstractInterfaceConfig {
private static final long serialVersionUID = 1L;
// 服务版本
protected String version;
// 服务分组
protected String group;
// 服务是否已经deprecated
protected Boolean deprecated;
// 延迟暴露
protected Integer delay;
// 是否暴露
protected Boolean export;
// 权重
protected Integer weight;
// 应用文档
protected String document;
// 在注册中心上注册成动态的还是静态的服务
protected Boolean dynamic;
// 是否使用令牌
protected String token;
// 访问日志
protected String accesslog;
protected List<ProtocolConfig> protocols;
// 允许执行请求数
private Integer executes;
// 是否注册
private Boolean register;
public class ServiceConfig<T> extends AbstractServiceConfig {
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private static final Map<String, Integer> RANDOM_PORT_MAP = new HashMap<String, Integer>();
private final List<URL> urls = new ArrayList<URL>();
private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
// 接口类型
private String interfaceName;
private Class<?> interfaceClass;
// 接口实现类引用
private T ref;
// 服务名称
private String path;
// 方法配置
private List<MethodConfig> methods;
private ProviderConfig provider;
private transient volatile boolean exported;
private transient volatile boolean unexported;
private volatile String generic;
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
private static transient ApplicationContext SPRING_CONTEXT;
private transient ApplicationContext applicationContext;
private transient String beanName;
private transient boolean supportedApplicationListener;
可以在dubbo的配置实体中看到我们日常配置的参数信息在这些实体中都有对应的属性信息 ,目前我们看到了dubbo设计配置属性和JavaBean 完成了spring Schema扩展的第一步,
第二步需要定义配置的xsd规则,dubbo的xsd规则文件在源码的config工程资源文件下dubbo.xsd 这里面定义了标签有哪些属性,属性的取值范围等,具体怎么编写可以XML Schema教程学习。
- 编写NamespaceHandler和BeanDefinitionParser完成解析工作
- DubboNamespaceHandler
/**
* dubbospring配置扩展解析处理器
* DubboNamespaceHandler
*
* @author william.liangf
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
//init方法继承 spring NamespaceHandlerSupport 的方法,负责初始化对各个配置解析器的注册。
//在这里,我们可以看到dubbo分別对不同的element标注册了相同的解析器,只是传入的解析配置实体不同,可以看出duubo是用相同的解析器去根据不同标签解析组装不同的配置信息
public void init() {
//注册对应elementName的解析器
//<dubbo:application />
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
//<dubbo:module />
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
//<dubbo:registry />
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
//<dubbo:monitor />
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
//<dubbo:provider />
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
//<dubbo:service />
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}
- DubboBeanDefinitionParser
// DubboBeanDefinitionParser 解析element组装加载配置实体真正的地方
//从传入参数可以看到,给定了element的信息,以及需要构建组装配置类型Class<?> beanClass
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
//获取ID 此ID为全局唯一的需要注册到spring容器中
String id = element.getAttribute("id");
if ((id == null || id.length() == 0) && required) {
//ID属性为空时,默认获取name属性为ID
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
if (generatedBeanName == null || generatedBeanName.length() == 0) {
//当ID,name属性都不存在时默认加载className为当前的ID
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
//校验是否全局唯一,当不唯一 ,默认给ID后加入序列号
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
//将配置实例注册到spring容器中
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
//下面所有操作都是根据element标签配置的属性信息设置bean的属性字段值,
//dubbo是根据反射拿到所有有set方法的属性名称,在标签中查找是否设置了当前属性的值,如果设置了,将值写入到bean实体中,如果设置的是另一个实体信息会创建RuntimeBeanReference类型的值放入到实体中
//在Spring的解析段,其实容器中是没有依赖的Bean的实例的因此,那么这是这个被依赖的Bean如何在BeanDefinition中表示呢?
//答案就是RuntimeBeanReference,在解析到依赖的Bean的时侯,解析器会依据依赖bean的name创建一个RuntimeBeanReference对像,将这个对像放入BeanDefinition的MutablePropertyValues中。
if (ProtocolConfig.class.equals(beanClass)) {
//解析<dubbo:protocol/>
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
Object value = property.getValue();
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
} else if (ServiceBean.class.equals(beanClass)) {
//解析<dubbo:service/>
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
} else if (ProviderConfig.class.equals(beanClass)) {
//解析<dubbo:provider/>
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
//解析<dubbo:consumer/>
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
Set<String> props = new HashSet<String>();
ManagedMap parameters = null;
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
Class<?> type = setter.getParameterTypes()[0];
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");
props.add(property);
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
try {
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
}
}
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
} else if ("methods".equals(property)) {
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
} else if ("arguments".equals(property)) {
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
String value = element.getAttribute(property);
if (value != null) {
value = value.trim();
if (value.length() > 0) {
if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig);
} else if ("registry".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("registries", value, beanDefinition, parserContext);
} else if ("provider".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("providers", value, beanDefinition, parserContext);
} else if ("protocol".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("protocols", value, beanDefinition, parserContext);
} else {
Object reference;
if (isPrimitive(type)) {
if ("async".equals(property) && "false".equals(value)
|| "timeout".equals(property) && "0".equals(value)
|| "delay".equals(property) && "0".equals(value)
|| "version".equals(property) && "0.0.0".equals(value)
|| "stat".equals(property) && "-1".equals(value)
|| "reliable".equals(property) && "false".equals(value)) {
// 兼容旧版本xsd中的default值
value = null;
}
reference = value;
} else if ("protocol".equals(property)
&& ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value)
&& (!parserContext.getRegistry().containsBeanDefinition(value)
|| !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
if ("dubbo:provider".equals(element.getTagName())) {
logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />");
}
// 兼容旧版本配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName(value);
reference = protocol;
} else if ("monitor".equals(property)
&& (!parserContext.getRegistry().containsBeanDefinition(value)
|| !MonitorConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {
// 兼容旧版本配置
reference = convertMonitor(value);
} else if ("onreturn".equals(property)) {
int index = value.lastIndexOf(".");
String returnRef = value.substring(0, index);
String returnMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(returnRef);
beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
} else if ("onthrow".equals(property)) {
int index = value.lastIndexOf(".");
String throwRef = value.substring(0, index);
String throwMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(throwRef);
beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
} else {
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
if (!refBean.isSingleton()) {
throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
}
}
reference = new RuntimeBeanReference(value);
}
beanDefinition.getPropertyValues().addPropertyValue(property, reference);
}
}
}
}
}
}
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node node = attributes.item(i);
String name = node.getLocalName();
if (!props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
parameters.put(name, new TypedStringValue(value, String.class));
}
}
if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
return beanDefinition;
}
上面这些完成配置的解析以及实体的组装,但没有被纳入spring的体系,是无法工作的。接下来完成编写spring.handlers和spring.schemas串联起所有部件纳入spring的体系,让spring识别,并完成将自定义的配置分配给我们自定义的解析器负责处理
- 编写spring.handlers和spring.schemas串联起所有部件
dubbo配置在config工程的资源文件夹放置。
- spring.handlers
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
- spring.schemas
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
到此,dubbo对spring的扩展已经完成,我们可以很轻松的通过spring的配置来集成dubbo服务,并且对原有的程序无任何侵入。同时通过跟踪dubbo的扩展,也学习了扩展机制,可以定制化自己的spring扩展应用。
网友评论