1. Service export process

This article , Let's look at Dubbo The process of exporting Services .Dubbo The service export process starts with Spring Container publish refresh event ,Dubbo After receiving the event , The service export logic is executed immediately . The whole logic can be roughly divided into three parts , The first is front-end work , Mainly used to check parameters , assemble URL. The second is export service , Include export service to local (JVM), And export service to remote . The third is to register services with the registry , For service discovery . This article will analyze these three parts of the code in detail , Before analysis , Let's take a look at the export process of the service first .

Dubbo Two service export methods are supported , Delayed export and immediate export, respectively . The entry to delayed export is ServiceBean Of afterPropertiesSet Method , The entry for immediate export is ServiceBean Of onApplicationEvent Method . This article intends to analyze the service delay export process , So we won't analyze afterPropertiesSet Method . The following from onApplicationEvent Methods , The method received Spring After the refresh event of the container , Would call export Method to perform the service export operation . Before exporting the service , It's going to be a series of configuration checks , And generate URL. Get the work done , Then start exporting the service . First export to local , And then export to remote . Exporting to local is exporting the service to local JVM in , This process is relatively simple . Exporting to remote is much more complex , With dubbo Agreement, for example ,DubboProtocol Class export The method will be called . This method is mainly used to create Exporter and ExchangeServer.ExchangeServer They don't have the ability to communicate , We need to use a lower level of Server Realize the communication function . therefore , Creating ExchangeServer When an instance , You need to create NettyServer perhaps MinaServer example , And pass the instance as a parameter to ExchangeServer Implementation class construction method .ExchangeServer After the instance is created , The process of exporting services to remote is coming to an end . After exporting the service , Service consumers can consume services through direct connection . Of course , Generally, we don't use direct connection to consume services . therefore , At the end of the service export , The next thing to do is to register services with the registry . here , Clients can discover services from the registry .

That's all Dubbo The process of service export , More complicated . Let's start to analyze the source code , Show the whole process from the perspective of source code .

2. Source code analysis

a Dubbo The marathon of source code analysis is about to begin , Now we're at the start of the track to warm up . The starting point of this competition is ServiceBean Of onApplicationEvent method . Okay , The starting gun went off , I will be with some friends from onApplicationEvent The way to start , Explore Dubbo The whole process of service export . So let's see onApplicationEvent Method source code .

public void onApplicationEvent(ContextRefreshedEvent event) {
// Whether there is a delayed export && Whether exported && Whether the export has been cancelled
if (isDelay() && !isExported() && !isUnexported()) {
// Export service
export();
}
}

onApplicationEvent It's an event response method , The method will receive Spring Execute after context refresh event . This method first determines whether to export the service according to the conditions , For example, some services set delay export , Then you should not export . There are also services that have been exported , Or the current service is cancelled , At this time, the related services cannot be exported again . Notice the isDelay Method , This method literally means “ Whether to delay the export of services ”, return true Indicates delayed Export ,false Does not delay export . But the real meaning of this method is not so , When the method returns true when , Indicates that there is no need to delay the export . return false when , Indicates the need to delay the export . Contrary to the literal meaning , It's strange . Let's take a look at the logic of this method .

// -*- ServiceBean
private boolean isDelay() {
// obtain delay
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
// If the previous acquisition delay It's empty , Here we continue to get
delay = provider.getDelay();
}
// Judge delay Is it empty , Or equal to -1
return supportedApplicationListener && (delay == null || delay == -1);
}

Temporary neglect supportedApplicationListener This condition , When delay It's empty , Or equal to -1 when , This method returns true, instead of false. The return value of this method is a bit confusing , So I refactored the code for this method , And give Dubbo I mentioned one Pull Request, Finally, this PR It's closed to Dubbo In the main branch . For details, see Dubbo #2686.

Now explain supportedApplicationListener Variable meaning , This variable is used to represent the current Spring Does the container support ApplicationListener, So this value starts off at zero false. stay Spring The container sets itself to ServiceBean In the middle of the day ,ServiceBean Of setApplicationContext The method will detect Spring Does the container support ApplicationListener. If supported , Will supportedApplicationListener Set as true. The code is not analyzed , Let's check it for ourselves .

ServiceBean yes Dubbo And Spring The key to framework Integration , It can be seen as a bridge between the two frames . There are also classes that have the same effect ReferenceBean.ServiceBean Realized Spring Some extended interfaces of , Yes FactoryBean、ApplicationContextAware、ApplicationListener、DisposableBean and BeanNameAware. These interfaces I'm in Spring Source analysis series The article introduces , You can refer to it , I won't repeat it here .

Now we know Dubbo The starting point of the service export process . So next , Let's whip it up , Keep the game going . The schedule , The next stop is “ Pre work of service export ”.

2.1 Front work

There are two parts in the front work , Configuration check , as well as URL assembly . Before exporting Services ,Dubbo We need to check whether the user's configuration is reasonable , Or add the default configuration to the user . After the configuration check , Next you need to assemble according to these configurations URL. stay Dubbo in ,URL It's very important .Dubbo Use URL As a configuration carrier , All the expansion points are through URL Get configuration . This point , It's stated in the official documents .

use URL As a unified format for configuration information , All extension points are passed through URL Carry configuration information .

Next , Let's first analyze the source code of the configuration check part , I'll analyze it later URL Assembly part of the source code .

2.1.1 Check the configuration

In this section, we will continue the previous source code analysis , As I said before onApplicationEvent Methods after some judgment , Will decide whether to call export Method export service . So let's go from export Methods start the analysis , as follows :

