This module integrates with the reflections library to implement a fully automatic annotation processor. This is very useful for repeatedly performing certain operations on classes.
// Dependencies
dependencies {
// annotation module
compileOnly(files("libs/annotation-1.0-SNAPSHOT.jar"))
}
At first, the entire process might seem a bit complicated. Don't worry, let's simplify it and first consider a question:
what is the meaning of annotation
?
Imagine you are a book lover with a vast collection of books. You like to put different types of stickers on the cover of each book to quickly distinguish them.
Now, you want to find all the books with "Science Fiction" stickers. You might ask an assistant for help:
- Assistant 1: Responsible for browsing all the books and finding the ones with "Science Fiction" stickers. This
assistant is the
AnnotationProcessingService
. - Assistant 2: Responsible for organizing the found books, such as sorting them by publication year. This assistant is
the
CustomAnnotationProcessor
.
The AnnotationProcessingService
scans a large number of classes (like flipping through books) and checks whether each
class is marked with the annotation it cares about (like a sticker). Once the target annotation is found, it extracts
the class and hands it over to the CustomAnnotationProcessor
you specified for subsequent processing.
For example, we want to get all classes with the SimplixSerializerSerializableAutoRegister
annotation and perform some
processing on them.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SimplixSerializerSerializableAutoRegister {
}
@AnnotationProcessor(SimplixSerializerSerializableAutoRegister.class)
public class SimplixSerializerSerializableAutoRegisterProcessor implements CustomAnnotationProcessor {
@Override
public void process(Class<?> clazz) throws Exception {
// Perform some operations
}
@Override
public void exception(Class<?> clazz, Exception exception) {
// When an exception occurs
}
}
We defined a runtime annotation and a processor that implements the CustomAnnotationProcessor
interface and uses the
AnnotationProcessor
annotation.
This might be a bit confusing, but let's take it slow.
The SimplixSerializerSerializableAutoRegister
annotation is used to mark the classes we need to process. The
AnnotationProcessor
annotation needs to indicate that the annotation processor is dedicated to processing a certain
annotation. The CustomAnnotationProcessor
interface is used to determine the processing method.
It's simple, right? But now that the definition is complete, how does all this work? What if we only want to process a few specific annotations?
@FairyLaunch
public class Launcher extends Plugin {
@Autowired
private AnnotationProcessingServiceInterface annotationProcessingService;
@Override
public void onPluginEnable() {
List<String> basePackages = List.of(
// Packages expected to be processed
"org.example",
/*
* The package where the Processor of the annotation to be processed is located
* For example "annotation.serialize.net.legacy.library.configuration.SimplixSerializerSerializableAutoRegister"
* So the package name is "net.legacy.library.configuration.serialize.annotation"
*/
"net.legacy.library.configuration.serialize.annotation"
);
annotationProcessingService.processAnnotations(
basePackages,
// Whether to use Fairy IoC for dependency injection of the Processor
false,
/*
* All ClassLoaders used for basePackages scanning
* Here, we need to pass in not only the ClassLoader of the current class,
* but also the ClassLoader of the Launcher when we need to use the Processor that comes with legacy-lands-library
*/
this.getClassLoader(),
ConfigurationLauncher.class.getClassLoader() // ClassLoader of the configuration module
// Or CommonsLauncher.class.getClassLoader(), which is the ClassLoader of the commons module
);
}
}
Let's take a look at this. We just need to get the AnnotationProcessingService
through dependency injection and then
call processAnnotations
.
Why do we need to pass in the ClassLoader
of other modules (such as ConfigurationLauncher
)? In a plugin-based
architecture, each module is like an independent "library." Each "library" has its own "collection" (i.e., classes).
ClassLoader
: Equivalent to the "librarian" of the "library," responsible for managing and loading classes.- If you only provide the
ClassLoader
of the current plugin, theAnnotationProcessingService
can only search in the current "library." - To access the "collection" of other "libraries," you need to provide their
ClassLoader
s to theAnnotationProcessingService
.
This way, the AnnotationProcessingService
can cross the boundaries of these "libraries," find all matching classes,
and hand them over to the CustomAnnotationProcessor
for processing.