精通Dubbo——dubbo2.0源码中Spring Bean的加载
2017-06-05 22:15
597 查看
感觉Dubbo涉及的知识点非常多,在这里我们以学习为目的,遇到相关知识点都展开来详细介绍,这也是一个学习的过程。
由于Dubbo的启动注册都是依赖Spring的加载来实现的,我们先来分析下Spring Bean的加载过程。在Dubbo的源码中有个dubbo-demo工程
1、设计配置属性和JavaBean
2、编写XSD文件
3、编写NamespaceHandler和BeanDefinitionParser完成解析工作
4、编写spring.handlers和spring.schemas串联起所有部件
5、在Bean文件中应用
1、设计配置属性和JavaBean
id默认需要
2、编写XSD文件
为上一步设计好的配置项编写XSD文件,XSD是schema的定义文件,配置的输入和解析输出都是以XSD为契约,本例中XSD如下:
关于xsd:schema的各个属性具体含义就不作过多解释,可以参见http://www.w3school.com.cn/schema/schema_schema.asp
对应着配置项节点的名称,因此在应用中会用people作为节点名来引用这个配置
和对应着配置项people的两个属性名,因此在应用中可以配置name和age两个属性,分别是string和int类型完成后需把xsd存放在classpath下,一般都放在META-INF目录下(本例就放在这个目录下)
3、编写NamespaceHandler和BeanDefinitionParser完成解析工作
其中registerBeanDefinitionParser(“people”, new PeopleBeanDefinitionParser());就是用来把节点名和解析类联系起来,在配置中引用people配置项时,就会用PeopleBeanDefinitionParser来解析配置。PeopleBeanDefinitionParser就是本例中的解析类:
其中element.getAttribute就是用配置中取得属性值,bean.addPropertyValue就是把属性值放到bean中。
以上表示当使用到名为”
会通过study.schemaExt.MyNamespaceHandler来完成解析
spring.schemas如下所示:
以上就是载入xsd文件
4、编写spring.handlers和spring.schemas串联起所有部件
到此为止一个简单的自定义配置以完成,可以在具体应用中使用了。使用方法很简单,和配置一个普通的spring bean类似,只不过需要基于我们自定义schema,本例中引用方式如下所示:
其中xmlns:fuyuwei2015=”http://blog.csdn.net/fuyuwei2015/schema/people”是用来指定自定义schema,xsi:schemaLocation用来指定xsd文件。
5、在Bean文件中应用
看完上面那个例子我们再来分析下dubbo加载过程可能就有点眉目了。
我们先来分析下dubbo-demo-provider,首先我们看下dubbo-demo-provider的配置
我们来看下这个配置文件时在什么地方加载的,在dubbo-demo-provider中有个DemoProvider.java
源码如下
我们进一步看下Main.main实现过程
看到这里我们发现程序一旦启动就一直在运行,但是我们还是没有到如何加载dubbo-demo-provider配置文件的,不要着急,我们继续看start和stop方法
终于找到了加载dubbo-demo-provider.xml地方,这里是通过ClassPathXmlApplicationContext来启动和停止的。Spring在解析xml文件时遇到dubbo名称空间时,会回调DubboNamespaceHandler,对所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。例如
1、基于dubbo.jar内的META-INF/spring.handlers配置,Spring在遇到dubbo名称空间时,会回调DubboNamespaceHandler。
2、所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。
3、在ServiceConfig.export()或ReferenceConfig.get()初始化时,将Bean对象转换URL格式,所有Bean属性转成URL的参数。
4、然后将URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露或引用。
我们看下DubboNamespaceHandler和DubboBeanDefinitionParser源码实现
从这里也可以看到,对应的支持的标签其实不多。所有的 Parser 都封装到了DubboBeanDefinitionParser 中。对应的 class,就是传入的 beanClass。比如 application 的就是ApplicationConfig。 module 的就是 ModuleConfig。经过 Parser 的转换, provider.xml 大概可以
变成如下的样子(具体的解析不多解释了)
DubboBeanDefinitionParser就类似我们上面提到的例子PeopleBeanDefinitionParser 来解析配置文件,由于Dubbo的配置比较复杂,PeopleBeanDefinitionParser 的解析方法也尤为复杂我们可以简单看下这里就不一一解释
下面我们分析下ServiceConfig.export和ReferenceConfig.get何时调用,ServiceBean.java和ReferenceBean.java分别继承了这两个类,ServiceBean.java是服务提供方的类
ServiceBean继承了ServiceConfig,并且实现了一些列的Spring接口。实现了InitializingBean,所以在bean的创建过程中,每个接口配置
afterPropertiesSet代码如下:
由于类实现了ApplicationListener接口,如下
所以在Spring容器加载完毕之后会调用onApplicationEvent方法,代码如下
在这里我们看到了export()方法。
下面我看服务消费方的ReferenceBean.java
ReferenceBean继承了ReferenceConfig,原理对应ServiceBean和ServiceConfig。不同之处在于,服务提供方ServiceBean需要在Spring启动的时候就提供服务,所以是通过onApplicationEvent在容器初始化完成之后立即就发布和注册了服务;但是服务消费方ReferenceBean是在程序需要的时候才会去执行,如果通过配置在Spring启动的过程中完成初始化是并不是合适的做法,而是在应用程序需要用到的时候,再去创建,所以ReferenceBean实现了FactoryBean,实现了接口方法getObject(),那么在spring容器getBean()方法获取对象实例其实调用的是ReferenceBean的getObject()方法完成消费地址的注册以及服务的订阅;
我们看些getObject的实现
在这里我们看到get方法。至此,ServiceConfig.export()和ReferenceConfig.get()方法我们都已经找到,这就是dubbo源码中Spring Bean加载的大致过程,还有很多细节值得我们去探讨。
由于Dubbo的启动注册都是依赖Spring的加载来实现的,我们先来分析下Spring Bean的加载过程。在Dubbo的源码中有个dubbo-demo工程
Spring 可扩展Schema
在继续之前我们先学习下如何通过配置文件生成实现类对象基于 Spring 可扩展 Schema 提供自定义配置支持
在很多情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于Spring的标准Bean来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。一般的做法会用原生态的方式去解析定义好的xml文件,然后转化为配置对象,这种方式当然可以解决所有问题,但实现起来比较繁琐,特别是是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。Spring提供了可扩展Schema的支持,这是一个不错的折中方案,完成一个自定义配置一般需要以下步骤:1、设计配置属性和JavaBean
2、编写XSD文件
3、编写NamespaceHandler和BeanDefinitionParser完成解析工作
4、编写spring.handlers和spring.schemas串联起所有部件
5、在Bean文件中应用
1、设计配置属性和JavaBean
id默认需要
public class People { private String id; private String name; private Integer age; }
2、编写XSD文件
为上一步设计好的配置项编写XSD文件,XSD是schema的定义文件,配置的输入和解析输出都是以XSD为契约,本例中XSD如下:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://blog.csdn.net/fuyuwei2015/schema/people" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://blog.csdn.net/fuyuwei2015/schema/people" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans" /> <xsd:element name="people"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string" /> <xsd:attribute name="age" type="xsd:int" /> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
关于xsd:schema的各个属性具体含义就不作过多解释,可以参见http://www.w3school.com.cn/schema/schema_schema.asp
对应着配置项节点的名称,因此在应用中会用people作为节点名来引用这个配置
和对应着配置项people的两个属性名,因此在应用中可以配置name和age两个属性,分别是string和int类型完成后需把xsd存放在classpath下,一般都放在META-INF目录下(本例就放在这个目录下)
3、编写NamespaceHandler和BeanDefinitionParser完成解析工作
public class MyNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("people", new PeopleBeanDefinitionParser()); } }
其中registerBeanDefinitionParser(“people”, new PeopleBeanDefinitionParser());就是用来把节点名和解析类联系起来,在配置中引用people配置项时,就会用PeopleBeanDefinitionParser来解析配置。PeopleBeanDefinitionParser就是本例中的解析类:
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.util.StringUtils; import org.w3c.dom.Element; public class PeopleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { protected Class getBeanClass(Element element) { return People.class; } protected void doParse(Element element, BeanDefinitionBuilder bean) { String name = element.getAttribute("name"); String age = element.getAttribute("age"); String id = element.getAttribute("id"); if (StringUtils.hasText(id)) { bean.addPropertyValue("id", id); } if (StringUtils.hasText(name)) { bean.addPropertyValue("name", name); } if (StringUtils.hasText(age)) { bean.addPropertyValue("age", Integer.valueOf(age)); } } }
其中element.getAttribute就是用配置中取得属性值,bean.addPropertyValue就是把属性值放到bean中。
http\://blog.csdn.net/fuyuwei2015/schema/people=study.schemaExt.MyNamespaceHandler
以上表示当使用到名为”
http://blog.csdn.net/fuyuwei2015/schema/people“的schema引用时,
会通过study.schemaExt.MyNamespaceHandler来完成解析
spring.schemas如下所示:
http\://blog.csdn.net/fuyuwei2015/schema/people.xsd=META-INF/people.xsd
以上就是载入xsd文件
4、编写spring.handlers和spring.schemas串联起所有部件
到此为止一个简单的自定义配置以完成,可以在具体应用中使用了。使用方法很简单,和配置一个普通的spring bean类似,只不过需要基于我们自定义schema,本例中引用方式如下所示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fuyuwei2015="http://blog.csdn.net/fuyuwei2015/schema/people" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://blog.csdn.net/fuyuwei2015/schema/people http://blog.csdn.net/fuyuwei2015/schema/people.xsd"> <cutesource:people id="fuyuwei2015" name="孙悟空" age="500"/> </beans>
其中xmlns:fuyuwei2015=”http://blog.csdn.net/fuyuwei2015/schema/people”是用来指定自定义schema,xsi:schemaLocation用来指定xsd文件。
<fuyuwei2015:people id="fuyuwei2015" name="孙悟空" age="500"/>是一个具体的自定义配置使用实例。最后就可以在具体程序中使用基本的bean载入方式来载入我们的自定义配置对象了,如:
5、在Bean文件中应用
ApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml"); People p = (People)ctx.getBean("cutesource"); System.out.println(p.getId()); System.out.println(p.getName()); System.out.println(p.getAge());
看完上面那个例子我们再来分析下dubbo加载过程可能就有点眉目了。
我们先来分析下dubbo-demo-provider,首先我们看下dubbo-demo-provider的配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" /> <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" /> </beans>
我们来看下这个配置文件时在什么地方加载的,在dubbo-demo-provider中有个DemoProvider.java
源码如下
public class DemoProvider { public static void main(String[] args) { com.alibaba.dubbo.container.Main.main(args); } }
我们进一步看下Main.main实现过程
public class Main { public static final String CONTAINER_KEY = "dubbo.container"; public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook"; private static final Logger logger = LoggerFactory.getLogger(Main.class); private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class); private static volatile boolean running = true; /** * 启动发布 * @author fuyuwei * 2017年6月4日 下午10:01:16 * @param args */ public static void main(String[] args) { try { // 开始判断main函数的传入参数,在args参数为空的情况下,从部署环境中取得dubbo.container属性, if (args == null || args.length == 0) { // 读取dubbo.properties中dubbo.container属性值,为空时通过loader.getDefaultExtensionName()获取默认值 String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName()); args = Constants.COMMA_SPLIT_PATTERN.split(config); } final List<Container> containers = new ArrayList<Container>(); // 遍历获取指定名称的扩展加入到列表中 for (int i = 0; i < args.length; i ++) { containers.add(loader.getExtension(args[i])); } logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce."); // 添加jvm关闭的钩子,用来在jvm关闭时关闭容器 if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { for (Container container : containers) { try { container.stop(); logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!"); } catch (Throwable t) { logger.error(t.getMessage(), t); } synchronized (Main.class) { running = false; Main.class.notify(); } } } }); } // 启动服务 for (Container container : containers) { container.start(); logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); } System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!"); } catch (RuntimeException e) { e.printStackTrace(); logger.error(e.getMessage(), e); System.exit(1); } synchronized (Main.class) { while (running) { try { Main.class.wait(); } catch (Throwable e) { } } } }
看到这里我们发现程序一旦启动就一直在运行,但是我们还是没有到如何加载dubbo-demo-provider配置文件的,不要着急,我们继续看start和stop方法
public class SpringContainer implements Container { private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class); public static final String SPRING_CONFIG = "dubbo.spring.config"; public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml"; static ClassPathXmlApplicationContext context; public static ClassPathXmlApplicationContext getContext() { return context; } public void start() { String configPath = ConfigUtils.getProperty(SPRING_CONFIG); if (configPath == null || configPath.length() == 0) { configPath = DEFAULT_SPRING_CONFIG; } context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+")); context.start(); } public void stop() { try { if (context != null) { context.stop(); context.close(); context = null; } } catch (Throwable e) { logger.error(e.getMessage(), e); } } }
终于找到了加载dubbo-demo-provider.xml地方,这里是通过ClassPathXmlApplicationContext来启动和停止的。Spring在解析xml文件时遇到dubbo名称空间时,会回调DubboNamespaceHandler,对所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。例如
1、基于dubbo.jar内的META-INF/spring.handlers配置,Spring在遇到dubbo名称空间时,会回调DubboNamespaceHandler。
2、所有dubbo的标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为Bean对象。
3、在ServiceConfig.export()或ReferenceConfig.get()初始化时,将Bean对象转换URL格式,所有Bean属性转成URL的参数。
4、然后将URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露或引用。
我们看下DubboNamespaceHandler和DubboBeanDefinitionParser源码实现
public class DubboNamespaceHandler extends NamespaceHandlerSupport { static { Version.checkDuplicate(DubboNamespaceHandler.class); } /** * 用来把节点名和解析类联系起来,在配置中引用<dubbo:service等配置项时,就会用DubboBeanDefinitionParser来解析配置 */ 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 DubboBeanDefinitionParser(AnnotationBean.class, true)); } }
从这里也可以看到,对应的支持的标签其实不多。所有的 Parser 都封装到了DubboBeanDefinitionParser 中。对应的 class,就是传入的 beanClass。比如 application 的就是ApplicationConfig。 module 的就是 ModuleConfig。经过 Parser 的转换, provider.xml 大概可以
变成如下的样子(具体的解析不多解释了)
<bean id="hello-world-app" class="com.alibaba.dubbo.config.ApplicationConfig"/> <bean id="registryConfig" class="com.alibaba.dubbo.config.RegistryConfig"> <property name="address" value="10.125.195.174:2181"/> <property name="protocol" value="zookeeper"/> </bean> <bean id="dubbo" class="com.alibaba.dubbo.config.ProtocolConfig"> <property name="port" value="20880"/> </bean> <bean id="demo.service.DemoService" class="com.alibaba.dubbo.config.spring.ServiceBean"> <property name="interface" value="demo.service.DemoService"/> <property name="ref" ref="demoService"/> </bean> <bean id="demoService" class="demo.service.DemoServiceImpl" />
DubboBeanDefinitionParser就类似我们上面提到的例子PeopleBeanDefinitionParser 来解析配置文件,由于Dubbo的配置比较复杂,PeopleBeanDefinitionParser 的解析方法也尤为复杂我们可以简单看下这里就不一一解释
@SuppressWarnings("unchecked") 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 ((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 ++); } } 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); } 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)); } } } } else if (ServiceBean.class.equals(beanClass)) { 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 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; }
下面我们分析下ServiceConfig.export和ReferenceConfig.get何时调用,ServiceBean.java和ReferenceBean.java分别继承了这两个类,ServiceBean.java是服务提供方的类
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware
ServiceBean继承了ServiceConfig,并且实现了一些列的Spring接口。实现了InitializingBean,所以在bean的创建过程中,每个接口配置
<dubbo:service ...>被解析成ServiceBean实例, 其在初始化后,会调用afterPropertiesSet()方法完成整个dubbo配置的加载过程;实现了ApplicationListener,所以会在整个Spring容器加载完成后接收到消息,完成onApplicationEvent()方法的调用,该方法就会将该ServiceBean配置的接口等服务进行发布和注册。
afterPropertiesSet代码如下:
public void afterPropertiesSet() throws Exception { if (getProvider() == null) { Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false); if (providerConfigMap != null && providerConfigMap.size() > 0) { Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false); if ((protocolConfigMap == null || protocolConfigMap.size() == 0) && providerConfigMap.size() > 1) { // 兼容旧版本 List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>(); for (ProviderConfig config : providerConfigMap.values()) { if (config.isDefault() != null && config.isDefault().booleanValue()) { providerConfigs.add(config); } } if (providerConfigs.size() > 0) { setProviders(providerConfigs); } } else { ProviderConfig providerConfig = null; for (ProviderConfig config : providerConfigMap.values()) { if (config.isDefault() == null || config.isDefault().booleanValue()) { if (providerConfig != null) { throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config); } providerConfig = config; } } if (providerConfig != null) { setProvider(providerConfig); } } } }
由于类实现了ApplicationListener接口,如下
public interface ApplicationListener extends EventListener { public abstract void onApplicationEvent(ApplicationEvent applicationevent); }
所以在Spring容器加载完毕之后会调用onApplicationEvent方法,代码如下
public void onApplicationEvent(ApplicationEvent event) { if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) { if (isDelay() && ! isExported() && ! isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } export(); } } }
在这里我们看到了export()方法。
下面我看服务消费方的ReferenceBean.java
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean
ReferenceBean继承了ReferenceConfig,原理对应ServiceBean和ServiceConfig。不同之处在于,服务提供方ServiceBean需要在Spring启动的时候就提供服务,所以是通过onApplicationEvent在容器初始化完成之后立即就发布和注册了服务;但是服务消费方ReferenceBean是在程序需要的时候才会去执行,如果通过配置在Spring启动的过程中完成初始化是并不是合适的做法,而是在应用程序需要用到的时候,再去创建,所以ReferenceBean实现了FactoryBean,实现了接口方法getObject(),那么在spring容器getBean()方法获取对象实例其实调用的是ReferenceBean的getObject()方法完成消费地址的注册以及服务的订阅;
public interface FactoryBean { public abstract Object getObject() throws Exception; public abstract Class getObjectType(); public abstract boolean isSingleton(); }
我们看些getObject的实现
public Object getObject() throws Exception { return get(); }
在这里我们看到get方法。至此,ServiceConfig.export()和ReferenceConfig.get()方法我们都已经找到,这就是dubbo源码中Spring Bean加载的大致过程,还有很多细节值得我们去探讨。
相关文章推荐
- 精通Dubbo——dubbo2.0源码中Spring Bean的加载
- 精通Dubbo——dubbo2.0源码中的设计模式与SPI介绍
- dubbo源码解析(一): 扩展点加载(ExtensionLoader)
- Dubbo系列(九)Dubbo源码分析之dubbo中bean的加载
- Dubbo源码分析(四):dubbo中bean的加载
- Spark 2.0从入门到精通:Scala编程、大数据开发、上百个实战案例、内核源码深度剖析(278讲全)
- dubbo源码:配置文件加载自定义标签解析
- Google Maps API 2.0解析(1-API加载)
- ASP.NET 2.0 Page 加载的过程
- [转载]全面精通Web 2.0,做互联网潮头人
- Web C#2.0 DataSet和Reader封装组件实现自动多数据库切换(含组件源码和实例)
- [转贴] ASP.Net C#2.0全能数据库组件 (含下载实例源码地址)
- Asp 2.0动态加载用户控件(Ascx)
- 读GI源码、学JS编程——Javascript动态加载技术。
- 用IBatisNet实现简单的CRUD FOR .NET 2.0(附源码)
- Web C#2.0 DataSet和Reader封装组件实现自动多数据库切换(含组件源码和实例)
- nhibernate源码七: HQL数据加载
- 全面精通Web 2.0,做互联网潮头人
- 全面精通Web 2.0,做互联网潮头人
- Web C#2.0 DataSet和Reader封装组件实现自动多数据库切换(含组件源码和实例)