Expand my Community achievements bar.

Improving the performance of custom injectors in automated unit tests?

Avatar

Level 4

We're currently using unit tests with AEM Mock to test our models in our application. We've recently introduced a custom injector and custom annotations and this has been bogging down the tests significantly. Even in tests that don't do any work, merely injecting the custom injector adds a half second or more of startup time. Is there a way to improve the performance this? My understanding based on this question and the answer is that each custom injector is forced to interrogate every Inject annotation of every model registered in the system at startup. Is that understanding correct? It's possible that there's no way to speed this up, but I wanted to make sure we weren't missing something. If this is the case and each new AEM Context will simply have to apply the injector to every model with an Inject annotation, then I can't see a way to improve this outside of shunting these tests off to an Integration test suite or something like that. 

We were hoping there was an ability to hint the AEM Context in the mock to only read certain models. Or maybe in the custom injector implementation is there a way to scope down which annotations the custom injector reads. Can someone let me know if I'm missing something? 

4 Replies

Avatar

Community Advisor

Hi,

 

Correct. If you are using the @inject annotation, it means you don't have a clear idea of which implementation of an "injector" needs to be selected. Consequently, the system will have to iterate through all the available "injectors" to determine which one to use. That's why it is highly recommended to use the most specific injector possible. Please take a look at this great article[1] explaining the behavior and the performance implications of using @inject.

 

In your situation, the ideal scenario would be to not have the @inject annotation in your Sling Model. This way, when the AEM Context runs, it won't evaluate all the injectors but only the ones specific to your sling model. Can you check if refactoring one Sling model which is using @inject to use the specific annotations helps and improves your performance issue? If so, this would be preferred because having the @inject annotation as explained in the article would potentially impact your application's performance.

 

On the other hand and AFAIK there is no way to mock the Inject annotation to narrow down the scope of available injectors.

 

 

I would love to hear back on your results.

 

[1]. https://cqdump.joerghoh.de/2022/11/28/sling-model-performance/  

Another great article: https://medium.com/ida-mediafoundry/sling-models-the-story-behind-inject-c51615164b11 



Esteban Bustamante

Avatar

Level 4

The behavior we're seeing that's making us most concerned about the performance is that when we breakpoint the injector's createAnnotationProcessor method, we can see references to elements like ImageImpl.pageManager  and com.adobe.cq.wcm.core.components.internal.models.v2.ListImpl.linkManager, for example. Meaning that it appears like even models that aren't part of our custom code base are being applied against our custom injector. 

So when you say:


In your situation, the ideal scenario would be to not have the @inject annotation in your Sling Model. This way, when the AEM Context runs, it won't evaluate all the injectors but only the ones specific to your sling model. Can you check if refactoring one Sling model which is using @inject to use the specific annotations helps and improves your performance issue? If so, this would be preferred because having the @inject annotation as explained in the article would potentially impact your application's performance.

Won't we still deal with the slow start time, because the Context is trying to apply all of the other models that use Inject against our custom injector? Or am I still missing something here? What we're seeing is that the simple act of adding our custom injector to the context is causing it to be tested against every model in the system, whether custom code, or OOTB or AEM Core Components. 

 

So for now at least we're not concerned about the performance of the models per se. We're more concerned that in the context of trying to do automated testing against our models, that the test suite is slow to spin up because of the fact that our custom injector is being applied against all of these models, which we can't figure out how to avoid or if we even can. 

Avatar

Community Advisor

Interestingly, I was not able to reproduce what you are seeing, this is what I did:

  1. I created a custom injector, using this link as a reference: https://aemhints.com/2023/06/01/create-custom-sling-injector-aem-6-5/. I explicitly added a Thread.sleep(5000) to ensure that the performance degrades when this is invoked.

  2. I created a DummyModel that utilizes my custom annotation.

  3. I ran the test both before registering the new annotation and after registering the annotation, but I didn't observe any differences.

  4. I added a breakpoint in the createAnnotationProcessor method as you suggested, but I didn't observe the behavior you described. Could it be possible that I did something wrong?

 

EstebanBustamante_0-1697052361899.png

 

Could you please share some code related to your annotation? Please pay attention to the ranking value, and ensure that it returns null if the injector doesn't match your annotation. It should look something like this:

@component(service = {Injector.class, StaticInjectAnnotationProcessorFactory.class}, property = {
        Constants.SERVICE_RANKING + ":Integer=" + 2500
})
public class MyCustomLinkInjector implements Injector, StaticInjectAnnotationProcessorFactory {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public String getName() {
        return "my-custom-link-value";
    }

    @Override
    public Object getValue(final Object adaptable,
            final String name,
            final Type type,
            final AnnotatedElement element,
            final DisposalCallbackRegistry callbackRegistry) {

        // Injectors are cycled through, so it is important to get yours otherwise return NULL
        final MyCustomLinkAnnotation annotation = element.getAnnotation(MyCustomLinkAnnotation.class);
        if (annotation == null) {
            return null;
        }
        //Now should comes your business logic 
        ....
}//End of getValue()

    @Override
    public InjectAnnotationProcessor2 createAnnotationProcessor(final AnnotatedElement element) {
        // Check if the element has the expected annotation
        final MyCustomLinkAnnotation annotation = element.getAnnotation(MyCustomLinkAnnotation.class);
        if (annotation != null) {
            return new MyCustomLinkValueProcessor(annotation);
        }
        return null;
    }



Hope this helps

 

 



Esteban Bustamante

Avatar

Level 4

So for posterity we had something akin to your Thread Sleep going on in our injector, it turns out. We had an I/O operation going on that we didn't notice and once we removed that the tests blazed. 

With that, I'm confused about what you're seeing. Everything I've read on these forums, in documentation and even from the folks at AEM Mocks says that the AEM Context will try to test every model with an Inject annotation against all injectors until it finds an injector that can handle the annotation. So that knowledge makes me think what we're seeing is the correct behavior. Especially if we don't specify OOTB or 3rd party injectors in our test, but those models are part of our classpath. We did change the priority level and that doesn't make a difference, which I assume is because we don't have other injectors in place that could handle those models.