zoukankan      html  css  js  c++  java
  • 轻量级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

  • 相关阅读:
    给Debian安装Xfce桌面
    【兄弟连ThinkPHP】1、介绍和安装
    CROSS JOIN,NATURAL JOIN
    表的连接查询
    多表查询在数据量非常大的时候性能不好,慎用!
    伪列:Oracle显示查询结果前几条记录用rownum<=。去掉重复记录,保留最早录入记录:取出最小ROWID
    其他函数:值为NULL时的默认值NVL,DECODE
    转换函数TO_CHAR,TO_DATE,TO_NUMBER
    50-用Python监听鼠标和键盘事件
    49-Python 安装pythoncom库和pyHook
  • 原文地址:https://www.cnblogs.com/maratong/p/12333755.html
Copyright © 2011-2022 走看看