【问题标题】:How do I inject spring beans into non-managed beans in a Spring boot console application?如何在 Spring Boot 控制台应用程序中将 Spring Bean 注入到非托管 Bean 中?
【发布时间】:2021-10-06 14:19:54
【问题描述】:

我已尝试遵循此处的建议: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 缓慢集成到遗留代码库中的更复杂项目的简单示例。我不能简单地“让它全部成为 Spring”,也不能转移 Spring 的初始化位置,因为这个遗留代码是从另一个用另一种语言编写但在 JVM 中运行的应用程序(尽管是一个更简单的应用程序)调用的。是的,这很可怕,我知道。

【问题讨论】:

    标签: java spring spring-boot aspectj


    【解决方案1】:

    我没有克隆你的项目,只是开始在浏览器中快速查看你的 POM。立即让我大吃一惊的是:

    您的 Maven POM 错误。您只需在 pluginManagement 部分中预配置 AspectJ Maven 插件,而无需在常规 plugins 部分中实际声明插件。即,在您的构建期间不会使用该插件。

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


    更新:我之前说的是真的,但我也注意到您的 POM 和测试代码中存在更多疏忽:

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

    更新 2:您可以直接接受this pull request

    【讨论】:

    • 我有一点时间来修复您的 POM。请注意我更新的答案。这个答案实际上回答了您最初的问题,而没有指出其他解决方法。
    【解决方案2】:

    问题是:

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

    解决方案:

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

    【讨论】:

    • 是的,我可以将整个应用程序变成一个 Spring 应用程序,这将是这个 noddy 示例的解决方案,但不是我试图应用相同逻辑的庞大遗留代码库的解决方案。我创建了这个例子来演示这个问题,但是我正在使用的代码库是巨大的,我不能一次性用自动装配的 bean 替换所有“新闻”。我会在这里直到 2030 年圣诞节。不过谢谢。
    • 你能检查这个链接docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/…。您可能已经考虑过,但值得指出。
    • 我假设您正在引用 BeanFactoryAware 并且我可以使用一种静态类来解决问题,该类包含对上下文的引用并从非 Spring 类中获取 bean - 是的一种方法,但不是一个很好的方法,并且在这种情况下使用 Spring 毫无意义。
    • 是的,但这也是问题所在。它也不完全是春天。但我想,如果它不是可取的,那就不是。
    • 好的,如果我能想到任何办法,我会告诉你的。
    【解决方案3】:

    @SpringBootApplication 注解必须在你的主类中使用。因此将其从SpringApp 移动到DummyApplication

    【讨论】:

    • 我认为这不是问题,但无论如何我不能这样做,因为“DummyApplicaiton”代表调用我的 Scala 应用程序。我在上面添加了更多信息。无论如何,这不是解决方案。我已经设法让 Spring 在之前没有遗留调用并使用相同的初始化工作。
    【解决方案4】:

    解决此问题的一种方法是使用 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 类有一个依赖,但如果你对整个应用程序进行 Spring 化,它就会及时消失,而且还不错。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-01
      • 2018-03-08
      • 2019-01-18
      • 1970-01-01
      • 2016-11-12
      • 1970-01-01
      相关资源
      最近更新 更多