在java中根据一个子类获取其父类或接口信息很是方便,可是根据一个接口获取该接口的全部实现类却没那么容易。java
有一种比较笨的办法就是扫描classpath全部的class与jar包中的class,而后用ClassLoader加载进来,而后再判断是不是给定接口的子类。可是很显然,不会使用这种方法,代价太大。apache
java自己也提供了一种方式来获取一个接口的子类,那就是使用java.util.ServiceLoader#load(java.lang.Class<S>)
方法,可是直接使用该方法也是不能获取到给定接口全部的子类的。数组
须要接口的子类以配置的方式主动注册到一个接口上,才能使用ServiceLoader进行加载到子类,而且子类须要有一个无参构造方法,用于被ServiceLoader进行实例化app
一、 编写Serviceide
package com.mogujie.uni.sl; /** * Created by laibao */ public interface Animal { void eat(); }
二、编写实现类(注意:实现类不必定要与接口在同一个工程中,能够存在于其余的jar包中)测试
package com.mogujie.uni.sl; /** * Created by laibao */ public class Pig implements Animal { @Override public void eat() { System.out.println("Pig eating..."); } }
package com.mogujie.uni.sl; /** * Created by laibao */ public class Dog implements Animal { @Override public void eat() { System.out.println("Dog eating..."); } }
三、 在实现类所在的工程的classpath下面的创建META-INF/services目录,该目录是固定的,必定要按照规定的名称去建立,该目录用于配置接口与实现类的映射关系
而后根据接口全名 在该目录建立一个文件,例如上面例子中接口全名是com.mogujie.uni.sl.Animal,那么就须要在实现类的工程中创建META-INF/services/com.mogujie.uni.sl.Animal这样一个文件,而后在该文件中配置该接口的实现类,若是该接口有多个实现类,一行写一个(以换行符分割),例如:spa
com.mogujie.uni.sl.Pig
com.mogujie.uni.sl.Dog
四、接下来就能使用ServiceLoader的方法获取com.mogujie.uni.sl.Animal接口的全部子类了。测试类以下:.net
package com.mogujie.uni; import com.mogujie.uni.sl.Animal; import java.util.Iterator; import java.util.ServiceLoader; /** * Created by laibao */ public class TestServiceLoader { public static void main(String[] args) { ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class); Iterator<Animal> animalIterator = serviceLoader.iterator(); while(animalIterator.hasNext()){ Animal animal = animalIterator.next(); animal.eat(); } } }
输出以下:设计
Pig eating...
Dog eating...
ServiceLoader的原理其实很简单,就是根据给定的参数(接口)就能定位到该接口与实现类的映射配置文件的路径了,而后读取该配置文件,就能获取到该接口的子类code
下面本身实现一个CustomServiceLoader与系统的ServiceLoader具备一样的功能
package com.mogujie.uni; import org.apache.commons.io.IOUtils; import java.net.URL; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; /** * Created by laibao */ public class CustomServiceLoader { public static final String MAPPING_CONFIG_PREFIX = "META-INF/services"; public static <S> List<S> loade(Class<S> service) throws Exception{ String mappingConfigFile = MAPPING_CONFIG_PREFIX + "/" + service.getName() ; //因为一个接口的实现类可能存在多个jar包中的META-INF目录下,因此下面使用getResources返回一个URL数组 Enumeration<URL> configFileUrls = CustomServiceLoader.class.getClassLoader().getResources(mappingConfigFile); if(configFileUrls == null){ return null ; } List<S> services = new LinkedList<S>(); while(configFileUrls.hasMoreElements()){ URL configFileUrl = configFileUrls.nextElement(); String configContent = IOUtils.toString(configFileUrl.openStream()); String[] serviceNames = configContent.split("\n"); for(String serviceName : serviceNames){ Class serviceClass = CustomServiceLoader.class.getClassLoader().loadClass(serviceName); Object serviceInstance = serviceClass.newInstance(); services.add((S)serviceInstance); } } return services ; } }
测试类以下:
package com.mogujie.uni; import com.mogujie.uni.sl.Animal; import java.util.List; /** * Created by laibao */ public class CustomServiceLoaderTest { public static void main(String[] args) throws Exception { List<Animal> animals = CustomServiceLoader.loade(Animal.class); for (Animal animal : animals){ animal.eat(); } } }
输出:
Pig eating...
Dog eating...
java系统定义的ServiceLoader与咱们自定义的CustomServiceLoader的loade方法,它们的返回值类型是不同的,ServiceLoader的loade方法返回的是ServiceLoader对象,ServiceLoader对象实现了Iterable接口,经过ServiceLoader的成员方法iterator();就能遍历全部的服务实例,而咱们自定义的CustomServiceLoader的load方法返回的是一个List对象,直接将全部的服务实例封装在一个集合里面返回了。 系统的ServiceLoader经过返回一个Iterator对象可以作到对服务实例的懒加载 只有当调用iterator.next()方法时才会实例化下一个服务实例,只有须要使用的时候才进行实例化,具体实现读者能够去阅读源码进行研究,这也是其设计的亮点之一。