This second RC presents a further opportunity to test Micronaut and provide feedback as we build up to the GA release, which is scheduled for the 23rd of October – just in time for Oracle Code One 2018 in San Francisco where I will be presenting on Micronaut!

In my previous post, I elaborated on some of the design choices we made with Micronaut with regards to avoiding the use of reflection as much as possible and how that benefits both the developer and the performance of the application.

In this post, I will elaborate a bit more on the challenges facing traditional Java frameworks and how Micronaut solves those challenges.

JAVA GIVETH AND TAKETH AWAY

Java and the JVM provide a rich platform on which to build frameworks that enhance developer productivity. Features such as annotations, reflection, the ability to create runtime proxies, and so on are the staple of how most Java frameworks work.

Unfortunately, there are some challenges and limitations framework developers have to deal with that result in compromising either memory consumption or startup time including:

Type Erasure. The Java generics system was added later in Java's lifecycle; since backward compatibility was a requirement, the result is type erasure. The amount of runtime logic in existing Java frameworks and tools to deal with type erasure is mind blowing.

Missing Annotation Metadata. Java has annotations, but a number of patterns have emerged in Java frameworks for using annotation stereotypes (or meta-annotations) that are, by default, not supported by the Java API. Computing annotation metadata is therefore left to each framework to implement.

No Parameter Names. By default, Java and the JVM do not retain parameter name information. It is possible to work around this by adding -parameters flag to the Java compiler, but it is disabled by default.

Why are these issues a challenge for Java frameworks like Spring and Jakarta EE? Let's take a simple example where you define an interface:

interface HelloOperations<T>{

@Get("/hello/{name}")

T hello(@NotBlank T name);

@Get("/hello-many/{names}")

T helloMany(@NotEmpty List<T> names);

}

@Controller("/")

class HelloController implements HelloOperations<String>{

@Override

String hello(String name){

// logic here

}

@Override

String helloMany(List<String> names){

// logic here

}

}

Lines 2 and 3 on the HelloOperations interface define a route using annotations, and in addition, define a @NotBlank constraint on the name parameter. (Note that I have used Micronaut annotations, but the same example could be written in Jakarta EE or Spring).

The HelloController implements the interface and provides the logic. Now, you would think this seemingly simple example would be easy for framework developers to handle, but the tasks involved include:

Computing the annotation metadata and stereotypes for each method and on the class of the controller

Traversing the class and interface hierarchy of the HelloController class to figure out the inherited annotations on the name parameter

Dealing with generics and type erasure requirements that the parameter introduces on the class for both the return type and the argument

Generating a runtime proxy to validate the @NotBlank constraint, which reflectively calls HelloController

All of this is just for a trivial example. As you add more methods, deeper inheritance hierarchies, more interfaces, and so on, the requirements become more and more complex, and all of these requirements have to be handled at runtime.

In order to support all of these features that Java developers love without adversely impacting runtime performance, traditional Java frameworks cache heavily, which leads to increasing memory consumption, since the two problems are not reconcilable – you have to choose between slow runtime performance or poor memory consumption.

The Micronaut Way

So how is this situation handled in Micronaut?

Instead of performing all of this analysis on your classes at runtime, Micronaut computes everything at compilation time using ahead-of-time (AOT) compilation.

Generic Type Information

All generic type information for beans and method arguments is computed ahead of time. For example, to retrieve the type parameter for List in the helloMany method inside an AOP interceptor, you can simply do the following:

Notice that the parameter name data is present and has not be erased. Processing at the source code level allows Micronaut to retain parameter name data.

In addition, Micronaut will also compute type arguments for types and store them in the BeanDefinition, so if you need to compute the type parameters for a type, you don't need to jump through reflective hoops either:

BeanDefinition<HelloController> helloDefinition =

beanContext.getBeanDefinition(HelloController.class);

List<Argument<?>> typeArguments =

helloDefinition.getTypeArguments(HelloOperations.class);

// do something with the type arguments

The above example retrieves the BeanDefinition for the HelloController bean and then retrieves the type arguments used for the HelloOperations interface, all without requiring expensive reflective processing.

Annotation Metadata

The regular Java API makes you jump through hoops to retrieve the annotation metadata on the previous example. You have to traverse through the class and interface hierarchy, reflectively loading each method and potentially dealing with visibility issues to retrieve all of the java.lang.reflect.Method instances that are included the hierarchy.

You then have to process each method to merge together all the potential annotations by looking at the getParameterAnnotations() method that returns a multi-dimension array with each parameter indexed by the order they appear in the method.

All this complexity is not needed with Micronaut, because the annotation metadata has already been computed at compile time:

Easier compatibility with GraalVM – although proxies are possible on GraalVM native image, these have to be configured ahead of time.

This may sound complex, but the simplicity it enables for developers is a huge win. For example, if you wish to implement your AOP advice, such as introduction advice, there are only a few steps required.

As an example, say you want to implement logic from an interface at compilation time. Testing frameworks, for example, often have tools for creating stubs or mocks that return alternative values from interfaces. Let's see how you could implement stubbing in Mironaut. Step 1 is to create an annotation, for example:

On line 1 the advice is defined as introduction advice and given a type of StubIntroduction on line 2. The StubInroduction type should implement MethodInterceptor interface. The following is a trivial implementation:

On line 6, the implementation tries to convert the value given to the @Stub annotation and return it as the result of the method call, otherwise null is returned. Now you can simply use the @Stub annotation on any interface:

@Stub

publicinterface StubExample {

@Stub("10")

int getNumber();

@Stub("Fred")

String getName();

}

The getNumber() method will return 10 and the getName() method will return "Fred". It is that simple. There is no need to rely on a container to add this functionality, or build a ProxyFactoryBean implementation to configure anything at runtime, it just works and it works without using any reflection whatsoever.

SUMMARY

In addition to being great for microservices, Micronaut is a general-purpose application framework that has huge potential to revolutionize the efficiency of modern Java applications.

Through AOT compilation, Micronaut is able to pre-compute your application's requirements and do a lot of the heavy lifting before it's up and running. This is a complete departure from how previous generations of Java application frameworks work, and it allows Micronaut to go places traditional Java frameworks don't normally tread.

* You may unsubscribe from our mailing list at any time using the 'unsubscribe' link at the bottom of every email. You can customize your email subscriptions here. To see how we keep your personal information secure, please visit our privacy policy.