Lightweight RPC Design and Implementation 4th Edition

Keywords: Java Spring Dubbo SpringBoot

SPI mechanisms have been introduced in this release, as described in previous articles on Java and Dubbo.
Port: The Difference and Principle Analysis between SPI Mechanism and JDK Mechanism of Dubbo
Because RPC frameworks are designed based on Spring, dependency injection issues are frequently encountered.There is also a SPI mechanism in Spring, but it has a disadvantage that when you instantiate a specific service class using the SPI mechanism, the instantiation will fail if other bean s are invoked in the specific service class.This is mainly because the specific service class is not placed in the Spring container.This project will solve this problem effectively.
This mechanism is incorporated into the RPC framework designed to switch between different serialization methods.

Spring's SPI mechanism

We know that many configurations and implementations in the SpringBoot have default implementations. We only need to modify some configurations, such as the database configuration. We only need to write the corresponding url, username, password in the configuration file to use them.In fact, he is using SPI to achieve this.Spring's SPI mechanism is consistent with Java's.
SpringBoot loads the META-INF/spring.factories file using SpringFactories Loader, searches for all META-INF/spring.factories configuration files from each Jar package under CLASSPATH, then parses the properties file, finds the configuration with the specified name, and returns.It is important to note that instead of just going to the ClassPath path and scanning all the Jar packages, this file will only be in the jar packages under ClassPath.

Call method:

List<AService> services = SpringFactoriesLoader.loadFactories(AService.class, null);
        for (AService service : services) {
            service.info();
        }

Related Sources:

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<>(factoryNames.size());
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// The format of the spring.factories file is: key=value1,value2,value3
// Find META-INF/spring.factories files from all jar packages
// Then all the value values of the key=factoryClass class name are resolved from the file
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    // Get the URL of the resource file
    Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    List<String> result = new ArrayList<String>();
    // Traverse all URL s
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // Parse the properties file based on the resource file URL to get a corresponding set of @Configuration classes
        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String factoryClassNames = properties.getProperty(factoryClassName);
        // Assemble data and return
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
    }
    return result;
}

Improved SPI mechanism

This mechanism has two cache variables:

private static final ConcurrentHashMap<Class<?>, Map<String, Class<?>>> cacheClasses= new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Class<?>, Map<String, Object>> cacheIntances = new ConcurrentHashMap<>();

Both Map key s are Class objects of the interface class that extends the service
The value of cacheClasses is also a map, where the key is the extension of the definition, that is, the key in the file under META-INF/roadspi/directory, and the value is the Class object of the specific extension class.
The value of the cacheIntances variable is also a map, where the key is the extension of the definition and the value is the concrete instantiation object of the extension class.
The main logic of this mechanism is to first get the Class object of the interface class to be extended, and then find out if there is a cached instance based on the key from the cacheIntances variable, if there is a direct return.If not, then look for the Class object of the specific extension class in the cacheClasses variable based on the interface class Class object and key. If it exists, get the Class object for use directly, and use BeanDefinitionBuilder to generate the bean.It is registered with the Spring container; if no corresponding Class object is found, the resource is loaded under the full name file of META-INF/roadspi/Extension Interface Class.
Custom LoadSpi annotations are supported to define specific service class implementations by default.

Major Part Implementation

 private void createService(Map<String, Object> extensionInstanceMap, Map<String, Class<?>> serviceClass, String serviceName, Class<?> type) {
        Class<?> obj = serviceClass.get(serviceName);
        if (obj == null) {
            log.error("serviceClass is null!");
        }
        String beanName = obj.getSimpleName().concat(serviceName);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(obj);
        GenericBeanDefinition definition = (GenericBeanDefinition)builder.getRawBeanDefinition();
        definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext)context;
        DefaultListableBeanFactory register = (DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();
        register.registerBeanDefinition(beanName, definition);
        extensionInstanceMap.put(serviceName, context.getBean(beanName));
        cacheIntances.put(type, extensionInstanceMap);
    }

Detailed code address: RoadSPI

Posted by Snatch on Wed, 19 Feb 2020 10:50:16 -0800