Home > Back-end >  why MessageSource not Autowired in RestController?
why MessageSource not Autowired in RestController?

Time:09-17

I write a simple program to work with internationalization capabilities of Spring framework like the following:

@SpringBootApplication
public class Application {

  @Bean
  public LocaleResolver localeResolver(){
    SessionLocaleResolver localeResolver = new SessionLocaleResolver();
    localeResolver.setDefaultLocale(Locale.US);
    return localeResolver;
  }

  @Bean
  public MessageSource resourceBundleMessageSource(){
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setUseCodeAsDefaultMessage(true);
    messageSource.setBasenames("message");
    return messageSource;
  }
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

and my controller:

@RestController
public class MyController {

  @Autowired
  @Qualifier(value = "resourceBundleMessageSource")
  private MessageSource messageSource;

  @GetMapping(path = "/get")
  public String getLocale(
      @RequestHeader(value = "Accept-Language", required = false) Locale locale) {
    return messageSource.getMessage("message.to.user",null, locale);
  }
}

and I also created message.properties, message_en.properties and message_es.properties in src/main/resources folder.

my question is even though I have one instance of MessageSource in my application context I have to use Qualifier(value = "resourceBundleMessageSource") in order to get response from my controller other wise I get errors like this. No message found under code 'message.to.user' for locale 'es'. now I wonder why @Autowired did not work according to expectations? i.e. autowire beans by type, and in this I have one bean of type MessageSource

CodePudding user response:

-- Edited --

It could be that it is autowiring another messagesource and that is why when you @Autowire it, and use it (It does not throw an exception, but rather tells you it cant find the message for translation)

You could resolve it by renaming your bean declaration for example:

  @Bean
  public MessageSource messageSource(){

  }

and then call it

  @Autowired
  private MessageSource messageSource;

For a fallback match, the bean name is considered a default qualifier value. Thus, you can define the bean with an id of main instead of the nested qualifier element, leading to the same matching result. However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches. They do not semantically express a reference to a unique bean id. Good qualifier values are main or EMEA or persistent, expressing characteristics of a specific component that are independent from the bean id, which may be auto-generated in case of an anonymous bean definition such as the one in the preceding example.

Qualifiers also apply to typed collections, as discussed earlier — for example, to Set. In this case, all matching beans, according to the declared qualifiers, are injected as a collection. This implies that qualifiers do not have to be unique. Rather, they constitute filtering criteria. For example, you can define multiple MovieCatalog beans with the same qualifier value “action”, all of which are injected into a Set annotated with @Qualifier("action").

Therefore I believe you have more than 1 type of (MessageSource) and that is why it is working when you use Qualifier..but not without it.

CodePudding user response:

Your question leaves out an important part and that is that you are using Spring Boot (although it can be inferred from the fact that you have a @SpringBootApplication annotation main class, it isn't in the tags).

Spring Boot already configures a MessageSource and does so under the name messageSource (which is also the magic name used in Spring to determine the main MessageSource to use).

You are adding an additional MessageSource named resourceBundleMessageSource.

With this you end up with 2 beans of the type MessageSource. Now what Spring will do, as it cannot unique determine which one to use, it will take the name of the field (in your case messageSource) and find one with the exact name (in this case the default one).

Actually it works as designed and documented, however you didn't realize there already was a MessageSource pre-configured.

I would also suggest to use the default one, and remove your own MessageSource definition. Instead add the following to the configuration

spring.messages.use-code-as-default-message=true

To achieve the behavior you have configured in your custom one.

In your controller you also don't need to get the Accept-Language as that is what the SessionLocaleResolver does, although you need to remove the defaultLocale for this to work.

  • Related