3

Is it possible to use ServiceLoader from within the init(ProcessingEnvironment) method of an Annotation Processor?

 interface Service {}

 class AnnotationProcessor extends AbstractProcessor {

     public static void main(String[] args) {
         ServiceLoader<Service> loader = ServiceLoader.load(Service.class);
         System.out.println("Found Services:");
         for (Service service : loader) {
             System.out.println(service);
         }
     }

     @Override
     public synchronized void init(ProcessingEnvironment env) {
         super.init(env);

         ServiceLoader<Service> loader = ServiceLoader.load(Service.class);
         System.out.println("Found Services:");
         for (Service service : loader) {
             System.out.println(service);
         }
     }

     ...
 }

Running the main method produces the services I have specified in the META-INF/services file. However, when the init(ProcessingEnvironment) method is called as part of a build of another project, it doesn't list any of the services.

Is there a way to make this work?

bnorm
  • 908
  • 1
  • 7
  • 14
  • Can you describe your setup a bit more? What projects do you have? Where are your services defined? The short answer, yes, it should be possible. But you should figure out why the META-INF/services file isn't on your classpath. What's the classpath when you run the main method? – jbunting Jul 14 '17 at 01:14
  • I'm using Maven to handle all the dependencies between project so the classpath is a little magical right now. I'll dig into that and see if I can come up with a better answer though. – bnorm Jul 14 '17 at 12:27
  • Something else I found interesting, when I loaded the Processor service (as in, annotation processor) from within an annotation processor, I got an empty list as well. And I know the classpath is setup correct there because I'm in an annotation processor! – bnorm Jul 14 '17 at 12:32
  • A description of how your maven projects are setup and how you're running the main method would probably be helpful. – jbunting Jul 14 '17 at 13:29

2 Answers2

9

The problem is ServiceLoader uses Thread.currentThread().getContextClassLoader() when a ClassLoader is not specified which cannot see the META-INF\services files from within an Annotation Processor but can from the main method.

Using ServiceLoader.load(Service.class, AnnotationProcessor.class.getClassLoader()) properly loads the services from within AnnotationProcessor.

(Feel free to add to my answer if you know why ContextClassLoader cannot see META-INF\services)

bnorm
  • 908
  • 1
  • 7
  • 14
0

If you intend to run your annotation processor with Java module system:

It seems to me that for Java 11 the javac compiler is not completely module-aware. I succeeded to properly setup javac for annotation processing by using --module-path, but loading a plug-in via ServiceLoader into my annotation processor in the same way turned out to be impossible. Thus after a very loooong time fiddling around with all possible kind of compiler options, I ended up in a hybrid way loading the processor via module path and loading the services via class path.

All in all, the following steps were required for me (I worked directly on the javac command line):

  1. proper module-info.java setup with correct uses and provided with declarations for both, the annotation processor and the services to be loaded
  2. load ServiceLoader via non-default ClassLoader (refer to bnorm's answer)
  3. provide META-INF/services file for services to be loaded
  4. specify path to services via -classpath (or -processorpath)
  • I use a maven project. And use java 9 and its module system. My annotation processor can not see my service. Even I use the processor's class loader, add a META-INF/services, add provide in the module-info. The apt work except can not find spi. If I load spi out of apt, it work. so just apt can not see the spi. – vipcxj Jan 18 '21 at 03:39