Home Java Don’t make listeners reflect

Don’t make listeners reflect

by admin

Introduction

Don't make listeners reflect
In the development process, it is very often necessary to create an instance of a class whose name is stored in a configuration XML file, or to call a method whose name is written as a string as the value of an annotation attribute.In such cases, the answer is the same: "Use reflection!

In the new version CUBAPlatform one of the goals for improving the framework was to get rid of the explicit creation of event handlers in the UI screen controller classes.In previous versions the declarations of the handlers in the initialization method of the controller cluttered up the code, so in version 7 we decided to clean it up.

The event listener is just a link to a method to be called at the right moment (see Observer template ). It’s easy enough to implement such a pattern using the class java.lang.reflect.Method At startup, we just need to scan the classes, pull the annotated methods from them, save references to them, and use the references to call the method (or methods) when an event occurs, as is done in the bulk of frameworks. The only thing that stopped us was that UI traditionally generates a lot of events, and with reflection API you have to pay some price in terms of method call time. So we decided to look at how else we could make event handlers without using reflection.

We’ve already published stuff on the hubra about MethodHandles and LambdaMetafactory , and this material is a continuation of sorts. We’ll look at the pros and cons of using the reflection API, and the alternatives – code generation with AOT compilation and LambdaMetafactory, and how this has been applied to the CUBA framework.

Reflection: Old. Good. Reliable

In computer science, reflection or reflection means the process by which a program can monitor and modify its own structure and behavior at runtime. (from) Wikipedia.

For most Java developers, reflection is nothing new. It seems to me that without this mechanism, Java would not be the Java that now holds a large share of the application development market. Just think about it: proxying, binding methods to events via annotations, implementing dependencies, aspects, and even instantiating the JDBC driver in the very first versions of the JDK! Reflection is everywhere, it’s the cornerstone of all modern frameworks.

Are there any problems with Reflection as applied to our task? We have highlighted three :

Speed – method call via Reflection API is slower than direct call. In every new version of JVM developers always speed up reflection calls, JIT compiler tries to optimize code even more, but still there is a noticeable difference compared to direct method call.

Typing – if you use java.lang.reflect.Method in the code, it’s just a reference to some method. And nowhere does it say how many parameters are passed or what type they are. A call with the wrong parameters will generate an error in runtime, not at the compile or load stage of the application.

Transparency – If a method called via reflection fails, we have to wade through several calls to invoke() , before we get to the real cause of the error.

But if we take a look at the code of the Spring or JPA colback event handlers in Hibernate, there’s the good old java.lang.reflect.Method And I don’t think that’s likely to change in the near future. These frameworks are too big and there are too many things tied to them, and there seems to be enough performance of server-side event handlers to think about what to replace calls via reflection with.

What other options are there?

AOT compilation and codogeneration – let’s get applications back to speed!

The first candidate to replace the reflection API is code generation. Now frameworks have started to appear, such as Micronaut or Quarkus which try to solve two problems: reducing application startup speed and reducing memory consumption. These two metrics are vital in this age of containers, microservices and serverless architectures, and new frameworks try to solve this by AOT compilation. Using different techniques (you can read here for example), the application code is modified so that all reflexive calls to methods, constructors, etc. are replaced by direct calls. This way you don’t have to scan classes and create bins at runtime, and JIT optimizes code more efficiently at runtime, which gives a significant performance boost to applications built on such frameworks. Does this approach have disadvantages? Answer : of course there are.

First of all you don’t run the code you wrote. During compilation the source code changes, so if something goes wrong it is sometimes hard to understand where the error is: in your code or in the generation algorithm (usually in yours, of course). And from here comes the problem of debugging – you have to debug not your own code.

The second is that you need a special tool to run an application written in a framework with AOT compilation. You can’t just go and run an application written in Quarkus, for example. You need a special plugin for maven/gradle that will preprocess your code. And now, if you find bugs in the framework, you need to update not only the libraries, but also the plugin.

Truth be told, codogeneration is not new in the Java world either, it didn’t appear with Micronaut Or Quarkus Some frameworks use it in one form or another. Here you can think of lombok, aspectj with its pre-generated code for aspects, or eclipselink, which adds code to entity classes for more efficient deserialization. In CUBA we use code generation to generate events about entity state changes and to include validator messages in the class code to simplify the handling of entities in the UI.

For CUBA developers, implementing static code generation for event handlers would be a bit extreme, because you would have to do a lot of changes in the internal architecture and in the plugin to generate the code. Is there something that’s similar to reflection but faster?

LambdaMetafactory- the same method calls, but faster

Java 7 has a new instruction for the JVM – There is an excellent report about it by Vladimir Ivanov on jug.ru here Originally conceived for use in dynamic languages like Groovy, this instruction was an excellent candidate for calling methods in Java without using reflection. At the same time as the new instruction, a related API appeared in the JDK:

  • Class MethodHandle – appeared in Java 7, but is still not very often used
  • LambdaMetafactory – this class is already from Java 8, it was a further development of the API for dynamic calls, uses MethodHandle Internally.

It seemed that MethodHandle being essentially a typed pointer to a method, (constructor, etc.) would be able to fulfill the role of java.lang.reflect.Method And the calls will be faster, because all the type matching checks that are done in the Reflection API on every call, in this case are done only once, when creating MethodHandle

But alas, pure MethodHandle was even slower than reflection API calls. It is possible to get a performance improvement by making MethodHandle static, but this is not possible in all cases. There’s an excellent discussion about the call speed of MethodHandle on the OpenJDK mailing list

