为啥我的 Spring @Autowired 字段为空?

Posted

技术标签:

【中文标题】为啥我的 Spring @Autowired 字段为空?【英文标题】:Why is my Spring @Autowired field null?为什么我的 Spring @Autowired 字段为空? 【发布时间】:2022-01-04 04:29:49 【问题描述】:

注意:这是针对常见问题的规范答案。

我有一个 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 中自动装配的服务 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)
    ...

【问题讨论】:

另一种情况是在另一个 bean S 的构造函数中调用 bean F。在这种情况下,将所需的 bean F 作为参数传递给其他 bean S 构造函数,并用 @Autowire 注释 S 的构造函数。记得用@Component注释第一个beanF的类。 我在这里使用 Gradle 编写了一些与此非常相似的示例:github.com/swimorsink/spring-aspectj-examples。希望有人会觉得它有用。 【参考方案1】:

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

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

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

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

注入你的 bean

最好的选择是让 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);
    

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

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

使用@Configurable

如果您确实需要使用new 创建的对象进行自动装配,您可以use the Spring @Configurable annotation along with AspectJ compile-time weaving 注入您的对象。这种方法将代码插入到对象的构造函数中,以提醒 Spring 它正在创建,以便 Spring 可以配置新实例。这需要在您的构建中进行一些配置(例如使用 ajc 编译)并打开 Spring 的运行时配置处理程序(@EnableSpringConfigured 使用 JavaConfig 语法)。 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:不推荐

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

【讨论】:

另一件事是在 @Configuration bean 中为 bean 创建对象,其中创建特定 bean 类实例的方法用 @Bean 注释。 @DonalFellows 我不完全确定你在说什么(“制作”是模棱两可的)。您是在谈论使用 Spring Proxy AOP 时多次调用 @Bean 方法的问题吗? 您好,我遇到了类似的问题,但是当我使用您的第一个建议时,我的应用程序在调用“mileageFee”方法时认为“calc”为空。就好像它从未初始化@Autowired MileageFeeCalculator calc。有什么想法吗? 我认为您应该在答案的顶部添加一个条目,说明检索第一个 bean,即您执行所有操作的根,应该通过 ApplicationContext 完成。一些用户(我已将其关闭为重复用户)不理解这一点。 如果我错了,请纠正我,但在 MilegageFeeCalculator 上同时指定 @Service@Configurable 注释可能不正确,根据 Spring AOP documentation: .. .确保您不要在注册为常规 Spring bean 的 bean 类上使用@Configurable:否则,您将获得双重初始化,一次通过容器,一次通过方面。 所以本质上,你应该只选择其中一个。【参考方案2】:

如果您没有编写 Web 应用程序,请确保完成 @Autowiring 的类是 Spring bean。通常,spring 容器不会意识到我们可能认为是 spring bean 的类。我们必须告诉 Spring 容器我们的 Spring 类。

这可以通过在 appln-contxt 中配置来实现,或者更好的方法是将类注解为 @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();

    


【讨论】:

嗨,我通过了你的解决方案,这是正确的。在这里我想知道“为什么我们不使用 new 运算符创建带注释的类的实例,我可以知道背后的原因。 如果您使用 new 创建对象,您将处理与 IOC 概念相矛盾的 bean 的生命周期。我们需要让容器来做这件事,它以更好的方式来做【参考方案3】:

实际上,您应该使用 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);
    

【讨论】:

这就是答案。因为您正在自己实例化一个新的 MilageFeeCalculator,所以 Spring 不参与实例化,因此 Spring spring 不知道该对象的存在。因此,它不能对它做任何事情,比如注入依赖项。【参考方案4】:

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

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

【讨论】:

这个永远帮助了我!【参考方案5】:

你的问题是新的(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());
        
    



【讨论】:

【参考方案6】:

我是 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 进行实例化)并且以静态方式

【讨论】:

这种模式对于让旧代码可以访问 Spring bean 是必要的,但在新代码中应该避免使用。 就我而言,我需要这个,因为第三方课程很少。 Spring (IOC) 无法控制它们。这些类从未从我的 Spring Boot 应用程序中调用。我遵循了这种方法,它对我有用。【参考方案7】:

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

我们使用 @Inject 代替 Spring 支持的 javaee 标准 @Autowired。每个地方都运行良好,豆子注入正确,而不是一个地方。 bean注入似乎一样

@Inject
Calculator myCalculator

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

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

【讨论】:

【参考方案8】:

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

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

使用@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
    

【讨论】:

