首页 文章

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

提问于
浏览
473

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

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

12 回答

  • 18

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

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

  • 2

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

    例如,在_565156中:

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

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

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

    IoC容器并不神奇,除非你以某种方式告知它们,否则它无法知道Java对象 . 当您调用 new 时,JVM会实例化新对象的副本并直接交给您 - 它永远不会经历配置过程 . 您可以通过三种方式配置Bean .

    我发布了所有这些代码,使用Spring Boot启动,在this GitHub project;您可以查看每个方法的完整运行项目,以查看使其工作所需的一切 . Tag with the NullPointerException: nonworking

    注入你的 beans 子

    最可取的选择是让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进行注入 .

    Tag that works by injecting the @MileageFeeCalculator service object: working-inject-bean

    使用@Configurable

    如果您确实需要使用 new 创建的对象进行自动装配,则可以use the Spring @Configurable annotation along with AspectJ compile-time weaving注入您的对象 . 此方法将代码插入到正在创建的对象_565096中,以便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());
        }
    }
    

    Tag that works by using @Configurable on the service object: 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);
        }
    }
    

    Tag that works by manually looking up the service object in the Spring context: working-manual-lookup

  • 526

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

    这可以通过在appln-contxt中配置来实现,或者 the better way 是将类注释为 @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();
    
        }
    
    }
    
  • 2

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

    MileageFeeCalculator calc = new MileageFeeCalculator();
    

    所以它不会那样工作 .

    该解决方案使此MileageFeeCalculator成为Controller本身的自动连线对象 .

    像下面一样更改您的Controller类 .

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

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

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

    我们使用 @Inject 而不是 @Autowired 这是Spring支持的javaee标准 . 每个地方都运转良好, beans 子正确注入,而不是一个地方 . beans 注射似乎是一样的

    @Inject
    Calculator myCalculator
    

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

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

  • 4

    我'm new to Spring, but I discovered this working solution. Please tell me if it'是一种不可改变的方式 .

    我在这个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;       
        }
    }
    

    如果需要,您也可以将此代码放在主应用程序类中 .

    其他类可以像这样使用它:

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

    这样 any bean can be obtained by any object in the application (也用 new 实例化)和 in a static way .

  • 26

    我想你错过了指示spring用注释扫描类 .

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

    @Service, @Component etc annotations添加元描述 .

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

    标有注释的类需要在注入之前通过spring标识, @ComponentScan 指示spring查找标有注释的类 . 当Spring找到 @Autowired 时,它会搜索相关的bean,并注入所需的实例 .

    仅添加注释,不能修复或促进依赖注入,Spring需要知道在哪里寻找 .

  • 8

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

    您还可以使用服务类上的@Service注释修复此问题,并将所需的bean classA作为参数传递给其他beans classB构造函数,并使用@Autowired注释classB的构造函数 . 此处的示例代码段:

    @Service
    public class ClassB {
    
        private ClassA classA;
    
        @Autowired
        public ClassB(ClassA classA) {
            this.classA = classA;
        }
    
        public void useClassAObjectHere(){
            classA.callMethodOnObjectA();
        }
    }
    
  • 2

    UPDATE: 真聪明的人很快就指出this答案,这解释了古怪,如下所述

    ORIGINAL ANSWER:

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

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

    和SomeAbac类有一个声明为的属性

    @Autowired private Settings settings;
    

    由于某些未知原因,init()中的设置为null,当 <context:component-scan/> 元素根本不存在时,但当它存在并且有一些bs作为basePackage时,一切都运行良好 . 这条线现在看起来像这样:

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

    它的工作原理 . 可能有人可以提供解释,但对我而言,现在就足够了)

相关问题