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

注意:这旨在成为一个常见问题的规范答案。

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

控制器类:

@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中自动编辑的Service 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)
...
665078 次浏览

注释@Autowired的字段是null,因为Spring不知道您使用new创建的MileageFeeCalculator的副本,并且不知道自动装配它。

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

IoC容器并不神奇,除非您以某种方式通知它Java对象,否则它无法了解它们。当您调用new时,JVM实例化新对象的副本并将其直接交给您——它永远不会通过配置过程。您可以通过三种方式配置bean。

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

注射你的豆子

最可取的选择是让Spring自动装配你所有的bean;这需要最少的代码并且最可维护。要使自动装配像你想要的那样工作,还可以像这样自动装配MileageFeeCalculator

@Controller
public class MileageFeeController {


@Autowired
private MileageFeeCalculator calc;


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

如果您需要为不同的请求创建服务对象的新实例,您仍然可以使用Spring bean范围使用注入。

通过注入@MileageFeeCalculator服务对象来工作的标记:working-inject-bean

使用@Configurable

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

@Service
@Configurable
public class MileageFeeCalculator {


@Autowired
private MileageRateService rateService;


public float mileageCharge(final int miles) {
return (miles * rateService.ratePerMile());
}
}

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

手动bean查找:不推荐

这种方法仅适用于在特殊情况下与遗留代码的接口。几乎总是最好创建一个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

如果你不是在编写Web应用程序,请确保你的@Autow的类是一个Spring bean。通常,Spring容器不会知道我们可能认为是Spring bean的类。我们必须告诉Spring容器我们的Spring类。

这可以通过在Appln-Contxt或更好的方法中配置将类注释为@陈子强来实现,请不要使用new运算符创建注释类。 确保你从应用程序上下文中获得它,如下所示。

@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();


}


}

另一个解决方案是调用 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());
}
}

我是Spring的新手,但我发现了这个工作解决方案。请告诉我这是否是一个可弃用的方法。

我让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对应)和以静态的方式

你的问题是新的(java风格的对象创建)

MileageFeeCalculator calc = new MileageFeeCalculator();

带有注释@Service@Component@Configuration bean在
中创建 服务器启动时Spring的应用程序上下文。但是当我们创建对象时 使用new运算符,对象未在已创建的应用程序上下文中注册。例如Employee.java类i已使用。

看看这个:

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());
}
}
}


}

我曾经在不太习惯IoC世界的生活时遇到过同样的问题。我的一个bean的@Autowired字段在运行时为空。

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

这似乎是一个罕见的案例,但这是发生在我身上的事情:

我们使用@Inject而不是@Autowired,后者是Spring支持的javaee标准。每个地方都工作得很好,bean注入正确,而不是一个地方。bean注入看起来是一样的

@Inject
Calculator myCalculator

最后我们发现错误是我们(实际上是Eclipse自动完成功能)导入了com.opensymphony.xwork2.Inject而不是javax.inject.Inject

总而言之,请确保您的注释(@Autowired@Inject@Service…)具有正确的包!

实际上,您应该使用JVM托管对象或Spring托管对象来调用方法。 从控制器类中的上述代码中,您正在创建一个新对象来调用具有自动连接对象的服务类。

MileageFeeCalculator calc = new MileageFeeCalculator();

所以它不会那样工作。

该解决方案将此MileageFee计算器作为Controller本身中的自动连接对象。

像下面这样更改Controller类。

