I am trying to localize almost every parameter in the response of each API in my project.
I have figured out that we can do something like this in spring boot:
MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource, locale);
return accessor.getMessage(code);
and keep the code versus localized message mapping in messages_en.properties, messages_fr.properties etc.
But for my application I specifically have two requirements:
I want to separate this logic from my business logic i.e., I don't want to write localization logic in each and every controller.
I want to try it at each and every response parameter for all the response through the server, maybe while Jackson is converting objects to string or after conversion to JSON.
Is there a way in spring boot to achieve this or are there any libraries available for this?
I have found a solution for this. Instead of using String for fields, I am using a custom class like LocalizedText:
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class LocalizedText {
private String text;
}
For serialization, I have created a Deserializer LocalizedTextSerailizer, something like this:
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
#Component
public class LocalizedTextSerializer extends StdSerializer<LocalizedText> {
private static final long serialVersionUID = 619043384446863988L;
#Autowired
I18nUtil messages;
public LocalizedTextSerializer() {
super(LocalizedText.class);
}
public LocalizedTextSerializer(Class<LocalizedText> t) {
super(t);
}
#Override
public void serialize(LocalizedText value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(messages.get(value.getText()));
}
}
I18nUtil:
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
#Component
#Slf4j
public class I18nUtil {
#Autowired
private MessageSource messageSource;
public String get(String code) {
try {
MessageSourceAccessor accessor = new MessageSourceAccessor(messageSource, Locale.getDefault());
return accessor.getMessage(code);
} catch (NoSuchMessageException nsme) {
log.info("Message not found in localization: " + code);
return code;
}
}
}
This pretty much serves the purpose, I don't have to mess up with the business logic and I can localize any parameter for any response in the application.
Note:
Here I18nUtil, returns the same code if it couldn't find any message in the message.properties.
Default locale is used in I18nUtil, for demonstration.
Related
I'm trying to centralize the error handling in my spring boot app. Currently i'm only handling one potential exception (NoSuchElementException), this is the controller advice:
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(NoSuchElementException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public DispatchError dispatchNotFound(NoSuchElementException exception) {
System.out.println("asdasdasd");
return new DispatchError(exception.getMessage());
}
}
And here's the service which throws the exceptions:
import java.util.List;
import com.deliveryman.deliverymanapi.model.entities.Dispatch;
import com.deliveryman.deliverymanapi.model.repositories.DispatchRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class DaoService {
#Autowired
DispatchRepository dispatchRepo;
public Dispatch findByShipmentNumber(long shipmentNumber) {
return dispatchRepo.findById(shipmentNumber).orElseThrow();
}
public List<Dispatch> findByUser(String user, String status) {
if(status == null) {
return dispatchRepo.findByOriginator(user).orElseThrow();
} else {
return dispatchRepo.findByOriginatorAndStatus(user, status).orElseThrow();
}
}
public Dispatch createDispatch(Dispatch dispatch) { //TODO parameter null check exception
return dispatchRepo.save(dispatch);
}
}
The problem is that once I send a request for an inexistent resource, the json message shown is the spring's default one. It should be my custom json error message (DispatchError).
Now, this is fixed by adding a #ResponseBody to the exception handler method but the thing is that I was using an old code of mine as reference, which works as expected without the #ResponseBody annotation.
Can someone explain me why this is happening?
Either annotate your controller advice class with #ResponseBody
#ControllerAdvice
#ResponseBody
public class ExceptionController {
...
or replace #ControllerAdvice with #RestControllerAdvice.
Tested and verified on my computer with source from your controller advice.
From source for #RestControllerAdvice
#ControllerAdvice
#ResponseBody
public #interface RestControllerAdvice {
...
Hence, #RestControllerAdvice is shorthand for
#ControllerAdvice
#ResponseBody
From source doc for #ResponseBody
Annotation that indicates a method return value should be bound to the
web response body. Supported for annotated handler methods.
Alternative using #ControllerAdvice only:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<DispatchError> dispatchNotFound(NoSuchElementException exception) {
return new ResponseEntity<>(new DispatchError(exception.getMessage()), HttpStatus.NOT_FOUND);
}
}
I do have a theory on what's going on in your old app. With the advice from your question, and the error handler below, I can create a behaviour where the DispatchError instance appears to be returned by advice (advice is executed), but is actually returned by error controller.
package no.mycompany.myapp.error;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
#RestController
#RequiredArgsConstructor
public class ErrorHandler implements ErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
#RequestMapping(ERROR_PATH)
DispatchError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new DispatchError((String) attrs.get("message"));
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
Putting an implementation of ErrorController into classpath, replaces Spring's BasicErrorController.
When reinforcing #RestControllerAdvice, error controller is no longer in effect for NoSuchElementException.
In most cases, an ErrorController implementation that handles all errors, in combination with advice exception handlers for more complex exceptions like MethodArgumentNotValidException, should be sufficient. This will require a generic error DTO like this
package no.mycompany.myapp.error;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.Map;
#Data
#NoArgsConstructor
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {
private long timestamp = new Date().getTime();
private int status;
private String message;
private String url;
private Map<String, String> validationErrors;
public ApiError(int status, String message, String url) {
this.status = status;
this.message = message;
this.url = url;
}
public ApiError(int status, String message, String url, Map<String, String> validationErrors) {
this(status, message, url);
this.validationErrors = validationErrors;
}
}
For ErrorHandler above, replace handleError with this
#RequestMapping(ERROR_PATH)
ApiError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new ApiError(
(Integer) attrs.get("status"),
(String) attrs.get("message"), // consider using predefined message(s) here
(String) attrs.get("path"));
}
Advice with validation exception handling
package no.mycompany.myapp.error;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
#RestControllerAdvice
public class ExceptionHandlerAdvice {
private static final String ERROR_MSG = "validation error";
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleValidationException(MethodArgumentNotValidException exception, HttpServletRequest request) {
return new ApiError(
HttpStatus.BAD_REQUEST.value(),
ERROR_MSG,
request.getServletPath(),
exception.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
// mergeFunction handling multiple errors for a field
(firstMessage, secondMessage) -> firstMessage)));
}
}
Related config in application.yml
server:
error:
include-message: always
include-binding-errors: always
When using application.properties
server.error.include-message=always
server.error.include-binding-errors=always
When using Spring Data JPA, consider using the following setting for turning off a second validation.
spring:
jpa:
properties:
javax:
persistence:
validation:
mode: none
More information on exception handling in Spring:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc (revised April 2018)
https://www.baeldung.com/exception-handling-for-rest-with-spring (December 31, 2020)
I wanted to play around with the different types of bean scopes. So I wrote a test environment which should generate a random number so I could see if a bean had changed. My test setup does not work and I can not explain what I found out.
I'm using Spring Boot 2.13 with the Spring Framework 5.15.
Following setup:
Main class:
package domain.webcreator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class WebcreatorApplication {
public static void main(String[] args) {
SpringApplication.run(WebcreatorApplication.class, args);
}
}
Beans class:
package domain.webcreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Random;
#Configuration
public class Beans {
#Bean
public Random randomGenerator() {
return new Random();
}
}
Scoper class:
package domain.webcreator;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.Random;
#Service
#Scope("singleton")
public class Scoper {
private Random rand;
public Scoper(Random rand) {
this.rand = rand;
}
public int getNumber(int max) {
return rand.nextInt(max);
}
}
Index Controller
package domain.webcreator.controller;
import domain.webcreator.Scoper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class IndexController {
#GetMapping("/")
#ResponseBody
#Autowired
public String indexAction(Scoper scoper) {
return String.valueOf(scoper.getNumber(50));
}
}
My problem is, that I get an NPE while calling scoper.getNumber(50).
This is strange because when debugging, a Random bean is generated and passed to the scoper constructor.
Later on, in the controller, the rand property is null.
What am I doing wrong?
You're trying to apply #Autowired to a random method, which isn't how Spring works. Controller method parameters are for information specific to that HTTP request, not general dependencies, and so Spring is trying to create a new Scoper that is associated with the request--but it doesn't have any incoming values in the request to fill in. (I'm actually surprised you're not getting an error about no default constructor.)
Instead, pass your Scoper in a constructor.
#RestController
public class IndexController {
private final Scoper scoper;
public IndexController(Scoper scoper) {
this.scoper = scoper;
}
#GetMapping("/")
public String indexAction(Scoper scoper) {
return String.valueOf(scoper.getNumber(50));
}
}
A couple of notes:
Singleton scope is the default, and there's no need to specify it.
#RestController is preferable to repeating #ResponseBody unless you have a mixed controller class.
I realize that these are internal APIs, but if they're available internally why not make them usable by the less privileged masses, and they're also extremely useful. Even though these APIs were internal in Jersey 2.25 they could be used, and I'd like to upgrade my Jersey version without breaking my custom Jersey extensions.
It's certainly possible to extend ValueParamProvider in Jersey 2.27, but I no longer see a way to register that Provider along with it's triggering annotation. Looking at how Jersey does this for its own implementations, it now uses a BoostrapConfigurator, which seems to be internalized to such an extent that external implementations can't use the same methodology.
Maybe I'm wrong about that, and if someone has a clear description of how, that would be great. Otherwise, does anyone know of a method for doing the same thing?
This used to work...
ResourceConfig resourcceConfig = ...
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure (){
bind(MyParamValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(MyParamInjectionResolver.class).to(new TypeLiteral<InjectionResolver<EntityParam>>() {
}).in(Singleton.class);
}
}
});
With appropriate implementations of AbstractValueFactoryProvider and ParamInjectionResolver.
Now it looks like you need to implement ValueParamProvider, which is easy enough, but I'm not sure how to register that properly with the Jersey framework anymore. Any help appreciated.
You don't need to use any BootstrapConfigurator. All you need to is add the services to the injector and they will be added later to the list of value providers.
To configure it, you can still use the AbstractBinder, but instead of the HK2 one, use the Jersey one. The ValueParamProvider can still be bound the same way, but for the InjectionResolver, you should make sure to implement not the HK2 resolver, but the Jersey one. Then instead of binding to TypeLiteral, bind to GenericType.
I just want to add that a misconception that people have when trying to implement parameter injection is that we also need an InjectResolver to use a custom annotation for the method parameter. This is not the case. The method parameter annotation is just a marker annotation that we should check inside ValueParamProvider#getValueProvider() method. An InjectResolver is only needed for non-method-parameter injections, for instance field and constructor injection. If you don't need that, then you don't need the InjectionResolver.
Below is a complete example using Jersey Test Framework. I didn't use an InjectionResolver, just to show that it's not needed.
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public class ParamInjectTest extends JerseyTest {
#Target({ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Auth {
}
private static class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return this.username;
}
}
public static class AuthValueParamProvider implements ValueParamProvider {
#Override
public Function<ContainerRequest, ?> getValueProvider(Parameter parameter) {
if (parameter.getRawType().equals(User.class)
&& parameter.isAnnotationPresent(Auth.class)) {
return new UserParamProvider();
}
return null;
}
private class UserParamProvider implements Function<ContainerRequest, User> {
#Override
public User apply(ContainerRequest containerRequest) {
return new User("Peeskillet");
}
}
#Override
public PriorityType getPriority() {
return Priority.HIGH;
}
}
public static class AuthFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder() {
#Override
protected void configure() {
bind(AuthValueParamProvider.class)
.to(ValueParamProvider.class)
.in(Singleton.class);
}
});
return true;
}
}
#Path("test")
#Consumes("text/plain")
public static class TestResource {
#POST
#Produces("text/plain")
public Response post(String text, #Auth User user) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(AuthFeature.class);
}
#Test
public void testIt() {
final Response response = target("test")
.request()
.post(Entity.text("Test"));
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.readEntity(String.class)).isEqualTo("Peeskillet:Test");
}
}
Another thing I'll mention is that in previous versions where you extended AbstractValueFactoryProvider and implemented a ParamInjectionResolver, most people did this to follow how Jersey implemented parameter injection while still allowing for other injection points (field and constructor). If you still want to use this pattern, you can.
Below is the AuthFeature from the above test refactored
public static class AuthFeature implements Feature {
#Override
public boolean configure(FeatureContext context) {
InjectionManager im = InjectionManagerProvider.getInjectionManager(context);
AuthValueParamProvider authProvider = new AuthValueParamProvider();
im.register(Bindings.service(authProvider).to(ValueParamProvider.class));
Provider<ContainerRequest> request = () -> {
RequestProcessingContextReference reference = im.getInstance(RequestProcessingContextReference.class);
return reference.get().request();
};
im.register(Bindings.injectionResolver(new ParamInjectionResolver<>(authProvider, Auth.class, request)));
return true;
}
}
I figured this stuff out just digging through the source. All this configuration I saw in the ValueParamProviderConfigurator. You don't need to implement your own ParamInjectionResolver. Jersey has a concrete class already that we can just use, as done in the feature above.
If you change the TestResource to inject by field, it should work now
#Path("test")
#Consumes("text/plain")
public static class TestResource {
#Auth User user;
#POST
#Produces("text/plain")
public Response post(String text) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}
I want to intercept the JSON sent back from a Spring MVC Rest Controller and run it through a sanitizer that ensures it's valid and HTML escapes any dodgy characters. (Possibly the OWASP JSON Sanitizer)
We use the Jackson HTTP Message converter to convert the #ResponseBody to JSON, as far as I can see once I return the object as a #ResponseBody I lose control of it.
Is there a sensible way to intercept the JSON as a String to run sanitization code on it?
I'm currently investigating three avenues:
Writing a Filter and ResponseWrapper which sanitizes the JSON before it's sent back to the client.
Extending the JSON Mapper somehow to provide sanitized JSON.
Writing a Handler Interceptor and using it to modify the response.
I'm not sure if either of these will work or if there is a more sensible third option.
I know this answer may be too late, but I needed to do the same thing, so I added a serializer to the JSON mapper.
The web configuration:
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
// the list is empty, so we just add our converter
converters.add(jsonConverter());
}
#Bean
public HttpMessageConverter<Object> jsonConverter() {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
.json()
.serializerByType(String.class, new SanitizedStringSerializer())
.build();
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
And the string serializer:
import java.io.IOException;
import org.apache.commons.lang3.StringEscapeUtils;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.NonTypedScalarSerializerBase;
public class SanitizedStringSerializer extends NonTypedScalarSerializerBase<String> {
public SanitizedStringSerializer() {
super(String.class);
}
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
jgen.writeRawValue("\"" + StringEscapeUtils.escapeHtml4(value) + "\"");
}
}
I am trying to set up a Jersey ClientResponseFilter. It is working fine, but I want to deserialize my request parameters into a String so I can write helpful messages into a log file containing the actual data.
I was thinking about using MessageBodyWorkers for this. As this link below says:
"In case you need to directly work with JAX-RS entity providers, for example to serialize an entity in your resource method, filter or in a composite entity provider, you would need to perform quite a lot of steps."
Source: 7.4. Jersey MessageBodyWorkers API
This is exactly what I want to prevent.
So I was thinking about injecting the messagebodyworkers into my filter like this:
package somepackage.client.response;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.slf4j.Logger;
#Provider
public class ResponseFilter implements ClientResponseFilter {
// TODO: these workers are not injected
#Context
private MessageBodyWorkers workers;
private final Logger logger;
public ResponseFilter(Logger logger) {
this.logger = logger;
}
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
throws IOException {
if (responseValid(responseContext)) {
return;
}
logger.error("Error", "Some param");
}
private boolean responseValid(ClientResponseContext responseContext) {
if (responseContext.getStatus() == HttpServletResponse.SC_OK) {
return true;
}
return false;
}
}
But the reference is always null and remains null. Note that this filter is running in a standalone application, no servlet container is available.
Why isn't the annotation working in this case? How can I make it work? Or if making this approach to work is impossible, how can I work around this?
Any suggestions?
OK. Here is the workaround solution for the problem above: we should use #Inject and the HK2 Dependency Injection Kernel
HK2 Dependency Injection Kernel Link
First we need to make some changes to the filter:
package somepackage.client.response;
import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.slf4j.Logger;
public class ResponseFilter implements ClientResponseFilter {
#Inject
private MessageBodyWorkers workers;
private Logger logger;
#Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
throws IOException {
if (responseValid(responseContext)) {
return;
}
logger.error("Error", "Some param");
}
private boolean responseValid(ClientResponseContext responseContext) {
if (responseContext.getStatus() == HttpServletResponse.SC_OK) {
return true;
}
return false;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
As you can see the constructor changed, the class uses the default constructor, and the annotation changed to #Inject. Be aware that there are two #Inject annotations with the same name. Make sure you use: javax.inject.Inject.
Then we need to implement org.glassfish.hk2.utilities.binding.AbstractBinder:
package somepackage.client;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.glassfish.jersey.message.internal.MessageBodyFactory;
public class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(MessageBodyFactory.class).to(MessageBodyWorkers.class);
}
}
And finally we should register the filter and our binder in the client:
...
client.register(ResponseFilter.class);
client.register(new SitemapBinder());
...
Then the worker is going to be injected fine.