1 year ago

#98270

test-img

tquadrat

java.util.ServiceLoader does not work inside of an AnnotationProcessor

I am using Java 17.

I have created a library with a bunch of utility stuff, including a "Service", doing something. This Service is published both the old style (using a file META-INF/services/<name_of_the_Service_interface>) and also through the module-info.java file.

It works in JUnit tests as well as in the context of a bunch of programs I wrote so far, no matter if the programs are modularised or not.

But when used within the context of an AnnotationProcessor, it does not work. The AnnotationProcessor is invoked by javac.

This is the code that works for all other contexts so far:

final var moduleLayer = StringConverter.class.getModule().getLayer();
final var converters = isNull( moduleLayer ) ? ServiceLoader.load( StringConverter.class ) : ServiceLoader.load( moduleLayer, StringConverter.class );

ServiceLoop:for( final StringConverter<?> c : converters )
{
  for( final var subjectClass : retrieveSubjectClasses( c ) )
  {
     buffer.put( subjectClass, converter.getClass() ) );
  }
}   //  ServiceLoop:

When called from inside an AnnotationProcessor, the ServiceLoop is never executed because the ServiceLoader did not find any implementation.

Originally, the code lived in the library jar, but even when I copied it into the AnnotationProcessor, it does not work.

My workaround so far is to read the META-INF/services/StringConverter file myself ('StringConverter' is a placeholder here, originally it has a proper class name), and to rebuild the functionality of java.util.ServiceLoader:

final var classLoader = AP.class.getClassLoader();
final var resources = classLoader.getResources( format( "META-INF/services/%s", StringConverter.class.getName() ) );
for( final var file : list( resources ) )
{
  try( final var reader = new BufferedReader( new InputStreamReader( file.openStream() ) ) )
  {
    final var converterClasses = reader.lines()
      .map( String::trim )
      .filter( l -> !l.startsWith( "#" ) )
      .map( l -> loadClass( classLoader, l, StringConverter.class ) )
      .filter( Optional::isPresent )
      .map( Optional::get )
      .toList();
    CreateLoop: for( final var c : converterClasses )
    {
      try
      {
        final var constructor = c.getConstructor();
        final var instance = constructor.newInstance();
        for( final var subjectClass : retrieveSubjectClasses( instance ) )
        {
           buffer.put( subjectClass, c );
        }
      }
      catch( final InvocationTargetException | NoSuchMethodException |InstantiationException | IllegalAccessException e )
      {
        /* Deliberately ignored! */
        continue CreateLoop;
      }
    }   //  CreateLoop:
  }
}

Basically, this works.

But of course, I would like to avoid that workaround!

Any idea why java.util.ServiceLoader refuse to work when invoked in the context of an AnnotationProcessor called by javac?

java

annotation-processing

serviceloader

0 Answers

Your Answer

Accepted video resources