@Controller
public class MileageFeeController {


@Autowired
MileageFeeCalculator calc;


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

您还可以在服务类上使用@Service注释并将所需的bean类A作为参数传递给其他bean类B构造函数并使用@Autow的注释类B的构造函数来解决此问题。这里的示例片段:

@Service
public class ClassB {


private ClassA classA;


@Autowired
public ClassB(ClassA classA) {
this.classA = classA;
}


public void useClassAObjectHere(){
classA.callMethodOnObjectA();
}
}

我想你错过了指示Spring用注释扫描类。

您可以在Spring应用程序的配置类上使用@ComponentScan("packageToScan")来指示Spring进行扫描。

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

Spring只注入那些作为bean创建或用注释标记的类的实例。

有注解的类需要在注入前用Spring识别,@ComponentScan指示Spring查找有注解的类。当Spring找到@Autowired时,它会搜索相关的bean,并注入所需的实例。

仅添加注释,不会修复或方便依赖注入,Spring需要知道在哪里寻找。

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

原始答案:

我不知道它是否对任何人都有帮助,但即使在做看似正确的事情时,我也陷入了同样的问题。在我的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中,我只有2颗豆子:

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

而某些Abac类有一个属性声明为

@Autowired private Settings settings;

出于某种未知的原因,在init()中设置为null,当<context:component-scan/>元素根本不存在时,但是当它存在并有一些bs作为base Package时,一切都很好。这行现在看起来像这样:

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

也许有人能提供一个解释,但对我来说现在已经足够了)

如果这发生在测试类中,请确保您没有忘记注释类。

例如,在Spring Boot中:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
....

一些时间流逝…

Spring Boot继续发展。不再需要使用@RunWith如果您使用正确版本的JUnit

要使@SpringBootTest独立工作,您需要使用JUnit5而不是JUnit4中的@Test

//import org.junit.Test; // JUnit4
import org.junit.jupiter.api.Test; // JUnit5


@SpringBootTest
public class MyTests {
....

如果您的配置错误,您的测试将编译,但@Autowired@Value字段(例如)将是null。由于Spring Boot靠魔法运行,您可能没有多少直接调试此故障的途径。

另请注意,无论出于何种原因,如果您在@Service中创建了一个方法作为final,您将从中访问的自动保存的bean将始终是null

这是给NullPointerExceptionMileageFeeCalculator calc = new MileageFeeCalculator();的罪魁祸首我们使用的是Spring-不需要手动创建对象。对象创建将由IoC容器负责。

此处未提及的内容在这个文章“执行顺序”段落中进行了描述。

在“学习”我必须使用@Component或派生的@Service或@Reposaku(我想还有更多)注释一个类,以在其中自动装配其他组件之后,我突然想到这些其他组件在父组件的构造函数中仍然为空。

使用@PostConstruct可以解决这个问题:

@SpringBootApplication
public class Application {
@Autowired MyComponent comp;
}

和:

@Component
public class MyComponent {
@Autowired ComponentDAO dao;


public MyComponent() {
// dao is null here
}


@PostConstruct
public void init() {
// dao is initialized here
}
}

简单地说,@Autowired字段为null的原因主要有两个

  • 你的课不是春天的豆子。

您定义@Autowire anotation的类不是Spring bean。因此Spring不会自动连接成员。

  • 田野不是豆子。

没有具有您在@Autowired字段中指定的类型或层次结构中的类型的bean尚未出现在Spring应用程序上下文或注册表中

这仅在单元测试的情况下有效。

我的Service类有一个service的注释,它是@autowired另一个组件类。当我测试组件类时,它即将为空。因为对于服务类,我使用new创建对象

如果您正在编写单元测试,请确保您没有使用new object()创建对象。改为使用injectMock。

这解决了我的问题。这是一个有用的链接

与问题不完全相关,但如果字段注入为空,则基于构造函数的注入仍然可以正常工作。

    private OrderingClient orderingClient;
private Sales2Client sales2Client;
private Settings2Client settings2Client;


@Autowired
public BrinkWebTool(OrderingClient orderingClient, Sales2Client sales2Client, Settings2Client settings2Client) {
this.orderingClient = orderingClient;
this.sales2Client = sales2Client;
this.settings2Client = settings2Client;
}

下面的一个将工作:

  1. 您使用@Autow的类不是Bean(您可能在某个地方使用了new())。

  2. 在SpringConfig类中,您没有提到Spring应该查找@Component的包(我说的是@ComponentScan(base Packages“这里”))

如果上面两个不起作用……开始System.out.println()并找出它出了什么问题。

如果您使用的是private方法,它将是null,请尝试在控制器中将private更改为public

另外,不要注入static成员,它将是null