Last Update: 24.01.2019. By Jens in Spring Boot | Learning
Beginners and even long-time users of Spring can forget that the magic is all about proxies. Yep, when Spring works its magic, your classes are proxied.
By default, those are generated during runtime/startup when necessary. Let’s take a look at the following example and what’s wrong with it:
@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SecuritySchulungApplication.class, args);
}
}
And one simple @Controller:
@RestController
public class TaskController {
@Autowired
private TaskRepository taskRepo;
@PostAuthorize("hasRole('USER') and returnObject.user.username == principal.username")
@GetMapping("/task/{id}")
private Task getTask(@PathVariable("id") Long id) {
return taskRepo.findById(id).orElse(null);
}
}
TaskRepository is a Spring Data repository and irrelevant for the problem at hand.
We’ll get a nice NullPointerException because taskRepo is null. Yet, Spring could start the app and did not complain it can’t fulfill the @Autowired dependency.
Strange, huh?
Can you spot it?
EnableGlobalMethodSecurity(prePostEnabled=true) enables proxies of the TaskController because it uses one of the supported annotations @PostAuthorize. So, during runtime, Spring generates a CGLib proxy for our Controller. So, every method call from the outside of the class is going through the proxy. The proxy does its magic and finally calls our class.
In this case, it does not, because the problem is that our Controller method getTask is set to private. In this case, the proxy magic does not work, and the call ends up in the proxy itself, but the dependencies are always null there; by design.
Setting getTask to public fixed it.
When you run in those errors, remember to check your method visibility.