public synchronized void export() {
if (provider != null) {
// obtain export and delay To configure
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// If export by false, The service is not exported
if (export != null && !export) {
return;
} if (delay != null && delay > 0) { // delay > 0, Delay export service
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else { // Export service now
doExport();
}
}

export Two configurations were checked , And configure to perform corresponding actions . First of all export, This configuration determines whether the service is exported . Sometimes we just want to start the service locally and do some debugging work , At this time, we don't want to expose the locally started services to others . here , We can configure export Disable service export , such as :

<dubbo:provider export="false" />

delay See the name and know the meaning , For deferred export services . below , We continue to analyze the source code , This time I want to analyze doExport Method .

protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
// testing interfaceName Is it legal
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("interface not allow null!");
}
// testing provider Is it empty , If it is blank, a new , And initialize it with the system variable
checkDefault(); // Here are some if Statement is used to detect provider、application Whether the core configuration class object is empty ,
// If it is empty , Then try to get the corresponding instance from other configuration class objects .
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {...}
if (monitor == null) {...}
if (protocols == null) {...}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {...}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {...}
} // testing ref Whether to generalize service types
if (ref instanceof GenericService) {
// Set up interfaceClass by GenericService.class
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
// Set up generic = "true"
generic = Boolean.TRUE.toString();
}
} else { // ref Not GenericService type
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// Yes interfaceClass, as well as <dubbo:method> Check the necessary fields
checkInterfaceAndMethods(interfaceClass, methods);
// Yes ref To test the validity
checkRef();
// Set up generic = "false"
generic = Boolean.FALSE.toString();
} // local attribute Dubbo There's no official document saying , however local and stub The functions should be consistent , Used to configure local stubs
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
// Get the local stub class
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// Check whether the local stub class can be assigned to the interface class , If it is not assignable, an exception will be thrown , Remind the user that the local stub class type is illegal
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
} // stub and local Are used to configure local stubs
if (stub != null) {
// The code here is the same as the previous if The code of the branch is basically the same , Omit here
} // Check whether various objects are empty , If it is blank, a new , Or throw an exception
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
} // Export service
doExportUrls(); // ProviderModel Represents the service provider model , This object stores information about the service provider .
// For example, service configuration information , Service instance, etc . Each exported service corresponds to one ProviderModel.
// ApplicationModel Hold all of ProviderModel.
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}

The above is the relevant analysis of configuration check , More code , I need your patience to have a look at . The following is a brief summary of the logic of configuration checking , as follows :

  1. testing <dubbo:service> Labeled interface Attribute validity , Throw an exception against the law
  2. testing ProviderConfig、ApplicationConfig Whether the core configuration class object is empty , If it is empty , Then try to get the corresponding instance from other configuration class objects .
  3. Detect and handle generic services and generic service classes
  4. Detect local stub configuration , And deal with it accordingly
  5. Yes ApplicationConfig、RegistryConfig Wait for configuration class to detect , If it is empty, try to create , If it cannot be created, an exception is thrown

Configuration checking is not the focus of this article , So I'm not going to talk about doExport Method called by the method for analysis (doExportUrls Methods except ). In these methods , except appendProperties It's a little more complicated , Everything else is OK . therefore , You can make your own analysis . Okay , Not much else , I'm going to go down .

2.1.2 Multi protocol and multi registry export service

Dubbo Allows us to export services using different protocols , It also allows us to register services with multiple registries .Dubbo stay doExportUrls Method for multi protocol , Multiple registries support . The relevant code is as follows :

private void doExportUrls() {
// Load registry link
List<URL> registryURLs = loadRegistries(true);
// Traverse protocols, Export each service
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}

The above code is relatively simple , The first is through loadRegistries Load registry link , And I'm going to go through it again ProtocolConfig Collection exports each service . And in the process of exporting Services , Register the service with the registry . below , So let's see loadRegistries The logic of the method .

protected List<URL> loadRegistries(boolean provider) {
// Check if there is a registry configuration class , Throw an exception if it doesn't exist
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
String address = config.getAddress();
if (address == null || address.length() == 0) {
// if address It's empty , Set it to 0.0.0.0
address = Constants.ANYHOST_VALUE;
} // Load registry address from system properties
String sysaddress = System.getProperty("dubbo.registry.address");
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
// Judge address Is it legal
if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// add to ApplicationConfig Field information in to map in
appendParameters(map, application);
// add to RegistryConfig Field information to map in
appendParameters(map, config);
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
} // parsed URL list ,address There may be multiple registries ip,
// So the result of parsing is a URL list
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
// take URL The protocol header is set to registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
// By judging the conditions , Decide whether to add url To registryList in , The conditions are as follows :
// ( Service providers && register = true or null)
// || ( Non service providers && subscribe = true or null)
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}

The above code is not very complicated , It contains the following logic :

  1. Check if there is a registry configuration class , Throw an exception if it doesn't exist
  2. Build the parameter mapping set , That is to say map
  3. Build a list of registry Links
  4. Traverse the list of links , And decide whether to add it to registryList in

About the multi protocol multi registry export service, let's first analyze it , Not a lot of code , It's just a little more . The next analysis URL Assembly process .

2.1.3 assemble URL

After the configuration check , The next thing to do is according to the configuration , And some other information URL. As I said before ,URL yes Dubbo The carrier of configuration , adopt URL Allowing Dubbo Various configurations of are passed between modules .URL To Dubbo, What water is to fish , It's very important . Everybody is reading Dubbo In the process of exporting the relevant source code of the service , it is to be noted that URL Changes in content . since URL So important , So let's take a look at URL The assembly process .

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
// If the agreement name is empty , Or empty string , Set the protocol name variable to dubbo
if (name == null || name.length() == 0) {
name = "dubbo";
} Map<String, String> map = new HashMap<String, String>();
// add to side、 edition 、 Time stamp and process number to map in
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
} // Reflect the field information of an object to map in
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this); // methods by MethodConfig aggregate ,MethodConfig Stored in <dubbo:method> Tag configuration information
if (methods != null && !methods.isEmpty()) {
// This code is used to add Callback Configuration to map in , The code is too long , I'll analyze it later
} // testing generic Is it "true", And according to the test results to map Add different information to
if (ProtocolUtils.isGeneric(generic)) {
map.put(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
} // Generate a wrapper class for the interface Wrapper,Wrapper Interface details are included in , For example, an array of interface method names , Field information, etc
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
// Add the method name to map in , If it contains more than one method name , Separated by commas , such as method = init,destroy
if (methods.length == 0) {
logger.warn("NO method found in service interface ...");
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// Use comma as a separator to join method names , And put the connected string into map in
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
} // add to token To map in
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
// Determine whether the protocol name is injvm
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
} // Get context path
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
} // obtain host and port
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// assemble URL
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map); // Omit irrelevant code
}

The above code first gives some information , Such as version 、 Time stamp 、 The method name and the field information of various configuration objects are put into map in ,map The content in will be as URL The query string for . Build up map after , Next is getting the context path 、 Host name, port number, etc . The final will be map And the host name URL Constructors create URL object . It should be noted that , Here comes URL Is not java.net.URL, It is com.alibaba.dubbo.common.URL.

