自动装配订单NullPointerException

时间:2014-07-09 16:03:37

标签: spring

有关详细信息,请参阅以下代码。这会在NullPointerException方法中引发name()(在下面标注注释)。我的理解是Spring按顺序读取@Configuration,因此首先自动namePrinter,然后nameProvider。由于nameProvider用于name(),而NamePrinter需要为@Bean public Name name(NameProvider nameProvider) { return nameProvider.getName(); } 构造函数自动装配,因此可以解释NPE。我也知道两个解决方案:

  • 一个是按手工预定订单。我发现这应该是Spring的工作。
  • 另一种是通过参数自动装配,即:

    @Autowired

    我也觉得这应该是Spring的工作,并且希望在有许多参数的情况下避免这种情况 - 在这些情况下,在配置中使用import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; interface Name { String getName(); } @Component class NameProvider { public Name getName() { return new Name() { @Override public String getName() { return "Foo"; } }; } } @Component class NamePrinter { private final Name name; @Autowired public NamePrinter(Name name) { this.name = name; } public void print() { System.out.println(name.getName()); } } @Configuration @ComponentScan(basePackageClasses = { SpringAutowiringPrbConfig.class }) class SpringAutowiringPrbConfig { @Autowired private NamePrinter namePrinter; @Autowired private NameProvider nameProvider; @Bean public Name name() { return nameProvider.getName(); // NullPointerException here } } public class SpringAutowiringPrb { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( SpringAutowiringPrbConfig.class); NamePrinter namePrinter = context.getBean(NamePrinter.class); namePrinter.print(); context.close(); } } 对我来说更具可读性。

有些问题:

  • 关于上面的解决方案2 - 为什么Spring处理方法参数和自动装配的类变量不同?
  • 有没有合理的理由Spring没有命令bean通过依赖顺序自动装配,而是选择源文件定义顺序?
  • 你遇到过这样的问题吗?如果是这样,你是如何在特定情况下解决它们的?希望我忽略了一些有用的东西

版本:

  • Java 1.7
  • Spring 4.0.5.RELEASE

代码:

{{1}}

1 个答案:

答案 0 :(得分:1)

行为:

这是一个循环的例子。在调用@Configuration方法之前,Spring的@Autowired类确实会解析@Bean个目标。 但是,如果@Autowired解析过程的一部分需要调用@Bean方法,那么您必须能够处理它。

在您的示例中,您有

@Autowired
private NamePrinter namePrinter;
@Autowired
private NameProvider nameProvider;

看起来,在这种情况下(这与反射以及如何从Field对象中检索Class对象),Spring会首先尝试解析namePrinter字段。要执行该解决方案,Spring必须实例化并初始化NamePrinter bean。因此它将调用

@Autowired
public NamePrinter(Name name) {
   this.name = name;
}

要做到这一点,它需要一个Name bean注入构造函数。要获取Name bean,需要调用

@Bean
public Name name() {
   return nameProvider.getName(); // NullPointerException here
}

回到这里。 nameProvider尚未处理,null也是如此。

你可以查看堆栈跟踪,看看当NPE发生时,Spring正处于自动装配的过程中。


  

关于上述解决方案2 - 为什么是Spring处理方法   参数和自动装配的类变量有何不同?

它并不是真的。当Spring需要自动装载时

@Bean
public Name name(NameProvider nameProvider) {
   return nameProvider.getName();
}

它检查NameProvider bean定义并初始化bean(如果存在)。然后它可以将它注入构造函数中。以前,它没有任何关于在方法中可以使用哪些对象的提示。 Spring使用的反射无法查看方法的主体。它只能看它的定义。

  

有没有合理的理由Spring没有订购bean   由依赖顺序自动装配,而是选择源文件   定义顺序?

Spring使用反射来获取Field的{​​{1}}个对象。特别是,它使用Class表示

  

返回数组中的元素没有排序,也没有排序   特别的顺序。

所以Class#getDeclaredFields()首先出现就是(非)幸运。

  

你遇到过这样的问题吗?如果是这样,你是如何解决它们的?   你的特殊情况?希望我在这里忽略了一些可能的东西   有用

分析您的代码并尝试充实任何循环依赖项。考虑使用namePrinter,虽然这在这里没有帮助。

还可以选择放弃@DependsOn(及其专业化)并使用@Component方法执行所有操作。

你可以

@Bean

这是有效的,因为Spring创建了一个@Bean public NamePrinter namePrinter() { return new NamePrinter(name()); } @Bean public NameProvider nameProvider() { return new NameProvider(); } @Bean public Name name() { return nameProvider().getName(); } 类的自定义子类(和实例),它可以拦截对@Configuration方法的调用,缓存调用的结果,并提供相同的结果在以后的所有调用中。