I have a Java EE + Spring app that favors annotations over XML configuration. The beans always have prototype scope.
I now have in my app business rules that depend
Create your own annotation that is used to decorate instance variables or setter methods, then a post-processor that processes the annotation and injects a generic proxy which resolves the correct implementation at runtime and delegates the call to it.
@Component
public class TransactionService {
@LocalizedResource
private TransactionRules rules;
//..
}
@Retention(RUNTIME)
@Target({FIELD, METHOD})
public @interface LocalizedResource {}
Here is the algorithm for the postProcessBeforeInitialization(bean, beanName)
method in your bean post-processor :
InjectionMetadata
for this purpose. You can look for examples on how it works by searching references to this classe in spring code.Here is the InvocationHandler for the proxy 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 some more controls over the bean type, or add the requested bean type in the InvocationHandler.
The next thing is to autodetect implementations of a given interface, which are local-dependant, and register them with the qualifier corresponding to the locale. You can implement a BeanDefinitionRegistryPostProcessor
or BeanFactoryPostProcessor
for that purpose, in order to add new BeanDefinition
s to the registry, with proper qualifier, one for each implementation of locale-aware interfaces. You can guess the locale of an implementation by following naming conventions : if a locale-aware interface is called TransactionRules, then implementations may be named TransactionRules_ISOCODE in the same package.
If you cannot afford such a naming convention, you will need to have some sort of classpath scanning + a way to guess the locale of a given implementation (maybe an annotation on the implementation classes). Classpath scanning is possible but quite complex and slow, so try to avoid it.
Here's a summary of what happens:
Not really trivial, but it works. This is actually how @PersistenceContext is processed by Spring, except for implementations lookup, which is an additional feature of your use case.
You could provide a Configuration class that will return the correct bean based on the ThreadLocal value. This assumes you are using Spring 3. I did a little test to make sure that the provider method was called on each request. Here's 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 referenced the value in my Controller as follows.
@Resource( name="joel" )
private List<String> joel;
in your implementation of the provider you could check the ThreadLocal for the locale and return the correct TransactionRules object or something like that. The ScopedProxy stuff is because I was injecting into a Controller, which is Singleton scoped whereas the value is request scoped.