The Dubbo service references the process and implementation details welcome thumbs up, thumbs up, thumbs up.
Follow my public account Solomon for more highlights
Process and implementation details for Dubbo service references
1, the introduction of
In Dubbo, we can refer to remote services in two ways. The first is to reference a service directly, and the second is to reference it based on a registry. Direct service connection is only suitable for commissioning or testing services, not for online environments. Therefore, in this article I will focus on the process of referencing services through a registry. Getting the service configuration from the registry is just one step in the service reference process. Besides, the service consumer also needs to go through the Invoker creation, proxy class creation, and so on.
2. Principle of service reference
The Dubbo service can be referenced when the Spring container calls the afterPropertiesSet method of the ReferenceBean, or when the corresponding service of the ReferenceBean is injected into another class. The difference in timing between these two reference services is that the first is hungrier and the second is slacker. By default, Dubbo uses the lazy reference service. If you need to use the hanhan-type function, configure the init property of Dubbo :reference to enable it. Let’s do the analysis with the default Dubbo configuration, starting with the getObject method of the ReferenceBean. When our service is injected into another class, Spring first calls the getObject method, which performs the service reference logic. As a rule, configuration check and collection should be performed before any specific work. The collected information is then used to determine how the service will be used. There are three ways to reference a local (JVM) service, a direct connection to a remote service, and a registry to reference a remote service. Either way, you end up with an Invoker instance. If you have multiple registries and multiple service providers, you will get a set of Invoker instances, and you will need to merge these invokers into one instance through the Cluster management class Cluster. The merged Invoker instance already has the ability to invoke local or remote services, but it cannot be exposed to the user for use, which would be intrusive to the user’s business code. At this point, the framework also needs to generate proxy classes for the service interface through the ProxyFactory class (ProxyFactory) and let the proxy classes invoke the Invoker logic. It avoids the intrusion of Dubbo framework code into business code and also makes the framework easier to use.
3. Source code analysis
The entry method to the service reference is the ReferenceBean’s getObject method, which is defined in Spring’s FactoryBean interface and implemented by ReferenceBean. The implementation code is as follows:
public Object getObject(a) throws Exception {
return get();
}
public synchronized T get(a) {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
// Check if ref is empty and init if it is
if (ref == null) {
The init method is mainly used to handle configuration and createProxy classes by calling createProxy
init();
}
return ref;
}
Copy the code
The code for the above two methods is short and not hard to understand. It is important to note that if you are debugging getObject 2.6.4 and below, you will encounter some strange problems. This assumes that you use IDEA and keep the default configuration of IDEA. If (ref == null) of the get method, you will find that ref is not null, so you can’t go into the init method to continue debugging. The reason for this is that the Dubbo framework itself has some minor issues. The issue was fixed in Pull Request #2754 and released with version 2.6.5. If you are learning 2.6.4 or later, you can circumvent this problem by modifying the IDEA configuration. First search for toString in the IDEA configuration popup and then uncheck Enable ‘toString’ Object view. Details are as follows:
! [img] (Dubbo service reference and the process of the implementation details. Assets / 15417503733794 JPG)
3.1 Handling the Configuration
Dubbo provides rich configurations for tuning and optimizing framework behavior, performance, and more. Dubbo checks and processes these configurations before referencing or exporting services to ensure correct configurations. The configuration parsing logic is encapsulated in the init method of ReferenceConfig, which is analyzed below.
private void init(a) {
// Avoid repeated initialization
if (initialized) {
return;
}
initialized = true;
// Check the validity of the interface name
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("interface not allow null!");
}
// Test if the consumer variable is empty
checkDefault();
appendProperties(this);
if (getGeneric() == null&& getConsumer() ! =null) {
/ / set the generic
setGeneric(getConsumer().getGeneric());
}
// Check if it is a generalized interface
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
/ / load the classes
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ✨ ✨ line 1 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
// Get the attribute value corresponding to the interface name from the system variable
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
// Get the parse file path from system properties
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
// Loads the configuration file from the specified location
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
// Get the absolute file pathresolveFile = userResolveFile.getAbsolutePath(); }}if(resolveFile ! =null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
// Load the configuration from a file
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload ... , cause:...");
} finally {
try {
if (null! = fis) fis.close(); }catch(IOException e) { logger.warn(e.getMessage(), e); }}// Get the configuration corresponding to the interface nameresolve = properties.getProperty(interfaceName); }}if(resolve ! =null && resolve.length() > 0) {
// Assign resolve to the URL
url = resolve;
}
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ✨ ✨ line 2 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
if(consumer ! =null) {
if (application == null) {
// Get the Application instance from consumer, same as below
application = consumer.getApplication();
}
if (module= =null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) { monitor = consumer.getMonitor(); }}if (module! =null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor(); }}if(application ! =null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) { monitor = application.getMonitor(); }}// Check Application validity
checkApplication();
// Check the validity of the local stub configuration
checkStubAndMock(interfaceClass);
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 3 ✨ ✨ segmentation -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
// Add side, protocol version information, timestamp, and process number to map
map.put(Constants.SIDE_KEY, Constants.CONSUMER_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()));
}
// Non-generic services
if(! isGeneric()) {// Get the version
String revision = Version.getVersion(interfaceClass, version);
if(revision ! =null && revision.length() > 0) {
map.put("revision", revision);
}
// Get the list of interface methods and add them to the map
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
// Add the fields of ApplicationConfig, ConsumerConfig, ReferenceConfig and other objects to the map
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 4 ✨ ✨ rules -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
String prefix = StringUtils.getServiceKey(map);
if(methods ! =null && !methods.isEmpty()) {
// Iterate over the MethodConfig list
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
// Check if map contains methodname.retry
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
// Adds the retries configuration to methodname.retries
map.put(method.getName() + ".retries"."0"); }}// Add the "Properties" field in MethodConfig to attributes
// Such as onreturn, onthrow, oninvoke, etc
appendAttributes(attributes, method, prefix + "."+ method.getName()); checkAndConvertImplicitConfig(method, map, attributes); }}/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 5 ✨ ✨ rules -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
// Get the service consumer IP address
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property..." );
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
// Store attributes in the system context
StaticContext.getSystemContext().putAll(attributes);
// Create the proxy class
ref = createProxy(map);
// Construct the ConsumerModel according to the service name, ReferenceConfig, and the proxy class.
// Store the ConsumerModel into the ApplicationModel
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
Copy the code
The above code is long and does a lot of things. This is where the code is partitioned according to the code logic.
The first is the code between the start of the method and the splitter line 1. This code checks for the existence of the ConsumerConfig instance, creates a new instance if it doesn’t, and populates the ConsumerConfig fields either through system variables or through the Dubo.properties configuration file. Next, detect the generalized configuration and set the value of the interfaceClass based on the configuration. Now let’s look at the logic between secant 1 and secant 2. This logic is used to load the configuration corresponding to the interface name from system properties or configuration files and assign the result of the parsing to the URL field. The URL field is typically used for point-to-point calls. Moving on, the code between splitter lines 2 and splitter line 3 is used to detect if several core configuration classes are empty, and if they are, try to fetch from other configuration classes. The code between splitter 3 and splitter 4 is mainly used to collect various configurations and store them in a map. The code between split lines 4 and 5 is used to handle the MethodConfig instance. This instance contains event notification configurations such as onReturn, onThrow, onInvoke, and so on. The code from line 5 to the end of the method is mainly used to resolve the service consumer IP and create the proxy object by calling createProxy.
3.2 Reference Service
In this section we’ll start with createProxy. Literally, createProxy seems to be used only to createProxy objects. But that’s not the case; the method also calls other methods to build and merge Invoker instances. The details are as follows.
private T createProxy(Map<String, String> map) {
URL tmpUrl = new URL("temp"."localhost".0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
// if the url configuration is specified, no local reference is made
if(url ! =null && url.length() > 0) {
isJvmRefer = false;
// Check whether local references are required according to the protocol, scope, and injVM parameters of the URL
For example, if scope=local is explicitly specified, isInjvmRefer returns true
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
isJvmRefer = true;
} else {
isJvmRefer = false; }}else {
// Get injVM configuration value
isJvmRefer = isInjvm().booleanValue();
}
// Local reference
if (isJvmRefer) {
// Generate a local reference URL with the protocol injvm
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
// Call the refer method to build InjvmInvoker instance
invoker = refprotocol.refer(interfaceClass, url);
// Remote reference
} else {
// The URL is not empty, indicating that the user may want to make a point-to-point call
if(url ! =null && url.length() > 0) {
// If multiple urls need to be configured, use semicolons to split them
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if(us ! =null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
// Set the fully qualified name of the interface to url path
url = url.setPath(interfaceName);
}
// Check whether the URL protocol is Registry, if the user wants to use the specified registry
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
// Convert map to a query string and add it to the URL as the value of the refer parameter
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
// Merge urls to remove some of the service provider's configurations from user-configured URL attributes,
For example, thread pool configuration. And retain some of the configuration of the service provider, such as version, group, timestamp, and so on
// Finally set the merged configuration to the URL query string.urls.add(ClusterUtils.mergeUrl(url, map)); }}}}else {
// Load the registry URL
List<URL> us = loadRegistries(false);
if(us ! =null && !us.isEmpty()) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if(monitorUrl ! =null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
// Add the refer parameter to the URL and add the URL to the urlsurls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); }}// No registry is configured, an exception is thrown
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference..."); }}// Single registry or service provider (services directly connected, same below)
if (urls.size() == 1) {
// Build the Invoker instance by calling the refer of RegistryProtocol
invoker = refprotocol.refer(interfaceClass, urls.get(0));
// Multiple registries or multiple service providers, or a mixture of both
} else{ List<Invoker<? >> invokers =newArrayList<Invoker<? > > (); URL registryURL =null;
// Get all invokers
for (URL url : urls) {
// Build Invoker by calling refer with refProtocol at run time
// Load the specified Protocol instance according to the URL Protocol header and call the refer method of the instance
invokers.add(refprotocol.refer(interfaceClass, url));
if(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; }}if(registryURL ! =null) {
AvailableCluster is used if the registry link is not empty
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
// Create an instance of StaticDirectory and merge invokers by Cluster
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null&& consumer ! =null) {
c = consumer.isCheck();
}
if (c == null) {
c = true;
}
// Invoker availability check
if(c && ! invoker.isAvailable()) {throw new IllegalStateException("No provider available for the service...");
}
// Generate the proxy class
return (T) proxyFactory.getProxy(invoker);
}
Copy the code
The above code is a lot, but the logic is clear. First check whether the call is local based on the configuration. If so, call the refer method of InjvmProtocol to generate an instance of InjvmInvoker. If not, it reads the directly connected configuration item, or the registry URL, and stores the read URL in urls. Then follow up based on the number of urls elements. If the number of urls elements is 1, the Invoker instance interface is built directly from the Protocol adaptive extension class. If the number of urls elements is greater than 1, that is, there are multiple registry or service directly linked urls, then the Invoker is built from the URL first. It then merges multiple invokers through the Cluster and finally calls ProxyFactory to generate the proxy class. The Invoker build process and the proxy class process are important, so these two processes will be examined in the next two sections.
3.2.1 create Invoker
Invoker is the core model of Dubbo and represents an executable. On the service provider side, Invoker is used to invoke the service provider class. On the service consumer side, Invoker is used to make remote calls. Invoker is built from the Protocol implementation class. Protocol implementation classes there are many, this section will analyze the most common two, respectively is the RegistryProtocol and DubboProtocol, the rest of the analysis by yourself. The following to analyze DubboProtocol refer method source code. As follows:
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
/ / create DubboInvoker
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
Copy the code
The above method seems simple, but there is one call that needs our attention: getClients. This method is used to get a client instance of the type ExchangeClient. ExchangeClient does not actually have the ability to communicate; it needs to communicate based on a lower-level client instance. For example, NettyClient, MinaClient, etc. By default, Dubbo uses NettyClient to communicate. Next, let’s take a quick look at the logic of the getClients method.
private ExchangeClient[] getClients(URL url) {
// Whether to share the connection
boolean service_share_connect = false;
// Get the number of connections. The default value is 0, indicating that the connection is not configured
int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
// If connections is not configured, the connection is shared
if (connections == 0) {
service_share_connect = true;
connections = 1;
}
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
// Obtain the shared client
clients[i] = getSharedClient(url);
} else {
// Initializes the new clientclients[i] = initClient(url); }}return clients;
}
Copy the code
Depending on the number of connections, the decision is to get a shared client or create a new client instance. By default, the shared client instance is used. The getSharedClient method also calls the initClient method, so let’s look at both methods together.
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
// Get ExchangeClient with reference counting
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if(client ! =null) {
if(! client.isClosed()) {// Increase the reference count
client.incrementAndGetCount();
return client;
} else {
referenceClientMap.remove(key);
}
}
locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);
}
// Create ExchangeClient client
ExchangeClient exchangeClient = initClient(url);
/ / will ExchangeClient instance to ReferenceCountExchangeClient, here use the decorator pattern
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
returnclient; }}Copy the code
The above method accesses the cache first. If the cache fails, a new ExchangeClient instance is created through initClient. And will the instance to ReferenceCountExchangeClient constructor to create a ExchangeClient instance with reference counting function. ReferenceCountExchangeClient internal implementation is simple, is not analyzed. Let’s look at the code for the initClient method again.
private ExchangeClient initClient(URL url) {
// Get the client type. The default is netty
String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));
// Add codec and heartbeat packet parameters to the URL
url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
// Check if the client type exists and throw an exception if it does not
if(str ! =null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported client type: ...");
}
ExchangeClient client;
try {
// Gets the lazy configuration and determines the type of client to be created based on the configuration value
if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
// Create a lazy loaded ExchangeClient instance
client = new LazyConnectExchangeClient(url, requestHandler);
} else {
// Create a normal ExchangeClient instanceclient = Exchangers.connect(url, requestHandler); }}catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service...");
}
return client;
}
Copy the code
The initClient method first gets the client type configured by the user, which is netty by default. It then checks whether the client type configured by the user exists and throws an exception if it does not. Finally, the lazy configuration determines what type of client to create. Here LazyConnectExchangeClient code is not very complex, the class will be in the request method is invoked by the connect method of Exchangers create ExchangeClient client, this section is not analyzed the class code. Let’s examine Exchangers’ CONNECT method.
public static ExchangeClient connect(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");
// Recovery instance, the default is HeaderExchangeClient
return getExchanger(url).connect(url, handler);
}
Copy the code
As shown above, getExchanger loads an instance of HeaderExchange lient via SPI. This method is relatively simple, so take a look for yourself. Next, the implementation of HeaderExchange Lient is analyzed.
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
// This contains multiple calls, as follows:
// 1. Create HeaderExchangeHandler object
// 2. Create DecodeHandler object
// 3. Use Transporters to build a Client instance
// 4. Create HeaderExchangeClient object
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
Copy the code
There are more calls here, so let’s focus on the Transporters Connect method. As follows:
public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
ChannelHandler handler;
if (handlers == null || handlers.length == 0) {
handler = new ChannelHandlerAdapter();
} else if (handlers.length == 1) {
handler = handlers[0];
} else {
// If the number of handlers is greater than 1, create a ChannelHandler distributor
handler = new ChannelHandlerDispatcher(handlers);
}
// Get the Transporter adaptive extension class and call connect to generate a Client instance
return getTransporter().connect(url, handler);
}
Copy the code
As above, the getTransporter method returns an adaptive extension class that loads the specified Transporter implementation class at run time based on the client type. If the client type is not configured, the NettyTransporter is loaded by default and its connect method is invoked. As follows:
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
// Create NettyClient object
return new NettyClient(url, listener);
}
Copy the code
Netty API to build a Netty client, you are interested in their own look. This concludes the analysis of the refer method for DubboProtocol. Next, analyze the logic of the refer method of RegistryProtocol.
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// Take the registry parameter value and set it to the protocol header
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
// Get the registry instance
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// Convert the URL query string to Map
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
// Get the group configuration
String group = qs.get(Constants.GROUP_KEY);
if(group ! =null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
// Load the MergeableCluster instance through SPI and call doRefer to continue the service reference logic
returndoRefer(getMergeableCluster(), registry, type, url); }}// Call doRefer to continue the service reference logic
return doRefer(cluster, registry, type, url);
}
Copy the code
The code above first sets the protocol header for the URL and then loads the registry instance based on the URL parameters. It then gets the group configuration, which determines the type of the first parameter of doRefer. The focus here is on the doRefer method, as follows:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// Create a RegistryDirectory instance
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// Set up the registry and protocol
directory.setRegistry(registry);
directory.setProtocol(protocol);
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// Generate service consumer links
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
// Register service consumers, new node in the consumers directory
if(! Constants.ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(Constants.REGISTER_KEY,true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
// Subscribe to node data such as Providers, Configurators, and Routers
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
// A registry can have multiple service providers, so you need to consolidate multiple service providers into one
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
Copy the code
As above, the doRefer method creates an instance of RegistryDirectory, then generates a service consumer link and registers it with the registry. After the registration is complete, you can subscribe to the data of providers, Configurators, and Routers. After the subscription is complete, RegistryDirectory receives information about the children under these nodes. Since a service can be deployed on multiple servers, there will be multiple nodes in providers, which requires the Cluster to merge the multiple service nodes into one and generate an Invoker. About RegistryDirectory and Cluster.
3.2.2 Creating an Agent
Once Invoker is created, the next thing to do is generate proxy objects for the service interface. With a proxy object, you can make remote calls. The entry method generated by the proxy object is getProxy of ProxyFactory, which is analyzed next.
public <T> T getProxy(Invoker<T> invoker) throws RpcException {
// Call overloaded methods
return getProxy(invoker, false);
}
public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException { Class<? >[] interfaces =null;
// Get the list of interfaces
String config = invoker.getUrl().getParameter("interfaces");
if(config ! =null && config.length() > 0) {
// List of shard interfaces
String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
if(types ! =null && types.length > 0) {
interfaces = newClass<? >[types.length +2];
// Set the service interface class and echoService. class to interfaces
interfaces[0] = invoker.getInterface();
interfaces[1] = EchoService.class;
for (int i = 0; i < types.length; i++) {
// Load the interface class
interfaces[i + 1] = ReflectUtils.forName(types[i]); }}}if (interfaces == null) {
interfaces = newClass<? >[]{invoker.getInterface(), EchoService.class}; }// Provide generalized call support for HTTP and hessian protocols, see Pull Request #1827
if(! invoker.getInterface().equals(GenericService.class) && generic) {intlen = interfaces.length; Class<? >[] temp = interfaces;// Create a new interfaces array
interfaces = newClass<? >[len +1];
System.arraycopy(temp, 0, interfaces, 0, len);
// Set GenericService.class to array
interfaces[len] = GenericService.class;
}
// Call overloaded methods
return getProxy(invoker, interfaces);
}
public abstract <T> T getProxy(Invoker
invoker, Class
[] types)
;
Copy the code
As shown above, the entire code is used to retrieve an array of interfaces, so let’s move on. getProxy(Invoker, Class<? >[]) this method is abstract. Let’s look at the implementation code of JavassistProxyFactory.
public <T> T getProxy(Invoker
invoker, Class
[] interfaces)
{
// Subclass Proxy (Proxy is an abstract class). Create a Proxy instance by calling the newInstance method of the Proxy subclass
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
Copy the code
The above code is not very much, the first is to get the Proxy subclass through Proxy getProxy method, then create the InvokerInvocationHandler object, and pass this object to newInstance to generate a Proxy instance. InvokerInvocationHandler Implements the JDK’s InvocationHandler interface, which is used to intercept interface class calls. The logic of this class is relatively simple and will not be analyzed here. Let’s focus on Proxy’s getProxy method, as follows.
public static Proxy getProxy(Class
... ics) {
// Call overloaded methods
return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}
public static Proxy getProxy(ClassLoader cl, Class
... ics) {
if (ics.length > 65535)
throw new IllegalArgumentException("interface limit exceeded");
StringBuilder sb = new StringBuilder();
// Iterate over the interface list
for (int i = 0; i < ics.length; i++) {
String itf = ics[i].getName();
// Check whether the type is interface
if(! ics[i].isInterface())throw new RuntimeException(itf + " is not a interface."); Class<? > tmp =null;
try {
// Reload the interface class
tmp = Class.forName(itf, false, cl);
} catch (ClassNotFoundException e) {
}
// Check whether the interfaces are the same, TMP may be empty
if(tmp ! = ics[i])throw new IllegalArgumentException(ics[i] + " is not visible from class loader");
// The fully qualified name of the splicing interface is delimited by;
sb.append(itf).append('; ');
}
// Use the concatenated interface name as the key
String key = sb.toString();
Map<String, Object> cache;
synchronized (ProxyCacheMap) {
cache = ProxyCacheMap.get(cl);
if (cache == null) {
cache = new HashMap<String, Object>();
ProxyCacheMap.put(cl, cache);
}
}
Proxy proxy = null;
synchronized (cache) {
do {
// Get the Reference
instance from the cache
Object value = cache.get(key);
if (value instanceofReference<? >) { proxy = (Proxy) ((Reference<? >) value).get();if(proxy ! =null) {
returnproxy; }}// Concurrency control ensures that only one thread can perform subsequent operations
if (value == PendingGenerationMarker) {
try {
// Other threads wait here
cache.wait();
} catch (InterruptedException e) {
}
} else {
// Place the flag bit in the cache and break out of the while loop for subsequent operations
cache.put(key, PendingGenerationMarker);
break; }}while (true);
}
long id = PROXY_CLASS_COUNTER.getAndIncrement();
String pkg = null;
ClassGenerator ccp = null, ccm = null;
try {
// Create a ClassGenerator object
ccp = ClassGenerator.newInstance(cl);
Set<String> worked = new HashSet<String>();
List<Method> methods = new ArrayList<Method>();
for (int i = 0; i < ics.length; i++) {
// Check whether the interface access level is protected or private
if(! Modifier.isPublic(ics[i].getModifiers())) {// Get the interface package name
String npkg = ics[i].getPackage().getName();
if (pkg == null) {
pkg = npkg;
} else {
if(! pkg.equals(npkg))// Non-public interfaces must be in the same package, otherwise an exception will be thrown
throw new IllegalArgumentException("non-public interfaces from different packages"); }}// Add the interface to the ClassGenerator
ccp.addInterface(ics[i]);
// Iterate over the interface method
for (Method method : ics[i].getMethods()) {
// Get the description of the method
String desc = ReflectUtils.getDesc(method);
// If the method description string is already working, it is ignored. So if you think about this situation,
// Interfaces A and B contain exactly the same method
if (worked.contains(desc))
continue;
worked.add(desc);
int ix = methods.size();
// Get the method return value typeClass<? > rt = method.getReturnType();// Get the parameter listClass<? >[] pts = method.getParameterTypes();[] args = new Object[1...N]
StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
for (int j = 0; j < pts.length; j++)
// Generate args[1...N] = ($w)$1... N;
code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
// Generate the invoker method call statement for the InvokerHandler interface, as follows:
// Object ret = handler.invoke(this, methods[1...N], args);
code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
// Return void
if(! Void.TYPE.equals(rt))// Generate a return statement like return (java.lang.string) ret;
code.append(" return ").append(asArgument(rt, "ret")).append(";");
methods.add(method);
// Add method names, access control characters, parameter lists, method codes, and other information to ClassGeneratorccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); }}if (pkg == null)
pkg = PACKAGE_NAME;
// Build the interface proxy class name: PKG + ".proxy" + id, such as org.apache.dubo.proxy0
String pcn = pkg + ".proxy" + id;
ccp.setClassName(pcn);
ccp.addField("public static java.lang.reflect.Method[] methods;");
/ / private generated Java. Lang. Reflect. InvocationHandler handler.
ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
// Add a constructor to the interface proxy class with the InvocationHandler argument, such as:
// porxy0(java.lang.reflect.InvocationHandler arg0) {
// handler=$1;
// }
ccp.addConstructor(Modifier.PUBLIC, newClass<? >[]{InvocationHandler.class},newClass<? > [0]."handler=$1;");
// Add a default constructor for the interface proxy class
ccp.addDefaultConstructor();
// Generate the interface proxy classClass<? > clazz = ccp.toClass(); clazz.getField("methods").set(null, methods.toArray(new Method[0]));
// Build a Proxy subclass name, such as Proxy1, Proxy2, etc
String fcn = Proxy.class.getName() + id;
ccm = ClassGenerator.newInstance(cl);
ccm.setClassName(fcn);
ccm.addDefaultConstructor();
ccm.setSuperClass(Proxy.class);
// Generate an implementation code for newInstance, an abstract Proxy method, like this:
// public Object newInstance(java.lang.reflect.InvocationHandler h) {
// return new org.apache.dubbo.proxy0($1);
// }
ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
// Generate the Proxy implementation classClass<? > pc = ccm.toClass();// Create a Proxy instance through reflection
proxy = (Proxy) pc.newInstance();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
} finally {
if(ccp ! =null)
// Release resources
ccp.release();
if(ccm ! =null)
ccm.release();
synchronized (cache) {
if (proxy == null)
cache.remove(key);
else
/ / write cache
cache.put(key, new WeakReference<Proxy>(proxy));
// Wake up other waiting threadscache.notifyAll(); }}return proxy;
}
Copy the code
The code above is quite complex and we have written a lot of comments. When you read this code, you need to understand the purpose of CCP and CCM, otherwise you will get confused. CCP generates proxy classes for service interfaces. For example, if we have a DemoService interface, this interface proxy class is generated by CCP. CCM is used for org.apache.dubbo.com mon. The bytecode. Proxy abstract class generation subclasses, mainly is the abstract methods to realize the Proxy class. Below to org. Apache. Dubbo. Demo. DemoService this interface, for example, look at the code in the class of interface agent is roughly how (ignoring the EchoService interface).
package org.apache.dubbo.common.bytecode;
public class proxy0 implements org.apache.dubbo.demo.DemoService {
public static java.lang.reflect.Method[] methods;
private java.lang.reflect.InvocationHandler handler;
public proxy0(a) {}public proxy0(java.lang.reflect.InvocationHandler arg0) {
handler = $1;
}
public java.lang.String sayHello(java.lang.String arg0) {
Object[] args = new Object[1];
args[0] = ($w) $1;
Object ret = handler.invoke(this, methods[0], args);
return(java.lang.String) ret; }}Copy the code
Well, that’s the end of the proxy generation logic. The whole process is complicated, so you need to be patient.
4, summarize
In this article, the process of service reference is analyzed in detail, and some logic, such as Directory and Cluster, is not analyzed at present. The functions of these interfaces and implementation classes are independent and will be analyzed separately later. For the moment, we can think of these classes as black boxes, as long as we know what they are for. So much for the service reference process.
yourLike and followIs the continuing power of the Solomon_ Shogo shell structure.
Hot historical Articles
-
🔥Serverless Microservices elegant shutdown practices
-
🔥 this algorithm can not understand! How are the 9 images presented
-
🔥SpringBoot Mastery – Custom Condition annotations (Series 1)
-
🔥Java is how to take off the beautiful woman’s clothes
-
🔥 High-performance gateway was originally designed this way
-
🔥REST FUL look still don’t understand, you play me!
-
How much 🔥Serverless affects programmers
-
🔥 How does distributed transaction XID connect all microservices in tandem
-
🔥 Microservices Distributed Transaction TCC core implementation
-
🔥 hundreds of millions of traffic site performance optimization methodology steps
-
🔥 microservice Nacos implements proximity access through CMDB to improve performance
-
🔥 Micro service architecture DNS service registration and discovery mechanism
-
. More and more