Bean-компоненты, определенные с помощью @Bean, ведут себя по-разному в Autowiring по сравнению с bean-компонентами, определенными с помощью xml или @Component.

Я использую Spring 4.1.5. Bean-компоненты, определенные с помощью @Bean, ведут себя странно. В основном у меня возникают проблемы с автоматическим подключением этих компонентов, когда тип зависимости отличается от того, что определено в сигнатуре метода @Bean.

Например, если я определяю @Bean с его типом интерфейса (MessageService), я не могу автоматически связать его с другим зависимым компонентом с его типом реализации (MessageServiceImpl) (я не помещаю прокси в картину). Даже с типом другого интерфейса, который он реализует. Эти сценарии работают должным образом, когда bean-компонент определен в xml или с @Component. Вот код:

Основной интерфейс

package hello.annotations;

public interface MessageService {
    String getMessage();
}

Дополнительный интерфейс

package hello.annotations;

public interface AnotherInterface {
    boolean anotherMethod();
}

Реализация

package hello.annotations;

public class MessageServiceImpl implements MessageService, AnotherInterface {
    public String getMessage() {
        return "my msg";
    }

    public boolean anotherMethod() {
        return true;
    }
}

Зависимый компонент

package hello.annotations;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

public class MessagePrinter {

    @Autowired
    private MessageServiceImpl service1;

    @Autowired
    private AnotherInterface service2;

    @Autowired
    private MessageService service;

    public void printMessage() {
        System.out.println(System.identityHashCode(service));
        System.out.println(System.identityHashCode(service1));
        System.out.println(System.identityHashCode(service2));
        System.out.println(this.service.getMessage());
    }
}

Приложение

package hello.annotations;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Application {

    @Bean
    MessageService mockMessageService() {
        return new MessageServiceImpl();
    }

    @Bean
    MessagePrinter messagePrinter() {
        return new MessagePrinter();
    }

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        MessagePrinter printer = context.getBean(MessagePrinter.class);
        printer.printMessage();
    }
}

Итак, вы можете видеть, что в MessagePrinter я пытаюсь внедрить MessageServiceImpl различными способами: как интерфейс MessageService, как MessageServiceImpl и как AnotherInterface.

Я бы сказал, что этот код не работает, и он выдает эту ошибку:

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private hello.annotations.MessageServiceImpl hello.annotations.MessagePrinter.service1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [hello.annotations.MessageServiceImpl] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:561)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
    ... 12 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [hello.annotations.MessageServiceImpl] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
    ... 14 more{@org.springframework.beans.factory.annotation.Autowired(required=true)}

Но вот где это становится еще более странным: я только что заметил, что это не детерминировано. Иногда это работает. Запустите его 5-6 раз, и вы заметите, что иногда он работает. И еще одно наблюдение: если я изменю порядок полей зависимостей в MessagePrinter, поставив

 @Autowired private MessageService service; 

во-первых, то я думаю, что это всегда работает. По крайней мере, столько раз, сколько я запускал его.

Это ошибка или я что-то упускаю?

EDIT: я думаю, что для имитации точного поведения xml и @Component вы должны объявить возвращаемый тип метода @Bean типом реализации. Дайте мне знать, если я ошибаюсь, но, похоже, нет ничего плохого в том, чтобы всегда объявлять @Beans таким образом. @Bean по-прежнему может быть переопределен другим @Bean с тем же именем метода и другим типом возвращаемого значения.

Спасибо и извините за длинный пост.


person Nazaret K.    schedule 01.03.2015    source источник
comment
Соглашусь, что минусов в этом нет. Но еще лучше начинать с объявления зависимостей при автосвязывании в качестве интерфейсов, когда это возможно.   -  person grid    schedule 02.03.2015


Ответы (1)


Очень интересный вопрос. На самом деле я буду публиковать только то, что я предполагаю, не зная самих деталей весеннего автовайринга.

Во-первых, давайте примем как должное, что Spring создает таблицу определений bean-компонентов перед внедрением зависимостей. Эта таблица включает метаданные, такие как имя и тип компонента. Когда таблица заполнена, происходит DI.

Когда вы определяете свои bean-компоненты в xml, вы устанавливаете имя класса целевого bean-компонента. Это класс реализации. Точно так же при использовании @Component вы также устанавливаете его в объявлении класса реализации. Это тип, зарегистрированный для тех определений bean-компонентов в таблице. Однако при использовании @Bean и возврате типа интерфейса Spring регистрирует bean-компонент с этим типом интерфейса.

Во время процесса внедрения зависимостей, когда встречается @Autowired, Spring пытается обратиться к таблице bean-компонентов, используя информацию о типе. Когда для @Autowire требуется интерфейс, Spring с удовольствием сопоставляет определения интерфейса или реализации из таблицы. Однако, когда @Autowire требует тип реализации, а доступен только тип интерфейса, то зависимость не может быть разрешена, поскольку информация таблицы является более абстрактной.

Теперь в вашем классе MessagePrinter вы фактически вводите один и тот же компонент три раза. Если Spring удастся разрешить это один раз, возможно, обновит метаданные типа определения bean-компонента с типом реализации. Так что во второй раз он знает больше, чем в первый, и проводка проходит успешно. Из ваших наблюдений кажется, что порядок DI действительно недетерминирован, но в большинстве случаев предпочтение отдается порядку объявления. Я бы так не сказал, если бы вы сказали, что это никогда не работало с объявлением реализации.

Если кто-то больше знает о пружинных внутренностях и может дать более фактическое объяснение, мне также очень любопытно узнать.

person grid    schedule 01.03.2015
comment
Спасибо за ответ. Интересные мысли. Теперь я думаю, что для имитации точного поведения xml и \@Component вы должны объявить возвращаемый тип метода \@Bean типом реализации. Я могу ошибаться, но похоже, что нет ничего плохого в том, чтобы всегда объявлять \@Beans таким образом. \@Bean по-прежнему может быть переопределен другим \@Bean с тем же именем метода и другим типом возвращаемого значения. Я редактирую исходный пост, чтобы выразить эту мысль. - person Nazaret K.; 02.03.2015