Giter VIP home page Giter VIP logo

java-service-util's Introduction

Java service utilities

Download

This library provides some utilities for Java SPI (Service Provider Interface).

The Java SPI is a mechanism that decouples a service from its implementation(s). It allows the creation of extensible or replaceable modules/plugins. It is composed of four main components: service, service provider interface, service provider and service loader. If the service is a single interface then it is the same as a service provider interface.

Key points:

  • lightweight library with no dependency
  • no dependency at runtime, all the work is done at compile-time
  • Java 8 minimum requirement
  • has an automatic module name that makes it compatible with JPMS

@ServiceProvider

The @ServiceProvider annotation registers service providers on classpath and modulepath.

Features:

  • generates classpath files in META-INF/services folder
  • supports multiple registration of one class
  • can infer the service if the provider implements/extends exactly one interface/class
  • checks coherence between classpath and modulepath if module-info.java is available

Limitations:

Example:

public interface FooSPI {}

public interface BarSPI {}

@ServiceProvider
public class FooProvider implements FooSPI {}

@ServiceProvider ( FooSPI.class )
@ServiceProvider ( BarSPI.class )
public class FooBarProvider implements FooSPI, BarSPI {}

@ServiceDefinition

The @ServiceDefinition annotation defines a service usage and generates a specialized service loader that enforces that specific usage.

Features:

  • generates boilerplate code, thus reducing bugs and improving code coherence
  • improves documentation by declaring services explicitly and generating javadoc
  • checks coherence of service use in modules if module-info.java is available
  • allows use of custom service loader
  • allows batch loading of providers

Limitations:

The loading behavior is defined by the following properties:

Examples can be found in the examples project.

Quantifier

OPTIONAL: when a service is not guaranteed to be available such as OS-specific API

@ServiceDefinition ( quantifier = Quantifier.OPTIONAL )
public interface WinRegistry { 
    String readString(int hkey, String key, String valueName);
    int HKEY_LOCAL_MACHINE = 0;
}

