本文主要介绍dubbo与spring的集成细节,阅读本文前最好对springIOC的运作流程有较好的掌握 springIOC详解
dubbo是如何做到与spring集成的?这都依赖于Spring提供的XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义XML Bean解析器,并集成到SpringIOC容器中。
创建自定义扩展,主要有以下步骤:
1、创建XML Schema 文件,描述自定义的合法构建模块,也就是xsd文件,主要用于定义数据约束;
2、自定义个处理器类,并实现NamespaceHandler接口,在里面注册各个标签对应的BeanDefinitionParser;
3、自定义一个或多个解析器,实现BeanDefinitionParser接口,用于定义Bean的解析逻辑;
Spring扩展机制
Spring在解析Bean的时候,会判断要解析的BeanDefinition是否属于默认的命名空间,例如<bean>标签。这里会分两个流程:解析Spring本身相关的BeanDefinition;解析用户自定义相关的BeanDefinition。DefaultBeanDefinitionDocumentReader中定义了parseBeanDefinitions方法,然后委托给BeanDefinitionParserDelegate进行相关操。如下:
代码语言:javascript复制protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i ) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 默认解析
parseDefaultElement(ele, delegate);
}else {
// 自定义解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在此之前,先看看Spring中是怎么将各个XML Schema文件和NamespaceHandler对应起来的。
DefaultNamespaceHandlerResolver
代码语言:javascript复制public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
// 映射文件路径默认在 META-INF/spring.handlers,可以存在于多个jar文件中
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
// 资源路径
private final String handlerMappingsLocation;
// 缓存 namespace URI:NamespaceHandler 映射关系
private volatile Map<String, Object> handlerMappings;
public DefaultNamespaceHandlerResolver() {
this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
/**
* 根据 namespace URI 加载 NamespaceHandler
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler}, or {@code null} if none found
*/
@Override
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" className "] for namespace [" namespaceUri
"] does not implement the [" NamespaceHandler.class.getName() "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 实例化之后会调用init方法
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" className "] for namespace ["
namespaceUri "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" className "] for namespace ["
namespaceUri "]: problem with handler class file or dependent class", err);
}
}
}
private Map<String, Object> getHandlerMappings() {
// 只会在第一次调用的时候执行以下逻辑,即只会加载一次,只会就从缓存中获取了
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
// 加载所有配置文件:META-INF/spring.handlers
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
// 将 Properties解析成 Map
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" this.handlerMappingsLocation "]", ex);
}
}
}
}
return this.handlerMappings;
}
@Override
public String toString() {
return "NamespaceHandlerResolver using mappings " getHandlerMappings();
}
}
那么的DefaultNamespaceHandlerResolver的resolve方法是在什么时候被调用呢?前面提到,在Spring启动解析Bean时有可能涉及到自定义解析,即:delegate.parseCustomElement(root),看看它的代码
parseCustomElement
代码语言:javascript复制public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
// 这里就调用了resolve方法
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" namespaceUri "]", ele);
return null;
}
// 调用NamespaceHandler的parse方法,其内部委托各种BeanDefinitionParser进行真正的解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
集成Spring
前面已经介绍了Spring扩展机制的原理,dubbo也就是利用这种机制与spring实现集成的,下面简单介绍这个流程
XML Schema文件
dubbo的XML Schema文件位于dubbo-config模块下的dubbo-config-spring模块中:
dubbo.xsd文件的内容比较多,就不过多介绍了,主要就是定义了一些约束,重点看看spring.handlers的文件内容:
代码语言:javascript复制http://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
其实就是定义了namespace url 和 NamespaceHandler 的映射关系,从这里可以知道,dubbo相关命名空间的解析主要就是借助于DubboNamespaceHandler
DubboNamespaceHandler
代码语言:javascript复制// NamespaceHandlerSupport是一个抽象类,实现了NamespaceHandler接口
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
NamespaceHandlerSupport是一个抽象类,实现了NamespaceHandler接口,里面提供了一些默认实现,所以在自定义NamespaceHandler的时候,只要继承NamespaceHandlerSupport即可。
DubboNamespaceHandler中的init方法在上面已经介绍过,在调用DefaultNamespaceHandlerResolver的resolve方法的时候,实例化NamespaceHandler会被执行。
DubboBeanDefinitionParser
registerBeanDefinitionParser主要就是将标签与对应的BeanDefinitionParser缓存到一个Map中。这里涉及到一个DubboBeanDefinitionParser,它实现了BeanDefinitionParser接口,调用NamespaceHandler的parse方法的时,其内部委托DubboBeanDefinitionParser执行真正的Bean解析逻辑
代码语言:javascript复制private final Map<String, BeanDefinitionParser> parsers =new HashMap<String, BeanDefinitionParser>();
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
NamespaceHandler的parse方法在NamespaceHandlerSupport中有默认实现,如下:
代码语言:javascript复制public BeanDefinition parse(Element element, ParserContext parserContext) {
// 委托委托DubboBeanDefinitionParser执行真正的Bean解析逻辑
return findParserForElement(element, parserContext).parse(element, parserContext);
}
// 根据标签找到对应的DubboBeanDefinitionParser
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" localName "]", element);
}
return parser;
}
DubboBeanDefinitionParser中的代码比较多,如果你不是很熟悉dubbo中xml配置方式,估计很难看懂。项目中一般都是通过注解的使用引用服务,xml太过繁琐,简单看看代码吧,有机会再补充
代码语言:javascript复制public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
// 这里的beanClass主要有:ApplicationConfig.class、ModuleConfig.class、RegistryConfig.class、MonitorConfig.class、ProviderConfig.class、
// ConsumerConfig.class、ServiceBean.class、ReferenceBean.class
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
// 处理标签中的id,如果id设置为必输但又没有给值,则取name属性的值,如果name属性也没有值,则不同beanClass有不同的取值逻辑
String id = element.getAttribute("id");
if ((id == null || id.length() == 0) && required) {
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) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName (counter );
}
}
// 如果有多个相同的id,抛IllegalStateException异常
if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " id);
}
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
// 当beanClass为ProtocolConfig.class
if (ProtocolConfig.class.equals(beanClass)) {
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));
}
}
}
}
// 当beanClass为ServiceBean.class
else if (ServiceBean.class.equals(beanClass)) {
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
// 设置beanClass为dubbo标签中真正配置的接口所对应的Class
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
parseProperties(element.getChildNodes(), classDefinition);
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id "Impl"));
}
}
// 当beanClass为ProviderConfig.class
else if (ProviderConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
}
// 当beanClass为ConsumerConfig.class
else if (ConsumerConfig.class.equals(beanClass)) {
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}
// 以下的逻辑主要用于获取一些属性的值,然后存到MutablePropertyValues中的 List<PropertyValue> propertyValueList。一个属性对应一个
Set<String> props = new HashSet<String>();
ManagedMap parameters = null;
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();
// 获取beanClass中的所有 public、只有一个参数 并且以set开头的方法
if (name.length() > 3 && name.startsWith("set")
&& Modifier.isPublic(setter.getModifiers())
&& setter.getParameterTypes().length == 1) {
Class<?> type = setter.getParameterTypes()[0];
// 这里就是为了得到要注入对象的属性名,但是有一点要注意:属性 name => name;属性 userName => user-name
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() name.substring(4), "-");
props.add(property);
// 得到get方法
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;
}
// 和dubbo中的一一些标签属性相关
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)) {
// backward compatibility for the default value in old version's xsd
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 "" ... />");
}
// backward compatibility
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName(value);
reference = protocol;
} 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 ("oninvoke".equals(property)) {
int index = value.lastIndexOf(".");
String invokeRef = value.substring(0, index);
String invokeRefMethod = value.substring(index 1);
reference = new RuntimeBeanReference(invokeRef);
beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod);
} 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;
}
解析的主要流程都在代码中有注释,还有一些细节就不过多说明了。
有关于各个标签和具体抽象类的对应关系,其实在DubboNamespaceHandler已经简单的写出来的
代码语言:javascript复制registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
有关于各个标签的简单用法,如下图:
为了让大家看的更具体一些,我从官网扒了一张图下来
有关于它们的继承关系:
以下是dubbo的一个经典xml配置
代码语言:javascript复制<!-- 应用信息 -->
<dubbo:application name="demo"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 使用zookeeper注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://localhost:2181" id="registry"/>
<!-- 默认的服务端配置 -->
<dubbo:provider registry="registry" retries="0" timeout="5000"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
<!-- 引用服务 -->
<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>
结合上面的的对应关系可以发现,大部分标签对应的Bean只是用于维护一些配置信息,比如:ApplicationConfig、ProtocolConfig、ProviderConfig等,这些Bean的实例化细节就不介绍了。重点关注ServiceBean和
ReferenceBean。
已经将dubbo中的标签解析成对应的RootBeanDefinition,接下来就是Spring中正常的Bean实例化流程。Spring在实例化Bean的时候预留了很多接口,也就是生命周期函数,在实例化Bean的时候可以进行各种扩展,dubbo也就是借助这些接口完成了很多的功能。有关于dubbo实例化Bean的细节,将在下一篇文章中详细介绍 【DUBBO】 Bean实例化