Reputation: 686
Hy
I'm looking for a way to 'simplify'/shorten my spring configuration. I' ve got this Generic service that looks something like:
public class GenericService<T> {
private Class<T> targetClass;
public void setTargetClass(Class<T> targetClass) {this.targetClass = targetClass;}
public void doSomething() {...}
}
and in my spring-config.xml file I have
<bean id="entity1Service" class="GenericService">
<property name="targetClass" value="model.Entity1" />
</bean>
<bean id="entity2Service" class="GenericService">
<property name="targetClass" value="model.Entity2" />
</bean>
...
I'm trying to build a factory that will build all these services for me so that I could write something like this in my spring-config.xml
<bean class="ServiceFactory">
<property name="targets">
<list>
<value>model.Entity1</value>
<value>model.Entity2</value>
</list>
</property>
</bean>
which would generate 2 beans (one named entity1Service, the other entity2Service). Get-it?
How would I start? I've looked at BeanFactory (not to be confused with FactoryBean!) but fail to see how to hookup everything up.
It would be even better if my factory could scan my packages and generate a service when it finds an entity (either through annotation or interface implementation), a little like @EnableJpaRepositories annotation in spring-data does for all JpaRepository interfaces.
Thanks for any insights, examples, pointers...
w.
Upvotes: 5
Views: 8484
Reputation: 686
I believe I've figured it out and posting my result for future references.
public class GenericBeanGenerator <T, G> implements BeanFactoryPostProcessor, BeanPostProcessor {
/**
* The type of the bean to create
*/
private Class<T> type;
/**
* The list of generic values. Can be String, Class or whatever
*/
private Iterable<G> generics;
private Map<String, G> beanNameToGeneric = new HashMap<String, G>();
public GenericBeanGenerator(Class<T> type, G[] generics) {
this.type = type;
this.generics = Arrays.asList(generics);
}
public GenericBeanGenerator(Class<T> type, Iterable<G> generics) {
this.type = type;
this.generics = generics;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// for each 'generic' value, add a bean definition to the beanFactory with the appropriate bean name
for(G generic : generics) {
String beanName = getBeanName(generic);
beanNameToGeneric.put(beanName, generic);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(beanName, new AnnotatedGenericBeanDefinition(type) );
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (type.isInstance(bean) && beanNameToGeneric.containsKey(beanName)) {
@SuppressWarnings("unchecked")
T instance = (T) bean;
initiliaze(beanName, beanNameToGeneric.get(beanName), instance);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* Convert a 'generic' value to a string in order to name the bean
*/
public String getBeanName(G generic) {
return generic.toString();
}
/**
* Initialize the bean if needed.
*/
public void initiliaze(String beanName, G generic, T instance) {
}
}
Now if I want a number of generics services that extends a class like...
class GenericService<T> {
Class<T> entityClass;
public void setEntityClass(Class<T> clazz) {
this.entityClass = clazz;
}
....
}
I could have something like this in one of my @Configuration beans
class Config {
@Bean
public static GenericBeanGenerator<GenericService, Class<?>> genericServiceGenerator() {
List<Class<?>> entities = Arrays.asList(A.class, B.class);
return new GenericBeanGenerator<GenericService, Class<?>>(GenericService.class, entities) {
@Override
public String getBeanName(Class<?> generic) {
return generic.getSimpleName() + "Service";
}
@Override
public void initiliaze(String beanName, Class<?> generic, GenericService instance) {
instance.setEntityClass(generic);
}
};
}
}
This would generate two beans named AService and BService for my entities A and B.
And even more powerfull: If my entities all implement an interface named Idable, I could generate all my service beans if I used something like this to scan my packages:
BeanDefinitionRegistry bdr = new SimpleBeanDefinitionRegistry();
ClassPathBeanDefinitionScanner s = new ClassPathBeanDefinitionScanner(bdr);
TypeFilter tf = new AssignableTypeFilter(Idable.class);
s.addIncludeFilter(tf);
s.scan("org.willix.model");
entities = bdr.getBeanDefinitionNames();
Upvotes: 2
Reputation: 74
You can try doing it programmatically:
public class Application {
@Autowired
private ApplicationContext applicationContext;
public void loadBeans() {
NewBean newBean = applicationContext.getAutowireCapableBeanFactory().createBean(NewBean.class);
}
}
You should also be able to autowire these beans after they have been created.
Edit:
You can name these beans using an annotation in the bean's class:
@Component("NewBean1")
public class NewBean1 implements GenericService<T> {
}
And then when you autowire it, use the @Qualifier annotation
public class BeanController {
@Autowired
@Qualifier("NewBean1")
private GenericService<T> bean;
}
Upvotes: 1