Add Bean Programmatically to Spring Web App Context
Asked Answered
S

7

69

Because of a plug-in architecture, I'm trying to add a bean programmatically to my webapp. I have a Spring bean created through the @Component annotation, and i am implementing the ApplicationContextAware interface.

My override function looks like this:

@Override
public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {

    // this fails
    this.applicationContext = (GenericWebApplicationContext) applicationContext;
 }

Basically, I can't figure out how to add a bean to the applicationContext object given to setApplicationContext. Can anyone tell me how I am going about this the wrong way?

Ok, this is what i ended up with as the solution:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry bdr)
        throws BeansException {
    BeanDefinition definition = new RootBeanDefinition(
            <My Class>.class);

    bdr.registerBeanDefinition("<my id>", definition);
}
Stemma answered 27/12, 2010 at 18:4 Comment(0)
P
53

In Spring 3.0 you can make your bean implement BeanDefinitionRegistryPostProcessor and add new beans via BeanDefinitionRegistry.

In previous versions of Spring you can do the same thing in BeanFactoryPostProcessor (though you need to cast BeanFactory to BeanDefinitionRegistry, which may fail).

Phaeton answered 27/12, 2010 at 18:11 Comment(3)
thank you. i guess the hardest part is knowing what to look for.Stemma
Please take care that as per JavaDoc: A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.Carbonic
What to do with BeanDefinitionRegistryPostProcessor? Suppose I wrote this class. What next? How to activate it?Enceladus
A
71

Here is a simple code:

ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
beanFactory.registerSingleton(bean.getClass().getCanonicalName(), bean);
Aguste answered 15/5, 2014 at 7:34 Comment(3)
can you elabore more on the answer?Brannon
Is applicationContext guaranteed to be instance of ConfigurableApplicationContext? (If not, this could unfortunately prevent the application from deploying)Donetsk
How does the bean class created with properties ? Can you please post the full stackImmemorial
P
53

In Spring 3.0 you can make your bean implement BeanDefinitionRegistryPostProcessor and add new beans via BeanDefinitionRegistry.

In previous versions of Spring you can do the same thing in BeanFactoryPostProcessor (though you need to cast BeanFactory to BeanDefinitionRegistry, which may fail).

Phaeton answered 27/12, 2010 at 18:11 Comment(3)
thank you. i guess the hardest part is knowing what to look for.Stemma
Please take care that as per JavaDoc: A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing BeanPostProcessor instead.Carbonic
What to do with BeanDefinitionRegistryPostProcessor? Suppose I wrote this class. What next? How to activate it?Enceladus
P
11

Why do you need it to be of type GenericWebApplicationContext?
I think you can probably work with any ApplicationContext type.

Usually you would use an init method (in addition to your setter method):

@PostConstruct
public void init(){
    AutowireCapableBeanFactory bf = this.applicationContext
        .getAutowireCapableBeanFactory();
    // wire stuff here
}

And you would wire beans by using either

AutowireCapableBeanFactory.autowire(Class, int mode, boolean dependencyInject)

or

AutowireCapableBeanFactory.initializeBean(Object existingbean, String beanName)

Presbyopia answered 27/12, 2010 at 18:17 Comment(1)
When I do this, it does not add them to the ApplicationContextBaptiste
S
2

Actually AnnotationConfigApplicationContext derived from AbstractApplicationContext, which has empty postProcessBeanFactory method left for override

/**
 * Modify the application context's internal bean factory after its standard
 * initialization. All bean definitions will have been loaded, but no beans
 * will have been instantiated yet. This allows for registering special
 * BeanPostProcessors etc in certain ApplicationContext implementations.
 * @param beanFactory the bean factory used by the application context
 */
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}

To leverage this, Create AnnotationConfigApplicationContextProvider class which may look like following(given for Vertx instance example, you can use MyClass instead)...

public class CustomAnnotationApplicationContextProvider {
private final Vertx vertx;

public CustomAnnotationApplicationContextProvider(Vertx vertx) {
    this.vertx = vertx;
}

/**
 * Register all beans to spring bean factory
 *
 * @param beanFactory, spring bean factory to register your instances
 */
private void configureBeans(ConfigurableListableBeanFactory beanFactory) {
    beanFactory.registerSingleton("vertx", vertx);
}

/**
 * Proxy method to create {@link AnnotationConfigApplicationContext} instance with no params
 *
 * @return {@link AnnotationConfigApplicationContext} instance
 */
public AnnotationConfigApplicationContext get() {
    return new AnnotationConfigApplicationContext() {

        @Override
        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            super.postProcessBeanFactory(beanFactory);
            configureBeans(beanFactory);
        }
    };
}

/**
 * Proxy method to call {@link AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(DefaultListableBeanFactory)} with our logic
 *
 * @param beanFactory bean factory for spring
 * @return
 * @see AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(DefaultListableBeanFactory)
 */
public AnnotationConfigApplicationContext get(DefaultListableBeanFactory beanFactory) {
    return new AnnotationConfigApplicationContext(beanFactory) {

        @Override
        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            super.postProcessBeanFactory(beanFactory);
            configureBeans(beanFactory);
        }
    };
}

