欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详谈ServiceLoader实现原理

程序员文章站 2024-03-06 19:28:08
在java中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口获取该接口的所有实现类却没那么容易。 有一种比较笨的办法就是扫描classpath所有的class与...

在java中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口获取该接口的所有实现类却没那么容易。

有一种比较笨的办法就是扫描classpath所有的class与jar包中的class,然后用classloader加载进来,然后再判断是否是给定接口的子类。但是很显然,不会使用这种方法,代价太大。

java本身也提供了一种方式来获取一个接口的子类,那就是使用java.util.serviceloader#load(java.lang.class<s>) 方法,但是直接使用该方法也是不能获取到给定接口所有的子类的。

需要接口的子类以配置的方式主动注册到一个接口上,才能使用serviceloader进行加载到子类,并且子类需要有一个无参构造方法,用于被serviceloader进行实例化

下面介绍使用serviceloader的步骤

1、 编写service

package com.mogujie.uni.sl;
/**
 * created by laibao
 */
public interface animal {
    void eat();
}  

2、编写实现类(注意:实现类不一定要与接口在同一个工程中,可以存在于其他的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...");
  }
}

3、 在实现类所在的工程的classpath下面的建立meta-inf/services目录,该目录是固定的,一定要按照规定的名称去创建,该目录用于配置接口与实现类的映射关系

然后根据接口全名 在该目录创建一个文件,例如上面例子中接口全名是com.mogujie.uni.sl.animal,那么就需要在实现类的工程中建立meta-inf/services/com.mogujie.uni.sl.animal这样一个文件,然后在该文件中配置该接口的实现类,如果该接口有多个实现类,一行写一个(以换行符分割),例如:

com.mogujie.uni.sl.pig
com.mogujie.uni.sl.dog

4、接下来就能使用serviceloader的方法获取com.mogujie.uni.sl.animal接口的所有子类了。

测试类如下:

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的原理其实很简单,就是根据给定的参数(接口)就能定位到该接口与实现类的映射配置文件的路径了,然后读取该配置文件,就能获取到该接口的子类

下面自己实现一个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()方法时才会实例化下一个服务实例,只有需要使用的时候才进行实例化,具体实现读者可以去阅读源码进行研究,这也是其设计的亮点之一。

以上这篇详谈serviceloader实现原理就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。