Spring Java Configuration: Create Bean From Dynamic Class Name - spring

My situation is that the class name of a bean can be configured by an external properties-file. It's fine to do this with XML
<bean id="myService" class="${app.serviceClass}" />
and properties-file
app.serviceClass=com.example.GreatestClassThereIs
I tried to convert it to Java using a BeanFactoryPostProcessor:
#Configuration
public class MyConfiguration implements BeanFactoryPostProcessor {
#Value("${app.serviceClass}")
private String serviceClassName;
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
logger.warn("let's register bean of class " + serviceClassName + "...");
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(serviceClassName);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("myService", beanDefinition);
logger.warn("done " + beanDefinition);
}
}
The problem is that the lifecycle of Spring hasn't yet set handled the #Value and serviceClassName is null. How do I get the property in there?

Why not simply define a new Bean in your ApplicationContext by using Class.forName() from the #Value injected into the #Configuration?
So something like this:
#Configuration
public class MyConfiguration
{
#Value("${app.serviceClass}")
private String serviceClassName;
#Bean
public Object myService()
{
return Class.forName(serviceClassName).newInstance();
}
}
EDIT by sjngm (for better readability than in the comment):
#Bean
public MyInterface myService()
{
Class<?> serviceClass = Class.forName(serviceClassName);
MyInterface service = MyInterface.class.cast(serviceClass.newInstance());
return service;
}

Related

Defining constructor in prototype bean

Using SpringBoot, I have a Component bean that is defined as #Scope("protoype"). It also injects another prototype bean
The class is defined as
#Component
#Scope("prototype")
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(DataObj data) {
this.injectedBean = beanFactory.getBean(InjectedBean.class, data)
}
}
However, IntelliJ complains about the data field on the constructor: Could not autowire. No beans of 'DataObj' type found.. But DataObj is a POJO. I pass it in at runtime in order to create the bean. Am I defining the constructor incorrectly?
Update
Had the same problem doing it this way. It still wants to treat DataObj as a bean on the factory constructor class. Doesn't matter if I annotate the class with #Component or #Configuration
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
Also tried this example from that same link:
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
Update
I think I resolved this with some information in Spring Java Config: how do you create a prototype-scoped #Bean with runtime arguments?. Part of my problem is that I tried to put the factory bean in the Component itself, which doesn't work
In other words
#Component
public class MyClass{
#Autowired
public BeanFactory beanFactory
private InjectedBean injectedBean
public MyClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
#Bean
#Scope("prototype")
public MyClass myClass(DataObj data) {
InjectedBean injectedBean = beanFactory.getBean(InjectedBean.class, data)
return new MyClass(injectedBean);
}
}
In this cass, Spring tries to create a MyClass bean because of the #Component annotation, but another MyClass bean due to the #Bean annotation.
So I moved the #Bean to another class
#Configuration
public class ServiceConfig {
#Bean
public Function<DataObj, MyClass> thingFactory() {
return data-> myClass(data); //
}
#Bean
#Scope(value = "prototype")
public MyClass myClass(DataObj data) {
return new MyClass(data);
}
}
This appears to work, but IntelliJ still complains about DataObj. This might be an Intellij issue

how to add a dynamically created bean in a #PostConstruct in Spring Boot

I need to add a dynamically created bean when my 'normal' bean gets created. I tried this so far:
//generate a health bean dynamically, and register it
#PostConstruct
public void init(){
solrhealth = new SolrHealthIndicator(solr);
//context.??
}
I build a SolrHealthIndicatior bean programatically, as I am not using Spring Solr Data. Now I want it registered so it shows up in /health.
I have my context wired, but cannot find how to register the newly created bean in there...
You should be able to programatically define your bean by using the #Bean annotation within a #Configuration class.
#Bean
public SolrHealthIndicator solrHealthIndicatior() {
//you can construct the object however you want
return new SolrHealthIndicator();
}
Then you can just inject it like any other bean(#Autowired constructor, field, setter injection, etc.), if there are multiple beans with the same type you can use #Qualifier to distinguish between them.
You need to use #Lookup annotation.
#Component
public class SolrHealthIndicator {
public SolrHealthIndicator(Solr solr) {
}
}
public class BeanInQuestion {
#PostConstruct
public void init() {
solrHealthIndicator = getHealthIndicatorBean();
}
#Lookup
public SolrHealthIndicator getHealthIndicatorBean() {
//Spring creates a runtime implementation for this method
return null;
}
}
You could make the class containing your #PostConstruct implement BeanDefinitionRegistryPostProcessor. Then you'd then be able to register your beans programmatically:
#Bean
public class MyBean implements BeanDefinitionRegistryPostProcessor {
private BeanDefinitionRegistry registry;
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
}
#PostConstruct
public void init(){
registry.registerBeanDefinition("solrHealthIndicator", new SolrHealthIndicator(solr));
}
}

#Bean configuration of #Scheduled components

