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

本文翻译自:Why is my Spring @Autowired field null?

Note: This is intended to be a canonical answer for a common problem. 注意:这旨在作为常见问题的规范答案。

I have a Spring @Service class ( MileageFeeCalculator ) that has an @Autowired field ( rateService ), but the field is null when I try to use it. 我有一个Spring @Service类( MileageFeeCalculator ),它具有一个@Autowired字段( rateService ),但是当我尝试使用它时,该字段为null The logs show that both the MileageFeeCalculator bean and the MileageRateService bean are being created, but I get a NullPointerException whenever I try to call the mileageCharge method on my service bean. 日志显示同时创建了MileageFeeCalculator Bean和MileageRateService Bean,但是每当尝试在服务Bean上调用mileageCharge方法时,都会收到NullPointerException Why isn't Spring autowiring the field? Spring为什么不自动接线该领域?

Controller class: 控制器类:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

Service class: 服务等级:

@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
    }
}

Service bean that should be autowired in MileageFeeCalculator but it isn't: 应该在MileageFeeCalculator自动MileageFeeCalculator服务bean,但不是:

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

When I try to GET /mileage/3 , I get this exception: 当我尝试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)
    ...

#1楼

参考:https://stackoom.com/question/1LU5G/为什么我的Spring-Autowired字段为空


#2楼

The field annotated @Autowired is null because Spring doesn't know about the copy of MileageFeeCalculator that you created with new and didn't know to autowire it. 注释为@Autowired的字段为null因为Spring不知道您使用new创建的MileageFeeCalculator的副本,也不知道自动对其进行MileageFeeCalculator

The Spring Inversion of Control (IoC) container has three main logical components: a registry (called the ApplicationContext ) of components (beans) that are available to be used by the application, a configurer system that injects objects' dependencies into them by matching up the dependencies with beans in the context, and a dependency solver that can look at a configuration of many different beans and determine how to instantiate and configure them in the necessary order. Spring Inversion of Control(IoC)容器具有三个主要的逻辑组件:组件(bean)的注册表(称为ApplicationContext )可供应用程序使用,配置程序系统通过匹配将对象的依赖项注入对象上下文中具有bean的依赖关系,以及一个依赖关系求解程序,它可以查看许多不同bean的配置,并确定如何以必要的顺序实例化和配置它们。

The IoC container isn't magic, and it has no way of knowing about Java objects unless you somehow inform it of them. IoC容器不是魔术,除非您以某种方式告知Java对象,否则它无法了解Java对象。 When you call new , the JVM instantiates a copy of the new object and hands it straight to you--it never goes through the configuration process. 当您调用new ,JVM会实例化新对象的副本并将其直接交给您-它从未经历配置过程。 There are three ways that you can get your beans configured. 您可以通过三种方式配置bean。

I have posted all of this code, using Spring Boot to launch, at this GitHub project ; 我已经在GitHub项目上使用Spring Boot启动了所有这些代码; you can look at a full running project for each approach to see everything you need to make it work. 您可以针对每种方法查看一个正在运行的项目,以查看使其工作所需的一切。 Tag with the NullPointerException : nonworking 带有NullPointerException标记: nonworking

Inject your beans 注入你的豆子

The most preferable option is to let Spring autowire all of your beans; 最可取的选择是让Spring自动连接所有bean。 this requires the least amount of code and is the most maintainable. 这需要最少的代码量,并且最易于维护。 To make the autowiring work like you wanted, also autowire the MileageFeeCalculator like this: 要使自动装配工作如您所愿,还可以如下方式自动连接MileageFeeCalculator

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

If you need to create a new instance of your service object for different requests, you can still use injection by using the Spring bean scopes . 如果您需要为不同的请求创建服务对象的新实例,则仍可以通过Spring bean scopes使用注入。

Tag that works by injecting the @MileageFeeCalculator service object: working-inject-bean 通过注入@MileageFeeCalculator服务对象而起作用的标记: working-inject-bean