A piece of code has been omitted , Here is a brief analysis of . This code is used to detect <dubbo:argument> Configuration information in the tag , And add the relevant configuration to map in . The code is as follows :

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ... // methods by MethodConfig aggregate ,MethodConfig Stored in <dubbo:method> Tag configuration information
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
// add to MethodConfig Object field information to map in , key = Method name . Property name .
// Like storage <dubbo:method name="sayHello" retries="2"> Corresponding MethodConfig,
// key = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
appendParameters(map, method, method.getName()); String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
// testing MethodConfig retry Is it false, if , Set the number of retries to 0
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
} // obtain ArgumentConfig list
List<ArgumentConfig> arguments = method.getArguments();
if (arguments != null && !arguments.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// testing type Whether the attribute is empty , Or empty string ( Branch 1 ️)
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// Comparison method name , Find the target method
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
if (argument.getIndex() != -1) {
// testing ArgumentConfig Medium type Property and method parameter list
// Whether the parameter names in are consistent , Throw an exception if it is inconsistent ( Branch 2 ️)
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
// add to ArgumentConfig Field information to map in ,
// Key prefix = Method name .index, such as :
// map = {"sayHello.3": true}
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config error: ...");
}
} else { // Branch 3 ️
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
// From the list of parameter types, find the type named argument.type Parameters of
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("argument config error: ...");
}
}
}
}
}
}
} // User not configured type attribute , But with index attribute , And index != -1
} else if (argument.getIndex() != -1) { // Branch 4 ️
// add to ArgumentConfig Field information to map in
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config must set index or type");
}
}
}
}
} // ...
}

The above code for Circulation and if else Too many nested branches , It's too deep , Not conducive to reading , You need to take a look at it patiently . When you look at this code , Pay attention to find out several important conditional branches . As long as you understand the intentions of these branches , You can understand this code . I use... In the code above ️ The symbol identifies 4 It's an important branch , Let's use pseudo code to explain the meaning of these branches .

// obtain ArgumentConfig list 
for ( Traverse ArgumentConfig list ) {
if (type Not for null, It's not an empty string ) { // Branch 1
1. Get by reflection interfaceClass List of methods
for ( Traversal method list ) {
1. Comparison method name , Find the target method
2. Get the parameter type array of the target method through reflection argtypes
if (index != -1) { // Branch 2
1. from argtypes Get subscript from array index The element of argType
2. testing argType The name of ArgumentConfig Medium type Whether the attributes are consistent
3. add to ArgumentConfig Field information to map in , Or throw an exception
} else { // Branch 3
1. Traversal parameter type array argtypes, lookup argument.type Parameters of type
2. add to ArgumentConfig Field information to map in
}
}
} else if (index != -1) { // Branch 4
1. add to ArgumentConfig Field information to map in
}
}

In the source code analyzed in this section ,appendParameters This method appears many times , This method is used to add object field information to map in . In the implementation, the target object is obtained through reflection getter Method , And call this method to get the property value . And then through getter The method name resolves the property name , For example, from the method name getName Attributes can be parsed in name. If the user passes in a property name prefix , At this time, you need to add the attribute name to the prefix content . The final will be < Property name , Property value > Key value pairs are stored in map That's fine . Limited to space , There is no analysis here appendParameters Method source code , Please make your own analysis .

2.2 export Dubbo service

The front work is done , Next, you can export the service . Service export , Divided into export to local (JVM), And export to remote . Before in-depth analysis of service export source code , Let's look at the service export logic from a macro level . as follows :

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// Omit irrelevant code
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// load ConfiguratorFactory, And generate Configurator To configure url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
} String scope = url.getParameter(Constants.SCOPE_KEY);
// If scope = none, Then do nothing
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// scope != remote, Export to local
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
} // scope != local, Export to remote
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
// Load monitor link
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// Add the monitor link as a parameter to url in
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
} String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
} // Provide classes for services (ref) Generate Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker For holding only Invoker and ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // Export service , And generate Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
} else { // There is no registry , Export services only
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}

The above code is based on url Medium scope Parameters determine how the service is exported , They are as follows :

  • scope = none, Do not export services
  • scope != remote, Export to local
  • scope != local, Export to remote

Whether it's exported locally , Or remote . Before exporting Services , You need to create Invoker. This is a very important step , So next I'll analyze Invoker The creation process of .

2.2.1 Invoker The creation process

stay Dubbo in ,Invoker It 's a very important model . At the service provider , As well as service references Invoker.Dubbo In the official documents Invoker Explained , Here's a quote .

Invoker It's the entity domain , It is Dubbo Core model of , All the other models depend on it , Or into it , It represents an executable , You can start with it invoke call , It could be a local implementation , It could also be a remote implementation , It could also be a cluster implementation .

since Invoker So important , So it's necessary for us to find out Invoker Use of .Invoker By ProxyFactory Created from ,Dubbo default ProxyFactory The implementation class is JavassistProxyFactory. Now let's go to JavassistProxyFactory In the code , Explore Invoker The creation process of . as follows :

-- JavassistProxyFactory
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// Create... For the target class Wrapper
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// Create anonymity Invoker Class object , And implement doInvoke Method .
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// call Wrapper Of invokeMethod Method ,invokeMethod Finally, the target method
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}

Above ,JavassistProxyFactory Created an inheritance from AbstractProxyInvoker Class , And override the abstract method doInvoke. The over written doInvoke The logic is simple , Just forward the call request to Wrapper Class invokeMethod Method .Wrapper be used for “ The parcel ” Target class ,Wrapper Is an abstract class , Only through getWrapper(Class) Method to create a subclass . Creating Wrapper In the process of subclass , Subclass code generation logic will be for getWrapper Method passed in Class Object parsing , Get things like class methods , Class member variables and so on . And generate invokeMethod method code , And some other method code . After the code is generated , adopt Javassist Generate Class object , Finally, create... By reflection Wrapper example . The relevant code is as follows :

 public static Wrapper getWrapper(Class<?> c) {
while (ClassGenerator.isDynamicClass(c))
c = c.getSuperclass(); if (c == Object.class)
return OBJECT_WRAPPER; // Visiting and depositing
Wrapper ret = WRAPPER_MAP.get(c);
if (ret == null) {
// Cache miss , establish Wrapper
ret = makeWrapper(c);
// Write cache
WRAPPER_MAP.put(c, ret);
}
return ret;
}

getWrapper Method just contains some cache operation logic , Non emphasis . Let's focus on makeWrapper Method .

