Search code examples
javadependency-injectionmicronaut

Could not inject different beans with named qualifier in Micronaut


I have defined an ObjectMapper factory class like this:

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import io.micronaut.context.annotation.Factory;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Factory
public class MyObjectMapper {

    @Singleton
    @Named("jsonObjectMapper")
    public ObjectMapper getJsonObjectMapper() {
        return new ObjectMapper(new JsonFactory());
    }

    @Singleton
    @Named("yamlObjectMapper")
    public ObjectMapper getYamlObjectMapper() {
        return new ObjectMapper(new YAMLFactory());
    }

}

Then, on client class, I tried to inject them like this:

import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

@Singleton
public class MyServiceImpl implements MyService {

    private ObjectMapper jsonMapper;

    private ObjectMapper yamlMapper;

    @Inject
    @Named("jsonObjectMapper")
    public void setJsonMapper(ObjectMapper jsonMapper) {
        this.jsonMapper = jsonMapper;
    }

    @Inject
    @Named("yamlObjectMapper")
    public void setYamlMapper(ObjectMapper yamlMapper) {
        this.yamlMapper = yamlMapper;
    }
...

My goal is to have jsonMapper to be injected by the bean with @Named("jsonObjectMapper") on MyObjectMapper class, and yamlMapper with @Named("yamlObjectMapper"). But, when I tried to debug, jsonMapper and yamlMapper had the same reference, which means they are injected by the same ObjectMapper. My question is how to inject 2 different beans for json and yaml mapper on Micronaut?

Thank you!


Solution

  • The injection qualified by name can be done with the @Named annotation used on the method argument, and not the method itself. It means that in your case you would have to move the @Named annotation to the setJsonMapper and setYamlMapper methods arguments.

    @Singleton
    public class MyServiceImpl {
    
        private ObjectMapper jsonMapper;
    
        private ObjectMapper yamlMapper;
    
        @Inject
        public void setJsonMapper(@Named("jsonObjectMapper") ObjectMapper jsonMapper) {
            this.jsonMapper = jsonMapper;
        }
    
        @Inject
        public void setYamlMapper(@Named("yamlObjectMapper") ObjectMapper yamlMapper) {
            this.yamlMapper = yamlMapper;
        }
    
        // ...
    }
    

    Alternatively, you could use construction injection combined with the @Named annotation for each parameter. It allows you to mark both fields as private, just to make sure these objects are not re-assigned at the runtime.

    @Singleton
    public class MyServiceImpl {
    
        private final ObjectMapper jsonMapper;
    
        private final ObjectMapper yamlMapper;
    
        public MyServiceImpl(
                @Named("jsonObjectMapper") ObjectMapper jsonMapper,
                @Named("yamlObjectMapper") ObjectMapper yamlMapper) {
            this.jsonMapper = jsonMapper;
            this.yamlMapper = yamlMapper;
        }
    
        // ...
    }