Use @Configurable 使用@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. 如果确实需要自动创建使用new创建的对象,则可以使用Spring @Configurable批注以及AspectJ编译时编织来注入对象。 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. 这种方法将代码插入到对象的构造函数中,以警告Spring正在创建它,以便Spring可以配置新实例。 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). 这需要在构建中进行一些配置(例如,使用ajc编译)并打开Spring的运行时配置处理程序(使用JavaConfig语法的@EnableSpringConfigured )。 This approach is used by the Roo Active Record system to allow new instances of your entities to get the necessary persistence information injected. Roo Active Record系统使用这种方法来允许实体的new实例获取注入的必要持久性信息。

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Tag that works by using @Configurable on the service object: working-configurable 通过在服务对象上使用@Configurable起作用的标记: working-configurable

Manual bean lookup: not recommended 手动查找bean:不建议

This approach is suitable only for interfacing with legacy code in special situations. 这种方法仅适用于在特殊情况下与遗留代码接口。 It is nearly always preferable to create a singleton adapter class that Spring can autowire and the legacy code can call, but it is possible to directly ask the Spring application context for a bean. 几乎总是最好创建一个Spring可以自动装配并且遗留代码可以调用的Singleton适配器类,但是可以直接向Spring应用程序上下文请求一个bean。

To do this, you need a class to which Spring can give a reference to the ApplicationContext object: 为此,您需要一个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;
    }
}

Then your legacy code can call getContext() and retrieve the beans it needs: 然后,您的旧代码可以调用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);
    }
}

Tag that works by manually looking up the service object in the Spring context: working-manual-lookup 通过在Spring上下文中working-manual-lookup服务对象而起作用的标记: working-manual-lookup


#3楼

If you are not coding a web application, make sure your class in which @Autowiring is done is a spring bean. 如果您不编写Web应用程序的代码,请确保完成@Autowiring的类是spring bean。 Typically, spring container won't be aware of the class which we might think of as a spring bean. 通常,spring容器不会意识到我们可能将其视为spring bean的类。 We have to tell the Spring container about our spring classes. 我们必须告诉Spring容器我们的Spring类。

This can be achieved by configuring in appln-contxt or the better way is to annotate class as @Component and please do not create the annotated class using new operator. 这可以通过在appln-contxt中配置来实现,或者更好的方法是将类注释为@Component ,请不要使用new运算符创建带注释的类。 Make sure you get it from Appln-context as below. 确保从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();

    }

}

#4楼

Another solution would be putting call: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this) 另一个解决方案是调用: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
To MileageFeeCalculator constructor like 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()); 
    }
}

#5楼

I'm new to Spring, but I discovered this working solution. 我是Spring的新手,但是我发现了这个可行的解决方案。 Please tell me if it's a deprecable way. 请告诉我这是否是可弃的方法。

I make Spring inject applicationContext in this bean: 我在这个bean中使Spring注入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;       
    }
}

You can put this code also in the main application class if you want. 如果需要,您也可以将此代码放入主应用程序类。

Other classes can use it like this: 其他类可以这样使用它:

MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);

In this way any bean can be obtained by any object in the application (also intantiated with new ) and in a static way . 这样,应用程序中的任何对象 (也可以通过new实例化) 都可以以静态方式 获取任何bean


#6楼

Your problem is new (object creation in java style) 您的问题是新的(以Java风格创建对象)

MileageFeeCalculator calc = new MileageFeeCalculator();

With annotation @Service , @Component , @Configuration beans are created in the 使用注解@Service @Component@Service@Component @Configuration Bean在
application context of Spring when server is started. 服务器启动时Spring的应用程序上下文。 But when we create objects using new operator the object is not registered in application context which is already created. 但是,当我们使用new运算符创建对象时,该对象未在已创建的应用程序上下文中注册。 For Example Employee.java class i have used. 例如我使用的Employee.java类。

Check this out: 看一下这个:

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值