private static Wrapper makeWrapper(Class<?> c) {
// testing c Is private type , If so, throw an exception
if (c.isPrimitive())
throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c); String name = c.getName();
ClassLoader cl = ClassHelper.getClassLoader(c); // c1 Used to store setPropertyValue method code
StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
// c2 Used to store getPropertyValue method code
StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
// c3 Used to store invokeMethod method code
StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ "); // Generate type conversion code and exception capture code , such as :
// DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); // pts Used to store member variable names and types
Map<String, Class<?>> pts = new HashMap<String, Class<?>>();
// ms For storing method description information ( It can be understood as method signature ) And Method example
Map<String, Method> ms = new LinkedHashMap<String, Method>();
// mns List of method names
List<String> mns = new ArrayList<String>();
// dmns Used to store the name of the method defined in the current class
List<String> dmns = new ArrayList<String>(); // -------------------------------- Split line 1 ------------------------------------- // obtain public Access level fields , And generate conditional statements for all fields
for (Field f : c.getFields()) {
String fn = f.getName();
Class<?> ft = f.getType();
if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers()))
// Ignore keywords static or transient Decorated variable
continue; // Generate conditional judgment and assignment statements , such as :
// if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;}
// if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;}
c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }"); // Generate conditional judgment and return statements , such as :
// if( $2.equals("name") ) { return ($w)w.name; }
c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }"); // Storage < Field name , Field type > Key value to pts in
pts.put(fn, ft);
} // -------------------------------- Split line 2 ------------------------------------- Method[] methods = c.getMethods();
// testing c Whether to include methods declared in the current class in
boolean hasMethod = hasMethods(methods);
if (hasMethod) {
c3.append(" try{");
}
for (Method m : methods) {
if (m.getDeclaringClass() == Object.class)
// Ignore Object The method defined in
continue; String mn = m.getName();
// Generate method name statements , Examples are as follows :
// if ( "sayHello".equals( $2 )
c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
int len = m.getParameterTypes().length;
// Generate the number of parameters passed in at run time and the parameter list length judgment statement of the method , Examples are as follows :
// && $3.length == 2
c3.append(" && ").append(" $3.length == ").append(len); boolean override = false;
for (Method m2 : methods) {
// Check if the method is overloaded , Condition is : Methods are different from && Same method name
if (m != m2 && m.getName().equals(m2.getName())) {
override = true;
break;
}
}
// Handle overloaded methods , Consider the following approach :
// 1. void sayHello(Integer, String)
// 2. void sayHello(Integer, Integer)
// Same method name , The length of the parameter list is the same , So we can't judge whether the two methods are equal only by these two terms .
// The parameter type of the method needs to be further determined
if (override) {
if (len > 0) {
for (int l = 0; l < len; l++) {
// && $3[0].getName().equals("java.lang.Integer")
// && $3[1].getName().equals("java.lang.String")
c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"")
.append(m.getParameterTypes()[l].getName()).append("\")");
}
}
} // add to ) {, Complete method judgment statement , At this point, the generation method may be as follows ( Formatted ):
// if ("sayHello".equals($2)
// && $3.length == 2
// && $3[0].getName().equals("java.lang.Integer")
// && $3[1].getName().equals("java.lang.String")) {
c3.append(" ) { "); // Generate the target method call statement according to the return value type
if (m.getReturnType() == Void.TYPE)
// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
else
// return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");"); // add to }, At present ” Methods to judge the condition “ Code generation complete , The sample code is as follows ( Formatted ):
// if ("sayHello".equals($2)
// && $3.length == 2
// && $3[0].getName().equals("java.lang.Integer")
// && $3[1].getName().equals("java.lang.String")) {
//
// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
// return null;
// }
c3.append(" }"); // Add the method name to mns Collection
mns.add(mn);
// Check if the current method is c Declared in
if (m.getDeclaringClass() == c)
// if , Add the current method name to dmns in
dmns.add(mn);
ms.put(ReflectUtils.getDesc(m), m);
}
if (hasMethod) {
// Add Exception Catch statement
c3.append(" } catch(Throwable e) { ");
c3.append(" throw new java.lang.reflect.InvocationTargetException(e); ");
c3.append(" }");
} // add to NoSuchMethodException Exception throw code
c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); // -------------------------------- Split line 3 ------------------------------------- Matcher matcher;
// Handle get/set Method
for (Map.Entry<String, Method> entry : ms.entrySet()) {
String md = entry.getKey();
Method method = (Method) entry.getValue();
// Match with get Opening method
if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
// Get the property name
String pn = propertyName(matcher.group(1));
// Generate attribute judgments and return statements , Examples are as follows :
// if( $2.equals("name") ) { return ($w).w.getName(); }
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType()); // Match with is/has/can Opening method
} else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
String pn = propertyName(matcher.group(1));
// Generate attribute judgments and return statements , Examples are as follows :
// if( $2.equals("dream") ) { return ($w).w.hasDream(); }
c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }");
pts.put(pn, method.getReturnType()); // Match with set Opening method
} else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
Class<?> pt = method.getParameterTypes()[0];
String pn = propertyName(matcher.group(1));
// Generate attribute judgments and setter Call statement , Examples are as follows :
// if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }");
pts.put(pn, pt);
}
} // add to NoSuchPropertyException Exception throw code
c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }");
c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); // -------------------------------- Split line 4 ------------------------------------- long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
// Create a class generator
ClassGenerator cc = ClassGenerator.newInstance(cl);
// Set class name and superclass
cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
cc.setSuperClass(Wrapper.class); // Add default construction method
cc.addDefaultConstructor(); // Add fields
cc.addField("public static String[] pns;");
cc.addField("public static " + Map.class.getName() + " pts;");
cc.addField("public static String[] mns;");
cc.addField("public static String[] dmns;");
for (int i = 0, len = ms.size(); i < len; i++)
cc.addField("public static Class[] mts" + i + ";"); // Add method code
cc.addMethod("public String[] getPropertyNames(){ return pns; }");
cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
cc.addMethod("public String[] getMethodNames(){ return mns; }");
cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
cc.addMethod(c1.toString());
cc.addMethod(c2.toString());
cc.addMethod(c3.toString()); try {
// Generating classes
Class<?> wc = cc.toClass(); // Set field value
wc.getField("pts").set(null, pts);
wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
wc.getField("mns").set(null, mns.toArray(new String[0]));
wc.getField("dmns").set(null, dmns.toArray(new String[0]));
int ix = 0;
for (Method m : ms.values())
wc.getField("mts" + ix++).set(null, m.getParameterTypes()); // establish Wrapper example
return (Wrapper) wc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
cc.release();
ms.clear();
mns.clear();
dmns.clear();
}
}

