willix
willix

Reputation: 686

How to generate a list of beans dynamically

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

Answers (2)

willix
willix

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

John Miller
John Miller

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

Related Questions