Reputation: 172
I am writing spring controller, which injects a bean.
The bean is added in config(we use java config for everything):
@Bean
public NotificationService notificationService() {
return new NotificationService();
}
The service itself has few injected dependencies and few functions:
public class NotificationService {
@Inject
NotificationRepository notificationRepository;
@Inject
ProjectRepository projectRepository;
@Inject
ModelMapper modelMapper;
public NotificationDto create(NotificationDto notificationDto) {
//convert to domain object, save, return dto with updated ID
return notificationDto;
}
public void markAsRead(Long id, String recipientNip) {
//find notification, update status
}
}
Model mapper has almost no configuration, is only set to strict. Meanwhile repositoriers are interfaces extending JpaRepository
with no custom functions. They are found by @EnableJpaRepositories
.
Finally I have controller that tries to use the code above:
@RestController
@RequestMapping("/notifications")
public class NotificationController extends ExceptionHandlerController {
@Autowired
private NotificationService notificationService;
@PreAuthorize("isFullyAuthenticated() and hasRole('create_notification')")
@RequestMapping(method = RequestMethod.POST, consumes = MediaTypeExtension.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> createNotification(@Valid @RequestBody(required = true) final NotificationDto notification) {
this.notificationService.create(notification);
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
@PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
@RequestMapping(value = "/{id}/read", method = RequestMethod.PUT)
private ResponseEntity<?> markNotificationAsRead(@PathVariable("id") Long id, @AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
}
All controllers are added trough @ComponentScan, based on their package.
As you can see both functions use notificationService
. When I send POST
for create
on /notifications
the notificationService
is properly injected. In the same controller, when I do PUT
request on /{id}/read
, the notificationService
is null
.
I think it has something to do with spring putting things into its container, and for some reason not being able to do it for that one function. I have few more functions in the controller and in all of them notificationService
is properly injected. I don't see any real difference between createNotification
and markNotificationAsRead
functions and I couldn't find anything even remotely related on google/stack. In all cases the service wouldn't inject at all because of configuration mistake.
Edit I have tried changing things around in the function until it has started working. My final code looks like this:
@PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
@RequestMapping(value = "{id}/read", method = RequestMethod.PUT)
public ResponseEntity<?> read(@PathVariable("id") Long id, @AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
and it works. Honestly I can't see any difference from my original code, and I have been staring at it for last hour or so. The imports are the same too.
I have also noticed(on unworking code) that while all functions from the controller on debug stack were marked as
NotificationController.functionName(arguments) line: x
The non working function was:
NotificationController$$EnhancerBySpringCGLIB$$64d88bfe(NotificationController).markNotificationAsRead(ContractorDto) line: 86
Why this single function was enhanced by spring CGLIB I have no idea. I have tried looking it up, but for now I came empty handed. Even though the code started to work I am leaving the question open in order to find the underlying cause.
Upvotes: 1
Views: 2476
Reputation: 523
I was also facing the same issue. It was all due to the private access modifier used and @PreAuthorize. Making the controller method private does not make an issue if you do not make it secure. But, to make secure, make it public.
Upvotes: 3
Reputation: 4057
Your method markNotificationAsRead
is private and that probably causes the issue. I've just had same issue with final method - this message appeared in log:
2016-11-28 17:19:14.186 INFO 97079 --- [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy method [public final java.util.Map com.package.controller.MyController.someMethod(javax.servlet.http.HttpServletResponse)] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
Looks like in one case we see a CGLib proxy, and in another - the actual class. Only one of those has all the fields injected, looks like the proxy has all fields nulls. But it doesn't matter - the point is your method should be public and not final in order to be proxied properly by @PreAuthorize methods.
Upvotes: 8