I have the situation where a protoype bean contains a singleton bean. In order to achieve it, I had to create 2 configuration classes. Is it possible to merge my 2 confgiuration classes into a single one?
Singleton class:
public class MySingleton {
}
Prototype class:
public class MyPrototype {
private MySingleton b;
public MyPrototype(MySingleton b) {
this.b = b;
}
}
Configuration class 1:
#Configuration
public class ConfigClassA {
#Bean
public MySingleton myBean() {
return new MySingleton();
}
}
Configuration class 2:
#Configuration
public class ConfigClassB {
#Autowired
public MySingleton mb;
#Bean
#Scope("prototype")
public MyPrototype myPrototype() {
return new MyPrototype(mb);
}
}
Try this:
#Configuration
public class ConfigClass {
#Bean
public MySingleton myBean() {
return new MySingleton();
}
#Bean
#Scope("prototype")
public MyPrototype myPrototype(MySingleton myBean) {
return new MyPrototype(myBean);
}
}
The BeanFactory should search for a bean of type MySingleton when creating the prototype bean and inject it into the method myPrototype.
Related
I am trying to understand Spring/Spring-boot. My question is, can I use a Bean instantiated/declaired by #Bean to a #Autowired field? Below is my classes, what i have defined.
#SpringBootApplication
public class SpringBootTestApplication {
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
public static void main(String[] args) {
ApplicationContext appContext = new
AnnotationConfigApplicationContext(SpringBootTestApplication.class);
Arrays.asList(appContext.getBeanDefinitionNames()).forEach(beanName ->
System.out.println(beanName));
SpringApplication.run(SpringBootTestApplication.class, args);
}
}
#Component()
public class ServiceCaller {
#Autowired
#Qualifier(value = "TestServiceInterfaceImplBean")
TestServiceInterface testService;
public ServiceCaller(){
System.out.println("############################### ServiceCaller");
}
}
//Service Interface
public interface TestServiceInterface {}
//Interface Implementation Class
public class TestServiceInterfaceImpl implements TestServiceInterface {
public TestServiceInterfaceImpl() {
System.out.println("############################### TestServiceInterfaceImpl");
}
}
I know by tagging #Service/#Component to TestServiceInterfaceImpl and removing #Bean and the method getTestService(), i can have #Autowire successful but i am just tyring to understand whether i can Autowire a Bean?
In this case i am getting below exception. By looking at the exception i am not able to understand where and how the loop is created.
Exception:
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| springBootTestApplication (field public com.SpringBootTestApplication.service.ServiceCaller com.SpringBootTestApplication.SpringBootTestApplication.serviceCaller)
↑ ↓
| serviceCaller (field com.SpringBootTestApplication.service.TestServiceInterface com.SpringBootTestApplication.service.ServiceCaller.testService)
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
You'd better move below part to a Configuration (#Configuration) class:
#Bean(name = "TestServiceInterfaceImplBean")
TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
#Autowired
public ServiceCaller serviceCaller;
then do the test again. And another point, for ServiceCaller, you can even define its order after the Bean of TestServiceInterfaceImplBean created.
the 2 configuration class like:
#Configuration
#AutoConfigureAfter({ MyConfiguration2.class })
public class MyConfiguration {
public MyConfiguration() {
}
#Autowired
public ServiceCaller serviceCaller;
}
#Configuration
public class MyConfiguration2 {
public MyConfiguration2() {
}
#Bean(name = "TestServiceInterfaceImplBean")
public TestServiceInterface getTestService() {
return new TestServiceInterfaceImpl();
}
}
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
My service
#Service
public class StripeServiceImpl implements StripeService {
#Override
public int getCustomerId() {
return 2;
}
}
My test
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
The error: java.lang.NullPointerException. I had checked and the stripeService is actually null.
Since you are autowiring you need an applicationcontext so that Spring can manage the bean and then can get injected in your class. Therefore you are missing an annotation to create the applicationcontext for your testclass.
I have updated your code and it works now(with junit 5 on your classpath). In the case dat you are using junit 4 it should be #RunWith(SpringRunner.class) instead of #ExtendWith(SpringExtension.class):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfiguration.class)
public class StripeServiceTests {
#Autowired
StripeService stripeService;
#TestConfiguration
static class TestConfig {
#Bean
public StripeService employeeService() {
return new StripeServiceImpl();
}
}
#Test
public void findCustomerByEmail_customerExists_returnCustomer() {
assertThat(stripeService.getCustomerId()).isEqualTo(2);
}
}
I've coded this class:
#Component
public class AuditFactory {
private Supplier<String> auditIdSupplier;
public AuditFactory(Supplier<String> auditIdSupplier) {
this.auditIdSupplier = auditIdSupplier;
}
}
It's used as a dependency of a #Service class:
#Service
public class AuditService {
private AuditFactory auditFactory;
public AuditService(AuditFactory auditFactory) {
this.auditFactory = auditFactory;
}
}
How could I tell to Spring that injects a Supplier<String> when AuditFactory is injected?
EDIT
#Bean
public Supplier<String> auditIdSupplier(FrontOfficeProperties frontOfficeProperties) {
return () -> String.join(
"-",
frontOfficeProperties.getCpdId(),
frontOfficeProperties.getRedisAuditKeyPrefix(),
UUID.randomUUID().toString()
);
}
where FrontOfficeProperties is an #ConfigurationProperties annotated class.
below approach might help you to fix your issue.
also can you please share Supplier class as well.
#Component
public class AuditFactory {
private Supplier<String> auditIdSupplier;
public AuditFactory(Supplier<String> auditIdSupplier) {
this.auditIdSupplier = auditIdSupplier;
}
}
#Service
public class AuditService {
private AuditFactory auditFactory;
public AuditService(AuditFactory auditFactory) {
this.auditFactory = auditFactory;
}
}
While overrding a Javaconfig Bean by extending the original #Configuration class, I would like to add a #DependsOn for the new Bean definition.
However, this depends-on seems not to be taken in account. here is a TestCase reproducing my issues:
public class SpringTest {
#Test
public void testDependsOnTakenInAccount() {
AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext(AConfig.class, CConfig.class);
Assert.assertEquals("overriden", ctx2.getBean("bean"));
}
#Configuration
public static class AConfig {
#Bean
public Object bean() {
return "not overriden";
}
}
#Configuration
public static class CConfig extends AConfig {
protected boolean isInitialized = false;
#Bean
public Void doInit() {
isInitialized = true;
return null;
}
#Bean
#DependsOn("doInit")
public Object bean() {
if (!isInitialized) {
throw new RuntimeException("Not initialized");
}
return "overriden";
}
}
}
Is this an expected behavior? If yes, how can I add dependency while overriding a bean?
For me seems like a bug.
When overriding a #Bean factory method in a Configuration class, the parent BeanDefinition wins and get registered on the BeanFactory overriding the child one.
So you cannot configure the bean with annotaions (because it will be overriden).
The following Test result on
expected:<[doInit]> but was:<[otherBean]>
#RunWith(JUnit4ClassRunner.class)
public class DependOnTest {
#Test
public void testBeanDefinitionOverriding() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
BeanDefinition bd = ctx.getBeanDefinition("bean");
Assert.assertEquals("doInit", bd.getDependsOn()[0]);
}
#Configuration
public static class ParentConfig {
#Bean
#DependsOn("otherBean")
public String bean() {
return "not overriden";
}
#Bean
public String otherBean() {
return "otherBean";
}
}
#Configuration
public static class Config extends ParentConfig {
#Bean
public String doInit() {
return "doInit";
}
#Bean
#DependsOn("doInit")
public String bean() {
return "overriding";
}
}
}
I think that problem start on ConfigurationClassParser:
// recursively process the configuration class and its superclass hierarchy
do {
metadata = doProcessConfigurationClass(configClass, metadata);
}
while (metadata != null);
That result on overriden method added to CongurationClass.beanMethods
It could be fixed checking if the beanMethod was already added from a superclass in ConfigurationClass.addBeanMethod()
public void addBeanMethod(BeanMethod method) {
// Check if already added a bean method from superclass
for (BeanMethod beanMethod : beanMethods) {
if (beanMethod.getMetadata().getMethodName().equals(method.getMetadata().getMethodName()) &&
!(beanMethod.getMetadata().getDeclaringClassName()).equals(method.getMetadata().getDeclaringClassName()))
// log and return.
return;
}
this.beanMethods.add(method);
}
As pointed out by Jose Luis Martin, this has been confirmed as a bug by Spring team.
I've workarounded it with:
#DependsOn("doInit")
#Bean
public Void notOverridingBean() {
return null;
}
#Bean
public Object bean(Object notOverridingBean) {
return "overriden";
}
an alternative is to override the bean in another #Configuration class.