Java9 modules : 如何根据执行的优先级来执行 Provider?

Java9 modules : How to execute Provider based on some priority of execution?

我正在使用 java 9 个模块来实现提供程序,我有多个服务接口提供程序。我想为提供者提供一些优先级以执行它们而不是使用 findFirst();

Optional<ServiceInterface> loader=ServiceLoader.load(ServiceInterface.class).findFirst();

我有 service-interface 个模块如下,

ServiceInterface.Java

 public interface ServiceInterface {
    
        int theAnswer();
    
    }

模块-info.java

module ServiceInterface {
    exports com.si.serviceinterface;
}

我有 provider-module 有两个服务接口实现,

Provider1.java

public class Provider1 implements ServiceInterface {
    @Override
    public int theAnswer() {
        return 42;
    }
}

Provider2.java

public class Provider2 implements ServiceInterface {
  @Override
  public int theAnswer() {
    return 52;
  }
}

模块-info.java

module Provider {
  requires ServiceInterface;
  provides ServiceInterface with Provider1, Provider2;
}

现在,我有 consumer-module,它将使用 ServiceLoader 来加载 Provider。而不是使用 findFirst() 加载服务提供商。我想根据一些优先级示例加载我想加载 Provider2 然后我应该能够加载而不是加载提供程序 1.

Consumer.java

public class Consumer {

  public static void main(String[] args) {
    ServiceLoader<ServiceInterface> loader = ServiceLoader.load(ServiceInterface.class);
    for (final ServiceInterface service : loader) {
      System.out.println("The service " + service.getClass().getSimpleName() + " answers " + service.theAnswer());
    }
    
    
  }
}

关于实现优先加载 Provider 而不是使用 findFirst() 的任何建议。

设计服务 ServiceLoader documentation 部分说

[…] there are two general guidelines:

  • A service should declare as many methods as needed to allow service providers to communicate their domain-specific properties and other quality-of-implementation factors. An application which obtains a service loader for the service may then invoke these methods on each instance of a service provider, in order to choose the best provider for the application.

  • A service should express whether its service providers are intended to be direct implementations of the service or to be an indirection mechanism such as a "proxy" or a "factory". Service providers tend to be indirection mechanisms when domain-specific objects are relatively expensive to instantiate; in this case, the service should be designed so that service providers are abstractions which create the "real" implementation on demand. For example, the CodecFactory service expresses through its name that its service providers are factories for codecs, rather than codecs themselves, because it may be expensive or complicated to produce certain codecs.

按照这个指南,我们可以简单地在接口中添加一个优先级查询方法来匹配第一个项目符号并保持其他内容不变,因为实例化并不昂贵。

public interface ServiceInterface {
    int theAnswer();
    int priority();
}
public class Provider1 implements ServiceInterface {
    @Override
    public int theAnswer() {
        return 42;
    }

    @Override
    public int priority() {
        return 0;
    }
}
public class Provider2 implements ServiceInterface {
    @Override
    public int theAnswer() {
        return 52;
    }

    @Override
    public int priority() {
        return 1;
    }
}
public class ServiceConsumer {
    public static void main(String[] args) {
        ServiceLoader<ServiceInterface> loader=ServiceLoader.load(ServiceInterface.class);
        ServiceInterface service = loader.stream().map(Provider::get)
            .max(Comparator.comparingInt(ServiceInterface::priority)).orElseThrow();

        System.out.println("The service " + service.getClass().getSimpleName()
            + " answers " + service.theAnswer());
    }
}
The service Provider2 answers 52

但由于这只是一个示例,您的用例可能涉及创建成本高昂的服务实例。在这种情况下,您可以按照大多数 JDK 服务所做的那样,按照建议将服务提供者接口与实际服务分开。

public interface ServiceProviderInterface {
    /** Potentially expensive service instantiation */
    ServiceInterface getService();

    /** Can be cheaply queried without calling the expensive method */
    int priority();
}
public interface ServiceInterface {
    /**
     * The operation
     */
    int theAnswer();

    /**
     * Decide yourself if getting the provider is useful, e.g. java.nio.file.FileSystem
     * has such a method, java.nio.charset.Charset has not.
     */
    ServiceProviderInterface provider();
}
public class Provider1 implements ServiceProviderInterface {

    public static class ActualService implements ServiceInterface {
        private final ServiceProviderInterface provider;

        public ActualService(Provider1 p) {
            provider = p;
            System.out.println("potentially expensive Provider1.ActualService()");
        }
        @Override
        public int theAnswer() {
            return 42;
        }
        @Override
        public ServiceProviderInterface provider() {
            return provider;
        }
    }

    @Override
    public ServiceInterface getService() {
        return new ActualService(this);
    }

    @Override
    public int priority() {
        return 0;
    }
}
public class Provider2 implements ServiceProviderInterface {

    public static class ActualService implements ServiceInterface {
        private final ServiceProviderInterface provider;

        public ActualService(Provider2 p) {
            provider = p;
            System.out.println("potentially expensive Provider2.ActualService()");
        }
        @Override
        public int theAnswer() {
            return 52;
        }
        @Override
        public ServiceProviderInterface provider() {
            return provider;
        }
    }

    @Override
    public ServiceInterface getService() {
        return new ActualService(this);
    }

    @Override
    public int priority() {
        return 1;
    }
}

当然,必须修改 module-info 声明以提供或使用 ServiceProviderInterface 而不是 ServiceInterface。用例现在看起来像

public class ServiceConsumer {
    public static void main(String[] args) {
        ServiceInterface service = ServiceLoader.load(ServiceProviderInterface.class)
            .stream()
            .map(Provider::get)
            .max(Comparator.comparingInt(ServiceProviderInterface::priority))
            .map(ServiceProviderInterface::getService)
            .orElseThrow();

        System.out.println("The service " + service.getClass().getSimpleName()
            + " answers " + service.theAnswer());
    }
}

相同的结果但不实例化 Provider1.ActualService()。只实例化实际使用的Provider2.ActualService()


作为文档指南的替代方法,您可以使用第一种方法和注释而不是 priority() 方法。

public interface ServiceInterface {
    int theAnswer();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Priority {
    int value();
}
@Priority(0)
public class Provider1 implements ServiceInterface {
    public Provider1() {
        System.out.println("potentially expensive Provider1()");
    }

    @Override
    public int theAnswer() {
        return 42;
    }
}
@Priority(1)
public class Provider2 implements ServiceInterface {
    public Provider2() {
        System.out.println("potentially expensive Provider2()");
    }

    @Override
    public int theAnswer() {
        return 52;
    }
}
public class ServiceConsumer {
    public static void main(String[] args) {
        ServiceInterface service = ServiceLoader.load(ServiceInterface.class).stream()
            .max(Comparator.comparingInt(p->p.type().isAnnotationPresent(Priority.class)?
                    p.type().getAnnotation(Priority.class).value(): 0))
            .map(Provider::get)
            .orElseThrow();

        System.out.println("The service " + service.getClass().getSimpleName()
            + " answers " + service.theAnswer());
    }
}

这可以避免潜在的昂贵实例化,而无需处理两个接口,但是,您可以在实例化之前声明和查询的属性仅限于编译时常量。

另一方面,这种方法可用于扩展现有的服务框架,因为提供接口的模块不需要知道注解。可以将它们作为某些服务实现和使用站点之间的扩展合同引入。