/**
 * Proxy method to call {@link AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class[])} with our logic
 *
 * @param annotatedClasses, set of annotated classes for spring
 * @return
 * @see AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class[])
 */
public AnnotationConfigApplicationContext get(Class<?>... annotatedClasses) {
    return new AnnotationConfigApplicationContext(annotatedClasses) {

        @Override
        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            super.postProcessBeanFactory(beanFactory);
            configureBeans(beanFactory);
        }
    };
}

/**
 * proxy method to call {@link AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)} with our logic
 *
 * @param basePackages set of base packages for spring
 * @return
 * @see AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(String...)
 */
public AnnotationConfigApplicationContext get(String... basePackages) {
    return new AnnotationConfigApplicationContext(basePackages) {

        @Override
        protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            super.postProcessBeanFactory(beanFactory);
            configureBeans(beanFactory);
        }
    };
}
}

While creating ApplicationContext you can create it using

Vertx vertx = ...; // either create or for vertx, it'll be passed to main verticle
ApplicationContext context = new CustomAnnotationApplicationContextProvider(vertx).get(ApplicationSpringConfig.class);
Stop answered 11/4, 2018 at 14:3 Comment(0)
P
1

First initialize Property values

MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
mutablePropertyValues.add("hostName", details.getHostName());
mutablePropertyValues.add("port", details.getPort());

DefaultListableBeanFactory context = new DefaultListableBeanFactory();
GenericBeanDefinition connectionFactory = new GenericBeanDefinition();
connectionFactory.setBeanClass(Class);
connectionFactory.setPropertyValues(mutablePropertyValues);

context.registerBeanDefinition("beanName", connectionFactory);

Add to the list of beans

ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
beanFactory.registerSingleton("beanName", context.getBean("beanName"));
Phidias answered 10/5, 2020 at 14:9 Comment(0)
P
1

If someone looks how to add bean to the spring application before running it and made it by programmatically way, then there is simple example:

Here some bean for health checks, which we want to register in application:

// you no need @Component stereotype annotations here. You go programmatically way
public class HikariPoolHealthIndicatorImpl implements HealthIndicator {
    @Override
    public Health health() {
        if (health < 0.05) {
            return Health.status(Status.DOWN).withDetail("health is not good", health).build();
        }
        return Health.up().build();
    }
}

And how we are registering it with application:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        // Creating our application
        SpringApplication application = new SpringApplication(MyApplication.class);
        // Adding initializer for application context
        application.addInitializers((ApplicationContextInitializer) applicationContext -> {
            ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
            // Here goes our bean
            HikariPoolHealthIndicatorImpl  bean = new HikariPoolHealthIndicatorImpl ();
            // Registering it
            beanFactory.registerSingleton("Some name for our bean in one word", bean);
        });
        // Starting application, our bean in action 👍
        application.run(args);
    }
}
Pretorius answered 1/5, 2023 at 18:23 Comment(0)
M
0

Here are some more tips on how to create beans programmatically.

I used to create beans like this

private void createBean(final DefaultListableBeanFactory factory, final String name, final Object bean) {
    //apply BeanPostProcessors
    final var instance = factory.initializeBean(bean, name);
    //creates bean definition, autowire dependent beans if such exists, setPropertyValues
    factory.autowireBeanProperties(instance, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
    //add bean to DefaultSingletonBeanRegistry registeredSingletons and singletonObjects
    factory.registerSingleton(name, instance);
}

And it was quite fine. All dependencies were resolved and properties were set. Besides, in some cases it's quite enough to call only SingletonBeanRegistry#registerSingleton(String, Object).

However, when creating beans with generics I faced NoUniqueBeanDefinitionException exception ("expected single matching bean but found ") when they are autowired.

The solution was to register bean definition and set RootBeanDefinition.targetType explicitly. Finally,

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;

import java.util.function.Supplier;

@Configuration
class CreateBeanConfig {
    CreateBeanConfig(final DefaultListableBeanFactory factory) {
        createBean(factory, "testIntBean", (Supplier<Integer>)() -> 1, Supplier.class, Integer.class);
        createBean(factory, "testStringBean", (Supplier<String>)() -> "testString", Supplier.class, String.class);
    }

    private void createBean(
            final DefaultListableBeanFactory factory,
            final String name,
            final Object bean,
            final Class<?> type,
            final Class<?>... generics
    ) {
        final var bd = new RootBeanDefinition();
        bd.setTargetType(ResolvableType.forClassWithGenerics(type, generics));
        bd.setAutowireCandidate(true);
        bd.setScope(BeanDefinition.SCOPE_SINGLETON);
        bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        factory.registerBeanDefinition(name, bd);
        //apply BeanPostProcessors, autowire dependent beans if such exists, setPropertyValues
        final var instance = factory.configureBean(bean, name);
        //add bean to DefaultSingletonBeanRegistry registeredSingletons and singletonObjects
        factory.registerSingleton(name, instance);
    }
}

Besides, AbstractAutowireCapableBeanFactory#configureBean(Object,String) requires bean definition created otherwise it throws exception. So, if you want to use this method you need bean definition anyway.

If NoUniqueBeanDefinitionException is not reproduced in your Spring version for the first approach, feel free to use it.

Mizzle answered 21/1 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.