цитаты
Обычно я много слышу об API? Что такое SPI? Не беспокойтесь, давайте взглянем на взаимосвязь вызовов интерфейсно-ориентированного программирования, чтобы понять сходства и различия между API и SPI.
понимание SPI
Давайте начнем с введения в мандарин: SPI — это полное название (Service Provider Interface), который представляет собой механизм обнаружения поставщиков услуг, встроенный в JDK.
1. Определяем и реализуем интерфейсы на стороне службы, предаемся только интерфейсу для использования, когда вызывающая сторона называется API;
2. Когда интерфейс определен на звонилке (реализован на стороне сервиса), мы также даем ему имя — SPI.
Должно быть легче понять, не так ли?
Сценарии использования SPI
На самом деле SPI имеет много обширных приложений в рамках, вот несколько примеров:
1. Выбор драйвера Mysql driverManager определяет используемый драйвер в соответствии с конфигурацией;
2. Механизм расширения во фреймворке dubbo (Ссылка на официальный сайт Dubbo)
Пример использования
Прочитав приведенное выше введение и применение SPI в рамках, я, должно быть, создал прототип для SPI в мозгу читателя, разговоры дешевы! Покажите мне код. Сказав так много, давайте напишем простой пример, чтобы увидеть Чтобы увидеть эффект, проверьте SPI.
1. Сначала определите интерфейс, интерфейс службы ниндзя
public interface NinjaService {
void performTask();
}
2. Затем напишите два класса реализации: ForbearanceServiceImpl (верхний допуск), ShinobuServiceImpl (нижний допуск).
public class ForbearanceServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("上忍在执行A级任务");
}
}
public class ShinobuServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("下忍在执行D级任务");
}
}
3. Затем мы создаем каталог META-INF/services в main/resources/ и создаем файл com.scott.java.task.spi.NinjaService (полное имя класса обслуживания ниндзя) в каталоге services.
4. Создайте класс клиентской сцены для вызова результатов.
5. Наконец, вставьте структуру каталогов
Простой анализ исходного кода SPI
1. Сначала посмотрите на определение и свойства базового класса ServiceLoader.
// 继承了Iterable类 遍历的时候使用
public final class ServiceLoader<S> implements Iterable<S>
{
// 这就是为啥需要在META-INF/services/目录下创建服务类的文件
private static final String PREFIX = "META-INF/services/";
// 被加载的服务
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问控制类
private final AccessControlContext acc;
// 实现类的缓存 根据初始化的顺序 也就是在/services/文件中的定义顺序来定义的加载顺序
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒加载iterator
private LazyIterator lookupIterator;
2. Потом запустить с клиента, а потом отлаживать по очереди
ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前的类加载器 也就是AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
Последнее опущено, т.к. здесь просто инициализация по NinjaService, ничего сложного для понимания.
3. Смотрим на конкретный вызывающий процесс.Здесь используется class файл соответствующий клиенту,потому что добавление for(foreach)-это синтаксический сахар в java.На самом деле после компиляции так оно и есть.
public static void main(String[] args) {
ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
// 这里一下其实就是增加for解糖后的代码 有兴趣可以去了解下java的语法糖
Iterator var2 = ninjaServices.iterator();
while(var2.hasNext()) {
NinjaService item = (NinjaService)var2.next();
item.performTask();
}
}
4. Поскольку точка останова продолжается, мы вводим метод var2.hasNext()
public boolean hasNext() {
// knownProviders还没有加载过provider 走下面的分支
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
Здесь представлены свойства ServiceLoader выше lookupIterator, фактически это внутренний класс Iterator в ServiceLoader. Затем вызывается метод hasNext() внутреннего класса Iterator.
public boolean hasNext() {
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// ServiceLoader初始化没有设置过securityManager,所以acc是null,进入hasNextService()
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
5.hasNextService() анализ
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 这里加载了META-INF/services下的文件 也就是含有两个实现类全限定名的文件
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 因为loader是不为null 的AppClassLoader
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 这里是将上面加载的文件中的两个实现类的文件
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
6. Продолжайте видеть метод синтаксического анализа, вот окончательный результат. Iterator содержит два полностью определенных имени класса, на самом деле содержимое файловых служб / под загрузкой.
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
Кстати, поговорим о parseLine(service, u, r, lc, name), проверяем, соответствует ли имя класса спецификации, добавляем его в Iterator, если соответствует, идем сюда var2 .hasNext() выполняется, в результате загружается содержимое файла в службах
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
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);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 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;
}
pending = parse(service, configs.nextElement());
}
// 这里将下一个实现类的名字赋值给了LazyIterator的属性nextName
nextName = pending.next();
return true;
}
7. Выполняется следующий (метод) NinjaService item = (NinjaService) var2.next(), а затем продолжаю отладку. Здесь я опускаю некоторые вызовы методов и показываю только полезные методы. Это внутренний класс ServiceLoader NextService() метод LazyIterator.
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 这里nextName在上面已经赋值过了 所以反射创建实例
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
// 类型判断
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 强转类型
S p = service.cast(c.newInstance());
// 将类添加到ServiceLoader的providers属性中 然后返回
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
8. В этот момент возвращается класс реализации подкласса, и анализ завершается.
Суммировать:
1. Понять, что такое SPI;
2. Простая разница и связь между SPI и API;
3. Узнал, как использовать SPI для расширения служб;
4. Проанализировал процесс загрузки исходного кода. Вот предложение, простая вещь заключается в том, что Meta-inf / Services определяет интерфейс (имя файла) и класс реализации (содержимое файла), который должен быть реализован,
Класс внедрения не существует при нагрузке сервизника, но экземпляр создается путем отражения при пересечении итератора.
Если вы считаете, что сочинение в порядке, вы можете поставить лайк, подписаться на волну и продолжать писать лучшие статьи в будущем~ XD
Использованная литература:
1.Adult.open jdk.java.net/~nobody/jigsaw/… 2.Блог Woo Woo.cn на.com/happy frame… 3.Хироши Ватанабэ.apache.org/this-talent/blog/… 4.Блог 呜呜.cn на .com/google?OFT... 5.zhuanlan.zhihu.com/p/28909673