Skip to content

Instantly share code, notes, and snippets.

@IdelsTak
Last active February 4, 2024 12:19
Show Gist options
  • Save IdelsTak/6985af316d83a102b69d1c6d490d15c4 to your computer and use it in GitHub Desktop.
Save IdelsTak/6985af316d83a102b69d1c6d490d15c4 to your computer and use it in GitHub Desktop.
ServiceLoader auto service discovery using serviceloader-maven-plugin in a Maven Project

ServiceLoader auto discovery (that works!) in a maven multi-module project using serviceloader-maven-plugin

Demonstrating how to use the serviceloader-maven-plugin to simplify the ServiceLoader auto service discovery mechanism in a Maven project with loosely coupled modules. (code repo here)

Project Structure

The project is organized into three modules: service, provider, and consumer.

Service Module

TextService Interface

public interface TextService {
  String process(String text);
}

Provider Module

UpperCaseTextService Implementation

public class UpperCaseTextService implements TextService {
  @Override
  public String process(String text) {
    return text == null ? null : text.toUpperCase();
  }
}

Consumer Module

Consumer Class

public class Consumer {
  public static void main(String[] args) {
    // Discover and print available text service implementations
    for (var textService : ServiceLoader.load(TextService.class)) {
      System.out.println(
        "Found service implementation for %s: %s"
          .formatted(TextService.class.getName(), textService.getClass().getName()));
    }
  }
}

Consumer module POM

Configures Maven build for the consumer module as a service consumer, leveraging TextService from service module and dynamically discovering implementations like UpperCaseTextService from provider module.

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.github.idelstak</groupId>
        <artifactId>serviceloaderplugin-example</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <artifactId>consumer</artifactId>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>service</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>provider</artifactId>
            <version>${project.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <properties>
        <exec.mainClass>com.github.idelstak.consumer.Consumer</exec.mainClass>
        <deps.dir>libs</deps.dir>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.6.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                ${project.build.directory}/${deps.dir}
                            </outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>${deps.dir}/</classpathPrefix>
                            <mainClass>${exec.mainClass}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Dependencies:

  • service module: compile-time dependency for TextService.
  • provider module: runtime dependency for dynamic service discovery. Scope set to runtime to avoid compile-time transitive dependencies.

Runtime dependency on provider is crucial for ServiceLoader to dynamically discover and load TextService implementations during execution.

Properties:

  • exec.mainClass: main class for runtime execution.
  • deps.dir: directory for copying dependencies during build.

Build Plugins:

  • maven-compiler-plugin: ensures proper Java source code compilation.
  • maven-dependency-plugin: copies dependencies (including 'provider') to specified directory.
  • maven-jar-plugin: configures JAR packaging with manifest details for runtime execution.

provider module not a direct compile-time dependency for loose coupling.

Maven Configuration

Parent POM

<!-- parent/pom.xml -->
<project>
  <!-- ... other configurations ... -->

  <modules>
    <module>service</module>
    <module>provider</module>
    <module>consumer</module>
  </modules>

  <!-- ... other configurations ... -->

  <build>
    <plugins>
      <!-- Configure the ServiceLoader Maven Plugin -->
      <plugin>
        <groupId>eu.somatik.serviceloader-maven-plugin</groupId>
        <artifactId>serviceloader-maven-plugin</artifactId>
        <version>1.4.0</version>
        <configuration>
          <failOnMissingServiceClass>false</failOnMissingServiceClass>
          <services>
            <param>com.github.idelstak.service.TextService</param>
          </services>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment