与使用xml或@Component定义的bean相比,使用@Bean定义的Bean在自动装配中的行为有所不同

时间:2015-03-01 21:13:18

标签: spring autowired spring-java-config

我使用的是Spring 4.1.5。用@Bean定义的bean很奇怪。基本上我遇到问题当依赖类型与@Bean方法签名中定义的不同时,自动装配这些bean。

例如,如果我用它的接口类型(MessageService)定义一个@Bean,那么我就不能在另一个具有其实现类型(MessageServiceImpl)的依赖bean中自动装配它(我没有在代码中添加代理)。甚至没有它碰巧实现的另一个接口的类型。当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;
    }
}

依赖bean

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; 

首先,我认为它总能奏效。至少与我运行它的次数一样多。

这是一个错误还是我错过了什么?

编辑:我在想,为了模拟xml和@Component的确切行为,你必须将@Bean方法的返回类型声明为实现类型。如果我错了,请告诉我,但看起来总是以这种方式宣布@Beans没有任何缺点。 @Bean仍然可以被另一个具有相同方法名称和不同返回类型的@Bean覆盖。

感谢并为这篇长篇文章感到抱歉。

1 个答案:

答案 0 :(得分:1)

非常有趣的问题。事实上,我只会发布我所假设的内容,而不是了解弹簧自动装配的细节。

首先让我们理所当然地认为spring在注入依赖项之前会构建一个bean定义表。此表包含元数据,例如bean名称和类型。当表格完成后,DI就会发生。

在xml中定义bean时,可以设置目标bean的类名。这是实现类。类似地,当使用@Component时,您也可以在实现类声明中设置它。这是在表中为这些bean定义注册的类型。但是,当使用@Bean并返回接口类型时,spring会使用此接口类型注册bean。

在DI过程中,当遇到@Autowired时,spring尝试使用类型信息解析bean表。当@Autowire需要一个接口时,spring会愉快地匹配表中的接口或实现定义。但是,当@Autowire需要实现类型并且只有可用的接口类型时,由于表信息更抽象,因此无法解析依赖关系。

现在,在MessagePrinter类中,实际上是三次注入相同的bean。如果spring设法解析它一次,可能会使用实现类型更新bean定义类型元数据。所以它第二次知道的比第一次更多,接线成功。从您的观察中可以看出,DI的顺序确实是非确定性的,但声明顺序在大多数时候都是有利的。如果你说它从未与实现声明一起工作,我就不会这样说。

如果有人更了解春天的内部并且可以提供更实际的解释,我也很想知道。