It's a long code , Take a patient look . I made a lot of comments in the code above , And according to the function of the code block , To help you understand the logic of the code . This code is explained below . First of all, let's move to the dividing line 1 The code above , This code is mainly used for some initialization operations . Such as creating c1、c2、c3 as well as pts、ms、mns Equivariant , As well as to c1、c2、c3 Add method definition and type conversion code to . Next is the dividing line 1 To the dividing line 2 Code between , This code is for public Level field generation condition judgment value and assignment code . This code is not hard to understand , Not much . Keep looking down , Split line 2 And separation lines 3 Code between is used to generate judgment statements for methods defined in the current class , And method call statements . Because you need to verify method overloading , So this code looks a little bit complicated . But be patient , It's not hard to understand . Next is the dividing line 3 And separation lines 4 Code between , This code deals with getter、setter As well as is/has/can Opening method . The processing method is to get the method type through regular expression (get/set/is/...), And the attribute name . Then generate a judgment statement for the property name , Then generate the call statement for the method . Finally, let's look at the dividing line 4 The following code , This code passes ClassGenerator Build... For the code you just generated Class class , And create objects by reflection .ClassGenerator yes Dubbo Self encapsulated , The core of this class is toClass() Overload method of toClass(ClassLoader, ProtectionDomain), The method is passed by javassist structure Class. There is no analysis here toClass The method , Please make your own analysis .

read Wrapper Class code needs to javassist I know something about the framework . About javassist, If you are not familiar with , Please check the information by yourself , This section is not intended to introduce javassist Related content .

Okay , About Wrapper This is the analysis of class generation process . If you don't understand , You can do it alone Wrapper Create unit tests , Then step through the debugging . And copy the generated code , Observe and understand after formatting . Okay , This section starts with .

2.2.2 Export service to local

In this section, let's look at the code related to service export , In order of code execution , This section first analyzes the process of exporting the service to the local . The relevant code is as follows :

private void exportLocal(URL url) {
// If URL The agreement header of is equal to injvm, The description has been exported to the local , No need to export again
if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
URL local = URL.valueOf(url.toFullString())
.setProtocol(Constants.LOCAL_PROTOCOL) // Set the protocol header to injvm
.setHost(LOCALHOST)
.setPort(0);
ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
// establish Invoker, And export the service , there protocol Will be called at run time InjvmProtocol Of export Method
Exporter<?> exporter = protocol.export(
proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
exporters.add(exporter);
}
}

exportLocal The method is simple , First of all, according to the URL The protocol header determines whether to export the service . To export , Creates a new one URL And put the agreement header 、 Set the host name and port to new values . Then create Invoker, And call InjvmProtocol Of export Method export service . So let's see InjvmProtocol Of export What does the method do .

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
// establish InjvmExporter
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

Above ,InjvmProtocol Of export Method creates only one InjvmExporter, No other logic . At this point, the export service is analyzed locally , Next , Let's continue to analyze the process of exporting services to remote .

2.2.3 Export service to remote

Compared with exporting services to local , The process of exporting services to remote is much more complicated , It includes two processes: service export and service registration . These two processes involve a lot of calls , So it's more complicated . But no matter how hard it is , We all need to see , If you understand it . In order of code execution , This section first analyzes the service export logic , The service registration logic is analyzed in the next section . Let's start the analysis , We move our eyes to RegistryProtocol Of export On the way .

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// Export service
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); // Get the registry URL, With zookeeper For example, the Registration Center , The example we got URL as follows :
// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
URL registryUrl = getRegistryUrl(originInvoker); // according to URL load Registry Implementation class , such as ZookeeperRegistry
final Registry registry = getRegistry(originInvoker); // Get registered service providers URL, such as :
// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker); // obtain register Parameters
boolean register = registeredProviderUrl.getParameter("register", true); // Register the service provider with the service provider and consumer registry
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); // according to register The value of determines whether the service is registered
if (register) {
// Register services with the registry
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
} // Get subscription URL, such as :
// provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&check=false&anyhost=true&application=demo-provider&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
// Create a listener
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// Subscribe to the registry override data
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
// Create and return DestroyableExporter
return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}

The above code looks complicated , The main operations are as follows :

  1. call doLocalExport Export service
  2. Register services with the registry
  3. Subscribe to the registry override data
  4. Create and return DestroyableExporter

In the above operation , In addition to creating and returning DestroyableExporter It's not difficult , The other steps are not very simple . Among them , Export service and registration service are the logic to be analyzed in this chapter . subscribe override Data is not the key content , I'll give you a brief introduction later . Let's start the analysis of this section , Let's analyze it first doLocalExport The logic of the method , as follows :

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker) {
String key = getCacheKey(originInvoker);
// Access cache
ExporterChangeableWrapper<T> exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
synchronized (bounds) {
exporter = (ExporterChangeableWrapper<T>) bounds.get(key);
if (exporter == null) {
// establish Invoker For the delegate class object
final Invoker<?> invokerDelegete = new InvokerDelegete<T>(originInvoker, getProviderUrl(originInvoker));
// call protocol Of export Method export service
exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker); // Write cache
bounds.put(key, exporter);
}
}
}
return exporter;
}

The above code is a typical double check , You probably know that . Next , We focus on Protocol Of export On the way . Suppose the runtime protocol is dubbo, Here protocol Will load at run time DubboProtocol, And call DubboProtocol Of export Method . We turn to DubboProtocol Of export On the way , The correlation analysis is as follows :

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl(); // Get service ID , It's OK to understand it as a service coordinate . By service group name , service name , Service version number and port composition . such as :
// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880
String key = serviceKey(url);
// establish DubboExporter
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
// take <key, exporter> Key value pair in cache
exporterMap.put(key, exporter); // The following code should be related to the local stub , The code is not hard to understand , But the specific use is not clear , Ignore first
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
// Omit log print code
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
} // Start the server
openServer(url);
// Optimize serialization
optimizeSerialization(url);
return exporter;
}

Above , We focus on DubboExporter And openServer Method , It doesn't matter if you don't understand the other logic , Does not affect the understanding of the service export process . in addition ,DubboExporter The code is simpler , No analysis . The following analysis openServer Method .

private void openServer(URL url) {
// obtain host:port, And use it as the... Of the server instance key, Used to identify the current server instance
String key = url.getAddress();
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
// Access cache
ExchangeServer server = serverMap.get(key);
if (server == null) {
// Create a server instance
serverMap.put(key, createServer(url));
} else {
// Server created , According to url Configuration in reset server
server.reset(url);
}
}
}

