注意:这是一个常见问题的规范答案。

我有一个Spring @Service类(MileageFeeCalculator),它有一个@Autowired字段(rateService),但当我尝试使用它时,该字段为空。日志显示正在创建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
    }
}

服务bean,应该在MileageFeeCalculator自动连接,但它不是:

@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)
    ...

带注释的@Autowired字段为空,因为Spring不知道你用new创建的MileageFeeCalculator的副本,也不知道自动装配它。

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

IoC容器不是魔法,除非您以某种方式通知它,否则它无法知道Java对象。当您调用new时,JVM实例化一个新对象的副本,并直接将它交给您——它从不经过配置过程。有三种方法可以配置bean。

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

注入你的豆子

最可取的选择是让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- injection -bean

使用@Configurable

If you really need objects created with new to be autowired, you can use the Spring @Configurable annotation along with AspectJ compile-time weaving to inject your objects. This approach inserts code into your object's constructor that alerts Spring that it's being created so that Spring can configure the new instance. This requires a bit of configuration in your build (such as compiling with ajc) and turning on Spring's runtime configuration handlers (@EnableSpringConfigured with the JavaConfig syntax). This approach is used by the Roo Active Record system to allow new instances of your entities to get the necessary persistence information injected.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

通过在服务对象上使用@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上下文中手动查找服务对象来工作的标记:工作手工查找


如果你不是在编写web应用程序,确保你的类中@Autowiring是一个spring bean。通常,spring容器不会知道我们可能认为是spring bean的类。我们必须告诉Spring容器关于Spring类的信息。

这可以通过在appln- context中配置来实现,或者更好的方法是将类注释为@Component,并且请不要使用new操作符创建注释类。 确保从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();

    }

}

另一个解决方案是调用 SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(这) 到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);

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


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

MileageFeeCalculator calc = new MileageFeeCalculator();

使用注释@Service、@Component、@Configuration bean在 当服务器启动时,Spring的应用程序上下文。但是当我们创建对象时 使用new操作符,对象不会在已经创建的应用程序上下文中注册。例如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());
        }
    }
}

}

当我还不太适应IoC世界的生活时,我也遇到过同样的问题。我的一个bean的@Autowired字段在运行时为空。

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


这似乎是一种罕见的情况,但以下是发生在我身上的情况:

我们使用了@Inject而不是@Autowired,后者是Spring支持的javaee标准。每个地方都工作正常,豆子注射正确,而不是一个地方。豆子注射看起来是一样的

@Inject
Calculator myCalculator

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

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


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

MileageFeeCalculator calc = new MileageFeeCalculator();

所以它不会这样工作。

该解决方案使这个MileageFeeCalculator作为控制器本身的自动连接对象。

像下面这样更改你的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构造函数,并使用@Autowired注释类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等注释添加元描述。

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"/>

我注意到这个包装。路径已经不存在了,所以我干脆把这行删掉了。

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

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

SomeAbac类有一个属性声明为

@Autowired private Settings settings;

由于一些未知的原因,init()中的settings为空,当<context:component-scan/>元素根本不存在时,但是当它存在并且有一些bs作为basePackage时,一切都能正常工作。这条线现在看起来是这样的:

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

这很有效。也许有人可以提供一个解释,但对我来说,现在已经足够了)


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

例如,在Spring Boot中:

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

一段时间过去了……

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

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

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

@SpringBootTest
public class MyTests {
    ....

如果配置错误,测试将被编译,但是@Autowired和@Value字段(例如)将为空。由于Spring Boot的操作方式很神奇,因此可以直接调试此故障的方法很少。


还要注意,如果出于某种原因,您将@Service中的方法作为final,那么您将从中访问的自动连接bean将始终为空。


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


这里没有提到的,在本文的“执行顺序”一段中有描述。

在“学习”到必须用@Component或派生的@Service或@Repository(我猜还有更多)来注释一个类,以自动装配它们内部的其他组件之后,我突然发现这些其他组件在父组件的构造函数中仍然是空的。

使用@PostConstruct解决了这个问题:

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

and:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

简单来说,@Autowired字段为空主要有两个原因

你的班级不是一个春天的豆子。

在其中定义@Autowire注解的类不是spring bean。因此,弹簧不会自动连接成员。

这块地不是豆子。

您在@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;
    }

下面的一种方法可以奏效:

您正在使用@Autowired的类不是Bean(我确信您可能在某处使用过new())。 在SpringConfig类中,你没有提到Spring应该寻找的包@Component(我说的是@ComponentScan(basePackages“here”))

如果以上两个不工作....开始放入System.out.println(),并找出出错的地方。


如果您正在使用私有方法,它将为空,请尝试在控制器中将private更改为public。


另外,不要向静态成员注入,它将为空。