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.
CodePudding user response:
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.
I hope this helps you and clears your doubt. If not, let me know and I will help you solve it.
Good luck and Happy coding!
CodePudding user response:
@SpringBootApplication
annotation must be used in your main class. Thus move it from SpringApp
to DummyApplication
.
CodePudding user response:
Honestly, the easiest and cleanestway 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.
But if anyone has a better idea, please provide one and I'll review. Thanks.