Beans added dynamically are not considered by ConditionalOnBean - java

I am registering beans dynamically at startup with BeanDefinitionRegistryPostProcessor:
Bean class:
#RequiredArgsConstructor
public class DynamicBean {
private final String name;
private final Object value;
}
Properties class:
#Data
public class DynamicProperties {
private List<Bean> beans;
#Data
public static class Bean {
private String name;
private Object value;
}
}
BeanDefinitionRegistryPostProcessor:
#Configuration
public class DynamicBeanRegistrar implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
DynamicProperties dynamicProperties = Binder.get(environment)
.bind("dynamic", Bindable.of(DynamicProperties.class))
.orElseThrow(IllegalStateException::new);
dynamicProperties.getBeans().forEach(bean -> {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicBean.class);
String beanName = bean.getName();
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addGenericArgumentValue(bean.getName());
cav.addGenericArgumentValue(bean.getValue());
beanDefinition.setConstructorArgumentValues(cav);
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
log.info("Registered dynamic bean")
});
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
// noop
}
}
I also have another configuration class using #ConditionalOnBean annotation for some beans:
#Configuration(proxyBeanMethods = false)
public class AnotherConfiguration {
#Bean
public Object a() {
return new Object ();
}
#Bean
public Object b() {
return new Object ();
}
#Bean
#ConditionalOnBean(DynamicBean.class)
public Object x() {
return new Object();
}
#Bean
#ConditionalOnBean(DynamicBean.class)
public Object y() {
return new Object();
}
}
The problem is that the #ConditionalOnBean annotation doesn't seem to know about the dynamically registered beans and is therefore evaluating as false, causing beans x and y not to be created.
I have tried using #AutoConfigureBefore(AnotherConfiguration.class) on my DynamicBeanRegistrar as well as implementing PriorityOrdered, but that didn't seem to have any effect.
Is there any way to achieve the expected behaviour?

Related

Spring Boot Custom Bean Loader

I am using JDBI in tandem with Spring Boot. I followed this guide which results in having to create a class: JdbiConfig in which, for every dao wanted in the application context, you must add:
#Bean
public SomeDao someDao(Jdbi jdbi) {
return jdbi.onDemand(SomeDao.class);
}
I was wondering if there is some way within Spring Boot to create a custom processor to create beans and put them in the application context. I have two ideas on how this could work:
Annotate the DAOs with a custom annotation #JdbiDao and write something to pick those up. I have tried just manually injecting these into the application start up, but the problem is they may not load in time to be injected as they are not recognized during the class scan.
Create a class JdbiDao that every repository interface could extend. Then annotate the interfaces with the standard #Repository and create a custom processor to load them by way of Jdbi#onDemand
Those are my two ideas, but I don't know of any way to accomplish that. I am stuck with manually creating a bean? Has this been solved before?
The strategy is to scan your classpath for dao interface, then register them as bean.
We need: BeanDefinitionRegistryPostProcessor to register additional bean definition and a FactoryBean to create the jdbi dao bean instance.
Mark your dao intercface with #JdbiDao
#JdbiDao
public interface SomeDao {
}
Define a FactoryBean to create jdbi dao
public class JdbiDaoBeanFactory implements FactoryBean<Object>, InitializingBean {
private final Jdbi jdbi;
private final Class<?> jdbiDaoClass;
private volatile Object jdbiDaoBean;
public JdbiDaoBeanFactory(Jdbi jdbi, Class<?> jdbiDaoClass) {
this.jdbi = jdbi;
this.jdbiDaoClass = jdbiDaoClass;
}
#Override
public Object getObject() throws Exception {
return jdbiDaoBean;
}
#Override
public Class<?> getObjectType() {
return jdbiDaoClass;
}
#Override
public void afterPropertiesSet() throws Exception {
jdbiDaoBean = jdbi.onDemand(jdbiDaoClass);
}
}
Scan classpath for #JdbiDao annotated interfaces:
public class JdbiBeanFactoryPostProcessor
implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, EnvironmentAware, BeanClassLoaderAware, BeanFactoryAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private Environment environment;
private ClassLoader classLoader;
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// By default, scanner does not accept regular interface without #Lookup method, bypass this
return true;
}
};
scanner.setEnvironment(environment);
scanner.setResourceLoader(resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(JdbiDao.class));
List<String> basePackages = AutoConfigurationPackages.get(beanFactory);
basePackages.stream()
.map(scanner::findCandidateComponents)
.flatMap(Collection::stream)
.forEach(bd -> registerJdbiDaoBeanFactory(registry, bd));
}
private void registerJdbiDaoBeanFactory(BeanDefinitionRegistry registry, BeanDefinition bd) {
GenericBeanDefinition beanDefinition = (GenericBeanDefinition) bd;
Class<?> jdbiDaoClass;
try {
jdbiDaoClass = beanDefinition.resolveBeanClass(classLoader);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
beanDefinition.setBeanClass(JdbiDaoBeanFactory.class);
// Add dependency to your `Jdbi` bean by name
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("jdbi"));
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(jdbiDaoClass));
registry.registerBeanDefinition(jdbiDaoClass.getName(), beanDefinition);
}
}
Import our JdbiBeanFactoryPostProcessor
#SpringBootApplication
#Import(JdbiBeanFactoryPostProcessor.class)
public class Application {
}

