1. Introduction of Invoker in Dubbo
Why is Invoker the core model of Dubbo?
Invoker is an entity domain in Dubbo, that is, it really exists.Other models approach it or convert it, and it also represents an executable from which invoke calls can be made.At the service provider, Invoker is used to invoke the service provider class.On the service consumer side, Invoker is used to perform remote calls.
2. Invoker of Service Provider
Invoker in the service provider is created by ProxyFactory, and the default ProxyFactory implementation class for Dubbo is JavassistProxyFactory.
Create Invoker's entry method getInvoker:
1 public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { 2 // Create for Target Class Wrapper 3 final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf(36) < 0 ? proxy.getClass() : type); 4 // Create Anonymous Invoker Class object and Implementation doInvoke Method. 5 return new AbstractProxyInvoker<T>(proxy, type, url) { 6 @Override 7 protected Object doInvoke(T proxy, String methodName, 8 Class<?>[] parameterTypes, 9 Object[] arguments) throws Throwable { 10 // call Wrapper Of invokeMethod Method, invokeMethod The target method will eventually be called 11 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); 12 } 13 }; 14 }
JavassistProxyFactory creates an anonymous object that inherits from the AbstractProxyInvoker class and overrides the abstract method doInvoke.The overridden doInvoke logic is simpler, simply forwarding the call request to the invokeMethod method of the Wrapper class.And generate invokeMethod method code and some other method code.Once the code is generated, the Class object is generated through Javassist, and the Wrapper instance is created through reflection.
Note: Wapper is a packaging class.Mainly used for the Package target class, subclasses can only be created through the getWapper(Class) method.In the process of creating a subclass, the subclass code parses the incoming Class object to get information such as class methods, class member variables, and so on.This wrapper class holds the actual extension point implementation class.You can also move all the common logic of the extension point into the wrapper class, functionally as an AOP implementation.
Create construction methods for wrapper classes:
1 public static Wrapper getWrapper(Class<?> c) { 2 while (ClassGenerator.isDynamicClass(c)) 3 c = c.getSuperclass(); 4 5 if (c == Object.class) 6 return OBJECT_WRAPPER; 7 8 // Get from Cache Wrapper Example 9 Wrapper ret = WRAPPER_MAP.get(c); 10 if (ret == null) { 11 // Cache missed, create Wrapper 12 ret = makeWrapper(c); 13 // Write Cache 14 WRAPPER_MAP.put(c, ret); 15 } 16 return ret; 17 }
) Failure to get a Wapper in the cache will lead to the following method makeWapper:
1 private static Wrapper makeWrapper(Class<?> c) { 2 // Testing c Is it a base type, if it throws an exception 3 if (c.isPrimitive()) 4 throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c); 5 6 String name = c.getName(); 7 ClassLoader cl = ClassHelper.getClassLoader(c); 8 9 // c1 For storage setPropertyValue method code 10 StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ "); 11 // c2 For storage getPropertyValue method code 12 StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ "); 13 // c3 For storage invokeMethod method code 14 StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{ "); 15 16 // Generate type conversion code and exception capture code, such as: 17 // DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); } 18 c1.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); 19 c2.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); 20 c3.append(name).append(" w; try{ w = ((").append(name).append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); 21 22 // pts Used to store member variable names and types 23 Map<String, Class<?>> pts = new HashMap<String, Class<?>>(); 24 // ms Used to store method description information (understood as method signatures) and Method Example 25 Map<String, Method> ms = new LinkedHashMap<String, Method>(); 26 // mns For method name list 27 List<String> mns = new ArrayList<String>(); 28 // dmns Name used to store Method Defined in Current Class 29 List<String> dmns = new ArrayList<String>(); 30 31 // --------------------------------✨ Split Line 1 ✨------------------------------------- 32 33 // Obtain public Access level fields and generate conditional judgment statements for all fields 34 for (Field f : c.getFields()) { 35 String fn = f.getName(); 36 Class<?> ft = f.getType(); 37 if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) 38 // Ignore keywords static or transient Modified variable 39 continue; 40 41 // Generate conditional judgments and assignment statements, such as: 42 // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return;} 43 // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return;} 44 c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }"); 45 46 // Generate conditional judgments and return statements, such as: 47 // if( $2.equals("name") ) { return ($w)w.name; } 48 c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }"); 49 50 // storage <Field name, Field type> Key Value Pairing To pts in 51 pts.put(fn, ft); 52 } 53 54 // --------------------------------✨ Split Line 2 ✨------------------------------------- 55 56 Method[] methods = c.getMethods(); 57 // Testing c Whether or not to include methods declared in the current class 58 boolean hasMethod = hasMethods(methods); 59 if (hasMethod) { 60 c3.append(" try{"); 61 } 62 for (Method m : methods) { 63 if (m.getDeclaringClass() == Object.class) 64 // ignore Object Method defined in 65 continue; 66 67 String mn = m.getName(); 68 // Generate method name judgment statements, such as: 69 // if ( "sayHello".equals( $2 ) 70 c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); 71 int len = m.getParameterTypes().length; 72 // Generate a "Number of parameters passed in at runtime and length of method parameter list" judgment statement, such as: 73 // && $3.length == 2 74 c3.append(" && ").append(" $3.length == ").append(len); 75 76 boolean override = false; 77 for (Method m2 : methods) { 78 // Detect if overload exists with different method objects && Method Name Same 79 if (m != m2 && m.getName().equals(m2.getName())) { 80 override = true; 81 break; 82 } 83 } 84 // To handle overloaded methods, consider the following: 85 // 1. void sayHello(Integer, String) 86 // 2. void sayHello(Integer, Integer) 87 // The method names are the same and the parameter lists are the same length, so you can't just tell if the two methods are equal. 88 // The parameter type of the method needs to be further determined 89 if (override) { 90 if (len > 0) { 91 for (int l = 0; l < len; l++) { 92 // Generate parameter types to check code, such as: 93 // && $3[0].getName().equals("java.lang.Integer") 94 // && $3[1].getName().equals("java.lang.String") 95 c3.append(" && ").append(" $3[").append(l).append("].getName().equals(\"") 96 .append(m.getParameterTypes()[l].getName()).append("\")"); 97 } 98 } 99 } 100 101 // Add to ) {,Complete the method judgment statement, where the generated code may be as follows (formatted): 102 // if ("sayHello".equals($2) 103 // && $3.length == 2 104 // && $3[0].getName().equals("java.lang.Integer") 105 // && $3[1].getName().equals("java.lang.String")) { 106 c3.append(" ) { "); 107 108 // Generate target method call statements based on return value type 109 if (m.getReturnType() == Void.TYPE) 110 // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null; 111 c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;"); 112 else 113 // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 114 c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");"); 115 116 // Add to }, The generated code looks like (formatted): 117 // if ("sayHello".equals($2) 118 // && $3.length == 2 119 // && $3[0].getName().equals("java.lang.Integer") 120 // && $3[1].getName().equals("java.lang.String")) { 121 // 122 // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); 123 // return null; 124 // } 125 c3.append(" }"); 126 127 // Add method name to mns In collection 128 mns.add(mn); 129 // Detect if the current method is in c Declared 130 if (m.getDeclaringClass() == c) 131 // If so, add the current method name to the dmns in 132 dmns.add(mn); 133 ms.put(ReflectUtils.getDesc(m), m); 134 } 135 if (hasMethod) { 136 // Add exception capture statement 137 c3.append(" } catch(Throwable e) { "); 138 c3.append(" throw new java.lang.reflect.InvocationTargetException(e); "); 139 c3.append(" }"); 140 } 141 142 // Add to NoSuchMethodException Exception throw code 143 c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); 144 145 // --------------------------------✨ Split Line 3 ✨------------------------------------- 146 147 Matcher matcher; 148 // Handle get/set Method 149 for (Map.Entry<String, Method> entry : ms.entrySet()) { 150 String md = entry.getKey(); 151 Method method = (Method) entry.getValue(); 152 // Match with get Beginning Method 153 if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { 154 // Get Property Name 155 String pn = propertyName(matcher.group(1)); 156 // Generate attribute judgments and return statements as follows: 157 // if( $2.equals("name") ) { return ($w).w.getName(); } 158 c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }"); 159 pts.put(pn, method.getReturnType()); 160 161 // Match with is/has/can Beginning Method 162 } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { 163 String pn = propertyName(matcher.group(1)); 164 // Generate attribute judgments and return statements as follows: 165 // if( $2.equals("dream") ) { return ($w).w.hasDream(); } 166 c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append("(); }"); 167 pts.put(pn, method.getReturnType()); 168 169 // Match with set Beginning Method 170 } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { 171 Class<?> pt = method.getParameterTypes()[0]; 172 String pn = propertyName(matcher.group(1)); 173 // Generate attribute judgments and setter Call statement, as shown in the following example: 174 // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; } 175 c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }"); 176 pts.put(pn, pt); 177 } 178 } 179 180 // Add to NoSuchPropertyException Exception throw code 181 c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); 182 c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); 183 184 // --------------------------------✨ Split Line 4 ✨------------------------------------- 185 186 long id = WRAPPER_CLASS_COUNTER.getAndIncrement(); 187 // Create Class Generator 188 ClassGenerator cc = ClassGenerator.newInstance(cl); 189 // Set Class Name and Superclass 190 cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id); 191 cc.setSuperClass(Wrapper.class); 192 193 // Add Default Construction Method 194 cc.addDefaultConstructor(); 195 196 // Add Field 197 cc.addField("public static String[] pns;"); 198 cc.addField("public static " + Map.class.getName() + " pts;"); 199 cc.addField("public static String[] mns;"); 200 cc.addField("public static String[] dmns;"); 201 for (int i = 0, len = ms.size(); i < len; i++) 202 cc.addField("public static Class[] mts" + i + ";"); 203 204 // Add Method Code 205 cc.addMethod("public String[] getPropertyNames(){ return pns; }"); 206 cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }"); 207 cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }"); 208 cc.addMethod("public String[] getMethodNames(){ return mns; }"); 209 cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }"); 210 cc.addMethod(c1.toString()); 211 cc.addMethod(c2.toString()); 212 cc.addMethod(c3.toString()); 213 214 try { 215 // Generate Class 216 Class<?> wc = cc.toClass(); 217 218 // Setting field values 219 wc.getField("pts").set(null, pts); 220 wc.getField("pns").set(null, pts.keySet().toArray(new String[0])); 221 wc.getField("mns").set(null, mns.toArray(new String[0])); 222 wc.getField("dmns").set(null, dmns.toArray(new String[0])); 223 int ix = 0; 224 for (Method m : ms.values()) 225 wc.getField("mts" + ix++).set(null, m.getParameterTypes()); 226 227 // Establish Wrapper Example 228 return (Wrapper) wc.newInstance(); 229 } catch (RuntimeException e) { 230 throw e; 231 } catch (Throwable e) { 232 throw new RuntimeException(e.getMessage(), e); 233 } finally { 234 cc.release(); 235 ms.clear(); 236 mns.clear(); 237 dmns.clear(); 238 } 239 }
The code is long and there are many comments.Roughly speaking, the inner logic is:
-
- Create three strings, c1, c2, and c3, to store type conversion codes and exception capture codes, then pts to store member variable names and types, ms to store Method description information (which can be understood as Method signatures) and Method instances, mns to list Method names, and dmns to store the names of methods defined in the current class.I did some initial work here
- Getting all the public fields, using c1 storage condition judgment and assignment statements, you can understand that c1 can assign values to the public fields, while c2 is a conditional judgment and return statement, and the same is getting the values of the public fields.Use pts to store <field name, field type>.That is, you can now manipulate the target class fields. To manipulate some private fields, you need to access the methods at the start of the set and the start of the get, which also use c1 to save the set, c2 to get, pts to store <property name, property type>
- Now to the method in the class, check the parameters in the method before checking for overloaded methods.The statement calling the target method and the exception that may be thrown in the method are stored in c3, then the method name is stored in the mns collection, the declared method is stored in ms, and the undeclared but defined method exists in dmns.
- Class classes are built from ClassGenerator for the code just generated, and objects are created from reflection.ClassGenerator is encapsulated by Dubbo itself, and its core is the overloaded method toClass(ClassLoader, ProtectionDomain) of toClass(), which builds a Class from javassist.*
Finally, when the Wapper class is created, go back to the getInvoker method above and pass the following statement
1 return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); 2 3 //Enter invokeMethod in 4 5 public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException { 6 if ("getClass".equals(mn)) { 7 return instance.getClass(); 8 } else if ("hashCode".equals(mn)) { 9 return instance.hashCode(); 10 } else if ("toString".equals(mn)) { 11 return instance.toString(); 12 } else if ("equals".equals(mn)) { 13 if (args.length == 1) { 14 return instance.equals(args[0]); 15 } else { 16 throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error."); 17 } 18 } else { 19 throw new NoSuchMethodException("Method [" + mn + "] not found."); 20 } 21 } 22 };
This is where Invoker can implement methods to call service provider classes.That is, the Invoker entity domain of the service provider class is created.The bottom level is to build objects through javassist.
3. Invoker of Service Consumer
On the service consumer side, Invoker is used to perform remote calls.Invoker is built from the Protocol implementation class.There are many Protocol implementation classes, but the two most commonly used are RegistryProtocol and DubboProtocol.
refer method for DubboProtocol:
1 public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException { 2 optimizeSerialization(url); 3 // Establish DubboInvoker 4 DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers); 5 invokers.add(invoker); 6 return invoker; 7 }
The above method is simpler and the most important one is getClients.This method is used to get a client instance of type ExchangeClient.ExchangeClient does not actually have communication capabilities; it needs to communicate based on lower-level client instances.For example, NettyClient, MinaClient, and so on, by default, Dubbo uses NettyClient to communicate.Each time an Invoker is created, it is added to the invokers collection.That is, you can think of the service consumer's Invoker as a Netty client with communication capabilities
getClients method:
1 private ExchangeClient[] getClients(URL url) { 2 // Whether to share the connection 3 boolean service_share_connect = false; 4 // Gets the number of connections, defaulting to 0, indicating no configuration 5 int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0); 6 // If not configured connections,Then share the connection 7 if (connections == 0) { 8 service_share_connect = true; 9 connections = 1; 10 } 11 12 ExchangeClient[] clients = new ExchangeClient[connections]; 13 for (int i = 0; i < clients.length; i++) { 14 if (service_share_connect) { 15 // Get Shared Clients 16 clients[i] = getSharedClient(url); 17 } else { 18 // Initialize a new client 19 clients[i] = initClient(url); 20 } 21 } 22 return clients; 23 } 24 25 //Enter the Get Shared Client method 26 27 private ExchangeClient getSharedClient(URL url) { 28 String key = url.getAddress(); 29 // Gets the ExchangeClient 30 ReferenceCountExchangeClient client = referenceClientMap.get(key); 31 if (client != null) { 32 if (!client.isClosed()) { 33 // Increase Reference Count 34 client.incrementAndGetCount(); 35 return client; 36 } else { 37 referenceClientMap.remove(key); 38 } 39 } 40 41 locks.putIfAbsent(key, new Object()); 42 synchronized (locks.get(key)) { 43 if (referenceClientMap.containsKey(key)) { 44 return referenceClientMap.get(key); 45 } 46 47 // Establish ExchangeClient Client 48 ExchangeClient exchangeClient = initClient(url); 49 // take ExchangeClient Instances passed to ReferenceCountExchangeClient,Decoration mode is used here 50 client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap); 51 referenceClientMap.put(key, client); 52 ghostClientMap.remove(key); 53 locks.remove(key); 54 return client; 55 } 56 } 57 58 //Enter Initialize Client Method 59 60 private ExchangeClient initClient(URL url) { 61 62 // Gets the client type, which defaults to netty 63 String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); 64 65 // Add codec and heartbeat package parameters to url in 66 url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); 67 url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); 68 69 // Detect if client type exists, throw exception if none exists 70 if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { 71 throw new RpcException("Unsupported client type: ..."); 72 } 73 74 ExchangeClient client; 75 try { 76 // Obtain lazy Configure and determine the type of client to create based on the configuration value 77 if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) { 78 // Create lazy load ExchangeClient Example 79 client = new LazyConnectExchangeClient(url, requestHandler); 80 } else { 81 // Create Normal ExchangeClient Example 82 client = Exchangers.connect(url, requestHandler); 83 } 84 } catch (RemotingException e) { 85 throw new RpcException("Fail to create remoting client for service..."); 86 } 87 return client; 88 } 89 90 //Enter connect In the method, getExchanger Will pass SPI Load HeaderExchangeClient Examples, this method is relatively simple, let's take a look at it for yourself.Next analysis HeaderExchangeClient Implementation. 91 92 public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { 93 if (url == null) { 94 throw new IllegalArgumentException("url == null"); 95 } 96 if (handler == null) { 97 throw new IllegalArgumentException("handler == null"); 98 } 99 url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange"); 100 // Obtain Exchanger Instance, default to HeaderExchangeClient 101 return getExchanger(url).connect(url, handler); 102 } 103 104 //Establish HeaderExchangeClient Example 105 106 public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException { 107 // There are several calls, as follows: 108 // 1. Establish HeaderExchangeHandler object 109 // 2. Establish DecodeHandler object 110 // 3. adopt Transporters structure Client Example 111 // 4. Establish HeaderExchangeClient object 112 return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true); 113 } 114 115 //adopt Transporters structure Client Example 116 117 public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException { 118 if (url == null) { 119 throw new IllegalArgumentException("url == null"); 120 } 121 ChannelHandler handler; 122 if (handlers == null || handlers.length == 0) { 123 handler = new ChannelHandlerAdapter(); 124 } else if (handlers.length == 1) { 125 handler = handlers[0]; 126 } else { 127 // If handler Number greater than 1 creates one ChannelHandler Distributor 128 handler = new ChannelHandlerDispatcher(handlers); 129 } 130 131 // Obtain Transporter Adaptively extend the class and call connect Method Generation Client Example 132 return getTransporter().connect(url, handler); 133 } 134 135 //Establish Netty object 136 137 public Client connect(URL url, ChannelHandler listener) throws RemotingException { 138 // Establish NettyClient object 139 return new NettyClient(url, listener); 140 }
The source code above can be divided into several logic parts:
-
- Enter the creation of an instance of DubboInvoker by refer, in which serviceType, url, and invokers are virtually indifferent, and invokers are stored and created.The most critical is the getClient method.You can think of Invoker as a Netty client.Invoker on the service provider is a Wapper class.
- The getClient method first determines whether to get a shared client or create a new client instance based on the number of connections. By default, it gets a shared client, but if the corresponding client is not available in the cache, it also creates a new client.The final return is the ExchangeClient, which currently has no communication capabilities and requires a more underlying Netty client.
- The initClient method first gets the client type configured by the user, defaults to Netty, then detects if the client type configured by the user exists, throws an exception if it does not exist, and finally creates what type of client it feels like based on the lazy configuration.The LazyConnectExchangeClient code is not very complex, it creates an ExchangeClient client through Exchangers'connect method when the request method is called
- getExchanger loads the HeaderExchangeClient instance through SPI.Finally, Netty clients are created through the Transporter implementation class and the API that calls Netty.The last layer returns, and finally becomes such a class with Netty at the bottom and DubboInvoker at the top.
refer in RegistryProtocol:
1 public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { 2 // take registry Parameter value and set it as protocol header 3 url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); 4 // Get Registry Instance 5 Registry registry = registryFactory.getRegistry(url); 6 if (RegistryService.class.equals(type)) { 7 return proxyFactory.getInvoker((T) registry, type, url); 8 } 9 10 // take url Query string to Map 11 Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); 12 // Obtain group To configure 13 String group = qs.get(Constants.GROUP_KEY); 14 if (group != null && group.length() > 0) { 15 if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1 16 || "*".equals(group)) { 17 // adopt SPI Load MergeableCluster Instance and call doRefer Continue executing service reference logic 18 return doRefer(getMergeableCluster(), registry, type, url); 19 } 20 } 21 22 // call doRefer Continue executing service reference logic 23 return doRefer(cluster, registry, type, url); 24 } 25 private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) { 26 // Establish RegistryDirectory Example 27 RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url); 28 // Set up a registry and protocol 29 directory.setRegistry(registry); 30 directory.setProtocol(protocol); 31 Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters()); 32 // Generate service consumer links 33 URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters); 34 35 // Register service consumers at consumers New Node in Directory 36 if (!Constants.ANY_VALUE.equals(url.getServiceInterface()) 37 && url.getParameter(Constants.REGISTER_KEY, true)) { 38 registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, 39 Constants.CHECK_KEY, String.valueOf(false))); 40 } 41 42 // Subscribe providers,configurators,routers Equal Node Data 43 directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 44 Constants.PROVIDERS_CATEGORY 45 + "," + Constants.CONFIGURATORS_CATEGORY 46 + "," + Constants.ROUTERS_CATEGORY)); 47 48 // A registry may have multiple service providers, so you need to merge multiple service providers into one 49 Invoker invoker = cluster.join(directory); 50 ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory); 51 return invoker; 52 } 53 54 //Enter Cluster Creation Invoker Pattern 55 56 @SPI(FailoverCluster.NAME) 57 public interface Cluster { 58 59 /** 60 * Merge the Invoker of Directory into an Invoker 61 */ 62 @Adaptive 63 <T> Invoker<T> join(Directory<T> directory) throws RpcException; 64 } 65 66 //Enter MockerClusterWrapper In implementation class 67 68 public class MockClusterWrapper implements Cluster { 69 70 private Cluster cluster; 71 72 public MockClusterWrapper(Cluster cluster) { 73 this.cluster = cluster; 74 } 75 76 public <T> Invoker<T> join(Directory<T> directory) throws RpcException { 77 return new MockClusterInvoker<T>(directory, this.cluster.join(directory)); 78 } 79 } 80 81 //Concrete invoke Method 82 83 public Result invoke(Invocation invocation) throws RpcException { 84 Result result = null; 85 86 String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), 87 Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); 88 if (value.length() == 0 || value.equalsIgnoreCase("false")){ 89 //no mock 90 result = this.invoker.invoke(invocation); 91 } else if (value.startsWith("force")) { 92 if (logger.isWarnEnabled()) { 93 logger.info("force-mock: " + invocation.getMethodName() + 94 " force-mock enabled , url : " + directory.getUrl()); 95 } 96 //force:direct mock 97 result = doMockInvoke(invocation, null); 98 } else { 99 //fail-mock 100 try { 101 result = this.invoker.invoke(invocation); 102 }catch (RpcException e) { 103 if (e.isBiz()) { 104 throw e; 105 } else { 106 if (logger.isWarnEnabled()) { 107 logger.info("fail-mock: " + invocation.getMethodName() + 108 " fail-mock enabled , url : " + directory.getUrl(), e); 109 } 110 //fail:mock 111 result = doMockInvoke(invocation, e); 112 } 113 } 114 } 115 return result; 116 }
Roughly speaking, the logic above:
-
- The current Invoker base is still NettyClient, but at this point the registry is a cluster-building mode.So you need to merge multiple Invokers into one, which is a logical merge.In fact, there will still be many at the bottom of the Invoker, managed by just one cluster mode.So what's exposed is an Invoker in cluster mode.This enters the Cluster.join method.
- Cluster is a generic proxy class that locates the actual Cluster implementation class, FailoverCluster, based on the value of the cluster parameter in the URL.The @SPI annotation is used here, which requires the ExtensionLoader extension point loading mechanism, which automatically covers Wapper after instantiation on the instantiated object
- But it's cluster mode that requires another core mechanism in Dubbo, Mock.Mock can simulate various exceptions to service calls in tests and also implement service downgrades.In MockkerClusterInvoker, Dubbo first checks for the presence of a mock parameter in the URL.(This parameter can be set either through shielding and fault tolerance on the Consumer side of the Service Governance Background or directly by dynamically setting the mock parameter value) If there is a force start, this directly executes the demotion logic without launching a remote call.If a fail start exists, the demotion logic is executed only when a remote call exception is made.
- It can be said that when the registry is in cluster mode, Invoker wraps an extra layer of mock logic outside.It is implemented through the Wapper mechanism.Ultimately, one of the multiple Invokers can be selected for each call or retry through the load balancing mechanism within Dubbo
4. Summary
The implementation of Invoker is finished here. To summarize, Invoker, the service provider, is an instance of a service class created by javassist, which implements calling methods within the service class and modifying fields.The service consumer's Invoker is a Netty-based client.Finally, service class instances created by service providers are obtained through service consumer Netty clients.The consumer then needs to create a proxy class for the service class in order to protect it, so that it can safely and effectively call the internal methods of the service class remotely without instantiating the service class and get specific data.