轻量级RPC设计与实现第四版

在本版本中引入了SPI机制,关于Java的SPI机制与Dubbo的SPI机制在以前的文章中介绍过。
传送门:Dubbo的SPI机制与JDK机制的不同及原理分析
因为设计的RPC框架是基于Spring的,时常会遇到依赖注入问题。Spring中也有SPI机制,但是它有有个缺点,就是在利用SPI机制实例化具体的服务类时,如果具体的服务类中调用其他的bean,就会实例化失败。主要因为该具体的服务类并没有放入到Spring容器中。本项目将有效解决这个问题。
在设计的RPC框架中加入了该机制,来实现不同序列化方式的切换。

Spring的SPI机制

我们知道在SprngBoot中好多的配置和实现都有默认的实现,我们只需要修改部分配置,比如数据库配置,我们只要在配置文件中写上对应的url,username,password就可以使用了。其实他这边用的就是SPI的方式实现的。Spring的SPI机制原理与Java的SPI原理是一致的。
SpringBoot会利用SpringFactoriesLoader加载META-INF/spring.factories文件,从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

调用方式:

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

相关源码:

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";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    // 取得资源文件的URL
    Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    List<String> result = new ArrayList<String>();
    // 遍历所有的URL
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String factoryClassNames = properties.getProperty(factoryClassName);
        // 组装数据,并返回
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
    }
    return result;
}

改进的SPI机制

该机制有两个缓存变量:

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

两个Map的key都是扩展服务的接口类的Class对象
cacheClasses的value也是一个Map,这个map的key是定义的扩展名,即META-INF/roadspi/目录下文件中的key,value是具体的扩展类的Class对象。
cacheIntances变量的value也是一个Map,该map的key是定义的扩展名,value是扩展类的具体实例化对象。
该机制的主要逻辑是先获取要实现扩展的接口类Class对象,然后从cacheIntances变量中根据key查找是否有缓存的实例,如果有直接返回。如果没有,然后根据接口类Class对象和key在cacheClasses变量中进行查找具体扩展类的Class对象,如果存在,就直接获取对用的Class对象,然后利用BeanDefinitionBuilder生成bean,并注册到Spring容器中;如果找不到对应的Class对象,则到META-INF/roadspi/扩展接口类全称文件下进行资源加载。
支持自定义的RoadSpi注解,来定义默认的具体服务类实现。
整体流程

最主要部分实现

 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);
    }

具体详细代码地址:RoadSPI

posted @ 2020-02-19 23:02  房东的小黑黑  阅读(186)  评论(0编辑  收藏  举报