Areinu
Areinu

Reputation: 172

Service injected into spring controller is not available in one of the functions

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

Answers (2)

Sudip Bolakhe
Sudip Bolakhe

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

mvmn
mvmn

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

Related Questions