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();
}
});
}
}
Related
Here I have 3 Interfaces: InterfaceA and InterfaceB and SharedInterface
public interface InterfaceA {
/**
* print some message
*/
void printMsg();
}
public interface InterfaceB {
/**
* print some message
*/
void printMsg();
}
public interface SharedInterface {
/**
* print some message
*/
void printSharedMsg();
}
and there are 3 implementations of these interfaces:
public class ImplementA1 implements InterfaceA, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceA1");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA1");
}
}
public class ImplementA2 implements InterfaceA, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceA2");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA2");
}
}
public class ImplementB implements InterfaceB, SharedInterface {
#Override
public void printMsg() {
System.out.println("this is message of interfaceB");
}
#Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementB");
}
}
ImplementA1 and ImplementA2 are the same type of operation, ImplementB is another type of operation. So I decided to develop 2 config class to register ImplementA1,ImplementA2 and ImplementB which is showing below.
#Configuration
public class InterfaceAConfig {
#Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
#Bean
public InterfaceA registerInterfaceA2(){
return new ImplementA2();
}
}
#Configuration
public class InterfaceBConfig {
#Bean
public InterfaceB registerInterfaceB(){
return new ImplementB();
}
}
Now I want to let all beans which implement SharedInterface print their message in a component. And it works well,here is the code:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingContextGetBean();
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
But I found another way of inject beans to component, using
#Autowired
List<TargetType> myListName
So I decided to change my SharedInterfaceComponent to this for test, and it worked:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
#Autowired
private List<InterfaceA> autowiredList;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (InterfaceA interfaceA : autowiredList) {
if (SharedInterface.class.isAssignableFrom(interfaceA.getClass())){
((SharedInterface) interfaceA).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
But when I tried to use SharedInterface instead of InerfaceA to get beans from IOC , it goes wrong. The code is showing below:
#Component
#AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
#Autowired
private List<SharedInterface> autowiredList;
//print shared message after IOC container refreshed
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (SharedInterface sharedInterface : autowiredList) {
if (SharedInterface.class.isAssignableFrom(sharedInterface.getClass())){
((SharedInterface) sharedInterface).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
In this Demo the application will fail and showing
***************************
APPLICATION FAILED TO START
***************************
Description:
Field autowiredList in com.wwstation.test.config.SharedInterfaceComponent required a bean of type 'java.util.List' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'java.util.List' in your configuration.
But in my other projects, the same situation will not lead to a crush, I can get SharedInterface by using #Autowired but there I can only get beans implement InterfaceA or InterfaceB but never all of them. I thought, the not crushing case may be caused by some of my dependencies in other projects.
Can anyone help me about how to get all the SharedInterface more graceful? Thanks alot!
The problem is your configuration.
#Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
The problem with this is that Spring will use the return type of the method to see if it fullfils injection points (in this case your list). As InterfaceA isn't a SharedInterface eventually it will fail as there are no beans that implement the SharedInterface according to your configuration!.
What you should do with your own beans is to be as specific as possible in the return type. So instead of InterfaceA make it return the actual class ImplementA1 and ImplementA2. That way Spring, at configuration time, can determine that those implement SharedInterface and use those to fill the list.
#Bean
public ImplementA1 registerInterfaceA1(){
return new ImplementA1();
}
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?
Hi I am trying to use Strategy Design pattern. I am getting ReEncryptionOperation bean as null in my TestServiceImpl class.
this is my interface
public interface ReEncryptionOperation {
void performOperation (String name);
}
These are my implementation classes
public class Test1 implements ReEncryptionOperation {
#Override
public void performOperation(String name){
return ....;
}
}
public class Test2 implements ReEncryptionOperation {
#Override
public void performOperation(String name) {
return ....;
}
}
This is my configuration class where I am defining as a bean
#Configuration
#Slf4j
public class TestConfiguration
{
#Bean("reEncryptionOperation")
public ReEncryptionOperation getReEncryptionOperation () throws ReEncryptionException {
if (annotationSupport) {
return new Test1();
}
return new Test2();
}
}
this is my service class where i am trying to use ReEncryptionOperation using #Autowired. But I am getting null.
#Component
#Slf4j
public class TestServiceImpl
{
#Autowired
private ReEncryptionOperation reEncryptionOperation;
public ReEncryptionResponse submitJob (
final ReEncryptionRequest reEncryptionRequest) throws ReEncryptionException
{
reEncryptionOperation.performOperation(test);
}
}
Your configuration seems ok.
Check that TestConfiguration is located in a package scanned by spring.
To be sure your bean is created on runtime, place a breakpoint in the method getReEncryptionOperation
I am trying to use spring-data-cassandra custom repository implementation. I am using spring-data-cassandra 1.5.M1. The issue is the custom implementation method does not get executed.
Below is the code snippet.
public interface JobRepositoryCustom{
public Job getJobById(String id );
}
#Component
public class JobRepositoryCustomImpl implements JobRepositoryCustom{
#Autowired
CassandraOperations template;
public Job getJobById(String job ) {
System.out.println("Some custom implementation");
return job;
}
}
public interface JobRepository extends TypedIdCassandraRepository<Job,String>, JobRepositoryCustom {
}
The driver program of the same is below
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
#Bean
public CommandLineRunner demo( JobRepository repository) {
Job job = new Job();
job.setId("1817086421");
job.setDescription("My job2 description");
job.setOneMoreField("Just a new field");
job.setTitle("Technical Engineer job");
repository.save(job);
//The getJobById is custom method declared in JobRepositoryCustom
repository.getJobById("My String");
System.out.println("This after the job");
System.out.println("repository = [" + repository+ "]");
return null;
}
}
Wether I am missing something?
rename JobRepositoryCustomImpl to JobRepositoryImpl:
Here is a example from my code:
public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {
}
interface ProductRepositoryCustom {
public String anyFunction();
}
#Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {
#Override
public String anyFunction(){
return "Hello";
}
}
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.