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

java spring null nullpointerexception autowired


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

我有一个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)
    ...




Answer 1 chrylis -on strike-


注释为 @Autowired 的字段为 null ,因为Spring不知道您使用 new 创建的 MileageFeeCalculator 的副本,也不知道自动对其进行接线 。

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

IoC容器不是魔术,除非您以某种方式告知Java对象,否则它无法了解Java对象。 当您调用 new 时 ,JVM会实例化新对象的副本并将其直接交给您-它从未经历配置过程。 您可以通过三种方式配置bean。

我已经在GitHub项目上使用Spring Boot启动了所有这些代码; 您可以针对每种方法查看一个正在运行的项目,以查看使其工作所需的一切。 带有 NullPointerException 的标记: 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 scopes使用注入。

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

使用@Configurable

如果确实需要自动创建使用 new 创建的对象,则可以使用Spring @Configurable 批注以及AspectJ编译时编织来注入对象。 这种方法将代码插入到对象的构造函数中,以警告Spring正在创建它,以便Spring可以配置新实例。 这需要在构建中进行一些配置(例如,使用 ajc 进行编译)并打开Spring的运行时配置处理程序(使用JavaConfig语法的 @EnableSpringConfigured )。 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

手动豆子查询:不推荐

这种方法只适合在特殊情况下与遗留代码进行接口。几乎所有的方法都是创建一个单子适配器类,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




Answer 2 Shirish Coolkarni


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

这可以通过在appln-contxt中配置来实现,或者更好的方法是将类注释为@Component ,请不要使用new运算符创建带注释的类。 确保从Appln上下文中获取它,如下所示。

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

    }

}



Answer 3 Ravi Durairaj


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

MileageFeeCalculator calc = new MileageFeeCalculator();

所以,它不会这样工作。

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

改变你的控制器类,如下图所示。

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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



Answer 4 smwikipedia


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

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




Answer 5 Deepak


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

MileageFeeCalculator calc = new MileageFeeCalculator();

使用注解 @Service @Component , @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());
        }
    }
}

}