为什么我的spring@autowired字段为空?

gywdnpxw  于 2021-07-12  发布在  Java
关注(0)|答案(16)|浏览(340)

注:这是一个常见问题的标准答案。
我有一个Spring @Service 班级( MileageFeeCalculator )有一个 @Autowired 字段( rateService ),但这个领域 null 当我尝试使用它的时候。日志显示 MileageFeeCalculator 豆子和咖啡 MileageRateService 正在创建bean,但我得到一个 NullPointerException 每当我打电话给 mileageCharge 方法。为什么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)
    ...
bkkx9g8r

bkkx9g8r1#

已注解的字段 @Autowirednull 因为spring不知道 MileageFeeCalculator 你用它创造的 new 不知道自动连线。
springinversionofcontrol(ioc)容器有三个主要的逻辑组件:注册表(称为 ApplicationContext )在可供应用程序使用的组件(bean)中,配置程序系统通过将依赖项与上下文中的bean相匹配,将对象的依赖项注入其中,以及一个依赖性求解器,它可以查看许多不同bean的配置,并确定如何按必要的顺序示例化和配置它们。
ioc容器并不神奇,除非您以某种方式通知它,否则它无法了解java对象。当你打电话的时候 new ,jvm示例化新对象的一个副本并直接将其交给您--它从不经历配置过程。有三种方法可以配置bean。
我已经在这个github项目中发布了所有这些代码,使用springboot启动;您可以查看每种方法的完整运行项目,以了解使其工作所需的一切。标记为 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);
    }
}

如果您需要为不同的请求创建服务对象的新示例,您仍然可以通过使用springbean作用域来使用注入。
通过注入 @MileageFeeCalculator 服务对象: working-inject-bean ###使用@configurable
如果你真的需要用 new 要自动连线,可以使用Spring @Configurable 注解以及aspectj编译时编织来注入对象。这种方法将代码插入到对象的构造函数中,提醒spring正在创建它,以便spring可以配置新示例。这需要在构建中进行一些配置(例如使用 ajc )打开spring的运行时配置处理程序( @EnableSpringConfigured 使用javaconfig语法)。roo活动记录系统使用这种方法 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() 检索它需要的豆子:

@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

zkure5ic

zkure5ic2#

如果您没有编写web应用程序,请确保完成@autowiring的类是springbean。通常,spring容器不会知道我们可能认为是springbean的类。我们必须告诉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();

    }

}
svujldwt

svujldwt3#

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

pu3pd22g4#

我曾经遇到过同样的问题,当时我还不太习惯国际奥委会的生活。这个 @Autowired 我的一个bean的字段在运行时为空。
根本原因是,没有使用springioc容器(其 @Autowired 场确实被正确地注入了),我是 new 创建我自己的bean类型示例并使用它。当然这个是 @Autowired 字段为空,因为spring没有机会注入它。

ctrmrzij

ctrmrzij5#

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

}
b5lpy0ml

b5lpy0ml6#

我对spring还不熟悉,但我发现了这个有效的解决方案。请告诉我这是不是一条不可取的路。
我做Spring注射 applicationContext 在这个bean中:

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 )以一种静态的方式。

ecfdbz9o

ecfdbz9o7#

这似乎很罕见,但我的遭遇如下:
我们曾经 @Inject 而不是 @Autowired 这是spring支持的javaee标准。每个地方都很好,豆子注射正确,而不是一个地方。豆子注射剂似乎是一样的

@Inject
Calculator myCalculator

最后我们发现错误是我们(实际上,eclipse自动完成特性)导入的 com.opensymphony.xwork2.Inject 而不是 javax.inject.Inject !
总之,请确保您的注解( @Autowired , @Inject , @Service ,... ) 有正确的 Package !

to94eoyn

to94eoyn8#

如果在测试类中发生这种情况,请确保没有忘记对类进行注解。
例如,在spring boot中:

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

一段时间过去了。。。

Spring Boot继续发展。不再需要使用 @RunWith 如果您使用正确的junit版本。
为了 @SpringBootTest 要独立工作,需要使用 @Test 从junit5而不是junit4。

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

@SpringBootTest
public class MyTests {
    ....

如果配置错误,测试将编译,但是 @Autowired 以及 @Value 字段(例如)将 null . 由于springboot是通过魔术操作的,所以您可能没有什么方法可以直接调试这个失败。

bihw5rsg

bihw5rsg9#

我认为您没有指示spring扫描带有注解的类。
你可以用 @ComponentScan("packageToScan") 在spring应用程序的配置类上,指示spring进行扫描。 @Service, @Component etc注解添加元描述。
spring只注入那些创建为bean或用注解标记的类的示例。
在注入之前,用注解标记的类需要由spring识别, @ComponentScan 指示spring查找带有注解的类。当Spring来临 @Autowired 它搜索相关的bean,并注入所需的示例。
只添加注解,并不能修复或促进依赖注入,spring需要知道在哪里寻找。

35g0bw71

35g0bw7110#

另一个解决方案是打电话: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this) 要像这样计算里程数:

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

hlswsv3511#

简单地说,这主要有两个原因 @Autowired 待处理字段 null 你们班不是 Spring Bean 。
田地不是豆子。

tquggr8v

tquggr8v12#

更新:真正聪明的人很快就指出了这个答案,这就解释了下面描述的奇怪之处
原始答案:
我不知道这是否对任何人有帮助,但即使我做的事情看起来是对的,我也会遇到同样的问题。在我的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 我只有两颗豆子:

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

someabac类有一个声明为

@Autowired private Settings settings;

由于某些未知的原因,当 <context:component-scan/> 元素根本不存在,但是当它存在并且有一些bs作为基本包时,一切都正常。这条线现在看起来像这样:

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

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

gdrx4gfi

gdrx4gfi13#

这是给予nullpointerexception的罪魁祸首 MileageFeeCalculator calc = new MileageFeeCalculator(); 我们使用的是spring,不需要手动创建对象。对象创建将由ioc容器负责。

gev0vcfq

gev0vcfq14#

您还可以在服务类上使用@service annotation修复此问题,并将所需的bean classa作为参数传递给其他bean classb构造函数,并使用@autowired对classb的构造函数进行注解。此处是示例代码段:

@Service
public class ClassB {

    private ClassA classA;

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

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}
i2loujxw

i2loujxw15#

本文在“执行顺序”一段中描述了这里没有提到的内容。
在“学习”了我必须用@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
    }
}

相关问题