1. Spring SPI 機制總結

        共 5961字,需瀏覽 12分鐘

         ·

        2021-07-28 08:42

        作者 |  ocean.wen

        來源 |  urlify.cn/m6niA3


        1、概念:

            SPI(Service Provider Interface)服務提供接口,簡單來說就是用來解耦,實現(xiàn)插件的自由插拔,具體實現(xiàn)方案可參考JDK里的ServiceLoader(加載classpath下所有META-INF/services/目錄下的對應給定接口包路徑的文件,然后通過反射實例化配置的所有實現(xiàn)類,以此將接口定義和邏輯實現(xiàn)分離)
            Spring在3.0.x的時候就已經(jīng)引入了spring.handlers,很多博客講Spring SPI的時候并沒有提到spring.handlers,但是通過我自己的分析對比,其實spring.handlers也是一種SPI的實現(xiàn),只不過它是基于xml的,而且在沒有boot的年代,它幾乎是所有三方框架跟spring整合的必選機制。

        在3.2.x又新引入了spring.factories,它的實現(xiàn)跟JDK的SPI就基本是相似的了。

        spring.handlers和spring.factories我都把它歸納為Spring為我們提供的SPI機制,通過這兩種機制,我們可以在不修改Spring源碼的前提下,非常輕松的做到對Spring框架的擴展開發(fā)。

        2、實現(xiàn):

        2.1 先看看spring.handlers SPI

            在Spring里有個接口NamespaceHandlerResolver,只有一個默認的實現(xiàn)類DefaultNamespaceHandlerResolver,而它的作用就是加載classpath下可能分散在各個jar包中的META-INF/spring.handlers文件,resolve方法中關鍵代碼如下:

        //加載所有jar包中的META-INF/spring.handlers文件
        Properties mappings=
          PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);

        //把META-INF/spring.handlers中配置的namespaceUri對應實現(xiàn)類實例化
        NamespaceHandler namespaceHandler =
          (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

        DefaultNamespaceHandlerResolver.resolve()主要被用在BeanDefinitionParserDelegate的parseCustomElement和decorateIfRequired,所以spring.handlers SPI機制主要也是被用在bean的掃描和解析過程中。

        2.2 再來看spring.factories SPI

        // 獲取某個已定義接口的實現(xiàn)類,跟JDK的ServiceLoader SPI相似度為90%
        List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(BeanInfoFactory.class, classLoader);
        // spring.factories文件的格式為:key=value1,value2,value3
        // 從所有jar文件中找到MET-INF/spring.factories文件(注意是:classpath下的所有jar包,所以可插拔、擴展性超強)
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
         ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
         String propertyValue = properties.getProperty(factoryClassName);
         for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
          result.add(factoryName.trim());
         }
        }
        return result;

        更多細節(jié),大家可以參考SpringFactoriesLoader類,Spring自3.2.x引入spring.factories SPI后其實一直沒怎么利用起來,只有CachedIntrospectionResults(初始化bean的過程中)用到了,而且在幾大核心jar包里,也只有bean包里才有用到。
        真正把spring.factories發(fā)揚光大的,是到了Spring Boot,可以看到boot包里配置了非常多的接口實現(xiàn)類。大家跟蹤boot的啟動類SpringApplication可以發(fā)現(xiàn),有很多地方都調(diào)用了getSpringFactoriesInstances()方法,這些就是spring boot開給我們的擴展機會,就像一座寶藏一樣,大家可以自己去發(fā)掘。

        3、應用:

            先來看看mybatis和dubbo早期跟Spring整合的實現(xiàn),他們無一例外都用到了spring.handlers SPI機制,以此來向IOC容器注入自己的Bean。


        進入boot時代后,spring.factories SPI機制應用得更加廣泛,我們可以在容器啟動、環(huán)境準備、初始化、上下文加載等等環(huán)節(jié)輕輕松松的對Spring做擴展開發(fā)(例如:我們項目中用到spring.factories SPI機制對配置文件中的變量實現(xiàn)動態(tài)解密,以及前篇博文中提到的@Replace注解等)。

        4、實踐(加載application.xyz配置文件):

            Spring里有兩種常見的配置文件類型:application.properties 和 application.yml,其中yml是近年興起的,但說實話同事也包括我自己是被它坑過,沒有合適的編輯器時很容易把格式寫錯,導致上線出問題。所以我就在想有沒有辦法讓Spring支持一種新的配置文件格式,既保留yml的簡潔優(yōu)雅,有能夠有強制的格式校驗,暫時我想到了json格式。

        # 這是spring.factories中的配置
        org.springframework.boot.env.PropertySourceLoader=top.hiccup.json.MyJsonPropertySourceLoader
        public class MyJsonPropertySourceLoader implements PropertySourceLoader {
            @Override
            public String[] getFileExtensions() {
                return new String[]{"xyz"};
            }
            @Override
            public List<PropertySource<?>> load(String name, Resource resource) throws IOException {

                BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                // 這里只是做了簡單解析,沒有做嵌套配置的解析
                JSONObject json = JSONObject.parseObject(sb.toString());
                List<PropertySource<?>> propertySources = new ArrayList<>();
                MapPropertySource mapPropertySource = new MapPropertySource(resource.getFilename(), json);
                propertySources.add(mapPropertySource);
                return propertySources;
            }
        }
                ConfigurableApplicationContext ctx = SpringApplication.run(BootTest.class, args);
                Custom custom = ctx.getBean(Custom.class);
                System.out.println(custom.name);
                System.out.println(custom.age);

        具體代碼可以參考(https://github.com/hiccup234/web-advanced/tree/master/configFile) ,運行得到結果如下:

        可見我們在不修改Spring源碼的前提下,輕松通過Spring開放給我們的擴展性實現(xiàn)了對新的配置文件類型的加載和解析。

        這就是Spring SPI的魅力吧。


        最近給大家找了  通用權限系統(tǒng)


        資源,怎么領???


        掃二維碼,加我微信,回復:通用權限系統(tǒng)

         注意,不要亂回復 

        沒錯,不是機器人
        記得一定要等待,等待才有好東西
        瀏覽 46
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 美女洗澡18隐私免费 | 538任你躁精品视频网免费 | 91精品国产综合久久久蜜臀酒店 | 电车侵犯肉肉动漫在线播放 | 午夜福利视频91 |