如何将 spring bean 注入 Spring 引导控制台应用程序中的非托管 bean?

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

我已尝试遵循此处的建议:https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects

才发现它正在编译但实际上并没有做它应该做的事情。未在非托管对象中设置自动装配的 bean。


@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";
    }
}

这就是代码的要点。完整代码在 github 此处结束:https://github.com/AlexMakesSoftware/SpringConsoleApp3

我得到的输出是:

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)

这只能意味着 @Autowired MessageSender 没有被注入。

知道我做错了什么吗?

编辑:我应该指出,这是一个更复杂项目的简单示例,用于将 Spring 缓慢集成到遗留代码库中。我不能简单地 'make it all Spring',也不能移动 Spring 初始化的位置,因为这个遗留代码是从另一个用另一种语言编写的应用程序(尽管更简单)调用的,但是 运行虚拟机。是的,这很可怕,我知道。

问题是:

您正在使用 new 关键字初始化 LegacyCode。现在的问题是 LegacyCode 使用 autowire,它仅适用于使用 Spring 创建的 bean。因此 NPE 作为 @autowired 将不适用于 new.

解决方案:

您可以用 @Component 标记您的 LegacyCode,然后在 ApiClient 中标记 autowire。这样 MessageSender bean 将被创建并可用。

@SpringBootApplication 注释必须在您的主 class 中使用。因此将它从 SpringApp 移动到 DummyApplication.

解决此问题的一种方法是使用 ApplicationContextAware,如下所示:

/** 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;
    }
}

然后您可以在遗留代码的构造函数中调用它来检索您需要的内容:

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

...它有效。

好的,您现在对这个 SpringContext class 有依赖性,但如果您 Spring-ify 整个应用程序并且它还不错。

我没有克隆你的项目,只是开始在浏览器中快速查看你的 POM。立即跳到我面前的是:

你的 Maven POM 是错误的。您只需在 pluginManagement 部分中预配置 AspectJ Maven 插件,而无需在常规 plugins 部分中实际声明该插件。也就是说,该插件不会在您的构建过程中使用。

如果修复后还有后续问题,我可以再看一下。


更新:我之前说的是真的,但我也注意到你的POM和测试代码中还有一些疏忽:

  • 您使用了一个不存在的 AspectJ Maven 插件版本。
  • 对于编译时编织,您需要 class 路径上的 AspectJ 运行时 aspectjrt
  • 你需要spring-tx在依赖列表中,否则将找不到spring-aspects引用的class。
  • 您的测试预期结果不同于实际的“ok”。

更新 2: 您可以简单地接受 this pull request