【参考方案9】:

以下其中一项将起作用:

    您使用 @Autowired 的类不是 Bean(我确定您可能在某处使用过 new())。

    在 SpringConfig 类中,您没有提到 Spring 应该寻找 @Component 的包(我说的是 @ComponentScan(basePackages"here"))

如果以上两个不起作用....开始放置 System.out.println() 并找出问题所在。

【讨论】:

我在我的代码中使用了 new() 并导致了问题。我需要使用那个 new()。但还需要在那个新类中使用@Autowire。如何做到这一点。 你不需要做新的!您可以简单地声明类变量并在其上方使用@Autowire。您还必须确保在要自动装配的类上方(例如,在 class ABC ... 上方)包含 @ Component。它会工作:)【参考方案10】:

如果在测试类中发生这种情况,请确保您没有忘记对类进行注释。

例如,在Spring Boot中:

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

过了一段时间...

Spring Boot continues to evolve。不再需要使用@RunWith如果您使用正确版本的 JUnit

要让@SpringBootTest 独立工作,您需要使用JUnit5 instead of JUnit4 中的@Test

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

@SpringBootTest
public class MyTests 
    ....

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

【讨论】:

另见:***.com/questions/4130486/… 注意:@Valuestatic 字段一起使用时将为空。 Spring 提供了许多失败的方法(没有编译器的帮助)。当出现问题时,最好的办法是回到第一方——只使用你知道可以协同工作的注释组合。【参考方案11】:

另一种解决方案是拨打电话: 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()); 
    

【讨论】:

这使用了不安全的出版物。【参考方案12】:

我认为您错过了指示 spring 扫描带有注释的类。

你可以在你的spring应用的配置类中使用@ComponentScan("packageToScan")来指示spring进行扫描。

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

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

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

只添加注解,不修复也不方便依赖注入,Spring需要知道去哪里找。

【讨论】:

当我忘记将&lt;context:component-scan base-package="com.mypackage"/&gt; 添加到我的beans.xml 文件时遇到了这个问题【参考方案13】:

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

你的课程不是春豆。

田野不是豆子。

【讨论】:

【参考方案14】:

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

原始答案:

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

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

并且 SomeAbac 类有一个属性声明为

@Autowired private Settings settings;

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

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

它有效。可能有人可以提供解释,但对我来说现在就足够了)

【讨论】:

That answer 是解释。 &lt;context:component-scan/&gt; 隐式启用 &lt;context:annotation-config/&gt; 所需的 @Autowired 工作。【参考方案15】:

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

【讨论】:

【参考方案16】:

您还可以在服务类上使用@Service 注释并将所需的bean classA 作为参数传递给其他bean classB 构造函数并使用@Autowired 注释classB 的构造函数来解决此问题。此处为 sn-p 示例:

@Service
public class ClassB 

    private ClassA classA;

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

    public void useClassAObjectHere()
        classA.callMethodOnObjectA();
    

【讨论】:

这对我有用,但你能详细说明这是如何解决问题的吗? @CruelEngine,看看这是构造函数注入(你显式设置一个对象),而不是仅仅使用字段注入(这主要是通过弹簧配置完成的)。因此,如果您使用“new”运算符创建 ClassB 的对象是其他一些范围,那么 ClassA 的设置将不可见或自动装配。因此,虽然调用 classB.useClassAObjectHere() 会抛出 NPE,因为如果您仅声明字段注入,则 classA 对象未自动装配。阅读 chrylis 试图解释相同。这就是为什么建议使用构造函数注入而不是字段注入的原因。现在有意义吗?【参考方案17】:

还要注意,如果出于某种原因,您在 @Service 中创建了一个方法为 final,那么您将从它访问的自动装配的 bean 将始终是 null

【讨论】:

【参考方案18】:

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

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

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

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

【讨论】:

【参考方案19】:

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

    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;
    

【讨论】:

【参考方案20】:

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

【讨论】:

【参考方案21】:

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

【讨论】:

我认为访问修饰符在这里没有任何作用

以上是关于为啥我的 Spring @Autowired 字段为空?的主要内容,如果未能解决你的问题,请参考以下文章

为啥以及何时在 Spring 中使用 @Inject 而不是 @Autowired? [复制]

Spring @Autowired 字段 - 哪个访问修饰符,私有或包私有?

Spring io @Autowired:空白的最终字段可能尚未初始化

Spring 注解 @Resource和@Autowired

[spring]@Resource和@Autowired区别对比

Spring注解@Resource和@Autowired区别对比