1、Dubbo源码解析--Dubbo如何驱动Spring IOC容器并配合工作的?
首先Spring要注入自己的bean需要在Spring-provider.xml(提供者spring注入文件,名字可能不一样)添加bean注入,其中有dubbo的自定义标签,xml如何识别这些标签?拿到标签如何注入到Spring Container?一般需要如下几个步骤:
1)、设计配置属性和JavaBean
设计属性即dubbo.xsd中的attribute属性,如下
<dubbo:reference timeout="40000" check="false" id="order" interface="com.xx.order" group="test" version="0.1" />
xsd定义这些属性,匹配JavaBean的属性,如下:
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.config; import com.alibaba.dubbo.common.Constants; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.common.Version; import com.alibaba.dubbo.common.bytecode.Wrapper; import com.alibaba.dubbo.common.extension.ExtensionLoader; import com.alibaba.dubbo.common.utils.ConfigUtils; import com.alibaba.dubbo.common.utils.NetUtils; import com.alibaba.dubbo.common.utils.ReflectUtils; import com.alibaba.dubbo.common.utils.StringUtils; import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.config.model.ApplicationModel; import com.alibaba.dubbo.config.model.ConsumerModel; import com.alibaba.dubbo.config.support.Parameter; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Protocol; import com.alibaba.dubbo.rpc.ProxyFactory; import com.alibaba.dubbo.rpc.StaticContext; import com.alibaba.dubbo.rpc.cluster.Cluster; import com.alibaba.dubbo.rpc.cluster.directory.StaticDirectory; import com.alibaba.dubbo.rpc.cluster.support.AvailableCluster; import com.alibaba.dubbo.rpc.cluster.support.ClusterUtils; import com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol; import com.alibaba.dubbo.rpc.service.GenericService; import com.alibaba.dubbo.rpc.support.ProtocolUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import static com.alibaba.dubbo.common.utils.NetUtils.isInvalidLocalHost; /** * ReferenceConfig * * @export */ public class ReferenceConfig<T> extends AbstractReferenceConfig { private static final long serialVersionUID = -5864351140409987595L; private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private static final Cluster cluster = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); private final List<URL> urls = new ArrayList<URL>(); // interface name private String interfaceName; private Class<?> interfaceClass; // client type private String client; // url for peer-to-peer invocation private String url; // method configs private List<MethodConfig> methods; // default config private ConsumerConfig consumer; private String protocol; // interface proxy reference private transient volatile T ref; private transient volatile Invoker<?> invoker; private transient volatile boolean initialized; private transient volatile boolean destroyed; @SuppressWarnings("unused") private final Object finalizerGuardian = new Object() { @Override protected void finalize() throws Throwable { super.finalize(); if (!ReferenceConfig.this.destroyed) { logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE"); /* don't destroy for now try { ReferenceConfig.this.destroy(); } catch (Throwable t) { logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t); } */ } } }; public ReferenceConfig() { } public ReferenceConfig(Reference reference) { appendAnnotation(Reference.class, reference); } private static void checkAndConvertImplicitConfig(MethodConfig method, Map<String, String> map, Map<Object, Object> attributes) { //check config conflict if (Boolean.FALSE.equals(method.isReturn()) && (method.getOnreturn() != null || method.getOnthrow() != null)) { throw new IllegalStateException("method config error : return attribute must be set true when onreturn or onthrow has been setted."); } //convert onreturn methodName to Method String onReturnMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_RETURN_METHOD_KEY); Object onReturnMethod = attributes.get(onReturnMethodKey); if (onReturnMethod != null && onReturnMethod instanceof String) { attributes.put(onReturnMethodKey, getMethodByName(method.getOnreturn().getClass(), onReturnMethod.toString())); } //convert onthrow methodName to Method String onThrowMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_THROW_METHOD_KEY); Object onThrowMethod = attributes.get(onThrowMethodKey); if (onThrowMethod != null && onThrowMethod instanceof String) { attributes.put(onThrowMethodKey, getMethodByName(method.getOnthrow().getClass(), onThrowMethod.toString())); } //convert oninvoke methodName to Method String onInvokeMethodKey = StaticContext.getKey(map, method.getName(), Constants.ON_INVOKE_METHOD_KEY); Object onInvokeMethod = attributes.get(onInvokeMethodKey); if (onInvokeMethod != null && onInvokeMethod instanceof String) { attributes.put(onInvokeMethodKey, getMethodByName(method.getOninvoke().getClass(), onInvokeMethod.toString())); } } private static Method getMethodByName(Class<?> clazz, String methodName) { try { return ReflectUtils.findMethodByMethodName(clazz, methodName); } catch (Exception e) { throw new IllegalStateException(e); } } public URL toUrl() { return urls == null || urls.isEmpty() ? null : urls.iterator().next(); } public List<URL> toUrls() { return urls; } public synchronized T get() { if (destroyed) { throw new IllegalStateException("Already destroyed!"); } if (ref == null) { init(); } return ref; } public synchronized void destroy() { if (ref == null) { return; } if (destroyed) { return; } destroyed = true; try { invoker.destroy(); } catch (Throwable t) { logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ").", t); } invoker = null; ref = null; } private void init() { if (initialized) { return; } initialized = true; if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!"); } // get consumer's global configuration checkDefault(); appendProperties(this); if (getGeneric() == null && getConsumer() != null) { setGeneric(getConsumer().getGeneric()); } if (ProtocolUtils.isGeneric(getGeneric())) { interfaceClass = GenericService.class; } else { try { interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); } String resolve = System.getProperty(interfaceName); String resolveFile = null; if (resolve == null || resolve.length() == 0) { resolveFile = System.getProperty("dubbo.resolve.file"); if (resolveFile == null || resolveFile.length() == 0) { File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); if (userResolveFile.exists()) { resolveFile = userResolveFile.getAbsolutePath(); } } if (resolveFile != null && resolveFile.length() > 0) { Properties properties = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream(new File(resolveFile)); properties.load(fis); } catch (IOException e) { throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e); } finally { try { if (null != fis) fis.close(); } catch (IOException e) { logger.warn(e.getMessage(), e); } } resolve = properties.getProperty(interfaceName); } } if (resolve != null && resolve.length() > 0) { url = resolve; if (logger.isWarnEnabled()) { if (resolveFile != null && resolveFile.length() > 0) { logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service."); } else { logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service."); } } } if (consumer != null) { if (application == null) { application = consumer.getApplication(); } if (module == null) { module = consumer.getModule(); } if (registries == null) { registries = consumer.getRegistries(); } if (monitor == null) { monitor = consumer.getMonitor(); } } if (module != null) { if (registries == null) { registries = module.getRegistries(); } if (monitor == null) { monitor = module.getMonitor(); } } if (application != null) { if (registries == null) { registries = application.getRegistries(); } if (monitor == null) { monitor = application.getMonitor(); } } checkApplication(); checkStubAndMock(interfaceClass); Map<String, String> map = new HashMap<String, String>(); Map<Object, Object> attributes = new HashMap<Object, Object>(); map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion()); map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); if (ConfigUtils.getPid() > 0) { map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); } if (!isGeneric()) { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put("revision", revision); } String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("NO method found in service interface " + interfaceClass.getName()); map.put("methods", Constants.ANY_VALUE); } else { map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } map.put(Constants.INTERFACE_KEY, interfaceName); appendParameters(map, application); appendParameters(map, module); appendParameters(map, consumer, Constants.DEFAULT_KEY); appendParameters(map, this); String prefix = StringUtils.getServiceKey(map); if (methods != null && !methods.isEmpty()) { for (MethodConfig method : methods) { appendParameters(map, method, method.getName()); String retryKey = method.getName() + ".retry"; if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { map.put(method.getName() + ".retries", "0"); } } appendAttributes(attributes, method, prefix + "." + method.getName()); checkAndConvertImplicitConfig(method, map, attributes); } } String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY); if (hostToRegistry == null || hostToRegistry.length() == 0) { hostToRegistry = NetUtils.getLocalHost(); } else if (isInvalidLocalHost(hostToRegistry)) { throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); } map.put(Constants.REGISTER_IP_KEY, hostToRegistry); //attributes are stored by system context. StaticContext.getSystemContext().putAll(attributes); ref = createProxy(map); ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods()); ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); } @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) private T createProxy(Map<String, String> map) { URL tmpUrl = new URL("temp", "localhost", 0, map); final boolean isJvmRefer; if (isInjvm() == null) { if (url != null && url.length() > 0) { // if a url is specified, don't do local reference isJvmRefer = false; } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) { // by default, reference local service if there is isJvmRefer = true; } else { isJvmRefer = false; } } else { isJvmRefer = isInjvm().booleanValue(); } if (isJvmRefer) { URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map); invoker = refprotocol.refer(interfaceClass, url); if (logger.isInfoEnabled()) { logger.info("Using injvm service " + interfaceClass.getName()); } } else { if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address. String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url); if (us != null && us.length > 0) { for (String u : us) { URL url = URL.valueOf(u); if (url.getPath() == null || url.getPath().length() == 0) { url = url.setPath(interfaceName); } if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); } else { urls.add(ClusterUtils.mergeUrl(url, map)); } } } } else { // assemble URL from register center's configuration List<URL> us = loadRegistries(false); if (us != null && !us.isEmpty()) { for (URL u : us) { URL monitorUrl = loadMonitor(u); if (monitorUrl != null) { map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString())); } urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); } } if (urls == null || urls.isEmpty()) { throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config."); } } if (urls.size() == 1) { invoker = refprotocol.refer(interfaceClass, urls.get(0)); } else { List<Invoker<?>> invokers = new ArrayList<Invoker<?>>(); URL registryURL = null; for (URL url : urls) { invokers.add(refprotocol.refer(interfaceClass, url)); if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; // use last registry url } } if (registryURL != null) { // registry url is available // use AvailableCluster only when register's cluster is available URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); invoker = cluster.join(new StaticDirectory(u, invokers)); } else { // not a registry url invoker = cluster.join(new StaticDirectory(invokers)); } } } Boolean c = check; if (c == null && consumer != null) { c = consumer.isCheck(); } if (c == null) { c = true; // default true } if (c && !invoker.isAvailable()) { throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion()); } if (logger.isInfoEnabled()) { logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); } // create service proxy return (T) proxyFactory.getProxy(invoker); } private void checkDefault() { if (consumer == null) { consumer = new ConsumerConfig(); } appendProperties(consumer); } public Class<?> getInterfaceClass() { if (interfaceClass != null) { return interfaceClass; } if (isGeneric() || (getConsumer() != null && getConsumer().isGeneric())) { return GenericService.class; } try { if (interfaceName != null && interfaceName.length() > 0) { this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } } catch (ClassNotFoundException t) { throw new IllegalStateException(t.getMessage(), t); } return interfaceClass; } /** * @param interfaceClass * @see #setInterface(Class) * @deprecated */ @Deprecated public void setInterfaceClass(Class<?> interfaceClass) { setInterface(interfaceClass); } public String getInterface() { return interfaceName; } public void setInterface(Class<?> interfaceClass) { if (interfaceClass != null && !interfaceClass.isInterface()) { throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); } this.interfaceClass = interfaceClass; setInterface(interfaceClass == null ? null : interfaceClass.getName()); } public void setInterface(String interfaceName) { this.interfaceName = interfaceName; if (id == null || id.length() == 0) { id = interfaceName; } } public String getClient() { return client; } public void setClient(String client) { checkName("client", client); this.client = client; } @Parameter(excluded = true) public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public List<MethodConfig> getMethods() { return methods; } @SuppressWarnings("unchecked") public void setMethods(List<? extends MethodConfig> methods) { this.methods = (List<MethodConfig>) methods; } public ConsumerConfig getConsumer() { return consumer; } public void setConsumer(ConsumerConfig consumer) { this.consumer = consumer; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } // just for test Invoker<?> getInvoker() { return invoker; } @Parameter(excluded = true) public String getUniqueServiceName() { StringBuilder buf = new StringBuilder(); if (group != null && group.length() > 0) { buf.append(group).append("/"); } buf.append(interfaceName); if (version != null && version.length() > 0) { buf.append(":").append(version); } return buf.toString(); } }
2)、编写XSD文件,全称就是XML Schema
------用来校验XML,定义一系列的语法来规范XML
对应的XSD文件如下:
这个文件在dubbo.xsd中
<xsd:complexType name="referenceType"> <xsd:complexContent> <xsd:extension base="abstractReferenceType"> <xsd:choice minOccurs="0" maxOccurs="unbounded"> <xsd:element ref="method" minOccurs="0" maxOccurs="unbounded"/> <xsd:element ref="parameter" minOccurs="0" maxOccurs="unbounded"/> </xsd:choice> <xsd:attribute name="interface" type="xsd:token" use="required"> <xsd:annotation> <xsd:documentation><![CDATA[ The service interface class name. ]]></xsd:documentation> <xsd:appinfo> <tool:annotation> <tool:expected-type type="java.lang.Class"/> </tool:annotation> </xsd:appinfo> </xsd:annotation> </xsd:attribute> <xsd:attribute name="url" type="xsd:string" use="optional"> <xsd:annotation> <xsd:documentation><![CDATA[ Provider list url. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="client" type="xsd:string" use="optional"> <xsd:annotation> <xsd:documentation><![CDATA[ Protocol transport client type. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="consumer" type="xsd:string" use="optional"> <xsd:annotation> <xsd:documentation><![CDATA[ Deprecated. Replace to reference-default. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="protocol" type="xsd:string" use="optional"> <xsd:annotation> <xsd:documentation><![CDATA[ The service protocol. ]]></xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:anyAttribute namespace="##other" processContents="lax"/> </xsd:extension> </xsd:complexContent> </xsd:complexType>
3)、编写NamespaceHandler和BeanDefinitionParser完成解析工作
编写继承自Spring的NamespaceHandlerSupport类,重写init()方法,机械属性文件(spring-provider.xml dubbo bean注入文件),如下:
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.config.spring.schema; import com.alibaba.dubbo.common.Version; import com.alibaba.dubbo.config.*; import com.alibaba.dubbo.config.spring.ReferenceBean; import com.alibaba.dubbo.config.spring.ServiceBean; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; /** * DubboNamespaceHandler * * @export */ public class DubboNamespaceHandler extends NamespaceHandlerSupport { static { Version.checkDuplicate(DubboNamespaceHandler.class); } 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()); } }
4)、编写spring.handlers和spring.schemas串联起所有部件
spring.handlers文件如下,告诉springdubbo的处理类在哪里
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源码的如下路径:dubbo\dubbo-config\dubbo-config-spring\src\main\resources\META-INF
5)在Bean文件中应用
这里就是用ClassPathXmlApplicationContext读取Spring-provider.xml注入bean到Spring容器,然后在Service中通过@Resource或@Autowired注解拿到Bean使用。
其实大体流程如下:
1)入口是ClassPathXmlApplicationContext,这个我们一般会讲dubbo的提供者和消费者统一跟spring普通注入xml文件放在一块交由spring容器去解析,至于dubbo自定义标签如何解析往下看。
2)Spring会寻找spring.handlers和spring.schemas串联起所有部件,这里也就是dubbo.xsd已经dubbo自定义注解解析器DubboNamespaceHandler,dubbo.xsd负责解析spring-provider.xml配置的属性是否合法。DubboNamespaceHandler就能解析ClassPathXmlApplicationContext读入的spring-provider.xml读入的带有dubbo自定义标签的注入内容,然后通过DubboBeanDefinitionParser的parse方法进行解析并注入到Spring容器。
3)最后在Service或其他层面用@Resource 和 @Autowired等注解从Spring容器中取出使用。
至于dubbo的bean注入到spring容器中如何用netty进行通信、序列化、服务治理,后续会进行补充!