META-INF/services in JAR with Gradle

2019-02-03 00:14发布

问题:

I wanted to build a plugin module that can be loaded with a ServiceLoader. This requires adding a file to the META-INF/services directory, that is named after the service interface and that contains the qualifying path to the class that implements it. Then you can load these services by calling ServiceLoader.load().

Here is an example:

Say we want to provide a plugin interface called org.example.plugins.PluginService. We then provide an implementation of this service in the class org.example.plugins.impl.ExamplePlugin.

If we want to have some sort of plugin mechanism, we could create a JAR file, that contains the implementation. This JAR file must also contain the file META-INF/services/org.example.plugins.PluginService. This file must contain one line

org.example.plugins.impl.ExamplePlugin

to enable the ServiceLoader to find the implementation. If that JAR file is in the build path, you can load the plugin by calling

Iterator<PluginService> it = ServiceLoader.load(PluginService.class).iterator();

That iterator will give you access too all plugins that are found by the ServiceLoader.

For some reason Gradle doesn't include files into the META-INF directory by default. Is there a way to let the resulting JAR contain such a file?

I already found the method metaInf in class Jar. But I don't know groovy good enough to find the solution on my own.

回答1:

You place META-INF/services/org.example.plugins.PluginService in src/main/java, but it's not a source, it's a resource file, therefore it should be placed in resources folder according to Maven directory layout convention, that is

src/main/resources/META-INF/services/org.example.plugins.PluginService

In this case everything should work out of the box.



回答2:

Meanwhile I found a solution to my problem in a (somewhat) similar Question.

Adding the following to the gradle.build file, resolves my problem

jar {
  from ('./src/main/java') {
    include 'META-INF/services/org.example.plugins.PluginService'
  }
}

Now the JAR file looks as expected

.
|- org
|  `- example
|     `- plugins
|        `- impl
|           `- ExamplePlugin.class
`- META-INF
   |- MANIFEST.MF
   `- services
      `- org.example.plugins.PluginService


回答3:

Hopefully they will implement this in the jar task just like ant does. Somebody already worked on it: http://fgaliegue.blogspot.fr/2013/06/gradle-serviceloader-support.html



回答4:

If you happen to inherit some ant based legacy code that does not follow the maven conventions, the following may help.

Define your source sets to match the legacy structure, and include a line like this:

include 'META-INF/services/**'

In your source sets. This pattern is generic and will pick up all your meta inf services.

Full example below.

sourceSets {
    main {
        java {
            srcDir 'src'
            exclude '**/Test*.java'
        }
        resources {
            srcDir 'src'
            include '**/*.xml'
            include 'META-INF/services/**'
        }
    }
    test {
        java {
            srcDir 'src'
            include '**/Test*.java'

        }
        resources { srcDir 'resources' }
    }
}


回答5:

Hi Can try this: https://plugins.gradle.org/plugin/com.github.harbby.gradle.serviceloader

Usage

serviceLoader {
    serviceInterface 'org.example.plugins.PluginService'
    serviceInterface 'org.example.plugins.PluginService2'
}