Above , On the same machine ( Single network card ), Only one server instance is allowed to start on the same port . If a server instance already exists on a port , Call at this time reset Method to reset some configuration of the server . Considering the length , The code about server instance reset will not be analyzed . Next, analyze the creation process of the server instance . as follows :

private ExchangeServer createServer(URL url) {
url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,
// Add heartbeat detection configuration to url in
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// obtain server Parameters , The default is netty
String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER); // adopt SPI Detect the presence of server The parameter represents Transporter expand , Throw an exception if it doesn't exist
if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
throw new RpcException("Unsupported server type: " + str + ", url: " + url); // Add codec parameters
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
ExchangeServer server;
try {
// establish ExchangeServer
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server...");
} // obtain client Parameters , Can be specified netty,mina
str = url.getParameter(Constants.CLIENT_KEY);
if (str != null && str.length() > 0) {
// Get all Transporter Implementation class name collection , such as supportedTypes = [netty, mina]
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
// Detect current Dubbo Supported by the Transporter In the list of implementation class names ,
// Does it include client Represented by Transporter, If it doesn't include , Throw an exception
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type...");
}
}
return server;
}

Above ,createServer There are three core operations . The first is to detect the presence of server The parameter represents Transporter expand , Throw an exception if it doesn't exist . The second is to create a server instance . The third is to test whether it supports client The parameter represents Transporter expand , If there is no exception, it will throw an exception . The code corresponding to the two detection operations is quite straightforward , There is no need to say more . But the operation of creating the server is not very clear at present , Let's move on .

public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// obtain Exchanger, The default is HeaderExchanger.
// Next call HeaderExchanger Of bind Method creation ExchangeServer example
return getExchanger(url).bind(url, handler);
}

The above code is relatively simple , Not much . So let's see HeaderExchanger Of bind Method .

public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
// establish HeaderExchangeServer example , This method includes multi-step operation , This paper is as follows :
// 1. new HeaderExchangeHandler(handler)
// 2. new DecodeHandler(new HeaderExchangeHandler(handler))
// 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}

HeaderExchanger Of bind Methods contain a lot of logic , But for now we just need to care about Transporters Of bind Method logic is enough . The code for this method is as follows :

public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
// If handlers The number of elements is greater than 1, Create ChannelHandler The dispenser
handler = new ChannelHandlerDispatcher(handlers);
}
// Get adaptive Transporter example , And call the instance method
return getTransporter().bind(url, handler);
}

Above ,getTransporter() Method acquired Transporter Created dynamically at run time , Class called Transporter$Adaptive, That is, adaptive extension class . I am here Last article The generation process of adaptive extension class is analyzed in detail , Students who don't know about adaptive expansion can refer to my previous article , No more details here .Transporter$Adaptive Will be based on the incoming URL Parameters determine what type of Transporter, The default is NettyTransporter. Now let's continue to follow , This analysis is NettyTransporter Of bind Method .

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
// establish NettyServer
return new NettyServer(url, listener);
}

There is only one sentence to create NettyServer Code for , There's nothing to talk about , Let's keep looking down .

public class NettyServer extends AbstractServer implements Server {
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
// Call the parent class constructor
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
} public abstract class AbstractServer extends AbstractEndpoint implements Server {
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
// Call the parent class constructor , There's no need to follow in here , There's no complicated logic
super(url, handler);
localAddress = getUrl().toInetSocketAddress(); // obtain ip And port
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
// Set up ip by 0.0.0.0
bindIp = NetUtils.ANYHOST;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
// Get the maximum number of accepted connections
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
try {
// Call the template method doOpen Start the server
doOpen();
} catch (Throwable t) {
throw new RemotingException("Failed to bind ");
} DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
} protected abstract void doOpen() throws Throwable; protected abstract void doClose() throws Throwable;
}

Most of the above codes are assignment codes , No need to talk about it . We focus on doOpen Abstract method , This method needs subclass implementation . Let's go back to NettyServer in .

protected void doOpen() throws Throwable {
NettyHelper.setNettyLoggerFactory();
// establish boss and worker Thread pool
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); // establish ServerBootstrap
bootstrap = new ServerBootstrap(channelFactory); final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
bootstrap.setOption("child.tcpNoDelay", true);
// Set up PipelineFactory
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
// Bind to the specified ip And ports
channel = bootstrap.bind(getBindAddress());
}

That's all NettyServer Creation process ,dubbo Default NettyServer Is based on netty 3.x Version implementation , It's older . therefore Dubbo In addition, we provide netty 4.x Version of NettyServer, You can use Dubbo Configure as needed in the process of .

Here we are , The process of service export is analyzed . The whole process is complicated , Please be patient in the process of analysis . And write more Demo Carry out debugging , In order to better understand the code logic . Okay , This section starts with , Next, analyze another piece of logic for service export -- Service registration .

2.2.4 Service registration

In this section, we will analyze the service registration process , Service registration operation for Dubbo It's not necessary , The registry can be bypassed through direct service connection . But usually we don't do that , Direct connection is not conducive to service governance , It is only recommended to use when testing services in test environment . about Dubbo Come on , A registry is not required , But it is necessary . therefore , About the registry and the logic of service registration , We also need to understand .

This section is based on Zookeeper Registry as the target of analysis , For other types of registration centers, you can make your own analysis . Next, we start with the entry method of service registration , We turn our eyes to RegistryProtocol Of export On the way . as follows :

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// ${ Export service }
// Omit other code
boolean register = registeredProviderUrl.getParameter("register", true);
if (register) {
// Registration service
register(registryUrl, registeredProviderUrl);
ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
} final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// subscribe override data
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); // Omitted code
}

RegistryProtocol Of export Method contains the service export , register , And data subscription logic . The section on service export logic has been analyzed , This section analyzes the service registration logic , The data subscription logic will be analyzed in the next section . Let's start the analysis of this section , The relevant code is as follows :

public void register(URL registryUrl, URL registedProviderUrl) {
// obtain Registry
Registry registry = registryFactory.getRegistry(registryUrl);
// Registration service
registry.register(registedProviderUrl);
}

register Method consists of two steps , The first step is to get the registry instance , The second step is to register the service with the registry . Next , I will analyze these two steps in two sections . In order , Let's first analyze the logic of getting the registry .

2.2.4.1 Create a registry

This section is based on Zookeeper Take the registry as an example . Let's take a look at getRegistry Method source code , This method consists of AbstractRegistryFactory Realization . as follows :