Create singelton beans from enum

I have an enum that looks like the following
public enum MyBeanType {
Type1,
Type2
...
Type100;
}
I would like to create a Bean for each of these enum values.
public Class MyBean {
private MyBeanType type;
public MyBean(MyBeanType type) { this.type = type; }
}
I know I could list each of these in my config like so:
#Configuration
public class MyBeanConfig() {
#Bean public MyBean myBeanType1() { return new MyBean(MyBeanType.Type1);
#Bean public MyBean myBeanType2() { return new MyBean(MyBeanType.Type2);
...
#Bean public MyBean myBeanType100() { return new MyBean(MyBeanType.Type100);
}
But is there a way to do this more dynamically?
I'm typically wiring all of these in as a List, but there are some instances where I'd like to wire myBeanType2 by name as well.
Write a custom BeanFactoryPostProcessor to play around with bean definitions.
#Bean
public BeanFactoryPostProcessor getBeanFactoryPostProcessor() {
return beanFactory -> {
for (int i = 0; i < MyBeanType.values().length; i++) {
beanFactory.registerSingleton(MyBeanType.class.getSimpleName() + i,
new MyBean(MyBeanType.values()[i]));
}
};
}
You could simply register the beans programmatically. Something like this should do it.
#Configuration
public class MyBeanConfig() implements ApplicationContextAware {
#Override
public void setApplicationContext(final ApplicationContext ctx) {
final ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) ctx).getBeanFactory();
for(final MyBeanType beanType: MyBeanType.values()) {
beanFactory.registerSingleton(MyBean.class.getCanonicalName() + "_" + beanType, new MyBean(beanType));
}
}
}

What is the proper spring boot way to apply dependency injection

