android自定義serviceloader接口隔離及獲取自定義properties參數(shù)配置

1.簡述:

??之前看過大神的美團組件化方案,其中提到了通過servicelaoder進行解耦的思路,主要是通過配置接口及其實現(xiàn)類的方式坐到接口隔離作用,本文主要是實現(xiàn)此思路并延伸出通過加載自定義properties文件獲取參數(shù)配置信息

2.系統(tǒng)ServiceLoader簡介

??通過查看ServiceLoader源碼可知,ServiceLoader是通過加載META-INF/services/路徑下的接口實現(xiàn)類,加載方式是通過讀取配置文件并通過反射的方式獲取類的實例
1.配置文件讀取,獲取文件流

       private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //文件路徑
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                //通過流獲取實現(xiàn)類的class全路徑String集合
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

其中PREFIX = "META-INF/services/";
由此可見加載路徑是META-INF文件夾下面的文件

2.通過流獲取實現(xiàn)類全路徑

private Iterator<String> parse(Class<?> service, URL u)
            throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        //存儲獲取的類全路徑
        ArrayList<String> names = new ArrayList<>();
        try {
            //通過URL獲取流
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            //通過流逐行讀取文件并存入names
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
           ...
        } finally {
            ...
        }
        return names.iterator();
    }

其中parseLine方法里面是做了類全路徑名校驗

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
            throws IOException, ServiceConfigurationError
    {
        ...
        if (n != 0) {
            //判斷是否有空格
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            //確定是否允許將字符(Unicode 代碼點)作為 Java 標識符中的首字符
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                //確定字符(Unicode 代碼點)是否可以是 Java 標識符中首字符以外的部分
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            ...
        }
        return lc + 1;
    }
3.自定義ServiceLoader

思路:
??1.讀取配置文件
??2.獲取配置的類全名
??3.通過反射獲取類的實例

??我們的配置文件將寫在assets文件夾下


image.png

??通過查看apk包結(jié)構(gòu)可以發(fā)現(xiàn)assets文件夾位置是與META-INF平級的,由此我們可以將系統(tǒng)的ServiceLoader加載文件路徑改為assets路徑

1.配置文件讀取,獲取文件流

class Load {

    private ClassLoader loader;
    private Enumeration<URL> configs = null;

    Load() {
        //初始化加載器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (null != cl) {
            loader = cl;
        } else {
            loader = ClassLoader.getSystemClassLoader();
        }
    }
    //獲取URL
    URL initLoad(String location) {
        if (configs == null) {
            try {
                if (loader == null)
                    configs = ClassLoader.getSystemResources(location);
                else
                    configs = loader.getResources(location);
            } catch (IOException x) {
                x.printStackTrace();
            }
        }
        return configs.nextElement();
    }

    ClassLoader getLoader() {
        return loader;
    }
}

2.通過流獲取接口類與實現(xiàn)類的對應集合
??由于接口類與實現(xiàn)類是一對一關(guān)系,所以通過Map以鍵值對的方式存儲接口類與實現(xiàn)類,在系統(tǒng)ServiceLoader做簡單修改:

private static Map<String,String> parse(URL u)
            throws ServiceConfigurationError
    {
       ...
        Map<String,String> names = new HashMap<>();
        try {
          ...
            while ((lc = parseLine(r, lc, names)) >= 0);
        } catch (IOException x) {
//            fail(service, "Error reading configuration file", x);
        } finally {
           ...
        }
        return names;
    }
 private static int parseLine(BufferedReader r, int lc,
                                 Map<String,String> names)
            throws IOException, ServiceConfigurationError
    {
        ...
        if(lns.length == 2){
            if(!isJavaIdentifier(lns[0]) || !isJavaIdentifier(lns[1])){
                return -1;
            }
            names.put(lns[0],lns[1]);
        }else {
            return -1;
        }
        return lc + 1;
    }

3.獲取實現(xiàn)類
??在上一步已經(jīng)獲取了所有接口類和實現(xiàn)類的集合,在此通過接口類全名來獲取實現(xiàn)類全名,并通過反射的方式獲取實現(xiàn)類實例:

<T> T load(Context context,Class<T> server){

            String cn = pending.get(server.getName());
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, dzmLoad.getLoader());
            } catch (ClassNotFoundException x) {
               ...
            }
            ...
            try {
                T p = server.cast(c.newInstance());
                servers.put(server.getName(),p);
                return p;
            } catch (Throwable x) {
                ...
            }
            throw new Error();          // This cannot happen
        }

到此我們自定義ServiceLoader已經(jīng)初步實現(xiàn),在實際開發(fā)中,我們一般只需要一個實例及單利,在此我們可以用Map將類的實例與接口類名綁定起來即可。

配置文件
image.png

使用

MyServicesLoader.getService(TestService.class).test()
4.延伸---加載properties配置參數(shù)

??加載properties配置參數(shù)的思路與ServiceLoader基本一致,只是獲取配置參數(shù)可以通過java類Properties獲取
1.獲取流
??和自定義ServiceLoader獲取流一致

2.獲取Properties實例

private Properties loadProperties(String... resourcesPaths) {
        Properties props = new Properties();

        for (String location : resourcesPaths) {
            InputStream is = null;
            try {
                //獲取流
                URL url = new Load().initLoad(location);
                URLConnection con = url.openConnection();
                is = con.getInputStream();
                //加載流配置
                props.load(is);
            } catch (IOException ex) {
                ex.printStackTrace();
            } finally {
                try {
                    if(null != is)
                        is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return props;
    }

3.獲取value

    private String getValue(String key) {
        String systemProperty = System.getProperty(key);
        if (systemProperty != null) {
            return systemProperty;
        }
        if (properties.containsKey(key)) {
            return properties.getProperty(key);
        }
        return "";
    }

4.使用

PropertiesLoader propertiesLoader = new PropertiesLoader("assets/services/data.properties");
propertiesLoader.getProperty("ip")
配置:
image.png

image.png
結(jié)果
image.png

image.png
注:

1.在查看Iterable 接口時無意中發(fā)現(xiàn)了default關(guān)鍵字,經(jīng)查看資料顯示為java8新加的,用于在接口中寫默認的方法函數(shù)體

有興趣的可以去https://github.com/dengzhi00/deployloader看看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,590評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,275評論 6 342
  • feisky云計算、虛擬化與Linux技術(shù)筆記posts - 1014, comments - 298, trac...
    不排版閱讀 4,355評論 0 5
  • 很久沒有認真的寫一篇了,都是零零碎碎的記錄一些事情,有時真的感觸很深才會想要記錄下來,有時懶,睡一覺第二天也就過了...
    牡羊人閱讀 364評論 14 0
  • 今天還好吧!算是寫了字每天小結(jié)的目的就在于知道自己每天做了多少事情,要堅持,不能因為不打卡就不寫了明天又要開始學習...
    Cheer_up閱讀 93評論 0 0

友情鏈接更多精彩內(nèi)容