Today we are going to talk about Dubbo XML configuration. I plan to introduce this part into the following parts:
- Dubbo XML
- Spring custom XML tag parsing
- Dubbo custom XML tag parsing
- DubboBeanDefinitionParser.parse()
- End
Dubbo XML
Before starting this section let’s take a look at an example Dubbo XML configuration file:
dubbo-demo-provider.xml
<?xml version="1.0" encoding="UTF-8"? >
<! -- <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <! -- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<! -- use multicast registry center to export service -->
<! - "dubbo: registry address =" multicast: / / 224.5.6.7:1234 "/ > -- >
<dubbo:registry address=Zookeeper: / / 10.14.22.68: "2181"/>
<! -- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<! -- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<! -- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>
Copy the code
There are some XML tags in this configuration file that start with Dubbo, which intuitively tells us is closely related to Dubbo. So what are these labels for? And how are they recognized? Let’s talk about how Dubbo defines and loads these custom tags in conjunction with Spring’s custom XML tag implementation.
Spring custom XML tag parsing
Custom XML tags in Dubbo actually rely on Spring’s ability to parse custom tags. There are many articles about Spring parsing custom XML tags on the Internet. Here we only introduce the required files to achieve the relevant functions, to give you an intuitive impression, not to go into the Spring custom tag implementation for detailed analysis.
- XML Schemas Definition (XSD) refers to XML structure definitions. We can not only define new elements and attributes through XSD files, but also use it to constrain our XML file specification. A similar implementation can be found in the Dubbo project: dubo.xsd
- Spring.schemas This configuration file specifies the mapping between custom namespaces and XSD files for the Spring container to be aware of the location of our custom XSD files.
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
Copy the code
- Handlers This configuration file specifies the mapping between a custom namespace and the NamespaceHandler class. The NamespaceHandler class is used to register custom tag parsers.
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
Copy the code
- The namespace handler is mainly used to register the BeanDefinitionParser. Corresponding to the DubboNamespaceHandler in the Spring. handlers file above
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init(a) {
registerBeanDefinitionParser("application".new DubboBeanDefinitionParser(ApplicationConfig.class, true));
/ / to omit...
registerBeanDefinitionParser("annotation".newAnnotationBeanDefinitionParser()); }}Copy the code
- BeanDefinitionParser The BeanDefinitionParser implements the parse method in the BeanDefinitionParser interface for custom tag parsing. Dubbo in corresponding DubboBeanDefinitionParser class.
Dubbo parses custom XML tags
Here comes the main part of this article. Before introducing Dubbo’s custom XML tag parsing, here’s a diagram to help you understand how Spring parses and loads beans from XML files.
BeanDefinitionParserDelegate
Figure above process is longer, we’ll see BeanDefinitionParserDelegate emphatically several key methods in class.
BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// Get the namespaceURI of the current element
/ / such as dubbo. For http://dubbo.apache.org/schema/dubbo in XSD
String namespaceUri = this.getNamespaceURI(ele);
// Get the corresponding NamespaceHandler based on the URI
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
} else {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }}Copy the code
This method does three things
- Gets the Element’s namespaceURI and the corresponding NamespaceHandler object. Dubbo custom tags (such as Dubbo: the provider) namespaceUri values for http://dubbo.apache.org/schema/dubbo;
- Obtain the corresponding NamespaceHandler object according to the namespaceUri obtained by step1. Here is called DefaultNamespaceHandlerResolver class resolve () method, we will analysis the following;
- To call the handler’s parse() method, our custom handler inherits the NamespaceHandlerSupport class. Post analysis;
Before analyzing the resolver() and parse() methods involved in step2 and step3 in detail, let’s start with a sequence diagram to give you a basic idea:
DefaultNamespaceHandlerResolver.java
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = this.getHandlerMappings();
// Get the corresponding handlerOrClassName with namespaceUri as Key
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler)handlerOrClassName;
} else {
// If not empty and not an instance of NamespaceHandler, convert to String
// DubboNamespaceHandler executes this logic
String className = (String)handlerOrClassName;
try{ Class<? > handlerClass = ClassUtils.forName(className,this.classLoader);
// Whether handlerClass is an implementation class of NamespaceHandler. If not, throw an exception
if(! NamespaceHandler.class.isAssignableFrom(handlerClass)) {throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
} else {
// Initialize the handlerClass
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
// Execute the init() method of the handlerClass class
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
returnnamespaceHandler; }}catch (ClassNotFoundException var7) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
} catch (LinkageError var8) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8); }}}Copy the code
The resolve() method is used to get the NamespaceHandler object based on the namespaceUri in the method argument. An attempt will be made to fetch an object from the handlerMappings collection using the namespaceUri key. If handlerOrClassName is not null and is not an instance of NamespaceHandler. Then try to handlerOrClassName as the className and call the BeanUtils. InstantiateClass () method to initialize a NamespaceHandler instance. After initialization, its init() method is called. This init() method is important, so let’s move on.
DubboNamespaceHandler
public void init(a) {
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
private final Map<String, BeanDefinitionParser> parsers = new HashMap();
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
Copy the code
The init() method in the DubboNamespaceHandler class does something very simple, Is new DubboBeanDefinitionParser object and put it in parsers NamespaceHandlerSupport class collection. Let’s revisit the parseCustomElement() method.
BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
/ / to omit...
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
/ / to omit...
}
Copy the code
The Parse () method of the NamespaceHandlerSupport class is called. Let’s keep tracking.
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
Copy the code
Is there a sense of clarity here? The resolve() method actually gets the corresponding NamespaceHandler object (DubboNamespaceHandler for Dubbo) based on the current Element’s namespaceURI, And then call the DubboNamespaceHandler init () method is a new DubboBeanDefinitionParser from the set of objects and register to NamespaceHandlerSupport class parsers. The Parser method then gets the appropriate BeanDefinitionParser object from the parsers collection based on the current Element object. For Dubbo elements, finally actually enforce a DubboBeanDefinitionParser parse () method.
DubboBeanDefinitionParser.parse()
Finally, let’s look at a detailed implementation of Dubbo parsing XML files. If you are not interested in the implementation, you can skip it.
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<? > beanClass,boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
String id = element.getAttribute("id");
/ / DubboBeanDefinitionParser constructors are initialized to the required values;
/ / the init method of DubboNamespaceHandler class will create and register DubboBeanDefinitionParser class
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 {
// The name property is empty and not of type ProtocolConfig
generatedBeanName = element.getAttribute("interface"); }}if (generatedBeanName == null || generatedBeanName.length() == 0) {
// Get the fully qualified class name of the beanClass
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
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);
}
/ / register beanDefinition
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
// Add the id attribute to beanDefinition
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}
// If the current beanClass type is ProtocolConfig
// Traverses the registered bean object, if the bean object contains the protocol attribute
// Protocol is ProtocolConfig instance and name is the same as the current ID. Adds the protoCl attribute to the current beanClass object
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".newRuntimeBeanReference(id)); }}}}else if (ServiceBean.class.equals(beanClass)) {
// If the current element contains a class attribute, call reflectutils.forname () to load the class object
// Call parseProperties to resolve the other property Settings into the classDefinition object
// Finally set the ref attribute of beanDefinition to BeanDefinitionHolder wrapper class
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)) {
parseNested(element, parserContext, ServiceBean.class, true."service"."provider", id, beanDefinition);
} else if (ConsumerConfig.class.equals(beanClass)) {
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 propertyName = name.substring(3.4).toLowerCase() + name.substring(4);
String property = StringUtils.camelToSplitName(propertyName, "-");
props.add(property);
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), newClass<? > [0]);
} catch (NoSuchMethodException e) {
try {
getter = beanClass.getMethod("is" + name.substring(3), newClass<? > [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 the attribute is Registry and the value of the Registry attribute is "N/A", the identity will not be registered with any registry
// Create a new RegistryConfig and set it to the Registry property of beanDefinition
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) {
// Multi-registry resolution
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) {
/ / agreement
parseMultiRef("protocols", value, beanDefinition, parserContext);
} else {
Object reference;
if (isPrimitive(type)) {
// Type is the method parameter. Type is a basic 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)) {
// Old version XSD compatibility processing
// 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 the protocol property value has a corresponding extended implementation and is not registered in the Spring registry
// The corresponding bean in the Spring registry is not protocolConfig.class
if ("dubbo:provider".equals(element.getTagName())) {
logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\"... /> to + 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 the ref attribute value is already registered in the Spring registry
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
// A non-singleton exception is thrown
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(propertyName, 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, newTypedStringValue(value, String.class)); }}if(parameters ! =null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}
return beanDefinition;
}
Copy the code
The above large section on the configuration of the parsing code needs to be combined with the actual code debugging to better understand. I also took the time to understand Dubbo XML parsing again and again. ProtocolConfig and Protocol loading order:
dubbo-demo-provider.xml
<dubbo:protocol name="dubbo" port="20880"/>
Copy the code
- When we resolve the ProtocolConfig element first, we iterate over all beans in the registered Spring registry. If the bean object has a protocol attribute and matches name and the current ProtolConfig ID, a new RuntimeBeanReference object is created to override the protocol attribute. For the above configuration, we end up creating a New beanDefinition object with a name and port.
- ProtocolConfig is not resolved when the Protocol element is parsed first. At this time we cannot find the corresponding ProtocolConfig Bean in the Spring registry. At this point we will need to create a new ProtocolConfig and set its name property to the current property value. Finally, it is set to the Protocol property of the beanDefinition object. When the ProtocolConfig element is loaded later, the value of protocol is replaced.
End
Dubbo’s definition and parsing of custom XML tags actually leverage the Spring framework’s support for custom XML tags. Although this article is tedious and lengthy, it is important to understand the Dubbo initialization process. We’ll talk about Dubbo service exposure later.
Original articles on this BLOG are not allowed to be used for commercial purposes and traditional media without my permission. Network media reprint please indicate the source, otherwise belongs to infringement. https://juejin.cn/post/6844903746535702536