But, when the class appeared LambdaMetafactory class came along, there was a real chance to speed up method calls. LambdaMetafactory allows to create a lambda object and wrap a direct method call in it, which can be obtained via MethodHandle And then, using the generated object, you can call the desired method. Here is an example of a generation that "wraps" a method-getter passed in as a parameter into a BiFunction:

private BiFunction createGetHandlerLambda(Object bean, Method method)throws Throwable {MethodHandles.Lookup caller = MethodHandles.lookup();CallSite site = LambdaMetafactory.metafactory(caller, "apply", MethodType.methodType(BiFunction.class), MethodType.methodType(Object.class, Object.class, Object.class), caller.findVirtual(bean.getClass(), method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()[0])), MethodType.methodType(method.getReturnType(), bean.getClass(), method.getParameterTypes()[0]));MethodHandle factory = site.getTarget();BiFunction listenerMethod = (BiFunction) factory.invoke();return listenerMethod;}

As a result, we get an instance of BiFunction instead of Method. And now, even if we used Method in our code, we can easily replace it with BiFunction. Let’s take the real (slightly simplified, though) code of the method handler’s call, labeled @EventListener From Spring Framework:

public class ApplicationListenerMethodAdapterimplements GenericApplicationListener {private final Method method;public void onApplicationEvent(ApplicationEvent event) {Object bean = getTargetBean();Object result = this.method.invoke(bean, event);handleResult(result);}}

And here is the same code, but which uses a method call via a lambda :

public class ApplicationListenerLambdaAdapterextends ApplicationListenerMethodAdapter {private final BiFunction funHandler;public void onApplicationEvent(ApplicationEvent event) {Object bean = getTargetBean();Object result = funHandler.apply(bean, event);handleResult(result);}}

Minimal changes, functionality is the same, but there are advantages :

The lambda has a type of – it is specified at creation, so there is no way to call "just a method".

Stack Trace shorter – When calling a method through a lambda, only one additional call is added – apply() That’s all. Then the method itself is called.

But the speed has to be measured.

Measure speed

To test the hypothesis, we did a microbenchmark using JMH to compare execution times and throughput when calling the same method in different ways : through reflection API, through LambdaMetafactory, and added direct method calls for comparison. References to Method and to lambda were created and cached before running the test.

Test Parameters :

@BenchmarkMode({Mode.Throughput, Mode.AverageTime})@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)

The test itself can be downloaded from GitHub and run it yourself if you’re interested.

Test results for Oracle JDK 11.0.2 and JMH 1.21 (figures may differ, but the difference is still noticeable and about the same):

Test — Get Value Throughput (ops/us) Execution Time (us/op)
LambdaGetTest 72 0.0118
ReflectionGetTest 65 0.0177
DirectMethodGetTest 260 0.0048
Test — Set Value Throughput (ops/us) Execution Time (us/op
LambdaSetTest 96 0.0092
ReflectionSetTest 58 0.0173
DirectMethodSetTest 415 0.0031

On average, it turns out that method calls through the lambda are about 30% faster than through the reflection API. There’s another great discussion about method call performance here if anyone is interested in the details. In a nutshell, the speed gain comes from the fact that the generated lambdas can be inlined into the program code and there are no type checks, unlike reflection.

Of course, this benchmark is quite simple, it doesn’t include method calls across the class hierarchy or measure the speed of calling final methods. But we’ve done more complicated measurements, and the results were always in favor of using LambdaMetafactory.

Use

In CUBA framework version 7, in UI controllers you can use the annotation @Subscribe in order to "subscribe" a method to specific UI events. Internally, this is implemented on the LambdaMetafactory , references to method listeners are created and cached on the first call.

This innovation cleared up a lot of code, especially for forms with lots of elements, complex interactions, and hence lots of event handlers. A simple example from CUBA QuickStart: imagine that you need to recalculate the order amount when you add or remove items. You need to write code that runs the method calculateAmount() when the collection in the entity changes. What it used to look like :

public class OrderEdit extends AbstractEditor<Order> {@Injectprivate CollectionDatasource<OrderLine, UUID> linesDs;@Overridepublic void init(Map<String, Object> params) {linesDs.addCollectionChangeListener(e -> calculateAmount());}...}

And in CUBA 7 the code looks like this :

public class OrderEdit extends StandardEditor<Order> {@Subscribe(id = "linesDc", target = Target.DATA_CONTAINER)protected void onOrderLinesDcCollectionChange (CollectionChangeEvent<OrderLine> event) {calculateAmount();}...}

Bottom line : the code is cleaner and there is no magic method init() which tends to get bigger and fill up with event handlers as the complexity of the form increases. And we don’t even need to make a field with a component we subscribe to, CUBA will find this component by its ID.

Conclusions

Despite the emergence of a new generation of frameworks with AOT compilation ( Micronaut , Quarkus ), which have undeniable advantages over "traditional" frameworks (mostly compared to Spring ), there’s still a huge amount of code out there that’s written using the reflection API (and thanks to the same Spring for that). And it looks like Spring Framework is still the leader among application development frameworks at the moment and we will be working with reflection based code for a long time to come.

And if you’re thinking about using the Reflection API in your code – whether it’s an app or a framework – think twice. First about code generation and then about MethodHandles/LambdaMetafactory. The second way may be faster, and no more development effort will be expended than if you use the Reflection API.

Some more useful links :
A faster alternative to Java Reflection
Hacking Lambda Expressions in Java
Method Handles in Java
Java Reflection, but much faster
Why is LambdaMetafactory 10% slower than a static MethodHandle but 80% faster than a non-static MethodHandle?
Too Fast, Too Megamorphic: what influences method call performance in Java?

You may also like