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文件夾下

??通過查看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將類的實例與接口類名綁定起來即可。

使用
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")
配置:


結(jié)果


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