Как внедрить компоненты spring в неуправляемые компоненты в консольном приложении Spring boot?

#java #spring #spring-boot #aspectj

Вопрос:

Я попытался следовать советам, которые можно найти здесь: https://www.baeldung.com/spring-inject-bean-into-unmanaged-objects

Только для того, чтобы обнаружить, что он компилируется, но на самом деле не делает того, что должен. Компонент autowired не устанавливается в неуправляемом объекте.

 
@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 resu<
    }

}


...

@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 не вводится.

Есть идеи, что я делаю не так?

ПРАВКА: Я должен отметить, что это простой пример более сложного проекта по медленной интеграции Spring в устаревшую кодовую базу. Я не могу просто «сделать все это весной», и я не могу изменить местоположение инициализации Spring, потому что этот устаревший код вызывается из другого приложения (хотя и более простого), написанного на другом языке, но работающего в JVM. Да, это ужасно, я знаю.

Ответ №1:

Я не клонировал ваш проект, просто начал быстро просматривать ваш POM в браузере. Что сразу же бросилось мне в глаза, так это:

Ваш ПОМ Maven ошибается. Вы только предварительно настраиваете плагин AspectJ Maven в pluginManagement разделе, никогда фактически не объявляя плагин в обычном plugins разделе. Т. е. плагин не будет использоваться во время вашей сборки.

Если после исправления у вас все еще будут проблемы с последующими действиями, я могу взглянуть еще раз.


Обновление: То, что я сказал раньше, верно, но я также заметил еще несколько упущений в вашем POM и тестовом коде:

  • Вы используете несуществующую версию плагина AspectJ Maven.
  • Для создания во время компиляции вам нужна среда выполнения AspectJ aspectjrt в пути к классам.
  • Вам нужно spring-tx в списке зависимостей, иначе класс, на который ссылается, spring-aspects не будет найден.
  • Ваш тест ожидает результата, отличного от фактического «ок».

Обновление 2: Вы можете просто принять этот запрос на извлечение.

Комментарии:

1. У меня было немного времени, чтобы починить твой помпон. Пожалуйста, обратите внимание на мой обновленный ответ. Этот ответ фактически отвечает на ваш первоначальный вопрос, не указывая на обходные пути, как и другие.

Ответ №2:

Проблема в том, что :

Вы инициализируете LegacyCode new ключевое слово using. Теперь проблема в том, что LegacyCode использует autowire то, что работает только для бобов, созданных с помощью Spring. Следовательно, с NPE as @autowired работать не new будет .

Решение :

Вы можете пометить свое LegacyCode «с @Component «, а затем autowire вставить его ApiClient . Таким образом MessageSender , боб будет создан и будет доступен.

Комментарии:

1. Да, я мог бы превратить все приложение в приложение Spring, это было бы решением для этого примера noddy, но не решением для огромной устаревшей кодовой базы, к которой я пытаюсь применить ту же логику. Я создал этот пример, чтобы продемонстрировать проблему, но кодовая база, с которой я работаю, ОГРОМНА, и я просто не могу заменить все «новости» на автоматические компоненты за один раз. Я бы пробыл здесь до Рождества 2030 года. Хотя спасибо.

2. Можете ли вы проверить эту ссылку docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/… . Возможно, вы уже подумали об этом, но стоит отметить.

3. Я предполагаю, что вы ссылаетесь на программное обеспечение BeanFactoryAware и что я мог бы решить проблему, используя своего рода статический класс, который содержит ссылку на контекст и извлекает компонент из класса, не являющегося Spring-да, это один из способов сделать это, но не очень приятный и делает использование Spring в этом случае довольно бессмысленным.

4. Да, но в этом тоже проблема. Это тоже не совсем весна. Но я думаю, что если это не предпочтительно, то это не так.

5. Хорошо, я дам вам знать, если смогу придумать что-нибудь по этому поводу.

Ответ №3:

@SpringBootApplication аннотации должны использоваться в вашем основном классе. Таким образом, переместите его из SpringApp в DummyApplication .

Комментарии:

1. Я не думаю, что в этом проблема, но в любом случае я не могу этого сделать, потому что «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, но это пройдет со временем, если вы запустите все приложение, и это не так уж плохо.