The document summarizes Michael Pellaton's presentation on annotation processors and Spring configuration classes. It discusses using annotation processors to validate Spring @Configuration classes, and unit testing annotation processors by programmatically compiling sample classes and checking the compiler output. The presentation covers Spring @Configuration classes, the JSR-269 annotation processing API, implementing a validating annotation processor, and unit testing it using the Java compiler API.
2. AGENDA
> Spring @Configuration classes
> An Annotation Processor for Validation
> Unit Testing the Annotation Processor
2
3. Code Examples
The code in this presentation is simplified and doesn’t compile.
Full length, compiling version of the code:
http://github.com/pellaton/jazoon2012-talk
3
4. Spring @Configuration Classes
> Java-based Spring application context configuration
> An alternative to XML or annotation based configurations
> Example:
@Configuration
public class ExampleConfiguration {
@Bean
public Example exampleBean() {
return new ExampleImpl();
}
}
4
5. Let’s Read the Javadoc…
Must not Must have a visible
be final no-arg constructor
@Configuration
Must not
public class ExampleConfiguration {
be Private
...
@Bean
public Example exampleBean() {
return new ExampleImpl();
Must not }
be final } ...
Must not should be static if returning
return void a BeanFactoryPostProcessor
5
6. What If There’s Something Wrong?
Exception in thread "main"
org...BeanDefinitionParsingException:
Configuration problem: @Configuration class
'ExampleConfiguration' may not be final.
Remove the final modifier to continue.
6
8. Annotation Processors
> Annotations can be processed at
– Runtime reflection (methods on Class, Method & Co)
– Compile time annotation processor
> JSR-269 “Pluggable Annotation Processing API”
– Introduced with Java 6
– Replaces “Annotation Processing Tool APT” of Java 5
8
9. JSR-269 Annotation Processors
> A Java compiler plugin
> An implementation of Processor
> Interacts through ProcessingEnvironment injected in init()
– Messenger to emit compiler messages
– Filer to create classes and resources
> Works with the Mirror (Model) API
– Element represents a static Java language-level construct
(class, package, method, …)
– TypeMirror represents a Java type
> process() is called in rounds to process an annotation on a type
– Access to current compilation/processing state through
RoundEnvironment
– May claim an annotation: subsequent processors are not called
9
10. The Validating Annotation Processor
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes(value = "org...Configuration")
public class JazoonProcessor extends AbstractProcessor {
private Elements elementUtils;
private Messager messager;
private TypeElement configurationTypeElement;
public synchronized void init(ProcessingEnvironment env) {
elementUtils = env.getElementUtils();
messager = env.getMessager();
configurationTypeElement = elementUtils.getTypeElement(
"org.springframework...Configuration");
}
10
11. The Validating Annotation Processor
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
for (TypeElement ann : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(ann)) {
if (element instanceof TypeElement) {
processElement((TypeElement) element);
}
}
}
}
// don’t consume the annotation type
return false;
} 11
12. The Validating Annotation Processor
private void processElement(TypeElement typeElement) {
for (AnnotationMirror ann : typeElement.getAnnotationMirrors()) {
Element annTypeElement = ann.getAnnotationType().asElement();
if (annTypeElement.equals(configurationTypeElement)) {
if (typeElement.getModifiers().contains(Modifier.FINAL)) {
messager.printMessage(Kind.ERROR,
"@Configuration classes must not be final.",
typeElement);
}
}
}
}
12
14. The Result
$ mvn clean compile
...
[ERROR] Failed to execute goal ...: compile
Compilation failure:
[ERROR] /.../ExampleConfiguration.java:[7,13]
error: @Configuration classes must not be final.
[ERROR] /.../ExampleConfiguration.java:[10,16]
error: @Bean methods must not be private.
14
16. The Java Programming Language Compiler API
> Idea: A unit test that compiles (faulty) classes and checks
the compiler output produced by the annotation processor
> JSR-199 The Java Programming Language Compiler API
(Java 6)
> Programmatic interface to the Java compiler
> Requires a JDK
16
17. The Java Compiler API : Main Components
> The JavaCompiler is the
– interface to the Java compiler
– Factory for CompilationTasks
– Instance is obtained through ToolProvider
> The CompilationTask is a future of a compilation task
> Source and class files are read and written using the
JavaFileManager
> A message emitted during compilation is called a
Diagnostic
> Diagnostics are stored by the DiagnosticCollector
17
18. The Java Compiler API : Main Components
ToolProvider
creates
JavaCompiler
creates
CompilationTask
reads/writes
emits listens
java/class files
holds
JavaFileManager Diagnostic DiagnosticCollector
18
19. Compiling Classes in Unit Tests for Processors
/** Tests the detection of final @Configuration classes. */
@Test
public void finalConfigurationClass() throws IOException {
List<Diagnostic> diagnostics =
AnnotationProcessorTestCompiler.compileClass(
"/com/github/pellaton/jazoon2012/FinalTestConfiguration.java",
new JazoonProcessor());
DiagnosticsAssert.assertContainsSingleMessage(Kind.ERROR, 16,
diagnostics);
}
19
20. Compiling Classes in Unit Tests for Processors
public static List<Diagnostic> compileClass(String clazzName,
Processor processor) {
DiagnosticCollector<> collector = new DiagnosticCollector<>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Locale locale = Locale.getDefault();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(collector, locale, null);
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles(classToCompile);
// the infrastructure is now ready 20
21. Compiling Classes in Unit Tests for Processors
// compile the class and return the result
CompilationTask task = compiler.getTask(null, fileManager,
collector, "-proc:only", null, compilationUnits);
task.setProcessors(processor);
task.call();
return collector.getDiagnostics();
}
21
23. Conclusion
> Annotation processors can be used to extend the tooling and build beyond the
traditional purposes like code generation
> To unit test an annotation processor, employ the Java compiler API
> @Configuration and other annotation-based frameworks:
try hard to find problems as early as possible
23
24. Michael Pellaton www.netcetera.com
Netcetera michael.pellaton@netcetera.ch
Examples & Links: github.com/pellaton/jazoon2012-talk