Optional<WinRegistry> optional = WinRegistryLoader.load();
optional.ifPresent(reg -> System.out.println(reg.readString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName")));

SINGLE: when exactly one service is guaranteed to be available

@ServiceDefinition ( quantifier = Quantifier.SINGLE, fallback = FallbackLogger.class )
public interface LoggerFinder {
    Consumer<String> getLogger(String name);
}

public class FallbackLogger implements LoggerFinder {
    @Override
    public Consumer<String> getLogger(String name) {
        return message -> System.out.println(String.format("[%s] %s", name, message));
    }
}

LoggerFinder single = LoggerFinderLoader.load();
single.getLogger("MyClass").accept("some message");

MULTIPLE: when several instances of a service could be used at the same time

@ServiceDefinition ( quantifier = Quantifier.MULTIPLE )
public interface Translator {
    String translate(String text);
}

List<Translator> multiple = TranslatorLoader.load();
multiple.forEach(translator -> System.out.println(translator.translate("hello")));

Preprocessing

Preprocessing applies map/filter/sort to services instances during the loading.
It can be specified by using one of these two solutions:

  • Basic solution:
    • Map: @ServiceDefinition#wrapper property
    • Filter: @ServiceFilter annotation
    • Sort: @ServiceSorter annotation
  • Advanced solution:
    • Preprocessor: @ServiceDefinition#preprocessor property

Map/Filter/sort example:

@ServiceDefinition ( wrapper = FailSafeSearch.class )
public interface FileSearch {

    List<File> searchByName(String name);

    @ServiceFilter
    boolean isAvailable();

    @ServiceSorter
    int getCost();
}

class FailSafeSearch implements FileSearch {
  
    public FailSafeSearch(FileSearch delegate) { ... }

    @Override
    public List<File> searchByName(String name) {
        try {
            return delegate.searchByName(name);
        } catch (RuntimeException unexpected) {
            // log unexpected ...
            return Collections.emptyList();
        }
    }
}

FileSearchLoader.load().ifPresent(search -> search.searchByName(".xlsx").forEach(System.out::println));

Mutability

BASIC example:

@ServiceDefinition ( mutability = Mutability.BASIC )
public interface Messenger {
    void send(String message);
}

MessengerLoader loader = new MessengerLoader();
loader.get().ifPresent(o -> o.send("First"));

loader.set(Optional.of(msg -> System.out.println(msg)));
loader.get().ifPresent(o -> o.send("Second"));

loader.set(Optional.of(msg -> JOptionPane.showMessageDialog(null, msg)));
loader.get().ifPresent(o -> o.send("Third"));

loader.reload();
loader.get().ifPresent(o -> o.send("Fourth"));

Singleton

Local example:

@ServiceDefinition ( singleton = false )
public interface StatefulAlgorithm {
    void init(SecureRandom random);
    double compute(double... values);
}

StatefulAlgorithm algo1 = StatefulAlgorithmLoader.load().orElseThrow(RuntimeException::new);
algo1.init(SecureRandom.getInstance("NativePRNG"));

StatefulAlgorithm algo2 = StatefulAlgorithmLoader.load().orElseThrow(RuntimeException::new);
algo2.init(SecureRandom.getInstance("PKCS11"));

Stream.of(algo1, algo2)
      .parallel()
      .forEach(algo -> System.out.println(algo.compute(1, 2, 3)));

Global example:

@ServiceDefinition ( singleton = true )
public interface SystemSettings {
    String getDeviceName();
}

SystemSettingsLoader.get().ifPresent(sys -> System.out.println(sys.getDeviceName()));

Custom service loader

It is possible to use a custom service loader such as NetBeans Lookup instead of JDK ServiceLoader.

Example:

@ServiceDefinition ( backend = NetBeansLookup.class, cleaner = NetBeansLookup.class )
public interface IconProvider {
    Icon getIconOrNull(CommonIcons icon);
}

public enum NetBeansLookup implements Function<Class, Iterable>, Consumer<Iterable> {

    INSTANCE;

    @Override
    public Iterable apply(Class type) { return new NetBeansLookupResult(type); }

    @Override
    public void accept(Iterable iterable) { ((NetBeansLookupResult) iterable).reload(); }

    private static final class NetBeansLookupResult implements Iterable {

        private final Lookup.Result result;
        private Collection instances;

        private NetBeansLookupResult(Class type) {
            this.result = Lookup.getDefault().lookupResult(type);
            this.instances = result.allInstances();
        }

        @Override
        public Iterator iterator() { return instances.iterator(); }

        public void reload() { this.instances = result.allInstances(); }
    }
}

Batch loading

Batch loading loads several providers at once. It can be used to create a bridge between two services or to generate providers on the fly. It is enabled by the batch property.

Example:

@ServiceDefinition ( batch = true, quantifier = Quantifier.MULTIPLE )
public interface SwingColorScheme {
    List<Color> getColors();
}

@ServiceDefinition ( quantifier = Quantifier.MULTIPLE )
public interface RgbColorScheme {
    List<Integer> getColors();
}

@ServiceProvider
public class RgbToSwingProvider implements SwingColorSchemeBatch {

    @Override
    public Stream<SwingColorScheme> getProviders() {
        return RgbColorSchemeLoader.load().stream().map(this::convert);
    }

    private SwingColorScheme convert(RgbColorScheme colorScheme) {
        return () -> colorScheme.getColors().stream().map(Color::new).collect(Collectors.toList());
    }
}

@ServiceProvider
public class SwingProvider implements SwingColorScheme {
    @Override
    public List<Color> getColors() { return Collections.singletonList(Color.BLACK); }
}

@ServiceProvider
public class RgbProvider implements RgbColorScheme {
    @Override
    public List<Integer> getColors() { return Arrays.asList(-65536, -16711936, -16776961); }
}

SwingColorSchemeLoader.load().forEach(colorScheme -> System.out.println(colorScheme.getColors()));

SPI pattern

In some cases, it is better to clearly separate API from SPI. Here is an example on how to do it:

public final class FileType {

    private FileType() {}

    public static Optional<String> probeContentType(Path file) throws IOException {
        for (FileTypeSpi probe : internal.FileTypeSpiLoader.get()) {
            String result = probe.getContentTypeOrNull(file);
            if (result != null) {
                return Optional.of(result);
            }
        }
        return Optional.empty();
    }
}

@ServiceDefinition(
    quantifier = Quantifier.MULTIPLE,
    loaderName = "internal.FileTypeSpiLoader",
    singleton = true
)
public interface FileTypeSpi {

    enum Accuracy { HIGH, LOW }

    String getContentTypeOrNull(Path file) throws IOException;

    @ServiceSorter
    Accuracy getAccuracy();
}

String[] files = {"hello.csv", "stuff.txt"};
for (String file : files) {
    System.out.println(file + ": " + FileType.probeContentType(Paths.get(file)).orElse("?"));
}

Setup

<dependencies>
  <dependency>
    <groupId>com.github.nbbrd.java-service-util</groupId>
    <artifactId>java-service-annotation</artifactId>
    <version>LATEST_VERSION</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>com.github.nbbrd.java-service-util</groupId>
            <artifactId>java-service-processor</artifactId>
            <version>LATEST_VERSION</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

Alternate setup if the IDE doesn't detect the processor:

<dependencies>
  <dependency>
    <groupId>com.github.nbbrd.java-service-util</groupId>
    <artifactId>java-service-processor</artifactId>
    <version>LATEST_VERSION</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

Related work

java-service-util's People

Contributors

charphi avatar dependabot[bot] avatar dependabot-preview[bot] avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.