I am currently working on an spring boot application that wires some beans together in the following way (heavily simplified example):
#Component
#Order(0)
public class PlayingFieldByBeans implements CommandLineRunner {
#Override
public void run(String... arg0) throws Exception {
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Benedict");
names.add("Chloe");
System.out.println("Printing from lazy beans variant: ");
names.forEach(n -> {
System.out.println(player(n));
});
}
#Bean
#Lazy
public Player player(String name) {
return new Player(name, shoes());
}
#Bean
#Lazy
private Shoes shoes() {
return new Shoes("Adidas");
}
}
The actual beans however, require alot more configuration and setting than is shown here and it takes quite alot of lines of code in the PlayingFieldByBeans class when using the inner Lazy Bean methodology. So I created a different way of wiring it together using Component annotation:
#Component
#Order(1)
public class PlayingFieldByComponents implements CommandLineRunner {
#Autowired
private PlayerComponent playerComponent;
#Override
public void run(String... arg0) throws Exception {
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Benedict");
names.add("Chloe");
System.out.println("Printing from component variant: ");
names.forEach(n -> {
System.out.println(playerComponent.player(n));
});
}
}
The PlayerComponent class looks like this:
#Component
public class PlayerComponent {
#Autowired
private ShoesComponent shoesComponent;
public Player player(String name) {
return new Player(name, shoesComponent.shoes());
}
}
The ShoesComponent is very similar to the PlayerComponent class.
For maintainablity and TDD purposes I am not sure what is the most proper way to use the spring framework here.
Question
Given the Player and Shoes beans require more then just one line of initialization (multiple settings, multiple dependencies on other beans etc), what is the best way to design and wire them?
Edit - based on suggestion
Added a configuration class to bundle the beans:
#Configuration
public class BeanConfiguration {
#Bean
#Lazy
public Player player(String name) {
return new Player(name, shoes());
}
#Bean
#Lazy
public Shoes shoes() {
return new Shoes("Adidas");
}
}
And the matching executing class:
#Component
#Order(2)
public class PlayingFieldByConfiguration implements CommandLineRunner {
#Autowired
private BeanConfiguration beanConfiguration;
#Override
public void run(String... arg0) throws Exception {
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Benedict");
names.add("Chloe");
System.out.println("Printing from component variant: ");
names.forEach(n -> {
System.out.println(beanConfiguration.player(n));
});
}
}
Re uses the same first bean, so it doesn't seem to create a new one
Printing from component variant:
Player name: Alex has shoes of brand: Adidas
Player name: Alex has shoes of brand: Adidas
Player name: Alex has shoes of brand: Adidas
One solution would be to change scope of Player bean (and Shoes later on if we want to create different brands) as mentioned by Andriy Slobodyanyk
#Configuration
public class BeanConfiguration {
#Bean
#Lazy
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public Player player(String name) {
return new Player(name, shoes());
}
#Bean
#Lazy
public Shoes shoes() {
return new Shoes("Adidas");
}
}
If above would not be sufficient (since you mentioned real case scenario is more compilcated) another option is to use FactoryBean
public class PlayerFactoryBean implements FactoryBean<Player> {
private String name;
private Shoes shoes;
public void setName(String name) {
this.name = name;
}
public void setShoes(Shoes shoes) {
this.shoes = shoes;
}
#Override
public Player getObject() throws Exception {
//initialization logic goes here
System.out.println("Creating bean using factory");
return new Player(name, shoes);
}
#Override
public Class<Player> getObjectType() {
return Player.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
#Configuration
public class BeanConfiguration {
#Bean
#Lazy
public Shoes shoes() {
return new Shoes("Adidas");
}
#Bean
public PlayerFactoryBean playerFactoryBean(){
PlayerFactoryBean pfb = new PlayerFactoryBean();
pfb.setShoes(shoes());
return pfb;
}
}
#Component
#Order(2)
public class PlayingFieldByConfiguration implements CommandLineRunner {
#Autowired
private PlayerFactoryBean factoryBean;
#Override
public void run(String... arg0) throws Exception {
List<String> names = new ArrayList<>();
names.add("Alex");
names.add("Benedict");
names.add("Chloe");
System.out.println("Printing from component variant: ");
names.forEach(n -> {
try {
factoryBean.setName(n);
System.out.println(factoryBean.getObject());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

FactoryBean doesn't use when bean instantiates

I am investigating how does FactoryBean works in spring framework.
As I understand it allow configure instantiation process.
I have the following beans:
#Component
public class MyInjectionClass {
String name;
Integer age;
//get and set methods
}
and
#Component
public class MyComponent {
#Autowired
MyInjectionClass myInjectionClass;
public MyInjectionClass getMyInjectionClass() {
return myInjectionClass;
}
}
and following cutom FactoryBean:
#Component
public class MyInjectionClassFactoryBean implements FactoryBean<MyInjectionClass> {
#Override
public MyInjectionClass getObject() throws Exception {
MyInjectionClass myInjectionClass = new MyInjectionClass();
myInjectionClass.setName("name");
myInjectionClass.setAge(12);
return myInjectionClass;
}
#Override
public Class<?> getObjectType() {
return MyInjectionClass.class;
}
#Override
public boolean isSingleton() {
return false;
}
}
Also I have wrote following code in my main method:
MyComponent bean = context.getBean(MyComponent.class);
System.out.println(bean.getMyInjectionClass().getAge());
It returns null.
What did I forget to do ?
P.S.
I use #ComponentScan("com.example.domain")
All beans and FactoryBean located there.
Solution
remove #Component above MyInjectionClass

How to inject ANY information about test in spring testing?

I would like some of my beans know something about test. SOMETHING. May be test class name or some of it's methods.
For example, suppose my test class has a method
public String getTestName() {
return getClass().getSimpleName();
}
This method returns test name and can be overridden.
Is it possible to inject this name into some beans of Spring context, to use during test?
For example, with autowire feature:
#Autowired
public String testName;
not only in test class, but in other beans too.
UPDATE
Below are two (failed) attempts to implement injecting testInstance. May be there are some convenient ways to do that?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestClassAwareTry._Config.class)
#TestExecutionListeners(value = { TestClassAwareTry._Listener.class },
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class TestClassAwareTry {
/**
* Interface to tag beans, who want to know if they are in test
*/
public interface TestInstanceAware {
void setTestInstance(Object value);
}
/**
* Sample bean, which would like to know if it is in test
*/
public static class MyBean implements TestInstanceAware {
private Object testInstance;
{
System.out.println("MyBean constructed");
}
public void setTestInstance(Object value) {
this.testInstance = value;
System.out.println("testInstance set");
}
public Object getTestInstance() {
return testInstance;
}
}
/**
* Attempt to inject testInstance with a bean, implementing {#link BeanPostProcessor}
*/
public static class TestInstanceInjector implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if( bean instanceof TestInstanceAware ) {
TestInstanceAware aware = (TestInstanceAware) bean;
// we don't have access to test instance here
// otherwise I would write
//Object testInstance = getTestInstance();
//aware.setTestInstance(testInstance);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
/**
* Attempt to inject testInstance with test execution listener
*/
public static class _Listener extends AbstractTestExecutionListener {
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
Object testInstance = testContext.getTestInstance();
ApplicationContext context = testContext.getApplicationContext();
// we don't have setBean() method
// I would write if I have
// context.setBean("testInstance", context);
}
}
/**
* Java-based configuration
*/
#Configuration
public class _Config {
#Bean
public MyBean myBean() {
return new MyBean();
}
#Bean
public TestInstanceInjector testInstanceInjector() {
return new TestInstanceInjector();
// I would acquire test instance here and pass it to constructor, if I can
}
}
#Autowired
public MyBean myBean;
#Test
public void testInjected() {
assertSame( this, myBean.getTestInstance());
}
}
I've ended up creating ContextCustomizerFactory that registers BeanPostProcessor
package com.company.testing.base.spring;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
public class TestAwareContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.addBeanPostProcessor(
new TestInstanceAwareBeanPostProcessor(mergedConfig.getTestClass()));
};
}
}
TestInstanceAwareBeanPostProcessor
public class TestInstanceAwareBeanPostProcessor implements BeanPostProcessor {
private final Class<?> testClass;
TestInstanceAwareBeanPostProcessor(Class<?> testClass) {
this.testClass = testClass;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof TestClassAware) {
((TestClassAware) bean).setTestClass(testClass);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
resources/META-INF/spring.factories
# ContextCustomizerFactories for the Spring TestContext Framework
org.springframework.test.context.ContextCustomizerFactory = \
com.company.testing.base.spring.TestAwareContextCustomizerFactory
The only way I've been able to do this is by delaying creation of the subject until you are in the test method and to have the bean in the prototype scope.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { LiveConfig.class, DevConfig.class})
#ActiveProfiles("Dev")
public class MeTest {
#Autowired
public ApplicationContext context;
#Autowired
DevConfig devConfig;
#Rule
public TestName nameRule = new TestName();
#Before
public void setName() {
devConfig.setSettings(nameRule.getMethodName());
}
#Test
public void test() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
#Test
public void test2() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
}
#Configuration
class LiveConfig {
#org.springframework.context.annotation.Bean
public String getSettings() {
return "/some/real/file.txt";
}
#org.springframework.context.annotation.Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Bean getBean() {
return new Bean();
}
}
#Configuration
class DevConfig {
private String settings;
#org.springframework.context.annotation.Bean
#Profile("Dev")
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public String getSettings() {
return settings;
}
public void setSettings(String settings) {
this.settings = settings;
}
}
class Bean {
public Bean() {
System.out.println("Bean");
}
String settings;
#Autowired
void setSettings(String settings) {
System.out.println("Settings: " + settings);
this.settings = settings;
}
}
This uses Profiles to change what Live sees and what the tests see, and the a NameRule to get the name. It is clunky.
I would NOT use the TestName rule, but rather the TemporaryFolder rule and use that to set whatever setting your application uses for the output folder. I'd also only use DI in a test in very rare cases (i.e. full blown integration tests).
Do you mean like this?
public class MyTest {
#Test
public void testName() {
MyBean b = new MyBean(MyTest.class.getSimpleName());
b.doSomething();
}
}
You can achieve this in a more elegant way using Spring Boot Auto configuration feature by making yours, this way:
define a Configuration class that exposes or registers your bean this way:
#Configuration
public class MyBeanProviderConfiguration {
#ConditionalOnMissingBean
#Bean
public MyBean myBean() {
// return a fully initialised MyBean instance
}
}
Then define a custom annotation Spring Boot like, say #AutoConfigureMyBean this way:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ImportAutoConfiguration(MyBeanProviderConfiguration.class)
public #interface AutoConfigureMyBean {}
Then you can use this in your Spring test, here is an example:
#RunWith(SpringRunner.class)
#AutoConfigureMyBean
public class MyTest {
#Autowired
MyBean myBean;
}
Or also declare your MyBean #Autowired dependent bean in a regular Spring test (using a Config class), A MyBean instance will be automatically injected into it.

Categories

Resources