Контейнер Spring IoC создает экземпляры bean-компонентов с нулевой конфигурацией (например, поведение Google Guice)

У меня есть хобби-проект, который я хотел бы перенести на Spring.

В качестве примера у меня есть следующие классы:

public class OtherBean {
    public void printMessage() {
        System.out.println("Message from OtherBean");
    }
}


public class InjectInMe {
    @Inject OtherBean otherBean;

    public void callMethodInOtherBean() {
        otherBean.printMessage();
    }
}

Однако, когда я читаю документацию, я должен аннотировать все классы, которыми должен управлять Spring, аннотацией, такой как @Component (или другими подобными).

Запускаем его со следующим кодом:

public class SpringTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.refresh();

        InjectInMe bean = context.getBean(InjectInMe.class);
        bean.callMethodInOtherBean();
    }
}

Выдает мне ошибку:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [somepackage.InjectInMe] is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:371)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:968)
    at somepackage.SpringTest.main(SpringTest.java:10)

Мой вопрос: есть ли способ заставить Spring управлять любым классом, который я прошу ApplicationContext создать экземпляр без, чтобы мне пришлось регистрировать их в аннотированной конфигурации (или конфигурации XML)?

В Guice я могу просто ввести в класс

public class GuiceTest {
    static public class GuiceConfig extends AbstractModule {

        @Override
        protected void configure() {}

    }
    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceConfig());
        InjectInMe bean = injector.getInstance(InjectInMe.class);
        bean.callMethodInOtherBean();
    }
}

Дает мне вывод:

Message from OtherBean

Могу ли я заставить Spring работать как Guice? Как заставить Spring внедрить мои bean-компоненты без регистрации или сканирования пакетов на наличие классов, аннотированных аннотациями, подобными @Component?

У любого гуру Spring есть способ решить эту проблему?


person Alexandre Pretyman    schedule 21.11.2014    source источник


Ответы (3)


Мой вопрос: есть ли способ заставить Spring управлять любым классом, который я прошу ApplicationContext для создания экземпляра, без необходимости регистрировать их в аннотированной конфигурации (или конфигурации XML)?

Нет, это невозможно. Это просто не то, как спроектирован Spring. Вы должны либо неявно (например, сканирование + аннотация), либо явно (например, определение компонента XML) зарегистрировать свои компоненты.

Минимум, что вы можете сделать, это что-то вроде:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(OtherBean.class);
context.register(InjectInMe.class);
context.refresh();
person Khalid    schedule 21.11.2014
comment
Спасибо за ваш ответ, я отлаживал Spring AnnotationConfigApplicationContext и действительно не нашел простого способа добавить это поведение, подобное Guice. Я пока останусь с Guice, так как не собираюсь регистрировать все свои курсы. Я надеюсь, что Spring даст нам выбор в будущем. - person Alexandre Pretyman; 22.11.2014

Есть несколько способов предоставить определения bean-компонентов для Spring ApplicationContext:

  1. Используйте сканирование компонентов и аннотируйте свои типы с помощью @Component или одной из его мета-аннотаций (@Service, @Controller и т. д.).
  2. Определите <bean> в конфигурации XML или @Bean в конфигурации Java (или любой другой тип явной конфигурации).
  3. Используйте BeanDefinitionRegistryPostProcessor для регистрации bean-компонентов непосредственно в BeanDefinitionRegistry.
  4. Некоторые подтипы ApplicationContext реализуют BeanDefinitionRegistry, поэтому вы можете напрямую регистрировать в них определения bean-компонентов.

Компоненты, сгенерированные из этих определений компонентов, будут доступны, когда потребуется внедрение. Вы бы не хотели (по крайней мере, IMO), чтобы ваш контейнер создавал экземпляр типа, не сообщая ему каким-то образом, что это нормально.

person Sotirios Delimanolis    schedule 21.11.2014
comment
Спасибо за ваш ответ, я выделил жирным шрифтом наиболее важную часть моего вопроса. - person Alexandre Pretyman; 21.11.2014
comment
@discipline Верно. Именно на это я и намекал своей последней фразой. Я чувствую, что то, что делает Гайс, опасно. Вы явно не сказали, как получить экземпляр OtherBean. Вы можете получить очень неожиданное поведение с тем, как Guice решает создать экземпляр типа. - person Sotirios Delimanolis; 22.11.2014

Вы должны либо объявить класс как @Component или что-то еще, либо предоставить определение xml. Если вы не определяете bean-компонент, откуда Spring должен знать, что делать? Для свойств класса, например. MyController имеет частный сервис MyService. Если вы поместите тег @Autowired над экземпляром службы, Spring автоматически внедрит этот сервер в ваш контроллер, на что, похоже, намекает ваш комментарий о @Inject. Однако для автоматического связывания вы должны определить bean-компонент для этого класса, что означает, что вы должны использовать либо конфигурацию аннотаций @Component/@Controller и т. д., либо конфигурацию xml-бина.

person holtc    schedule 21.11.2014
comment
Если вы не определяете, как Spring должен знать, что делать? - Просто пытаетесь создать экземпляр, как это делает Google Guice? Спасибо за попытку ответить на него. - person Alexandre Pretyman; 21.11.2014
comment
Добавляя тег Inject, вы добавляете своего рода аннотацию, тем самым сообщая Guice, что делать. То же самое относится и к Spring, но вместо тега Inject вы используете @Autowire - person holtc; 21.11.2014
comment
Я добавил код в качестве примера. Еще раз спасибо за ваше время. Может быть, вопрос более ясен на примере :-) Я хотел бы знать, могу ли я имитировать это поведение Just Works (tm) Guice в Spring. - person Alexandre Pretyman; 21.11.2014
comment
Вы можете добавить @Component прямо над объявлением класса OtherBean и сделать то же самое над своим классом InjectInMe. Затем замените @Inject на @Autowire, и все будет работать, если ваш applicationContext настроен на прием аннотаций. - person holtc; 22.11.2014
comment
В этом суть моего вопроса: как я могу расставить точки без @Component-подобных аннотаций и без регистрации в контексте приложения, чтобы получить поведение Just Works (tm) из Гиса? - person Alexandre Pretyman; 22.11.2014
comment
Опять же, поведение Guice в Just Works вызвано аннотацией @Inject. Если вы не используете эту дополнительную аннотацию, Guice просто не будет работать. С любой структурой вы должны сказать ей, что делать, чтобы она работала. В Guice это достигается с помощью тега @Inject. В Spring это достигается с помощью аннотаций. - person holtc; 25.11.2014