Create singelton beans from enum - java

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));
}
}
}

Related

Beans added dynamically are not considered by ConditionalOnBean

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?

Spring Bean Factory Using Class Name

I have an interface/implementation like so:
public interface Processor {
void processMessage(Message m);
}
#Component
public class FooAProcessor implements Processor {
private FooA fooA;
public FooAProcessor(FooA fooA) {
this.fooA = fooA;
}
#Override
public void processMessage(Message m) {
//do stuff
}
}
#Component
public class FooBProcessor implements Processor {
private FooA fooA;
public FooBProcessor(FooA fooA) {
this.fooA = fooA;
}
#Override
public void processMessage(Message m) {
//do stuff
}
}
The FooA bean is simple, like this:
#Component
public class FooA {
//stuff
}
And the message class:
public class Message {
private Class clazz;
}
I am pulling messages off a queue. I need to provide a concrete Processor to handle the different types of messages appropriately. Here's the message receiver:
public class MessageReceiver {
public void handleMessage(Message m) {
Processor processor = //get concrete implementation from Message clazz
processor.processMessage(m);
}
}
How exactly can I use the class name/object to define a concrete implementation of Processor?
My first thought was to develop some sort of a factory that takes in a class and provides the concrete implementation. Something like this:
#Component
public class ProcessorFactory {
private FooAProcessor fooAProcessor;
private FooBProcessor fooBProcessor;
public ProcessorFactory(FooAProcessor fooAProcessor, FooBProcessor fooBProcessor) {
this.fooAProcessor = fooAProcessor;
this.fooBProcessor = fooBProcessor;
}
public Processor getFactory(Class clazz) {
if(clazz.isAssignableFrom(FooAProcessor.class)) {
return fooAProcessor;
}
}
}
Or to use the application context like this:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getBean(clazz);
Is this the best way to go about this problem? Is there a better practice?
You can inject ApplicationContext into your factory and get beans from there:
#Component
public class Factory {
#Autowired ApplicationContext applicationContext;
public Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
}
Or you can put your processors into map and get them from it:
#Component
public class ProcessorFactory {
private final Processor fooAProcessor;
private final Processor fooBProcessor;
private final Map<Class<T extends Processor>, Processor> beanMap;
public ProcessorFactory (Processor fooAProcessor, Processor fooBProcessor) {
this.fooAProcessor = fooAProcessor;
this.fooBProcessor = fooBProcessor;
this.beanMap = new HashMap(2);
}
#PostConstruct
public void init() {
beanMap.put(FooAProcessor.class, fooAProcessor);
beanMap.put(FooBProcessor.class, fooBProcessor);
}
public Processor getProcessor(Class<T extends Processor> clazz) {
return beanMap.get(clazz);
}
}
I recommend to not rely on class when working with spring context but use beanNames instead.

How to disable Spring autowiring for a certain bean?

There are some class in jar (external library), that uses Spring internally.
So library class has structure like a:
#Component
public class TestBean {
#Autowired
private TestDependency dependency;
...
}
And library provides API for constructing objects:
public class Library {
public static TestBean createBean() {
ApplicationContext context = new AnnotationConfigApplicationContext(springConfigs);
return context.getBean(TestBean);
}
}
In my application, I have config:
#Configuration
public class TestConfig {
#Bean
public TestBean bean() {
return Library.createBean();
}
}
It's throw en exception: Field dependency in TestBean required a bean of type TestDependency that could not be found..
But Spring should not trying to inject something, because bean is already configured.
Can i disable Spring autowiring for a certain bean?
Based on #Juan's answer, created a helper to wrap a bean not to be autowired:
public static <T> FactoryBean<T> preventAutowire(T bean) {
return new FactoryBean<T>() {
public T getObject() throws Exception {
return bean;
}
public Class<?> getObjectType() {
return bean.getClass();
}
public boolean isSingleton() {
return true;
}
};
}
...
#Bean
static FactoryBean<MyBean> myBean() {
return preventAutowire(new MyBean());
}
This worked for me:
import org.springframework.beans.factory.FactoryBean;
...
#Configuration
public class TestConfig {
#Bean
public FactoryBean<TestBean> bean() {
TestBean bean = Library.createBean();
return new FactoryBean<TestBean>()
{
#Override
public TestBean getObject() throws Exception
{
return bean;
}
#Override
public Class<?> getObjectType()
{
return TestBean.class;
}
#Override
public boolean isSingleton()
{
return true;
}
};
}
}
It seems like it's impossible to disable autowiring for a specific bean.
So there is some workaround.
We can make wrapper for a target bean and use it instead of original bean:
public class TestBeanWrapper {
private final TestBean bean;
public TestBeanWrapper(TestBean bean) {
this.bean = bean;
}
public TestBean bean() {
return bean;
}
}
#Configuration
public class TestConfig {
#Bean
public TestBeanWrapper bean() {
return new TestBeanWrapper(Library.createBean());
}
}
#RestController
public class TestController {
#Autowired
private TestBeanWrapper bean;
...
}
Not exactly but you can add required=false (#Autowired(required=false)) in your autowired annotation. But be careful that might get you NullPointer exception

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

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