I have a typed abstract RestController that contains some common logic for processing of all objects of the type. The service for processing is provided through the constructor.
During the bean instantiation of the subclass, both constructors are called with non-null parameters and the superclass non-null assertion successfully passed.
Calling the API endpoint (URI path is a composition of the subclass and superclass paths) calls the correct method, with correctly identified parameters. However, the endpoint method throws a null pointer exception because the provided service (the one that passed the non-null assertion) was null. Upon inspection all properties of both subclass and superclass of the bean whose method was called report all properties to be null.
Here is a simplified example:
Model:
public class Cookie {
public long id;
}
public class ChocolateCookie extends Cookie {
public long chipCount;
}
Service:
public interface CookieService<T extends Cookie> {
T findCookie(long cookieId);
void eatCookie(T cookie);
}
@Service
public class ChocolateCookieService implements CookieService<ChocolateCookie> {
@Override
public ChocolateCookie findCookie(long cookieId) {
// TODO Load a stored cookie and return it.
return new ChocolateCookie();
}
@Override
public void eatCookie(ChocolateCookie cookie) {
// TODO Eat cookie;
}
}
Rest Controllers:
public abstract class CookieApi<T extends Cookie> {
private final CookieService<T> cookieService;
public CookieApi(CookieService<T> cookieService) {
this.cookieService = cookieService;
Assert.notNull(this.cookieService, "Cookie service must be set.");
}
@PostMapping("/{cookieId}")
public ResponseEntity eatCookie(@PathVariable long cookieId) {
final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
cookieService.eatCookie(cookie);
return ResponseEntity.ok();
}
}
@RestController
@RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {
@Autowired
public ChocolateCookieApi(ChocolateCookieService cookieService) {
super(cookieService);
}
@PostMapping
public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) {
// TODO Process DTO and store the cookie
return ResponseEntity.ok(dto);
}
}
As a note, if instead of providing a service object to the superclass I defined an abstract method for getting the service on demand and implemented it in the subclass, the superclass would function as intended.
The same principle works in any case where @RestController and @RequestMapping are not included in the equation.
My question is two-fold:
EDIT 1:
I tried recreating the issue, but the provided code was working fine as people suggested. After tampering with the simplified project, I finally managed to reproduce the issue. The actual condition for reproducing the issue is that the endpoint method in the superclass must be inaccessible by the subclass (example: Classes are in different packages and the method has package visibility). This causes spring to create an enhancerBySpringCGLIB proxy class with zero populated fields.
Modifying the superclass methods to have protected/public visibility resolved the issue.
you can define an abstract method in your abstract class and autowire the correct service on each implementation :
public abstract class CookieApi<T extends Cookie> {
protected abstract CookieService<T> getCookieService();
@RequestMapping("/cookieId")
public void eatCookie(@PathVariable long cookieId) {
final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
this.getCookieService().eatCookie(cookie);
}
}
@RestController
@RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {
@Autowired
private ChocolateCookie chocolateCookie;
@Override
protected CookieService<T> getCookieService() {
return this.chocolateCookie;
}
@PostMapping
public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) {
// TODO Process DTO and store the cookie
return ResponseEntity.ok(dto);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With