Dynamically determining which bean for autowire in Spring (using qualifiers)

I have a Java EE + Spring application that supports XML configuration annotations. beans always has a prototype area.

Now I have applications in my business rules that depend on the country from which the user request was made. So I would have something like this (keep in mind that this example is greatly simplified):

@Component public class TransactionService { @Autowired private TransactionRules rules; //.. } @Component @Qualifier("US") public class TransactionRulesForUS implements TransactionRules { //.. } @Component @Qualifier("CANADA") public class TransactionRulesForCanada implements TransactionRules { //.. } 

I was looking for a way to make the automatic wiring mechanism automatically insert the correct bean (either the US or Canada in this example) depending on the country of the current request. The country will be stored in the ThreadLocal variable, and it will change in each request. There would also be a global class for all countries that did not have their own rules.

I guess I would have to tweak the way Spring decides how to create the objects that it will input. The only way I found for this is to use FactoryBean, but that was not quite what I was hoping for (not general enough). I was hoping to do something like this:

  • Before Spring creates an instance of the object, I have to call my own code.
  • If I find that the requested interface has more than one implementation, I would look in my ThreadLocal variable for the correct country and dynamically add the appropriate Qualifier to the automatic posting request.
  • After that, Spring will do all its usual magic. If a qualifier is added, this must be taken into account; if it is not, the flow will act as usual.

Am I on the right track? Any ideas for me on this?

Thanks.

+8
java spring
source share
2 answers

Create your own annotation, which is used to decorate instance variables or setter methods, and then a postprocessor that processes the annotation and enters a common proxy server that allows the correct implementation at run time and delegates the call.

 @Component public class TransactionService { @LocalizedResource private TransactionRules rules; //.. } @Retention(RUNTIME) @Target({FIELD, METHOD}) public @interface LocalizedResource {} 

Here is the algorithm of the postProcessBeforeInitialization(bean, beanName) method in the bean post processor:

  • Imagine a bean class to find instance variables or customization methods that are annotated with @LocalizedResource. Store the result in a cache (map only) indexed by class name. You can use Spring InjectionMetadata for this purpose. You can find examples of how this works by looking for references to this class in Spring code.
  • If such a field or method exists for the bean, create a proxy server using the InvocationHandler described below, passing it the current BeanFactory (the bean post processor must be ApplicationContextAware). Add this proxy to the instance variable or call the setter method with the proxy instance.

Here is the InvocationHandler for the proxy server that will be used to create localized resources.

 public class LocalizedResourceResolver implements InvocationHandler { private final BeanFactory bf; public LocalizedResourceResolver(BeanFactory bf) { this.bf = bf; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String locale = lookupCurrentLocale(); Object target = lookupTarget(locale); return method.invoke(target, args); } private String lookupCurrentLocale() { // here comes your stuff to look up the current locale // probably set in a thread-local variable } private Object lookupTarget(String locale) { // use the locale to match a qualifier attached to a bean that you lookup using the BeanFactory. // That bean is the target } } 

You may need to make a few more controls over the bean type or add the requested bean type to the InvocationHandler.

The next is to automatically detect implementations of this interface that are locally dependent, and register them using a classifier that matches the language. For this purpose, you can implement BeanDefinitionRegistryPostProcessor or BeanFactoryPostProcessor to add a new BeanDefinition to the BeanDefinition with the appropriate qualifier, one for each implementation of interfaces that support the locale. You can guess the implementation language by following naming conventions: if the language-oriented interface is called TransactionRules, then implementations can be called TransactionRules_ISOCODE in the same package.

If you cannot afford such a naming convention, you will need to have some kind of class scan / way of guessing the locale of a given implementation (possibly annotation on implementation classes). Scanning by class is possible, but rather difficult and slow, so try to avoid this.

Here is a brief description of what is happening:

  • When the application starts, TransactionRules implementations will be detected and bean definitions will be created for each of them with a qualifier corresponding to the language of each implementation. The bean name for these beans does not matter, because the search is based on the type and classifier.
  • At run time, set the current locale to a local local variable
  • Find the desired bean (e.g. TransactionService). The post processor will introduce a proxy for each field of the @LocalizedResource instance or installation method.
  • When you call the TransactionService method, which falls into some TransactionRules method, the call handler associated with the proxy switches to the correct implementation based on the value stored in the local variable of the stream, and then delegates the call to this implementation.

Not entirely trivial, but it works. This is in fact how @PersistenceContext is handled by Spring, with the exception of finding implementations, which is an additional feature of your use case.

+4
source share

You can provide a configuration class that will return the correct bean based on the ThreadLocal value. It is assumed that you are using Spring 3. I did a little test to make sure the provider method was called on every request. Here is what I did.

 @Configuration public class ApplicationConfiguration { private static int counter = 0; @Bean( name="joel" ) @Scope( value="request", proxyMode=ScopedProxyMode.TARGET_CLASS) List<String> getJoel() { return Arrays.asList( new String[] { "Joel " + counter++ } ); } } 

And it refers to the value in my controller as follows.

 @Resource( name="joel" ) private List<String> joel; 

in your provider implementation, you can check ThreadLocal for the locale and return the correct TransactionRules object or something like that. The ScopedProxy stuff is what I injected into the controller, which is the scope of Singleton, while the scope of the request has value.

0
source share

All Articles