为什么我的Spring @Autowired字段为空?

注意:这是针对常见问题的典型答案。

我有一个Spring @Service类( MileageFeeCalculator )具有@Autowired字段( rateService ),但是当我尝试使用它时,该字段为null 。 日志显示正在创buildMileageFeeCalculator bean和MileageRateService bean,但每当我尝试调用我的服务bean上的mileageCharge方法时,都会收到NullPointerException 。 Spring为什么不自动assembly这个字段?

控制器类:

 @Controller public class MileageFeeController { @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { MileageFeeCalculator calc = new MileageFeeCalculator(); return calc.mileageCharge(miles); } } 

服务等级:

 @Service public class MileageFeeCalculator { @Autowired private MileageRateService rateService; // <--- should be autowired, is null public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); // <--- throws NPE } } 

MileageFeeCalculator中应该自动MileageFeeCalculator服务bean,但不是:

 @Service public class MileageRateService { public float ratePerMile() { return 0.565f; } } 

当我尝试GET /mileage/3 ,我得到这个例外:

 java.lang.NullPointerException: null at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13) at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14) ... 

由于Spring不知道您使用new创build的MileageFeeCalculator的副本,因此Spring注释@Autowirednull因为它不知道如何自动MileageFeeCalculator它。

控制Spring IoC容器有三个主要的逻辑组件:一个可供应用程序使用的组件(bean)的registry(称为ApplicationContext ),一个configuration器系统,通过匹配来注入对象的依赖关系上下文中bean的依赖关系,以及一个依赖parsing器,它可以查看许多不同bean的configuration,并确定如何以必要的顺序实例化和configuration它们。

IoC容器并不神奇,除非您以某种方式告知Java对象,否则无法知道Java对象。 当您调用new ,JVM会实例化一个新对象的副本并直接传递给您 – 它永远不会经历configuration过程。 有三种方法可以configuration你的bean。

我已经发布了所有这些代码,使用Spring Boot启动,在这个GitHub项目 ; 您可以查看每个方法的完整运行项目,以查看使其工作所需的所有内容。 使用NullPointerException标记: nonworking

注入你的豆子

最好的select是让Spring autowire所有的bean; 这需要最less量的代码并且是最可维护的。 为了使自动assembly工作像你想要的那样,还要像这样自动MileageFeeCalculator

 @Controller public class MileageFeeController { @Autowired private MileageFeeCalculator calc; @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { return calc.mileageCharge(miles); } } 

如果您需要为不同的请求创build服务对象的新实例,那么仍然可以通过使用Spring bean作用域来使用注入。

通过注入@MileageFeeCalculator服务对象working-inject-bean标签: working-inject-bean

使用@Configurable

如果您确实需要使用new创build的对象进行自动assembly,则可以使用Spring @Configurable注释以及AspectJ编译时织入来注入对象。 这种方法将代码插入到对象的构造函数中,以提醒Spring正在创build它,以便Spring可以configuration新的实例。 这需要在构build中进行一些configuration(比如使用ajc编译),并打开Spring的运行时configuration处理程序( @EnableSpringConfigured使用JavaConfig语法configuration)。 Roo Active Record系统使用这种方法来允许实体的new实例获取必要的注入持久性信息。

 @Service @Configurable public class MileageFeeCalculator { @Autowired private MileageRateService rateService; public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } } 

通过在服务对象上使用@Configurable工作的标签: working-configurable

手动bean查找:不推荐

这种方法仅适用于在特殊情况下与遗留代码进行交互。 创build一个Spring可以自动调用的单身适配器类和遗留代码可以调用,但是可以直接向Spring应用程序上下文请求一个bean。

为此,您需要一个Spring可以引用ApplicationContext对象的类:

 @Component public class ApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public static ApplicationContext getContext() { return context; } } 

然后你的遗留代码可以调用getContext()并检索它需要的bean:

 @Controller public class MileageFeeController { @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class); return calc.mileageCharge(miles); } } 

通过在Spring上下文中working-manual-lookup服务对象的标记: working-manual-lookup

如果你没有编写一个web应用程序,确保你的@Autowiring类是一个spring bean。 通常情况下,spring容器不会意识到我们可能认为的spring豆类。 我们必须告诉Spring容器关于我们的Spring类。

