Invoker represents the object of remote communication
Directory represents a list of service addresses
Service publishing process
- Scan xml configuration or annotation
- URL assembly (dubbo is URL driven)
- Register with the registry
- Start and publish services
Dubbo source code usage example (without spring boot Starter component):
public class Application { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); context.start(); System.in.read(); } @Configuration @EnableDubbo(scanBasePackages = "com.anto.dubbo.dubboprovider") @PropertySource("classpath:/spring/dubbo-provider.properties") static class ProviderConfiguration { @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://172.30.2.7:2181"); return registryConfig; } } }
In the Dubbo spring boot starter component, you can directly configure the scanning path in the properties file without @ EnableDubbo.
dubbo.registry.address=zookeeper://172.30.2.7:2181 dubbo.scan.base-packages=com.anto.dubbo.dubboprovider
Because in the auto assembly class, dubbereaxedbinding2autoconfiguration will bind the above configuration to the specified Bean.
@Bean(name = BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME)//dubboScanBasePackagesPropertyResolver public PropertyResolver dubboScanBasePackagesPropertyResolver(ConfigurableEnvironment environment) { ConfigurableEnvironment propertyResolver = new AbstractEnvironment() { @Override protected void customizePropertySources(MutablePropertySources propertySources) { //Find Dubbo. In the properties file scan. to configure Map<String, Object> dubboScanProperties = getSubProperties(environment.getPropertySources(), DUBBO_SCAN_PREFIX); propertySources.addLast(new MapPropertySource("dubboScanProperties", dubboScanProperties)); } }; ConfigurationPropertySources.attach(propertyResolver); return new DelegatingPropertyResolver(propertyResolver); }
Find services to publish -- scan xml or annotations
dubbo service publishing form
- xml form
- Annotation form
@EnableDubbo contains two annotations @ EnableDubboConfig and @ DubboComponentScan
Question: is @ EnableDubbo a required annotation when dubbo is started?
Non essential notes, when used Spring-Boot Mode integration Starter Component, the scan path is read directly application.properties Of documents; as for ServiceAnnotationBeanPostProcessor Then in DubboAutoConfiguration Declared the Bean.
The following are initialized based on annotations.
- ServiceAnnotationBeanPostProcessor
In the @ DubboComponentScan annotation, the import class dubbocomponentscanregister will be, and then several beandefinitions will be registered in the IOC container in advance.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(DubboComponentScanRegistrar.class) public @interface DubboComponentScan {//This annotation imports the dubbocomponentscanregister class
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) { //Register the BeanDefinition of ServiceAnnotationBeanPostProcessor BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class); builder.addConstructorArgValue(packagesToScan); builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry); }
After registering the BeanDefinition of ServiceAnnotationBeanPostProcessor, you should instantiate the Bean.
//Triggered when AbstractApplicationContext calls the refresh() method invokeBeanFactoryPostProcessors(beanFactory); //PostProcessorRegistrationDelegate triggers the process of getBean currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
After initialization, it should really start its function.
If it implements BeanDefinitionRegistryPostProcessor, it should call its postProcessBeanDefinitionRegistry() method.
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // Once again, ensure that the dubbobootstrappapplicationlistener is registered. In fact, it is in the ` @ DubboComponentScan annotation, //Already registered when importing the 'dubbocomponentscanregister' class registerBeans(registry, DubboBootstrapApplicationListener.class); Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan); if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) { //Start registering beans with dubbo annotations registerServiceBeans(resolvedPackagesToScan, registry); } //... slightly }
Similar to Mybatis, Dubbo also defines a class DubboClassPathBeanDefinitionScanner specifically used to scan the specified path.
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) { DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader); BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry); scanner.setBeanNameGenerator(beanNameGenerator); // Serviceannotation types is a list containing dubboservice Class and service class //Make the scanner scan only annotations with these two paths serviceAnnotationTypes.forEach(annotationType -> { scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType)); }); for (String packageToScan : packagesToScan) { // Registers @Service Bean first scanner.scan(packageToScan); // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not. Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator); if (!CollectionUtils.isEmpty(beanDefinitionHolders)) { for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { registerServiceBean(beanDefinitionHolder, registry, scanner); } //... slightly } }
Publishing portal for services
When dubbo's service scanning is completed, we need to publish the service. To publish the service, we need to consider the following points:
What protocol is the service published under
Port for service publishing
- Service publishing portal
Dubbobootstrappapplicationlistener listens to the ContextRefreshedEvent event. When Spring finishes loading the Bean, it will trigger the event interface, which is the real publishing service entry
private void onContextRefreshedEvent(ContextRefreshedEvent event) { dubboBootstrap.start(); }
So where is this class registered?
When ServiceClassPostProcessor triggers the method postProcessBeanDefinitionRegistry(), it will explicitly register the Bean of dubbobootstrappapplicationlistener.
// This is to ensure that there is a Bean with dubbobootstrappapplicationlistener //In fact, in the ` @ DubboComponentScan annotation, the 'dubbocomponentscanregister' class was already registered when it was imported registerBeans(registry, DubboBootstrapApplicationListener.class);
- Service publishing process
The real publishing of dubbo service is completed through the start() of DubboBootstrap class.
public DubboBootstrap start() { if (started.compareAndSet(false, true)) { //... slightly //Initialize the necessary component configuration center, registry, verify the necessary configuration, etc initialize(); // 1. Expose dubbo services exportServices(); // Not only provider register if (!isOnlyRegisterProvider() || hasExportedServices()) { // 2. export MetadataService exportMetadataService(); //3. Register the local ServiceInstance if required registerServiceInstance(); } referServices(); //... slightly return this; }
Next, call the exportServices() method of the DubboBootstrap class
private void exportServices() { //Service by service exposure configManager.getServices().forEach(sc -> { // TODO, compatible with ServiceConfig.export() ServiceConfig serviceConfig = (ServiceConfig) sc; serviceConfig.setBootstrap(this); if (exportAsync) { ExecutorService executor = executorRepository.getServiceExporterExecutor(); Future<?> future = executor.submit(() -> { sc.export(); exportedServices.add(sc); }); asyncExportingFutures.add(future); } else { sc.export(); exportedServices.add(sc); } }); }
- Key object ServiceConfig
The service publishing process can be roughly divided into three processes:
1. Generate Invoker objects for specific services
2. Publish the agreement service (published in Dubbo by default), and the Invoker is transformed into an Exporter
3. Register the service address information to the registry
//ServiceConfig.doExportUrls() private void doExportUrls() { //Get the service repository and register the service in the globally unique service repository object ServiceRepository repository = ApplicationModel.getServiceRepository(); ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass()); repository.registerProvider( getUniqueServiceName(), ref, serviceDescriptor, this, serviceMetadata ); //Registry address collection List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true); //By default, there is only the default ProtocolConfig object of dubbo protocol < dubbo: protocol name = "dubbo" port = "20880" / > for (ProtocolConfig protocolConfig : protocols) { String pathKey = URL.buildKey(getContextPath(protocolConfig) .map(p -> p + "/" + path) .orElse(path), group, version); // In case user specified path, register service one more time to map it to path. repository.registerService(pathKey, interfaceClass); // TODO, uncomment this line once service key is unified serviceMetadata.setServiceKey(pathKey); doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
According to the configuration of RegistryConfig, the registryURL is assembled. The URL format is as follows:
registry://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=65324®istry=zookeeper&release=2.7.7×tamp=1604025007827
This URL indicates that it is a registry protocol, the address is the ip:port of the registry, the service interface is RegistryService, and the type of registry is zookeeper. When there are multiple registries, multiple registryurls are generated.
Next, start exposing the service according to the specific protocol (the default dubbo protocol) and register the service with one or more registries.
doExportUrlsFor1Protocol()
Local exposure service
When you call the doExportUrlsFor1Protocol() method in ServiceConfig to expose the service, you will have the following judgement:
//When the value of scope is not explicitly specified as remote, local exposure will occur if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); }
injvm://127.0.0.1/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=65324&release=2.7.7&side=provider×tamp=1604025491044
Specify explicitly how to expose the service by specifying the value of scope.
<Dubbo:service interface="org.apache.Dubbo.samples.local.api.DemoService" ref="target" scope="remote"/> #Specify the service exposure method of the consumer provider, and do not specify that the service will be exposed in dubbo and injvm at the same time dubbo.provider.scope=remote
No special configuration is required to use Dubbo local call, and the service can be exposed according to the normal Dubbo service.
While exposing remote services, any service will also expose local services with the injvm protocol. Injvm is a pseudo protocol. It does not open ports like other protocols. It is only used for local calls.
Exporter<?> exporter = PROTOCOL.export( PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
When publishing the injvm PROTOCOL, the PROTOCOL header is injvm, so PROTOCOL gets the InjvmProtocol according to the adaptive extension point. Therefore, the Exporter object generated here is of type InjvmExporter.
So what are the benefits of exposing services through the injvm?
Unlike method calls on local objects, Dubbo The local call will go through Filter Chain, including Consumer Terminal Filter Chain and Provider Terminal Filter Chain. Through such a mechanism, local consumers and other consumers are treated, monitored and managed in a unified manner.
When is a local call useless?
First, local calls cannot be used when generalizing calls. Second, consumers clearly specify URL Initiate a direct call.
Generate Invoker object
Invoker is an important object in Dubbo domain. In the process of service publishing and calling, the service itself will exist as an invoker object. Whether publishing Dubbo services or local injvm services, you need to generate an invoker object.
//Publish dubbo service Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
In Dubbo, an Invoker object is generated, and then protocol export(wrapperInvoker); To complete the release of the service.
//Publish local injvm services Exporter<?> exporter = PROTOCOL.export( PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
PROXY_FACTORY is an object obtained by an adaptive extension point.
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
If you see this, you must have a name called org. Org in the / dubbo/META-INF/internal path apache. dubbo. rpc. Configuration of ProxyFactory interface of ProxyFactory file.
stub=org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper jdk=org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory javassist=org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory
- First, judge according to the parameters in the URL. proxy="jdk". If it is configured, it will be found directly according to the configuration in the URL. The logic here is in the generated ProxyFactory$Adaptive class
- If not configured, the default @ SPI("javassist") is JavassistProxyFactory type.
According to the previous analysis, as long as the interface is modified by @ SPI and the method is modified by @ Adaptive, a proxy class ending in $Adaptive will be generated. Therefore, a class ProxyFactory$Adaptive generated by the Dubbo framework will be generated here.
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory { public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter("proxy", "javassist"); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getProxy(arg0); } public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = url.getParameter("proxy", "javassist"); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); return extension.getProxy(arg0, arg1); } public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException { if (arg2 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg2; //Get the proxy parameter in the url. If none, it is javassist String extName = url.getParameter("proxy", "javassist"); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); //Call javassistproxyfactory. Again Getinvoker() method return extension.getInvoker(arg0, arg1, arg2); } }
Therefore, the function of ProxyFactory$Adaptive is mainly to determine which implementation of ProxyFactory interface needs to be used according to the proxy parameters in the url. If it is not configured, use the value configured by @ SPI("javassist").
The parameter is
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) proxy: com.anto.dubbo.dubboprovider.HelloServiceImpl type: com.anto.dubbo.HelloService url: injvm://127.0.0.1/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=16136&release=2.7.7&side=provider×tamp=1606266760859
Then call the getInvoker method of JavassistProxyFactory.
//JavassistProxyFactory public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // Create a dynamic proxy final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { //invokeMethod() of the built dynamic proxy class return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }
Create a dynamic proxy through the Wrapper class (line 258 of the Wrapper class). Its core method is as follows:
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException { com.anto.dubbo.dubboprovider.HelloServiceImpl w; try { w = ((com.anto.dubbo.dubboprovider.HelloServiceImpl) $1); } catch (Throwable e) { throw new IllegalArgumentException(e); } try { //Judge whether the parameter is String if ("sayHello".equals($2) && $3.length == 1 && $3[0].getName().equals("java.lang.String")) { return ($w) w.sayHello((java.lang.String) $4[0]); } if ("sayHello".equals($2) && $3.length == 1 && $3[0].getName().equals("com.anto.dubbo.User")) { return ($w) w.sayHello((com.anto.dubbo.User) $4[0]); } } catch (Throwable e) { throw new java.lang.reflect.InvocationTargetException(e); } throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class com.anto.dubbo.dubboprovider.HelloServiceImpl."); }
Is there a trace of intimacy when you see this dynamic proxy class?
This is to decide which method of the interface to call according to different method names and parameter types!
After building the dynamic proxy generated by the Wrapper class, an anonymous Invoker object of AbstractProxyInvoker type is returned. So what are its characteristics?
You can see that it rewrites the doInvoke() method and finally calls the invokeMethod() of the dynamic proxy class, which is essentially the method of calling the dubbo interface.
Review the process of generating Invoker objects:
1.Dubbo The framework generates a ProxyFactory$Adaptive proxy class---Decide which to use ProxyFactory 2.Wrapper Class creates a dynamic proxy class for the specific service to be published 3.Generate an override doInvoke()Methodical AbstractProxyInvoker Anonymous class of type--Used to forward the request initiated by consumption to Wrapper Of the generated proxy class invokeMethod()
So, to sum up, Invoke should be an agent in essence. After layers of packaging, it is finally released. When the consumer initiates a request, it will get the Invoker to call.
The finally released Invoker is not just an agent, but also through multi-layer packaging
InvokerDelegate(DelegateProviderMetaDataInvoker(AbstractProxyInvoker()))
Remote exposure service
In the ServiceConfig class, the doExportUrlsFor1Protocol() method first exposes the local service, and then the remote service.
The process of remote service exposure is actually accompanied by the process of generating Exporter objects.
//Expose services through some kind of protocol Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
When the remote service is exposed, you first need to get a protocol object, which is an object of the adaptive extension point interface.
Get the Protocol object
private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Since the method of the PROTOCOL interface is marked with @ Adaptive, a proxy class object will be generated for it.
@Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
The dynamically generated classes are as follows: Protocol$Adaptive
package org.apache.dubbo.rpc; import org.apache.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { //... slightly public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); //The implementation of which protocol is loaded is determined according to the protocol header parameters in the url. The initial protocol header is registry String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } //... slightly }
Think about it: why do you need such a design? Every @ Adaptive extension class marked has heard of Compile to generate code, instead of designing a Protocol$Adaptive class separately?
Dubbo in the light of@SPI In the extension interface, the method is marked@Adaptive Annotated classes will generate a proxy class with the name of interface $Adaptive. This design is mainly scalability and flexibility. A dynamic adaptation class can be declared through annotations. At the same time, users can determine the adaptation target class according to the attributes declared in the configuration. Scalability is reflected in spi In terms of mechanism, when we develop the implementation of extension, we can also use this dynamic adaptation function to realize the routing of target class.
In the Invoker object generated in the above step, its URL is:
registry://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=12248&release=2.7.7&side=provider×tamp=1606719802290&pid=12248®istry=zookeeper&release=2.7.7×tamp=1606719800257
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
Therefore, getExtension("registry") above will look for RegistryProtocol, but there are the following statements in ExtensionLoader:
Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } }
The wrapperClasses in this case are these three
When parsing meta-inf / Dubbo / internal / org apache. dubbo. rpc. When using Protocol, the wrapper class of the Protocol interface will be placed in the cache attribute cachedWrapperClasses.
Think about it: how to judge whether the SPI interface is a wrapper type?
private boolean isWrapperClass(Class<?> clazz) { try { //When the implementation class has a constructor that passes in the interface itself, it is considered a wrapper type clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; } }
Dubbo uses decorator mode to enhance the spi interface of the protocol.
So expose the service code exporter <? > exporter = PROTOCOL. export(wrapperInvoker); At this point, protocol is the ProtocolListenerWrapper object. Then call the export() methods of QosProtocolWrapper and ProtocolFilterWrapper in turn.
ProtocolListenerWrapper: used to insert the listening mechanism when the service is export ed.
QoS protocol wrapper: if the registry is currently configured, a QoS server will be started QoS is dubbo's online operation and maintenance command, dubbo 2 5.8 the new version reconstructs the telnet module and provides new telnet command support. The telnet port of the new version is different from the port of dubbo protocol, which is 22222 by default.
ProtocolFilterWrapper: wrap the invoker with a filter to filter requests.
The call link is as follows:
ProtocolListenerWrapper.export()--->QosProtocolWrapper.export()---->ProtocolFilterWrapper.export()--->RegistryProtocol.export()
However, during the registration scenario, these extension points will not take effect. The executed logic will first judge whether it is a registration agreement. If so, the service will be published directly based on the agreement.
In a word, we finally get the RegistryProtocol object.
Start Netty listening service
Next, you will call registryprotocol Export() method.
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //Here is the URL of the zookeeper registry: zookeeper://ip:port URL registryUrl = getRegistryUrl(originInvoker); // Here is the URL to get the service provider, dubbo://ip:port... URL providerUrl = getProviderUrl(originInvoker); //Subscribe to override data. In the admin console, you can manage the service, such as modifying the weight and routing mechanism. When the registry has the coverage configuration of this service registered, push the message to the provider to re expose the service final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); //Here, it is left to the specific agreement to expose the service (such as dubbo) final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // url to registry final Registry registry = getRegistry(originInvoker); //Get the URL to register with the registry: dubbo://ip:port final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl); // If a registry is configured, register the service with a registry such as zookeeper boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { register(registryUrl, registeredProviderUrl); } // register stated url on provider model registerStatedUrl(registryUrl, registeredProviderUrl, register); // Deprecated! Subscribe to override rules in 2.6.x or before. registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); notifyExport(exporter); //Ensure that each export returns a new exporter instance return new DestroyableExporter<>(exporter); }
In registryprotocol In expor() T, there are two core processes:
- Call doLocalExport to start the local service, that is, netty server
- Call the register method to register the service address
Next, take a look at dolocalexport (origin invoker, provider URL); method.
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { //The value of key is a protocol string that publishes the Dubbo service dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?... String key = getCacheKey(originInvoker); //When the key of the service does not exist in the bouds Map, an Exporter of the service will be generated return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); }); }
First, generate a unique key value with dubbo protocol string for the service to be published.
dubbo://172.30.60.208:20880/com.anto.dubbo.HelloService?anyhost=true&application=dubbo-demo-annotation-provider&bind.ip=172.30.60.208&bind.port=20880&cluster=failsafe&deprecated=false&dubbo=2.0.2&generic=false&interface=com.anto.dubbo.HelloService&methods=sayHello&pid=3876&release=2.7.7&side=provider×tamp=1606963046596
At this point, call protocol again Export (invokerdelegate) will enter protocol $adaptive again In the export() method.
However, DubboProtocol is returned this time. The calling link is as follows:
ProtocolListenerWrapper.export()--->QosProtocolWrapper.export()---->ProtocolFilterWrapper.export()--->DubboProtocol.export()
So I came to dubboprotocol Export() method:
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { URL url = invoker.getUrl(); // Store the service name and port in an exportMap as key invoker and parameters of DubboExporter //key com.anto.dubbo.HelloService:20880 String key = serviceKey(url); DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap); exporterMap.put(key, exporter); //Is the parameter callback mechanism configured Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT); Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false); if (isStubSupportEvent && !isCallbackservice) { String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY); if (stubServiceMethods == null || stubServiceMethods.length() == 0) { if (logger.isWarnEnabled()) { logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) + "], has set stubproxy support event ,but no stub methods founded.")); } } } //Start a Netty service openServer(url); optimizeSerialization(url); return exporter; }
- openServer()
private void openServer(URL url) { // Get host:port and use it as the key of the server instance to identify the current server instance String key = url.getAddress(); //The client can also expose a service that only the server can call boolean isServer = url.getParameter(IS_SERVER_KEY, true); if (isServer) { ProtocolServer server = serverMap.get(key); if (server == null) { synchronized (this) { server = serverMap.get(key); if (server == null) { //Create server instance serverMap.put(key, createServer(url)); } } } else { // If the server has been created, reset the server according to the configuration in the url server.reset(url); } } }
Next is based on org apache. dubbo. remoting. transport. Netty4. Nettytransporter starts a process of Netty service.
There are several important steps in the process of publishing services based on DubboProtocol protocol
- Build an exporter map, take the service path name as the key, and wrap the invoker into a DubboExporter as the value storage;
- For multiple services on the same machine, only one service instance is started;
- Netty4 is used to publish services.
Registration service
When a registry such as Zookeeper is configured, the node information of the service will be written in the corresponding place.
// Register services with zookeeper boolean register = providerUrl.getParameter(REGISTER_KEY, true); if (register) { register(registryUrl, registeredProviderUrl); }
Dynamically find the service center to be registered according to the key of the URL. registryFactory is a dynamic extension point, which is wrapped first, and then ZookeeperRegistryFactory when it is zookeeper.
At this time, the registry url has been resolved to the url beginning with Zookeeper.
zookeeper://172.30.2.7:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.30.60.208%3A20880%2Fcom.anto.dubbo.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-annotation-provider%26bind.ip%3D172.30.60.208%26bind.port%3D20880%26cluster%3Dfailsafe%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.anto.dubbo.HelloService%26methods%3DsayHello%26pid%3D16172%26release%3D2.7.7%26side%3Dprovider%26timestamp%3D1606978398061&pid=16172&release=2.7.7×tamp=1606978397349
registeredProviderUrl is the service provider address starting with dubbo.
private void register(URL registryUrl, URL registeredProviderUrl) { //registry is listener registrywrapper, and registryfactory is also an object generated by dynamic extension points Registry registry = registryFactory.getRegistry(registryUrl); registry.register(registeredProviderUrl); }
- ListenerRegistryWrapper.register()
public void register(URL url) { try { //Call zookeeperregistry register() registry.register(url); } finally { if (CollectionUtils.isNotEmpty(listeners)) { RuntimeException exception = null; for (RegistryServiceListener listener : listeners) { if (listener != null) { try { listener.onRegister(url); } catch (RuntimeException t) { logger.error(t.getMessage(), t); exception = t; } } } if (exception != null) { throw exception; } } } }
Listener registry wrapper is a layer of packaging for ZookeeperRegistry and adds the corresponding functions of the listener.
- FailbackRegistry.register()
FailbackRegistry is the parent of the retry mechanism provided. It is the parent of ZookeeperRegistry, NacosRegistry, SofaRegistry and other specific registries.
There is no register() in the ZookeeperRegistry class, so it will enter the method of the parent class FailbackRegistry.
public void register(URL url) { //... slightly super.register(url); removeFailedRegistered(url); removeFailedUnregistered(url); try { // Really call zookeeperregistry doRegister() doRegister(url); } catch (Exception e) { Throwable t = e; // If the configured property check is true, an exception will be thrown directly without retry boolean check = getUrl().getParameter(Constants.CHECK_KEY, true) && url.getParameter(Constants.CHECK_KEY, true) && !CONSUMER_PROTOCOL.equals(url.getProtocol()); boolean skipFailback = t instanceof SkipFailbackWrapperException; if (check || skipFailback) { if (skipFailback) { t = t.getCause(); } throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t); } else { logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t); } // If the registration fails and throws an exception, the url of the registration failure will be put into the container of the registration failure addFailedRegistered(url); } }
- ZookeeperRegistry.doRegister()
public void doRegister(URL url) { try { zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true)); } catch (Throwable e) { throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
Therefore, the corresponding dubbo integrated zookeeper client (after curator 2.7) will be called to write the node information of the exposed service.