Why is my Spring @Autowired field null?

2020-01-22 10:44发布

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. 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. Why isn't Spring autowiring the field?

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:

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

When I try to GET /mileage/3, I get this exception:

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)
    ...

17条回答
做自己的国王
2楼-- · 2020-01-22 11:20

UPDATE: Really smart people were quick to point on this answer, which explains the weirdness, described below

ORIGINAL ANSWER:

I don't know if it helps anyone, but I was stuck with the same problem even while doing things seemingly right. In my Main method, I have a code like this:

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {
        "common.xml",
        "token.xml",
        "pep-config.xml" });
    TokenInitializer ti = context.getBean(TokenInitializer.class);

and in a token.xml file I've had a line

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

I noticed that the package.path does no longer exist, so I've just dropped the line for good.

And after that, NPE started coming in. In a pep-config.xml I had just 2 beans:

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

and SomeAbac class has a property declared as

@Autowired private Settings settings;

for some unknown reason, settings is null in init(), when <context:component-scan/> element is not present at all, but when it's present and has some bs as a basePackage, everything works well. This line now looks like this:

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

and it works. May be someone can provide an explanation, but for me it's enough right now )

查看更多
我命由我不由天
3楼-- · 2020-01-22 11:21

I'm new to Spring, but I discovered this working solution. Please tell me if it's a deprecable way.

I make Spring inject applicationContext in this 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;       
    }
}

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.

查看更多
混吃等死
4楼-- · 2020-01-22 11:22

If you are not coding a web application, make sure your class in which @Autowiring is done is a spring bean. Typically, spring container won't be aware of the class which we might think of as a spring bean. We have to tell the Spring container about our spring classes.

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. Make sure you get it from Appln-context as below.

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

    }

}
查看更多
▲ chillily
5楼-- · 2020-01-22 11:23

Actually, you should use either JVM managed Objects or Spring-managed Object to invoke methods. from your above code in your controller class, you are creating a new object to call your service class which has an auto-wired object.

MileageFeeCalculator calc = new MileageFeeCalculator();

so it won't work that way.

The solution makes this MileageFeeCalculator as an auto-wired object in the Controller itself.

Change your Controller class like below.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}
查看更多
SAY GOODBYE
6楼-- · 2020-01-22 11:24

I think you have missed to instruct spring to scan classes with annotation.

You can use @ComponentScan("packageToScan") on the configuration class of your spring application to instruct spring to scan.

@Service, @Component etc annotations add meta description.

Spring only injects instances of those classes which are either created as bean or marked with annotation.

Classes marked with annotation need to be identified by spring before injecting, @ComponentScan instruct spring look for the classes marked with annotation. When Spring finds @Autowired it searches for the related bean, and injects the required instance.

Adding annotation only, does not fix or facilitate the dependency injection, Spring needs to know where to look for.

查看更多
登录 后发表回答