public Registry getRegistry(URL url) {
url = url.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
String key = url.toServiceString();
LOCK.lock();
try {
// Access cache
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
} // Cache miss , establish Registry example
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry...");
} // Write cache
REGISTRIES.put(key, registry);
return registry;
} finally {
LOCK.unlock();
}
} protected abstract Registry createRegistry(URL url);

Above ,getRegistry Method to access the cache first , A cache miss calls createRegistry establish Registry, Then write to cache . there createRegistry It's a template method , Implemented by specific subclasses . therefore , Now let's go to ZookeeperRegistryFactory I want to explore it in the future .

public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
// zookeeperTransporter from SPI Inject... At run time , The type is ZookeeperTransporter$Adaptive
private ZookeeperTransporter zookeeperTransporter; public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
} @Override
public Registry createRegistry(URL url) {
// establish ZookeeperRegistry
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}

ZookeeperRegistryFactory Of createRegistry Method contains only one sentence of code , There is no need to explain , Keep following .

public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
} // Get group name , The default is dubbo
String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(Constants.PATH_SEPARATOR)) {
// group = "/" + group
group = Constants.PATH_SEPARATOR + group;
}
this.root = group;
// establish Zookeeper client , The default is CuratorZookeeperTransporter
zkClient = zookeeperTransporter.connect(url);
// Add a status listener
zkClient.addStateListener(new StateListener() {
@Override
public void stateChanged(int state) {
if (state == RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
}

In the code above , We focus on ZookeeperTransporter Of connect Method call , This method is used to create Zookeeper client . Create good Zookeeper client , This means that the creation of the registry is over . however , Obviously we can't stop there , Aren't you interested in learning about it Zookeeper The creation process of the client ? If there is , So keep looking down . If not , Jump straight to the next section . Then I went on to analyze .

As I said before , there zookeeperTransporter The type is adaptive extension class , therefore connect Method will decide what type to load when it is called ZookeeperTransporter expand , The default is CuratorZookeeperTransporter. Now let's go to CuratorZookeeperTransporter Take a look in the middle .

public ZookeeperClient connect(URL url) {
// establish CuratorZookeeperClient
return new CuratorZookeeperClient(url);
}

The above method is only used to create CuratorZookeeperClient example , There's nothing to say , Keep looking down .

public class CuratorZookeeperClient extends AbstractZookeeperClient<CuratorWatcher> {
private final CuratorFramework client;
public CuratorZookeeperClient(URL url) {
super(url);
try {
// establish CuratorFramework Constructors
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(1, 1000))
.connectionTimeoutMs(5000);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
// structure CuratorFramework example
client = builder.build();
// Add listener
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
if (state == ConnectionState.LOST) {
CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
} else if (state == ConnectionState.CONNECTED) {
CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
} else if (state == ConnectionState.RECONNECTED) {
CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
}
}
}); // Start client
client.start();
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}

CuratorZookeeperClient Construction methods are mainly used to create and start CuratorFramework example . The above is basically Curator Code for the framework , If you are right Curator I don't know much about the framework , You can refer to Curator Official documents , And write something Demo Running .

This section analyzes ZookeeperRegistry Instance creation process , The whole process is not very complicated . After reading the analysis , You can debug it yourself , To impress . Now the registry instance is created , The next thing to do is to register the service with the registry , Let's move on .

2.2.4.2 Node creation

With Zookeeper For example , So called service registration , It's essentially writing service configuration data to Zookeeper Under the node of a path of . To test that , Next we will Dobbo Official examples run , And then through Zookeeper Visualization client ZooInspector View node data . as follows :

As you can see from the above figure com.alibaba.dubbo.demo.DemoService Configuration information corresponding to this service ( Stored in URL in ) Finally registered to /dubbo/com.alibaba.dubbo.demo.DemoService/providers/ Under the node . Understand the essence of service registration , Then we can read the service registration code . The interface for service registration is register(URL), This method is defined in FailbackRegistry In an abstract class . The method code is as follows :

public void register(URL url) {
super.register(url);
failedRegistered.remove(url);
failedUnregistered.remove(url);
try {
// Template method , Implemented by subclasses
doRegister(url);
} catch (Exception e) {
Throwable t = e; // obtain check Parameters , if check = true Will throw an exception directly
boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
&& url.getParameter(Constants.CHECK_KEY, true)
&& !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to register");
} else {
logger.error("Failed to register");
} // Record the links that failed to register
failedRegistered.add(url);
}
} protected abstract void doRegister(URL url);

Above , We focus on doRegister Method call , Ignore the rest of the code .doRegister Method is a template method , So we went to FailbackRegistry Subclass ZookeeperRegistry Analysis in . as follows :

protected void doRegister(URL url) {
try {
// adopt Zookeeper The client creates the node , The node path is made by toUrlPath Method generation , The path format is as follows :
// /${group}/${serviceInterface}/providers/${url}
// such as
// /dubbo/com.tianxiaobo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register...");
}
}

Above ,ZookeeperRegistry stay doRegister Called in Zookeeper Client creates service node . The node path is made by toUrlPath Method generation , The logic of this method is not difficult to understand , No analysis . The next analysis create Method , as follows :

public void create(String path, boolean ephemeral) {
if (!ephemeral) {
// If the node type to be created is not a temporary node , Then, we need to detect whether the node exists
if (checkExists(path)) {
return;
}
}
int i = path.lastIndexOf('/');
if (i > 0) {
create(path.substring(0, i), false); // Recursively create the upper path
} // according to ephemeral Create a temporary or persistent node with the value of
if (ephemeral) {
createEphemeral(path);
} else {
createPersistent(path);
}
}

The above method is to create the upper path of the current node through recursion , And then based on ephemeral The value of determines whether to create a temporary or persistent node .createEphemeral and createPersistent Both methods are relatively simple , Here is a brief analysis of one of them . as follows :

