MICRONAUT under the hood — Part II

koolak82
3 min readNov 18, 2019

--

Continuing on the journey to explore Micronaut’s inner working, this is in continuation to Part I in retrospect. As the Annotation Processing took lot major chunk of Part I, and I said that ‘asm’ / code generation will need to be dig into too as that’s one of the key component of MICRONAUT too, I will try and keep my focus there. Please visit the Part I if you need the background a bit.

To begin with however, I will start with the the second processor in the javax.annotation.processing.Processor file.

micronaut-core/inject-java/src/main/resources/META-INF/services/javax.annotation.processing.Processor
io.micronaut.annotation.processing.TypeElementVisitorProcessor
io.micronaut.annotation.processing.PackageConfigurationInjectProcessor
io.micronaut.annotation.processing.BeanDefinitionInjectProcessor

As the simple javadoc at the top of class says, PackageConfigurationInjectProcessor outputs the io.micronaut.inject.BeanConfiguration at the end of it’s process() method.

Code Generation

Few things upfront, turned out that PackageConfigurationInjectProcessor’s process() method is way simpler than io.micronaut.annotation.processing.BeanDefinitionInjectProcessor and the code generation part of it is lot simpler too than BeanDefinitionInjectProcessor. We will go through it step by step, but first let’s see what is @Configuration ?

Micronaut docs define Configuration as

A bean @Configuration is a grouping of multiple bean definitions within a package.The @Configuration annotation is applied at the package level and informs Micronaut that the beans defined with the package form a logical grouping.The @Configuration annotation is typically applied to package-info class. For example:

I tried searching or thinking back of any such thing / counterpart in Spring. But could not think of or recall. It’s quite overload name and as such one can get confused with the Config, Property Source and such things. But it’s not that.

It’s quite close to and looks a lot like or alternative to component scan. Correct me if I am wrong, please her. However, coming back from distraction and let’s stick to the definition of Configuration as logical grouping of multiple Bean Definitions let’s move ahead. My target was to look for the things happening in the process method().

Turn out in case of PackageConfigurationInjectProcessor, it’s pretty simple. As usual there are two steps, (first one is pretty short):

  • Collect all the beans (generically saying) to be processed.
  • Process or ASM write them in the loop.

Like I said, it’s simpler in this scenario. First the packagesIn is used to search for packages annotated with @Configuration and then BeanConfigurationWriter is visited through classWriterOutputVisitor in a typical Visitor pattern.

  • BeanConfigurationWriter is what does the ASM writing part. It was also my side intention to learn it.

javadoc says Writes configuration classes for configuration packages using ASM. And it uses org.objectweb.asm.ClassWriter for generating the bytecode

@Override    public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {        AnnotationMetadataWriter annotationMetadataWriter = getAnnotationMetadataWriter();        
if (annotationMetadataWriter != null) { annotationMetadataWriter.accept(classWriterOutputVisitor);
}
try (OutputStream outputStream = classWriterOutputVisitor.visitClass(configurationClassName)) { ClassWriter classWriter = generateClassBytes(); outputStream.write(classWriter.toByteArray());
} classWriterOutputVisitor.visitServiceDescriptor(BeanConfiguration.class, configurationClassName);
}

generateClassBytes() also uses org.objectweb.asm.Type and org.objectweb.asm.commons.GeneratorAdapter further.

private ClassWriter generateClassBytes() {        
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); try {
Class<AbstractBeanConfiguration> superType = AbstractBeanConfiguration.class;
Type beanConfigurationType = Type.getType(superType);
startPublicClass(classWriter, configurationClassInternalName, beanConfigurationType);
writeAnnotationMetadataStaticInitializer(classWriter);
writeConstructor(classWriter);
writeGetAnnotationMetadataMethod(classWriter);
} catch (NoSuchMethodException e) {
throw new ClassGenerationException("Error generating configuration class. Incompatible JVM or Micronaut version?: " + e.getMessage(), e);
}
return classWriter;
}

The last line of the method is what, visitServiceDescriptor, it piqued my curiosity, turned out it’s a simple HashMap and a kind of service registry pattern.

  • And the classWriterOutputVisitor is actually an instance of io.micronaut.inject.writer.ClassWriterOutputVisitor init inthe superclass of PackageConfigurationInjectProcessor, i.e. AbstractInjectAnnotationProcessor.

Example of @Confiuguration BTW

Coming back to the usage of @configuration. Docs also says that an example usage could be using it along with @Requires and loading all the Bean Configuration depending on that Requires. Naturally I searched for the same in micronaut-core source code itself and didn’t find anything. It looks to me this is the way the different sub-projects of micronaut are developed. so for example it is used in micronaut-hibernate-validator. It simply checks for

@Configuration@Requires(classes = {Validator.class, HibernateValidator.class, ELContext.class})package io.micronaut.configuration.hibernate.validator;

Validator and HibernateValidator classed in the classpath, and then initialise 3 classes in package io.micronaut.configuration.hibernate.validator. It’s always a good practice to do the classpath check based loading of Beans and in case of Micronaut absolute necessity. Great that MICRONAUT provide such feature thus.

| => lsDefaultConstraintValidatorFactory.java DefaultParameterNameProvider.java  ValidatorFactoryProvider.java  package-info.java

--

--

koolak82

(Sphagetti) Javascript, (Bloated) Java .. in the middle, and (frightening, mysterious) DB at the back