dubbo source code series 3-dubbo start bean generation

From the  dubbo source code series 1,  we have learned about the overall architecture design of dubbo. The following explains in detail how dubbo is seamlessly connected to spring boot to load its own beans when it starts.

dubbo startup method

1. Standlone mode: Load Spring boot through the Main method

2. Start with a container: Load Spring to start via tomcat, jetty, etc.

These two methods of startup are to integrate into spring startup to load their own beans. Dubbo takes advantage of spring's extensibility and seamlessly integrates its own startup process into spring startup.

dubbo startup process

The web container is not inherited in dubbo, so the default is to start in standlone mode, and the entrance must be the Main method, as shown in the following figure:

The main method in the Main class finally calls the start method of the container to start the container. The containers here include Log4jContainer, LogbackContainer and SpringContainer. The functions of these containers can be seen from the name.

Log4jContainer and LogbackContainer: log configuration function

SpringContainer: Spring container start

The start method of SpringContainer is to enable spring start , the code is as follows:

public class SpringContainer implements Container {

    public static final String SPRING_CONFIG = "dubbo.spring.config";
    public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
    private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
    static ClassPathXmlApplicationContext context;

    public static ClassPathXmlApplicationContext getContext() {
        return context;
    }

    @Override
    public void start() {
        // 加载 dubbo.spring.config 配置路径下的所有文件
        String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
        if (StringUtils.isEmpty(configPath)) {
            // 默认加载 classpath*:META-INF/spring/*.xml 路径下的所有文件
            configPath = DEFAULT_SPRING_CONFIG;
        }
        context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
        // spring 加载bean
        context.refresh();
        context.start();
    }

    @Override
    public void stop() {
        try {
            if (context != null) {
                context.stop();
                context.close();
                context = null;
            }
        } catch (Throwable e) {
            logger.error(e.getMessage(), e);
        }
    }

}

The refresh method of AbstractApplicationContext is called here . This method is the whole process of Spring boot loading bean implementation. In this , dubbo makes use of Spring's custom label extension function. Dubbo customizes spring.schemas and spring.schemas in the MATE-INF directory. spring.handlers and dubbo.xsd files , refer to spring custom tags  , as shown in the following figure:

MATE-INF/spring.schemas file:

MATE-INF/spring.handlers 文件:

MATE-INF/dubbo.xsd file:

Spring.schemas, spring.handlers and other file contents will be loaded during Spring startup, and dubbo's own namespace handler , DubboNamespaceHandler, is defined in dubbo's MATE-INF/ spring.handlers. When Spring loads a bean, it encounters dubbo namespace. DubboNamespaceHandler will be called to parse and process classes with dubbo tags . The key process is as follows:

1. The loadBeanDefinitions method of AbstractXmlApplicationContext loads all the spatial processors of MATE-INF/spring.handlers :

2. The parseBeanDefinitions method of DefaultBeanDefinitionDocumentReader controls the calling of the parse element method according to whether it is the default space

Default space: http://www.springframework.org/schema/beans , call the parseDefaultElement method to parse the element

Non-default space: http://dubbo.apache.org/schema/dubbo , call the parseCustomElement method of BeanDefinitionParserDelegate to parse custom namespace elements

3. The resolve method of DefaultNamespaceHandlerResolver obtains the corresponding space processor according to the namespace, and then calls the init method of NamespaceHandler

Next we look at the DubboNamespaceHandler class defined in dubbo , as shown below:

The main work of the init method of DubboNamespaceHandler is to register the correspondence between the custom element and the custom parser (DubboBeanDefinitionParser) in the parser cache . After registering the custom parser, the parse method of DubboBeanDefinitionParser (custom parser) is called to parse xml. The element generates BeanDefinition, and the process is as follows:

Here is to call  the parse method of DubboBeanDefinitionParser , the code is as follows:

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");
        if (StringUtils.isEmpty(id) && required) {
            String generatedBeanName = element.getAttribute("name");
            if (StringUtils.isEmpty(generatedBeanName)) {
                if (ProtocolConfig.class.equals(beanClass)) {
                    generatedBeanName = "dubbo";
                } else {
                    generatedBeanName = element.getAttribute("interface");
                }
            }
            if (StringUtils.isEmpty(generatedBeanName)) {
                generatedBeanName = beanClass.getName();
            }
            id = generatedBeanName;
            int counter = 2;
            while (parserContext.getRegistry().containsBeanDefinition(id)) {
                id = generatedBeanName + (counter++);
            }
        }
        if (StringUtils.isNotEmpty(id)) {
            if (parserContext.getRegistry().containsBeanDefinition(id)) {
                throw new IllegalStateException("Duplicate spring bean id " + id);
            }
            // bean 注册到Spring容器缓存中,以便Spring管理bean
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("id", id);
        }
        if (ProtocolConfig.class.equals(beanClass)) {
            // ProtocolConfig bean 生成
            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)) {
            // ServiceBean bean 生成
            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)) {
            // ProviderConfig bean 生成
            parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
        } else if (ConsumerConfig.class.equals(beanClass)) {
            // ConsumerConfig bean 生成
            parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
        }
        Set<String> props = new HashSet<>();
        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 beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4);
                String property = StringUtils.camelToSplitName(beanProperty, "-");
                props.add(property);
                // check the setter/getter whether match
                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) {
                        // ignore, there is no need any log here since some class implement the interface: EnvironmentAware,
                        // ApplicationAware, etc. They only have setter method, otherwise will cause the error log during application start up.
                    }
                }
                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(beanProperty, registryConfig);
                            } else if ("provider".equals(property) || "registry".equals(property) || ("protocol".equals(property) && ServiceBean.class.equals(beanClass))) {
                                /**
                                 * For 'provider' 'protocol' 'registry', keep literal value (should be id/name) and set the value to 'registryIds' 'providerIds' protocolIds'
                                 * The following process should make sure each id refers to the corresponding instance, here's how to find the instance for different use cases:
                                 * 1. Spring, check existing bean by id, see{@link ServiceBean#afterPropertiesSet()}; then try to use id to find configs defined in remote Config Center
                                 * 2. API, directly use id to find configs defined in remote Config Center; if all config instances are defined locally, please use {@link org.apache.dubbo.config.ServiceConfig#setRegistries(List)}
                                 */
                                beanDefinition.getPropertyValues().addPropertyValue(beanProperty + "Ids", value);
                            } 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(ONRETURN.equals(property) || ONTHROW.equals(property) || ONINVOKE.equals(property)) {
                                    int index = value.lastIndexOf(".");
                                    String ref = value.substring(0, index);
                                    String method = value.substring(index + 1);
                                    reference = new RuntimeBeanReference(ref);
                                    beanDefinition.getPropertyValues().addPropertyValue(property + METHOD, method);
                                } 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(beanProperty, 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;
    }

The parse method implements ServiceConfig, ReferenceConfig, ApplicationConfig, RegistryConfig, MetadataReportConfig, MonitorConfig, ProviderConfig, ConsumerConfig, ProtocolConfig, ConfigCenterConfig and other corresponding BeanDefinitions in Dubbo. These BeanDefinitions are registered in the Spring container and handed over to the Spring container to uniformly manage all beans.

The Spring container finally calls the getBean method of the ClassPathXmlApplicationContext class to convert the BeanDefinition into the corresponding bean object 

reference:

https://www.jianshu.com/p/16b72c10fca8

https://segmentfault.com/a/1190000007047168

https://blog.csdn.net/MrZhangXL/article/details/78636494

Guess you like

Origin blog.csdn.net/ywlmsm1224811/article/details/102590449