public void createEphemeral(String path) {
try {
// adopt Curator The framework creates nodes
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}

Okay , This is the end of the process of service registration . The whole process can be summarized as : First, create a registry instance , Then register the service through the registry instance . This section starts with , Next, analyze the data subscription process .

2.2.5 subscribe override data

subscribe override I took a cursory look at the code corresponding to the data , The main purpose of this part of the code is to , Re export service . The specific use scenario should be when we pass Dubbo After the management background modifies the service configuration ,Dubbo Get notification that the service configuration has been modified , Then re export the service . This usage scenario is just a guess , I didn't verify it . If you are interested, you can verify it yourself .

override Data subscription related code is not rare , Considering the length and importance of the article , So I decided not to analyze this logic in detail . If you're interested , You can analyze it by yourself .

3. summary

This article analyzes in detail Dubbo Service export process , Including configuration detection ,URL assemble ,Invoker The creation process 、 Export services and registration services and so on . The space is quite large , We need to read patiently . For this article , I suggest you use it as a reference book . Skip to the specified chapter when you need to , Reading through may be a bit tiring . Because of the length of the article , So it might hide some mistakes that I didn't realize . If you find mistakes in the process of reading , Also please indicate . If you can give me any advice , So much the better , Thank you first .

Okay , That's all for this article . Thank you for reading .

This article is based on the knowledge sharing license agreement 4.0 Issue under , In case of reprint, the source shall be indicated clearly

author : Tian Xiao Bo

This article is published in my blog :http://www.tianxiaobo.com


This work adopts Creative Commons signature - Noncommercial use - No derivatives 4.0 International licensing agreement Licensing .

Dubbo Source code analysis - More articles on service export

  1. Dubbo Source code analysis - Service invocation procedure

    notes : This series of articles has been donated to Dubbo Community , You can also be in Dubbo Read this series in the official document . 1. brief introduction In the previous article , We analyzed Dubbo SPI. Service export and import . And the code for cluster fault tolerance . after ...

  2. Dubbo Source code analysis - The service reference

    1. brief introduction In the last article , I analyzed the principle of service export in detail . In this article, we strike while the iron is hot , Continue to analyze the principle of service reference . stay Dubbo in , We can reference remote services in two ways . The first is to use service direct connection to reference services , The second kind ...

  3. Dubbo Source code reading - Service export

    Dubbo The service export process starts with Spring Container publish refresh event ,Dubbo After receiving the event , The service export logic is executed immediately . The whole logic can be roughly divided into three parts , The first part is the front work , Mainly used to check parameters , assemble URL. The second part is export service ...

  4. Dubbo Source code analysis - Cluster fault tolerance LoadBalance

    1. brief introduction LoadBalance Load balancing , Its job is to make network requests , Or other forms of load " capitation " On different machines . Avoid excessive pressure on some servers in the cluster , Other servers are idle . through ...

  5. Dubbo Source code analysis - Cluster fault tolerance Cluster

    1. brief introduction To avoid a single point of failure , Today's applications will be deployed on at least two servers . For some services with high load , More servers will be deployed . such , The number of service providers in the same environment will be greater than 1. For service consumers , There are multiple service providers in the same environment ...

  6. Dubbo Source code analysis - Cluster fault tolerance Router

    1. brief introduction The last article analyzed the first part of cluster fault tolerance -- Service catalog Directory. The service directory is refreshing Invoker In the process of listing , Will pass Router Routing services . In the last article, there is no logic about service routing ...

  7. dubbo Source code analysis 2-reference bean Initiate a service method call

    dubbo Source code analysis 1-reference bean establish dubbo Source code analysis 2-reference bean Initiate a service method call dubbo Source code analysis 3-service bean Create and publish dubbo Source code points ...

  8. dubbo Source code analysis 6-telnet The way of management is realized

    dubbo Source code analysis 1-reference bean establish dubbo Source code analysis 2-reference bean Initiate a service method call dubbo Source code analysis 3-service bean Create and publish dubbo Source code points ...

  9. dubbo Source code analysis 1-reference bean establish

    dubbo Source code analysis 1-reference bean establish dubbo Source code analysis 2-reference bean Initiate a service method call dubbo Source code analysis 3-service bean Create and publish dubbo Source code points ...

Random recommendation

  1. bzoj 3438: Small M The crops of China

    Description background Small M I like to play MC Children's paper ... describe Small M stay MC Two huge plots of arable land have been opened up A and B( You can think of capacity as infinite ), Now? , Small P Yes n Seeds of crops in the world , The seeds of every crop have 1 individual ( It can be a kind of ...

  2. Audio Offload

    Audio Offload Audio distribution , It is the function of the system to load the audio to the sound card hardware . from Windows 8 Start , Hardware acceleration and offload of audio are back . Why is it back ? Because the sound card was invented by chuangtong company , of some length ...

  3. Advanced Foundation ( One ) And HashMap Implementation principle analysis

    HashMap Implementation principle analysis 1. HashMap Data structure of There are arrays and linked lists in the data structure to store the data , But the two are basically two extremes . Array The storage range of an array is continuous , Serious memory occupation , So the space is very complex . But two of the arrays ...

  4. golang stay windows The next edition translates linux A software suite of binary executable files [go 1.7.3 Environmental Science ]

    golang useful , But keep the tool chain intact . Or you will find that you can't compile cross platform ? How to write code without prompt ? ... It's not easy to get this whole set down . So we have carefully prepared a set of tools for everyone to use . The software list is shown in the figure . Installation sequence ...

  5. be based on mindwave EEG fatigue detection algorithm design (1)

    One . brief introduction brain waves , It's also called brain waves , It's the waves from the human brain , Very weak , It can only be detected by the device . Human brain waves are in different states , Would be different , Therefore, we can quantitatively analyze people's mental state through brain waves . Scientists say there are four types of brain waves , Here is a detailed explanation (1 ...

  6. ubuntu install cocos2dx

    operating system :ubuntu16.04 LTSIDE:Code::blocks 16.01Cocos2dx edition :cocos2d-x 3.11.1 This essay will briefly demonstrate how to ubuntu Lower installation cocos2dx ...

  7. Learn from scratch sfr and sbit

    1.reg52.h The header file , It defines some port physical addresses of MCU . #ifndef __REG52_H__ #define __REG52_H__ /* BYTE Registers */ sfr P0 ...

  8. Limit RICHTEXTBOX The input range of

        The attachment : http://files.cnblogs.com/xe2011/WindowsFormsApplication_LimitRichTextBoxInput.rar     using  ...

  9. svn Code rollback command svn up -r

    Case one : Changes have not been submitted (commit). In this case , Use svn revert You can cancel the previous changes . svn revert Usage is as follows : # svn revert [-R] something among so ...

  10. Win 2008 R2 install SQL Server 2008“ Performance counter registry hive consistency ” A failed solution

    Win 2008 R2 install SQL Server 2008“ Performance counter registry hive consistency ” A failed solution (2011-02-23 19:37:32) Reprint ▼   Install the database on the HP server today 2008 when , ...