Reputation: 1258
I have a simple Springboot application in which I am trying to use in memory HashMap as a DB to store some info. This is my restcontroler
@RestController
public class Parking {
@Autowired
ParkingServices parkingServices;
@Autowired
Vehicle vehicle;
@GetMapping("/test")
public void park() {
vehicle = Vehicle.builder().color("Blue").plateNumber("qwew").build();
parkingServices.parkCar(vehicle);
}
@GetMapping("/")
public void Test() {
System.out.println("test");
}
}
This is my simple parking service
@Service
public class ParkingServices {
@Autowired
ParkingLotService parkingLot;
public ParkingServices() {
parkingLot.init(10);
}
public int parkCar(Vehicle vehicle) {
return parkingLot.parkCar(vehicle);
}
}
And this is my ParkingLot service
@Service
public class ParkingLotService {
private int size = 0;
private HashMap<Integer, Slot> parkingSlots = new HashMap<>();
private Stack<Integer> st = new Stack<>();
public void init(int size) {
this.size = size;
parkingSlots = new HashMap<>();
for (int i = 0; i < size; i++) {
parkingSlots.put(i, new ParkingSlot());
st.push(i);
}
}
public int parkCar(Vehicle vehicle) {
int firstPark = st.pop();
Slot slot = parkingSlots.get(firstPark);
slot.parkVehicle(vehicle);
parkingSlots.put(firstPark, slot);
return firstPark;
}
public void removeCar(int parkingNumber) {
Slot slot = parkingSlots.get(parkingNumber);
}
}
Now when i try to run the application I am getting this error as a stack trace
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'parking': Unsatisfied dependency expressed through field 'parkingServices'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'parkingServices' defined in file [D:\Projects\parking\demo\target\classes\com\parking\demo\Services\ParkingServices.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.parking.demo.Services.ParkingServices]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:660) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.5.jar:5.3.5]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.5.jar:5.3.5]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144) ~[spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:769) [spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) [spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) [spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1313) [spring-boot-2.4.4.jar:2.4.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) [spring-boot-2.4.4.jar:2.4.4]
at com.parking.demo.ParkingApplication.main(ParkingApplication.java:11) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'parkingServices' defined in file [D:\Projects\parking\demo\target\classes\com\parking\demo\Services\ParkingServices.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.parking.demo.Services.ParkingServices]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1316) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:657) ~[spring-beans-5.3.5.jar:5.3.5]
... 21 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.parking.demo.Services.ParkingServices]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:225) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87) ~[spring-beans-5.3.5.jar:5.3.5]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1308) ~[spring-beans-5.3.5.jar:5.3.5]
... 32 common frames omitted
Caused by: java.lang.NullPointerException: null
at com.parking.demo.Services.ParkingServices.<init>(ParkingServices.java:15) ~[classes/:na]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_252]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_252]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_252]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_252]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:212) ~[spring-beans-5.3.5.jar:5.3.5]
... 34 common frames omitted
I know its due to Autowired is not working on ParkingLotService, its ability to work if instead of Autowired in ParkingService, I directly try to initialize Object but not the other way around. Can someone help me with it ? Why Autowired is not working and how to make it work.
Upvotes: 0
Views: 7348
Reputation: 9418
TL;DR: Use Constructor-based DI
@Autowired
atop constructorYou got this UnsatisfiedDependencyException
because:
Error creating bean with name 'parking':
This bean could not be created because:
Unsatisfied dependency expressed through field 'parkingServices';
So the field could not be autowired by Spring with a dependency because of a NullPointerException (NPE):
nested exception is org.springframework.beans.factory.BeanCreationException: Constructor threw exception; nested exception is java.lang.NullPointerException
Continue to answer explaining M. Deinum comment below:
The problem is the accessing of a variable before it is available.
The NPE was raised when the constructor of ParkingServices
tries to call init
on the field parkingLot
, because the field was null
.
@Autowired
ParkingLotService parkingLot; // field autowired in 2nd step
public ParkingServices() {
parkingLot.init(10); // constructor called in 1st step, hence NPE
}
@Autowired
ExplainedSpring's @Autowired
is a Java-annotation to facilitate Dependency-Injection (DI).
Spring as most DI-frameworks offers 3 different ways of DI:
In your case you use Field-based DI. This fails, because Spring first calls the constructor before autowiring fields (= injecting beans into Autowired annotated fields).
See the tutorial of LogicBig (2020): Spring - Different ways of injecting dependencies
It shows a nice illustration:
Your constructor fails with a NPE, because it calls a method on a null
field. The field is null
because it was not yet instantiated, i.e. autowired. And it will not unless the constructor succeeds.
Use Constructor-based DI by adding the field as parameter to the constructor and moving the @Autowired
annotation form field to constructor.
ParkingLotService parkingLot; // field autowired by constructor in 1nd step
@Autowired // tells Spring to inject suitable beans for parameters
public ParkingServices(ParkingLotService parkingLot) { // add field as required dependency
this.parkingLot = parkingLot; // instantiate field
this.parkingLot.init(10); // call init on non-null field, avoid NPE
}
This way you can instantiate and call init
methods on the field in the same code block. So related code is near in scope, near to understand, near to maintain.
See the comparison of DI-methods and their benefis in Rizvi's Blog: Different Types of Bean Injection in Spring:
As M. Deinum commented on your question:
Calling
init
is btw a bad idea, because if you use this in multiple classes this will lead to issues. The service is a singleton shared between those classes...
(bold emphasis by me)
This side-effect could be solved by "initializing" the ParkingLotService
right away in its constructor, like:
public ParkingLotService(int size) { // former init
this.size = size;
this.parkingSlots = new HashMap<>();
for (int i = 0; i < size; i++) {
this.parkingSlots.put(i, new ParkingSlot());
this.freeParkStack.push(i); // renamed `st` to `freeParkStack`
}
}
This way your singleton is "initialized" once, guaranteed and mandatory (because done with constructor, nobody will forget to init it). If a re-initialisation (here resizing) is still needed, the code can be extracted into a separate method later.
General advices:
this.
to distinguish them from parameters (also avoids compiler errors when same names assigned)Get further review, tips and improvements! Post your working code on the sister-site: http://codereview.stackexchange.com
Upvotes: 1
Reputation: 14227
Spring @Autowired
field injection after construction invoked.
See:
Autowired Fields
Fields are injected right after construction of a bean, before any config methods are invoked. Such a config field does not have to be public.
So for your scenario, you can use:
ParkingLotService
and invoke the init
method. @Autowired
public ParkingServices(ParkingLotService parkingLot) {
parkingLot.init(10);
this.parkingLot = parkingLot;
}
@PostConstruct
method to invoke the init
method@PostConstruct
public void myInit() {
parkingLot.init(10);
}
Upvotes: 2