@Autowired Bean with @Async got null field

88 Views Asked by At

The Springboot version is 2.6.13.

I have Class Data, it looks like this:

package com.example.asynctest;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@lombok.Data
@Async
public class Data {
    public class MyData{
        int age;
        String name = "";
    }

    private MyData myData = new MyData();
}

And when I try to get it autowired, the myData field is null.

package com.example.asynctest;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Async
class CmmandLine implements CommandLineRunner{
    @Autowired
    private Data data;
    @Override
    public void run(String... args) throws Exception {
        log.info(data.toString());
    }
}

The data field looks like this in IDEA: debug picture

And when I remove the @Async annotation, the autowired data.myData is correct.I traced this by myself, and the bean was modified by postProcessAfterInitialization of AsyncAnnotationBeanPostProcessor.And I have added @EnableAsync on the entrypoint class.

So, why this happened? And how should I do to use @Async and @Autowired together?

1

There are 1 best solutions below

4
Reveson On

The problem with your code is not that the data object is not getting injected. In fact it does. To be absolutely sure you can change your code to have injection by constructor:

@Component
@Slf4j
@Async
@RequiredArgsConstructor
class CmmandLine implements CommandLineRunner{

    private final Data data;

    @Override
    public void run(String... args) throws Exception {
        log.info(data.toString());
    }
}

If the data object was not injected correctly, you would get an exception Parameter 0 of constructor in industries.atende. Tester required a bean of type 'com.example.asynctest.Data' that could not be found.. Moreover, even (theoretically) if you didn't get this exception, the data.toString() would yield a NullPointerException. Notice that neither of these exceptions occurs. So the code works (almost) correctly. You're looking for the error in the wrong place.

The real problem is that your toString() method in Data class returns null. The object itself is not null,but it appears to be null in IDEA debug, because the Intellij Idea also uses method toString() to picture the object for you. And the IDEA gets null from the toString() method.

So why the toString() returns null? Because you made it @Async. Every @Async method should return values wrapped by a class capable of dealing with asynchronous responses. For example you should always return CompletableFuture<String>, not just String if the method is @Async. Why? Because the method is run on another thread and, without this wrapper, you have no way of blocking it to get result (in another words: You have no way to "wait" for the result).

So my advice is to remove the @Async annotation from the top level (type), and use it only on methods that you really need to be async. Because otherwise you have no way of fixing the issue. The toString() is inherited from the Object class, so it cannot return CompletableFuture<String> - it has to return String. So making the method async, you would always end up with null instead of real value.