为什么我的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注释@Autowired
为null
因为它不知道如何自动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(); } }