First time looking at Spring - and I have a question regarding configuration of a bean that I also would like to schedule.
Using Spring Boot, I've created a Application class, and a TaskClass that I would both like to Schedule and configure using a configuration class.
Application Class
#SpringBootApplication
#EnableScheduling
#ComponentScan("mmmi.pdws.cetrea")
public class Application {
public static void main(String[] args) {
SpringApplication.run(PdwsBackend.class, args); //Leftover from Boot project
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
taskClass task = ctx.getBean(taskClass.class);
}
}
TaskClass
#Component
public class TaskClass {
private String taskName;
#Scheduled(fixedRate=1000)
public void lifeSign(){
System.out.println("My name is " + taskName);
}
public void setName(String name){this.name = name}
}
AppConfig class
#Configuration
public class AppConfig {
#Bean
public TaskClass taskClass(){
TaskClass task = new TaskClass();
task.setName("SpringTask");
return task;
}
}
The instantiated object from the Scheduler and from the AppConfig class are naturally not the same...but is there a way to configure the Scheduled object?
Looking at Springs scheduling reference it seems to be doable with XML configuration? But what if I would like to do it with a Java configuration class?
If the value that you want to set is constant, you can put it in your application.properties file and inject it to using the #Value annotation directly into the scheduler bean.
application.properties
task.name=SpringTask
TaskClass
#Component
public class TaskClass {
private final String taskName;
public TaskClass(#Value("${task.name}") String taskName) {
this.taskName = taskName;
}
#Scheduled(fixedRate=1000)
public void lifeSign(){
System.out.println("My name is " + taskName);
}
public void setName(String name){this.name = name}
}
You can also inject the value directly to the field if you don't like constructor injection.
Of course the bean declared in the AppConfig is redundant since the TaskClass is annotated with #Component and will be found by #ComponentScan.
From the same spring ref link, you can read the following:
Make sure that you do not use #Configurable on bean classes which are annotated with #Scheduled and registered as regular Spring beans with the container: You would get double initialization otherwise, once through the container and once through the #Configurable aspect.
What you can do is simply use #PostConstruct method (called only once after bean creation) in your TaskClass to set taskName as shown below and then you don't need your AppConfig class:
#Component
public class TaskClass {
private String taskName;
#Postconstruct
public void init() {
taskName= "SpringTask";
}
#Scheduled(fixedRate=1000)
public void lifeSign(){
System.out.println("My name is " + taskName);
}
public void setName(String name){this.name = name}
}

Create spring managed beans with factory

I have a spring application in which I am trying to inject many beans of the same type. I do not know how many of these beans there will be before runtime, so it seems natural to use the factory pattern, as I cannot configure each bean in my java config class. However, these beans need to have some of their fields wired by Spring, and when I create them with "new" in my factory, they are of course not Spring managed.
Is there a way to have the beans I create in my factory class be managed by Spring? Or is the factory pattern the wrong way to go about this?
I am fairly new to posting, so please let me know if any more information is necessary.
You can define a beanFactory wired with the dependencies needed for your bean, then manually injected them in each new bean created by the beanFactory. For example:
public class MyBean {
private Dependency1 dep1;
private Dependency2 dep2;
public MyBean(Dependency1 dep1, Dependency2 dep2) {
this.dep1 = dep1;
this.dep2 = dep2;
}
}
#Component
public class MyBeanFactory {
#Autowired
private Dependency1 dep1;
#Autowired
private Dependency2 dep2;
public MyBean createInstance() {
return new MyBean(dep1, dep2);
}
}
#Component
public class MyBeanConsumer {
#Autowired
private MyBeanFactory myBeanFactory;
public void foo() {
final MyBean bean = myBeanFactory.createInstance();
}
}
You can't use #Autowired because of the variable number of beans, but you can still make use of ApplicationContextAware to create obtain the beans.
Using that you can programmatically create prototype beans from your Java code if the type of bean has been defined before in the configuration, or alternatively you can create the new object in your factory using new, and then set the dependencies by using this same method.
This is an example of an implementation:
public final class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
public void setApplicationContext(ApplicationContext context) throws BeansException {
CONTEXT = context;
}
public static Object getBean(String beanName) {
return CONTEXT != null ? CONTEXT.getBean(beanName) : null;
}
public static <T> T getBean(Class<T> objectClass) {
return CONTEXT != null ? CONTEXT.getBean(objectClass) : null;
}
}

Spring proxy beans from Interface

What is the correct way to create proxy beans by interfaces?
public class JdbiRepositoryAnnotationBeanPostProcessorTest {
private DBI dbi = mock(DBI.class);
#org.junit.Test
public void testIncompleteBeanDefinition() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(JdbiRepositoryAnnotationBeanPostProcessor.class);
ctx.register(MyConfig.class);
ctx.refresh();
ITest bean = ctx.getBean(ITest.class);
assertNotNull(bean);
}
#JdbiRepository
public static interface ITest {
}
#Configuration
#ComponentScan(basePackageClasses = {ITest.class},
includeFilters = {#ComponentScan.Filter(type = FilterType.ANNOTATION, value = JdbiRepository.class)})
public static class MyConfig {
}
}
I have tried bean post processor but It did not help me.
Edit:
I wanted to use component scanning by including annotation filter but it did not help me too.
Edit:
I want to create instances by another library which is creating proxy beans as this:
TestInterface proxy = factory.onDemand(TestInterface.class);
Edit:
I have extended InstantiationAwareBeanPostProcessorAdapter for JdbiRepositoryAnnotationBeanPostProcessor.
I have been just logging currently. But I can not see my interfaces as a bean.
Please note that I have also changed my test code above.
public class JdbiRepositoryAnnotationBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if(!(beanFactory instanceof ConfigurableListableBeanFactory)) {
throw new IllegalArgumentException("AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory");
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
// this.dbiMap = this.beanFactory.getBeansOfType(DBI.class);
}
}
The problem was related to ComponentScanning not PostBeanProcessor. ComponentScan is scanning only concrete classes that is why my processor did not work. I had to create a custom importer for interfaces.

Resources