这可以通过在appln-contxt中进行configuration来实现,或者更好的方法是将class注释为@Component,并且不要使用new运算符创build带注释的类。 确保你从Appln-context获取它如下。

 @Component public class MyDemo { @Autowired private MyService myService; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("test"); ApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml"); System.out.println("ctx>>"+ctx); Customer c1=null; MyDemo myDemo=ctx.getBean(MyDemo.class); System.out.println(myDemo); myDemo.callService(ctx); } public void callService(ApplicationContext ctx) { // TODO Auto-generated method stub System.out.println("---callService---"); System.out.println(myService); myService.callMydao(); } } 

当我不习惯the life in the IoC world时,曾经遇到同样的问题。 我的一个bean的@Autowired字段在运行时为空。

根本原因是,而不是使用由Spring IoC容器(其@Autowired字段indeed正确注入)维护的自动创build的bean,我正在newing自己的beantypes的实例并使用它。 当然这个@Autowired字段是空的,因为Spring没有机会注入它。

其实你应该使用JVMpipe理对象或Springpipe理对象来调用方法。 从你的控制器类的上面的代码,你正在创build新的对象来调用你的服务类,它有自动有线对象。

 MileageFeeCalculator calc = new MileageFeeCalculator(); 

所以它不会那样工作。

解决scheme是将此MileageFeeCalculator作为控制器本身的自动assembly对象。

像下面那样改变你的Controller类。

 @Controller public class MileageFeeController { @Autowired MileageFeeCalculator calc; @RequestMapping("/mileage/{miles}") @ResponseBody public float mileageFee(@PathVariable int miles) { return calc.mileageCharge(miles); } } 

你的问题是新的(Java风格的对象创build)

  MileageFeeCalculator calc = new MileageFeeCalculator(); 

用注解@Service @Component@Service@Component @Configuration bean都是在中创build的
当服务器启动时,Spring的应用上下文。 但是,当我们使用new运算符创build对象时,对象不会在已创build的应用程序上下文中注册。 例如我已经使用Employee.java类。

看一下这个:

 public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String name = "tenant"; System.out.println("Bean factory post processor is initialized"); beanFactory.registerScope("employee", new Employee()); Assert.state(beanFactory instanceof BeanDefinitionRegistry, "BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used."); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition definition = beanFactory.getBeanDefinition(beanName); if (name.equals(definition.getScope())) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, true); registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition()); } } } } 

我是Spring的新手,但是我发现了这个工作解决scheme。 请告诉我,如果这是一个deprecable的方式。

我让Spring在这个bean中注入applicationContext

 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; @Component public class SpringUtils { public static ApplicationContext ctx; /** * Make Spring inject the application context * and save it on a static variable, * so that it can be accessed from any point in the application. */ @Autowired private void setApplicationContext(ApplicationContext applicationContext) { ctx = applicationContext; } } 

如果你愿意的话,你也可以把这个代码放在主应用程序类中。

其他类可以像这样使用它:

 MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class); 

通过这种方式, 任何bean都可以通过应用程序中的任何对象 (也可以用new实例化) 以静态方式获得

我觉得你已经错过了指示spring用注释扫描课程。

你可以在你的spring应用程序的configuration类上使用@ComponentScan("packageToScan")来指示spring进行扫描。

@Service, @Component等注释添加元描述。

Spring只注入那些创build为bean或者带有注解标记的类的实例。

带注解的类需要在注入之前用spring标识, @ComponentScan指示Spring为带有注解标记的类寻找。 当Spring发现@Autowired它search相关的bean,并注入所需的实例。

只添加注释,不会修复或促进dependency injection,Spring需要知道在哪里查找。

另一个解决scheme将推出: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
到MileageFeeCalculator的构造函数是这样的:

 @Service public class MileageFeeCalculator { @Autowired private MileageRateService rateService; // <--- will be autowired when constructor is called public MileageFeeCalculator() { SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this) } public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } } 

这似乎是罕见的情况,但这是发生在我身上的事情:

我们使用@inject而不是@autowired ,这是spring支持的javaee标准。 每个地方,它工作得很好,并正确的注入豆,而不是一个地方。 注射豆似乎是一样的

 @inject Calculator myCalculator 

最后我们发现这个错误是我们(实际上,eclipse ide out complete feature)导入了com.opensymphony.xwork2.Inject而不是javax.inject.Inject

所以总结一下,确保你的注释( @Service @Autowired@Service @Inject@Service ,…)有正确的包!

更新:真正聪明的人很快指出这个答案,这解释了怪异,下面描述

原文答案:

我不知道它是否对任何人有帮助,但是即使在看似正确的事情上,我仍面临同样的问题。 在我的Main方法中,我有这样的代码:

  ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{ "common.xml", "token.xml", "pep-config.xml"}); TokenInitializer ti = context.getBean(TokenInitializer.class); 

并在一个token.xml文件中,我有一个线

  <context:component-scan base-package="package.path"/> 

我注意到package.path已经不存在了,所以我已经放弃了这一行。

之后,NPE开始进入。在pep-config.xml我只有两个bean:

 <bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/> <bean id="settings" class="com.pep.Settings"/> 

SomeAbac类有一个声明为的属性

  @Autowired private Settings settings; 

由于一些未知的原因,init()中的设置是null ,当<context:component-scan/>元素完全不存在时,但是当它存在并且有一些bs作为basePackage时,一切正常。 这行现在看起来像这样:

  <context:component-scan base-package="some.shit"/> 

它的工作。 可能有人可以提供解释,但对我来说现在已经够了)

您也可以在服务类上使用@Service注释来解决此问题,并将所需的bean classA作为parameter passing给其他bean classB构造函数,并使用@Autowired注释classB的构造函数。 这里的示例代码片段:

 @Service public class ClassB { private ClassA classA; @Autowired public ClassB(ClassA classA) { this.classA = classA; } public void useClassAObjectHere(){ classA.callMethodOnObjectA(); } }