background
I believe that students who have read the Dubbo source code should see that there is a @SPI annotation on many interfaces in Dubbo, and the author is no exception. But I have never known what this annotation is specifically for, in order to solve what problems, how to use it? Simple online search, the Chinese name: Service Provision Interface, see the following figure (from Baidu Encyclopedia).
Maybe dubbo itself is powerful, so I just know that dubbo can customize some strategies, such as load balancing, serialization, thread pool type, etc., but it has not been used in the officially online environment. Take advantage of the holidays to spend some time to study and record, hoping to be useful to everyone.
Code sample
The following codes are locally validated and purely hand-tapped. Detailed implementation is provided. Glory's Github
Note: Build spring-boot for test project+ spring-boot-dubbo
Verification idea
As shown in the figure above, @SPI is a simple implementation to implement a specific service. The most familiar one is the Load Balance strategy, which starts two providers locally with different ports and decides to access the specified provider through the participation of consumer.
Start the provider
The code is extremely simple, and the code framework is as follows
import org.apache.dubbo.config.annotation.Service; import org.apache.dubbo.rpc.RpcContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // Specified version and grouping @Service(version = "1.0.0",group = "glory") public class DemoServiceImpl implements DemoService { private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class); @Override public String sayHello(Integer port) { logger.info("Hello " + port + " request from consumer: " + RpcContext.getContext().getRemoteAddress()); return "Hello ,"+port+" response from provider: " + RpcContext.getContext().getLocalAddress(); } }
The following is the application.yml configuration file
server: port: 8083 dubbo: application: name: dubbo-common-provider scan: base-packages: com.redsun.rpc.dubbo protocol: name: dubbo port: 12345 registry: address: zookeeper://127.0.0.1:2181
It is also important to note that different ports need to be specified at startup time, whether they cannot be started.
At this time, two local applications can be booted normally, and the boot effect is as follows:
Start consumer
consumer's code framework is also simple
As you can guess, Glory LoadBalance is a load balancing strategy implemented by itself, which chooses invoker by the parameters passed in from the front end.
public class GloryLoadBalance implements LoadBalance { private static final Logger logger = LoggerFactory.getLogger(GloryLoadBalance.class); @Override public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { Integer port = (Integer) invocation.getArguments()[0];// Front-end incoming parameters logger.info("The front-end incoming port is:" + port); // Java 8 streaming programming, interested students can study, and later will write a special article Invoker<T> invokerRet = invokers.stream().filter(invoker -> invoker.getUrl().getPort() == port).findFirst().get(); return invokerRet == null ? invokers.get(0) : invokerRet; } }
Here we also write a simple controller, which is convenient for changing parameters dynamically.
@RestController @RequestMapping("/admin") public class AdminController { private static final Logger logger = LoggerFactory.getLogger(AdminController.class); @Reference(version = "1.0.0",group = "glory", loadbalance = "glory") private DemoService demoService; @RequestMapping("/invoke") public String invoke(@RequestParam(name = "port") Integer port) { logger.info("invoke method be invoked!port = " + port); return demoService.sayHello(port); } }
Of course, the last and most important step is to add a configuration file to the META-INF.dubbo.international directory. The glory before the equal sign below is actually the key of the loadbalance you configure. If the path is wrong or not configured, you will still get the default implementation random.
glory=com.redsun.rpc.dubbo.loadbalance.GloryLoadBalance
Dubbo loads all configuration information in the META-INF.dubbo.internal directory, and there are many default implementations in the Dubbo directory.
postman call
The following two graphs are tested to meet expectations.
Source code
Now it's time to turn over the source code. Here's a brief account of my own experience in looking at the source code.
- Don't get bogged down in the implementation of each method. It's debug and dizzy, so it's easy to follow and lose interest at last.
- Learn to read annotations, especially method names, class names, variable names, source code is different from the usual code written by themselves, since open source, is for everyone, so annotations, naming will often write very detailed and standardized, English do not know? Look at the translation, you will know more times.
- When debug follows, remember several core classes, comb the whole call chain after reading, and have a general understanding of the code structure (I really don't want to see, Baidu can do it, I often do it, and then follow it with myself and authenticate it).
Load
ExtensionLoader#getExtensionClasses
// synchronized in getExtensionClasses private Map<String, Class<?>> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
/** * put clazz in extensionClasses */ private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) { Class<?> c = extensionClasses.get(name); if (c == null) { // Store the extended class GloryLoadBalance in the map, and eventually store all the default offerings in the map. extensionClasses.put(name, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName()); } }
Loading the implementation class will eventually set the load balancing parameter of ReferenceConfig to glory
Function
AbstractClusterInvoker#invoke
When consumer initiates invoke, selective instances are created based on config's key
public Result invoke(final Invocation invocation) throws RpcException { checkWhetherDestroyed(); // binding attachments into invocation. Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { ((RpcInvocation) invocation).addAttachments(contextAttachments); } List<Invoker<T>> invokers = list(invocation); // Initialize Load Balancing Class LoadBalance loadbalance = initLoadBalance(invokers, invocation); RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); return doInvoke(invocation, invokers, loadbalance); }
ExtensionLoader#getExtensionLoader
Each file in the META-INF.dubbo.internal directory is an Extension Loader object stored in a static class variable EXTENSION_LOADERS
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } // Here, determine whether or not to be modified with SPI annotations if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
ExtensionLoader#getExtension
If it is empty at this time, it will be used through an instance of the lass information newInstance loaded above. The code is relatively simple, and you can follow it if you are interested.
summary
From the above, we can see that if you want to expand the use of dubbo, it will be very simple to add an implementation class (implementation of the corresponding interface), and then add a configuration file in the META-INF directory, key corresponding can be completed. Serialization can also be done as you wish.
When you follow the code, you can see that dubbo loads Class objects into some other extension classes that are not used, which is a little flaw.