ac_
ac_

Reputation: 1167

How do I inject spring beans into non-managed beans in a Spring boot console application?

I've tried to follow the advice found here: https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects

Only to find that it's compiling but not actually doing what it should. The autowired bean is not being set in the non-managed object.


@SpringBootApplication
@EnableSpringConfigured
public class SpringApp {

...


public class ApiClient {

    private static String result = "not set";

    //this would be called from another applicaiton written in a different language potentially.
    public String apiInterface(String message) {
        //This is where we're going to have to create Spring, where the languages 'join'.
        SpringApplicationBuilder builder = new SpringApplicationBuilder(SpringApp.class);
        builder.run();
        System.out.println("Running legacy code...");
        LegacyCode oldCode = new LegacyCode();
        result = oldCode.doLegacyStuff("hello world");
        
        return result;
    }

}


...

@Configurable(preConstruction = true)
public class LegacyCode {
    @Autowired
    MessageSender sender; //let's pretend we Spring-fied this bit of code but not the Legacy code that uses it.

    public String doLegacyStuff(String message) {        
        sender.send(message);
        sender.close();       
        
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return "interupted";
        }

        return "ok";
    }
}

That's the gist of the code. The full code is over at github here:https://github.com/AlexMakesSoftware/SpringConsoleApp3

The output I get is:

Exception in thread "main" java.lang.NullPointerException
        at demo.LegacyCode.doLegacyStuff(LegacyCode.java:13)
        at demo.ApiClient.apiInterface(ApiClient.java:17)
        at demo.DummyApplication.main(DummyApplication.java:7)

Which can only mean that the @Autowired MessageSender isn't getting injected.

Any ideas what I'm doing wrong?

EDIT: I should point out that this is a simple example of a more complicated project to slowly integrate Spring into a legacy codebase. I cannot simply 'make it all Spring', nor can I shift the location of Spring's initialisation because this legacy code gets called from another application (albeit a simpler one) written in another language but running in the JVM. Yes, it's horrible, I know.

Upvotes: 0

Views: 3308

Answers (4)

ac_
ac_

Reputation: 1167

One way to solve this problem is to use ApplicationContextAware, like so:

/** In a perfect world, this wouldn't exist. Maybe one day we can spring-ify everything and this won't need to. */
@Component
public class SpringContext implements ApplicationContextAware {
    private static ApplicationContext context;

    public static <T extends Object> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContext.context = applicationContext;
    }
}

You can then call this in the constructor of your legacy code to retrieve what you need to like so:

    public LegacyCode() {
        sender = SpringContext.getBean(MessageSender.class);
    }

...and it works.

Ok, you now have a dependency on this SpringContext class but that will go in time if you Spring-ify the entire app and it's not so bad.

Upvotes: 0

semicolon
semicolon

Reputation: 545

Problem is :

You are initializing your LegacyCode using new keyword. Now problem is that LegacyCode uses autowire which works only for beans created using Spring. Hence NPE as @autowired will not work with new.

Solution :

You can mark your LegacyCode with @Component and then autowire it in ApiClient. This way MessageSender bean will be created and will be available.

Upvotes: 1

kriegaex
kriegaex

Reputation: 67477

I did not clone your project, just started to take a quick look at your POM in the browser. What immediately jumped at me was this:

Your Maven POM is wrong. You only preconfigure the AspectJ Maven Plugin in the pluginManagement section without ever actually declaring the plugin in the regular plugins section. I.e., the plugin is not going to be used during your build.

If after fixing that you are still having follow-up problems, I can take a second look.


Update: What I said before is true, but I also noticed some more oversights in your POM and test code:

  • You use a non-existent AspectJ Maven Plugin version.
  • For compile-time weaving, you need the AspectJ runtime aspectjrt on the classpath.
  • You need spring-tx on the dependency list, otherwise a class referred to by spring-aspects will not be found.
  • Your test expects a result different from the actual "ok".

Update 2: You can simply accept this pull request.

Upvotes: 1

Jo&#227;o Dias
Jo&#227;o Dias

Reputation: 17510

@SpringBootApplication annotation must be used in your main class. Thus move it from SpringApp to DummyApplication.

Upvotes: 0

Related Questions