Defining constructor in prototype bean - spring-boot

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

Related

Can I Autowire a Bean?

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

How to use #TestConfiguration

How to override #Configuation which is present under src/main/java with #TestConfiguration during unit tests?
#Configuration
public class AppConfig {
#Bean
public EmployeeService employeeService(){
return new EmployeeService();
}
}
#Component
public class ServerStartSetup implements CommandLineRunner {
#Autowired
private EmployeeService employeeService;
public void run(String... args) {
// do something with employee service
}
}
I would like to override the above bean with some below custom bean for testing purposes.
#TestConfiguration
public class TestAppConfig {
#Bean
public EmployeeService employeeService(){
return new FakeEmployeeService();
}
}
#SpringBootTest
#Import(TestAppConfig.class)
public class UnitTest {
}
However AppConfig does not seem to be skipped. That is , it throws an error saying that there is a bean with same name employeeService. If I rename bean method name in the TestAppConfig, it injects the bean created via AppConfig.
How to fix this.?
Note: One possible solution is using #Profile. I am looking for anything other than using Profiles.
I tested locally and found that changing the method name or #Bean to #Bean("fakeEmployeeService") and adding the #Primary annotation works.
#SpringBootTest
class DemoApplicationTests {
#Autowired
private EmployeeService employeeService;
#TestConfiguration
static class TestConfig {
//#Bean("fakeEmployeeService")
#Bean
#Primary
public EmployeeService employeeServiceTest() {
return new EmployeeService() {
#Override
public void doSomething() {
System.out.println("Do something from test...");
}
};
}
}
...
}
If we want to override a bean definition in #TestConfiguration, we need:
To use the same name as the overridden bean. (Otherwise it would be an "additional" bean and we could get conflict/'d have to qualify/primary)
Since spring-boot:2.1: spring.main.allow-bean-definition-overriding=true (set this in tests ONLY!plz!)
#ref
Then, with:
#TestConfiguration
public class TestAppConfig {
#Bean // when same name, no #Primary needed
public EmployeeService employeeService(){ // same name as main bean!
return new FakeEmployeeService();
}
}
We can do that:
#Import(TestAppConfig.class)
#SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
public class UnitTest {
... // EmployeeService will be "fake", the rest is from "main config"
You can mock the AppConfig bean in your test like this:
#MockBean
private AppConfig config;
Or, like you said, just use profiles.

Spring configuration - Autowired bean required?

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.

MethodValidationPostProcessor breaks #Transactional

I have a Controller which calls a Service which has #Transactional annotation.
But when I declare a bean MethodValidationPostProcessor, no transaction is created (could not initialize proxy - no Session).
#EnableWebMvc
#ComponentScan(basePackages = {"my"})
public class Application extends WebMvcConfigurerAdapter {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
Controller bean:
#RestController
#RequestMapping(path = "/my", produces = APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private TransactionalService transactionalService;
#RequestMapping(method = POST)
public void post(#SafeHtml #RequestBody String hey) {
transactionalService.doStuff(hey);
}
}
Service bean:
#Service
public class TransactionalService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void doStuff(String hey) {
Item h = entityManager.find(Item.class, hey);
h.getParent(); // could not initialize proxy - no Session
}
}
I'd like to understand why #Transactional doesn't work when I declare MethodValidationPostProcessor. Thanks !
Note: If I add #Transactional on my Controller, it works. But it's not what I want to do.
Thanks to #Kakawait, I got a work-around: declaring my bean MethodValidationPostProcessor. Needs to be static so that #Transactional still work properly.
/**
* This bean must be static, to be instantiated before the other MethodValidationPostProcessors.
* Otherwise, some are not instantiated.
*/
#Bean
public static MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}

#Configuration and Bean property setting annotations

I'm using #Configuration to create beans and set their properties from other bean properties such as is possible with xml configuration
#Configuration
public class AppConfig {
#Bean
public MyBean myBean(#Value("#{someObject.value}") double value) {
MyBean myBean = new MyBean();
myBean.setValue(value);
return myBean;
}
#Bean
public SomeObject someObject() {
SomeObject someObject = new SomeObject();
someObject.setValue(1.0);
return someObject;
}
}
However, it is cumbersome and unnecessary to have to use the setter methods [eg. myBean.setValue(value)]. Instead I want something like so:
#Configuration
public class AppConfig {
#Bean
#MysticalPropertySetter(name="value", value="#{someObject.value}")
public MyBean myBean() {
return new MyBean();
}
#Bean
#MysticalPropertySetter(name="value", value="1.0", type="java.lang.Double")
public SomeObject someObject() {
return new SomeObject();
}
}
Anything like this possible? Much Thanks!!!
I think that this approach impossible because in your factory method annotated with #Bean you create objects manually, but Spring can inject properties only to beans that are instanteated by itself.
How about constructor injection?
#Bean
public MyBean myBean(#Value("#{someObject.value}") double value) {
return new MyBean(value);
}

Resources