I need to build mappings for classes (literally a Map<Class<?>, String>), which won't vary at runtime, and keeping things decoupled is a priority. Since I'm in a Spring application, I thought I'd use an annotation and ClassPathScanningCandidateComponentProvider more or less like so:
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Mapping {
String value();
}
And:
public class MappingLookUp {
private static final Map<Class<?>, String> MAPPING_LOOK_UP;
static {
Map<Class<?>, String> lookUp = new HashMap<>();
ClassPathScanningCandidateComponentProvider scanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
scanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Mapping.class));
for (BeanDefinition beanDefinition : scanningCandidateComponentProvider.findCandidateComponents("blah")) {
Class<?> clazz;
try {
clazz = Class.forName(beanDefinition.getBeanClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Mapping mapping = AnnotationUtils.getAnnotation(clazz, Mapping.class);
if (mapping == null) {
throw new IllegalStateException("This should never be null");
}
lookUp.put(clazz, mapping.value());
}
MAPPING_LOOK_UP = Collections.unmodifiableMap(lookUp);
}
public static String getMapping(Class<?> clazz) {
...
}
}
Although I believe this will work, this feels like:
a lot to put in a static initialization
a hacky use of the scanning component provider, even though it's commonly recommended for this purpose; BeanDefinition makes it sound like it's intended for finding Spring beans rather than general class definitions.
To be clear, the annotated values are data classes -- not Spring-managed beans -- so a BeanPostProcessor pattern doesn't fit, and indeed, that's why it feels awkward to use the scanning component provider that, to me, seems intended for discovery of Spring managed beans.
Is this the proper way to be implementing this pattern? Is it a proper application of the provider? Is there a feasible alternative without pulling in other classpath scanning implementations?
I will suggest this doesn't look like it is done in a very Spring-y way.
If I were to be doing this, I would utilize Spring's BeanPostProcessor or BeanFactoryPostProcessor. Both of these allow for introspection on all Bean's in Spring's BeanFactory, and would allow you to get away from the static-ness of your current setup, as the PostProcessors are just Spring Bean's themselves.
class MappingLookup implements BeanPostProcessor {
private final Map<Class<?>, String> lookup = new HashMap<>();
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// check bean's class for annotation...
// add to lookup map as necessary...
// make sure to return bean (javadoc explains why)
return bean;
}
public String getMapping(Class<?> clazz) {
// ...
}
// omitted other methods...
}
I asked a very similar question recently How to get list of Interfaces from #ComponentScan packages and finally implemented the first of suggested approaches.
You can see the code https://github.com/StanislavLapitsky/SpringSOAProxy see https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java and of course initialization annotation https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/annotation/ProxyableScan.java the key thing is to add #Import({ProxyableScanRegistrar.class})
The key code is
public class ProxyableScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Get the ProxyableScan annotation attributes
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());
if (annotationAttributes != null) {
String[] basePackages = (String[]) annotationAttributes.get("value");
if (basePackages.length == 0) {
// If value attribute is not set, fallback to the package of the annotated class
basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
}
Related
I have an interface as following:
public interface Solver {
void execute(final String definition);
}
I have several solver implementations. For example:
#Qualifier("naive")
#Service
public class NaiveSolver implements Solver {
#Override
public void execute(final String definition) {
}
}
#Qualifier("optimal")
#Service
public class OptimalSolver implements Solver {
#Override
public void execute(final String definition) {
}
}
Now, in my database I have the data for these solvers as following:
type: textual - examples: simple, naive
definition: textual
When fetching this data, I need to map the type column to the actual service instance, so it can solve the provided definition.
So, my question boils down to this: how can I, given a type string, get the instance of the corresponding Solver service instance of which the qualifier is equal to that type? I believe that #Autowired cannot be used here, as I need to find beans dynamically at runtime.
Since Spring 4 you can autowire multiple implemetations into a Map where bean qualifier is a key, and the bean itself is a value
#Autowired
private Map<String, Solver> solvers;
void doStuff() {
String type = ... // obtain type
Solver solver = solvers.get(type);
solver.execute(...)
}
Update
Correct way of naming a bean is not
#Qualifier("naive")
#Service
but
#Service("naive")
#Qualifier is used along with #Autowired to ensure the correct bean is injected
#Autowired
#Qualifier("naive")
private Solver naiveSolver;
You can create configuration which will just hold mapping to your solvers:
#Autowired
#Qualifier("optimal")
private Solver naiveSolver;
#Bean
public Map<String, Solver> mapSolver() {
Map<String, Solver> map = new HashMap<>();
map.put("naive", naiveSolver);
return map;
}
Or even going further you can follow factory pattern which will provide you different instances of solvers.
Another way you can dynamically get those beans from application context.
You could merge the following solutions:
How to inject dependencies into a self-instantiated object in Spring?
Creating an instance from String in Java
private #Autowired AutowireCapableBeanFactory beanFactory;
public void doStuff() {
Class c= Class.forName(className);
MyBean obj = c.newInstance();
beanFactory.autowireBean(obj);
// obj will now have its dependencies autowired.
}
I want implement strategy design pattern in spring boot application. I create BeanPostProcessor for construct strategy resolver:
#Component
public class HandlerInAnnotationBeanPostProcessor implements BeanPostProcessor {
private final UnpHandlersResolver unpHandlersResolver;
public HandlerInAnnotationBeanPostProcessor(UnpHandlersResolver unpHandlersResolver) {
this.unpHandlersResolver = unpHandlersResolver;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Annotation[] annotations = bean.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof HandlerIn) {
if (bean.getClass() != UnpHandler.class)
throw new RuntimeException("Not UnpHandler bean annotated by HandlerIn");
SmevMessageType[] type = ((HandlerIn) annotation).type();
for (SmevMessageType smevMessageType : type) {
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
}
}
}
return bean;
}
}
And I create resolver:
#Slf4j
#Component
public class UnpHandlersResolverImpl implements UnpHandlersResolver {
private Map<SmevMessageType, UnpHandler> map = new HashMap<>();
#Override
public void setHandler(SmevMessageType messageType, UnpHandler unpHandler) {
map.put(messageType, unpHandler);
}
#Override
public UnpHandler getUnpHandler(SmevMessageType type) {
UnpHandler sendRequestHandler = map.get(type);
if (sendRequestHandler == null)
throw new IllegalArgumentException("Invalid SendRequestHandler type: " + type);
return sendRequestHandler;
}
}
My BeanPostProcessor scan all beans with annotation HandlerIn and add to resolver's mup. I think it's wrong to do that:
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
But I not understand how can I add find beans to resolver. Before this implementation I faind beans in #Postconstruct method of resolver like:
context.getBeansWithAnnotation(HandlerIn.class);
But in this solution I have context in resolver and I think is bad.
Tell me how to properly implement what I want? In short, I want to have a set of classes that implement different behaviors. And the class that controls them. Give the class a parameter so that he chooses the right strategy and gives it to me. Like this:
Handler handler = handlersResolver.getHandler(messageType);
Result result = handler.somthing(param);
I'm going to try to make a simple example.
Interface Greeting {
void sayHello();
String getSupportedLanguage();
}
Then you have X number of implementations and you can loop through them in your "resolver"'s constructor to build the map. (I've seen this called a Proxy or a Decorator in code though, i.e. GreetingProxy or GreetingDecorator)
#Service
public GreetingResolver {
private Map<String, Greeting> languageToGreetingMap = new HashMap<>();
#Autowired
public GreetingResolver(List<Greeting> greetings) {
for (Greeting greeting : greetings) {
languageToGreetingMap.put(greeting.getSupportedLanguage(), greeting);
}
}
public void sayGreetingForLanguage(String language) {
languageToGreetingMap.get(language).sayHello();
}
}
This is a basic example of how one can do the strategy pattern in Spring. Every interface implementation of "Greeting" only knows about itself and what it can support. We then autowire all implementations in a list and loop through to create the map once and then during runtime only the relevant entry from the map in retrieved and used.
Note: this was typed "free hand" directly in the web page so please forgive any typos in the code.
I have the following classes:
#Component
#ConifgurationProperties("redis")
public class RedisProperties {
private List<String> hosts;
// getters, setters
}
#Component
public class StaticRedisHostsProvider implements RedisHostsProvider {
private final RedisProperties redisProperties;
public StaticRedisHostsProvider(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
#Override
public List<String> getAll() {
return redisProperties.getHosts();
}
}
#Component
public DiscoveryBasedRedisHostsProvider { ... }
I want StaticRedisHostsProvider to be used if redis.hosts property is specified, DiscoveryBasedRedisHostsProvider otherwise.
I could annotate StaticRedisHostsProvider with #ConditionalOnProperty(prefix = "redis", name = "hosts"), but there is no similar #ConditionalOnMissingProperty annotation for using with DiscoveryBasedRedisHostsProvider.
I tried to use #ConditionalOnExpression("#redisProperties.hosts.empty"), but it doesn't work for some reason:
Description:
A component required a bean named 'redisProperties' that could not be found.
Action:
Consider defining a bean named 'redisProperties' in your configuration.
Is there some way to fix that (maybe with #Order or similar annotations)?
Here's my take on this issue with the use of custom conditions in Spring autoconfiguration.
#Conditional annotations are executed very early in during the application startup. Properties sources are already loaded but ConfgurationProperties beans are not yet created. However we can work around that issue by binding properties to Java POJO ourselves.
First I introduce a functional interface which will enable us to define any custom logic checking if properties are in fact present or not. In your case this method will take care of checking if the property List is empty or null.
public interface OptionalProperties {
boolean isPresent();
}
Now let's create an annotation which will be metannotated with Spring #Conditional and allow us to define custom parameters. prefix represents the property namespace and targetClass represents the configuration properties model class to which properties should be mapped.
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnConfigurationPropertiesCondition.class)
public #interface ConditionalOnConfigurationProperties {
String prefix();
Class<? extends OptionalProperties> targetClass();
}
And now the main part. The custom condition implementation.
public class OnConfigurationPropertiesCondition extends SpringBootCondition {
#Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
String prefix = mergedAnnotation.getString("prefix");
Class<?> targetClass = mergedAnnotation.getClass("targetClass");
// type precondition
if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
}
// the crux of this solution, binding properties to Java POJO
Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
// if properties are not present at all return no match
if (bean == null) {
return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
}
OptionalProperties props = (OptionalProperties) bean;
// execute method from OptionalProperties interface
// to check if condition should be matched or not
// can include any custom logic using property values in a type safe manner
if (props.isPresent()) {
return ConditionOutcome.match();
} else {
return ConditionOutcome.noMatch("Properties are not present.");
}
}
}
Now you should create your own configuration properties class implementing OptionalProperties interface.
#ConfigurationProperties("redis")
#ConstructorBinding
public class RedisProperties implements OptionalProperties {
private final List<String> hosts;
#Override
public boolean isPresent() {
return hosts != null && !hosts.isEmpty();
}
}
And then in Spring #Configuration class.
#Configuration
class YourConfiguration {
#ConditionalOnConfigurationProperty(prefix = "redis", targetClass = RedisProperties.class)
StaticRedisHostsProvider staticRedisHostsProvider() {
...
}
#ConditionalOnMissingBean(StaticRedisHostsProvider.class)
DiscoveryBasedRedisHostsProvider discoveryRedisHostsProvider() {
...
}
}
There are two downsides to this solution:
Property prefix must be specified in two locations: on #ConfigurationProperties annotation and on #ConditionalOnConfigurationProperties annotation. This can partially be alleviated by defining a public static final String PREFIX = "namespace" in your configuration properties POJO.
Property binding process is executed separately for each use of our custom conditional annotation and then once again to create the configuration properties bean itself. It happens only during app startup so it shouldn't be an issue but it still is an inefficiency.
So, I have some generic components that use reflection to initialize themselves and by doing so, they require Class<T> objects at instanciation time. Those components use annotations in order to generate useful metadata and/or convert the object to another representation more appropriate for the task at hand.
I reduced my issue down to this sample component :
#Component
public class Instantiator<T> {
final Class<T> klass;
#Autowired
public Instantiator(Class<T> klass) {
this.klass = klass;
}
public T instantiate() {
try {
return klass.newInstance();
} catch (InstantiationException|IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
Spring does not know how to automatically inject Class<T> instances, so I tried writing the following boilerplate code for each T for which I want Class<T> to be available.
#Bean
Class<Instantiatee> instantiateeClass() {
return Instanciatee.class;
}
It does not work.
Spring since version 4 has support for Autowiring generic types, but in my case, it has to infer what T is assigned to in Class<T>. Since by default Spring creates singleton beans and therefore could not possibly infer an appropriate T, I tried adding #Scope("prototype") but I ended up with a ClassCastException since the container does not know how to infer T anyway.
So, I removed the #Component annotation from Instantiator and settled on this workaround for each T I have :
#Bean
Instantiator<Instantiatee> instantiator() {
return new Instantiator<>(Instantiatee.class);
}
Do you know a workaround to make this work so that T will be inferred each time I want an Instantiator or another generic component depending on it ?
FYI, we are using spring 4.1.4 with boot.
I posted a more complete sample there : https://gist.github.com/anonymous/79e1a7ebe7c25c00a6c2.
Defining beans with #Bean, you give the bean name in the method name by default - in your case getInstanciateeClass. Also, when autowiring the default bean name is considered the parameter name, in your case klass. Because of this, Spring cannot match the beans, since they have different names and most probably there are more than one Class instances in the ApplicationContext. It does not matter if one is Class<Foo> and another one is Class<Bar>, Spring sees them as Class so it cannot do autowiring by type.
You can fix this by using the same default name both when defining the bean and when autowiring it.
#Autowired
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean
Class<Instanciatee> klass() {
return Instanciatee.class;
}
You can also specify the name of the bean in the #Bean annotation:
#Autowired
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean(name = "klass")
Class<Instanciatee> getInstanciateeClass() {
return Instanciatee.class;
}
Or you can also give the bean name when autowiring:
#Autowired
#Qualifier("getInstanciateeClass")
public Instanciator(Class<T> klass) {
this.klass = klass;
}
#Bean
Class<Instanciatee> getInstanciateeClass() {
return Instanciatee.class;
}
Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}