Why is it needed to instatiate PropertySourcesPlaceholderConfigurer bean when having #EnableAsync configuration - spring

I have the following configuration class:
#Configuration
public class AppConfig {
#Value("${my.value}"
private String myValue;
// The rest of the code
}
And it is working fine and my.value from properties is assigned to myValue.
But if I add #EnableAsync annotation to this class, it is not working.
#Configuration
#EnableAsync
public class AppConfig {
#Value("${my.value}"
private String myValue;
// The rest of the code
}
This is the error:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException
It can be fixed by adding PropertySourcesPlaceholderConfigurer bean in the class.
#Configuration
#EnableAsync
public class AppConfig {
#Value("${my.value}"
private String myValue;
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
// The rest of the code
}
Can someone explain me why is it needed to instantiate PropertySourcesPlaceholderConfigurer bean when having #EnableAsync annotation?

Related

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.

Defining constructor in prototype bean

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

Spring boot test wrong configuration classes

I am trying to test my repository layer using Spring Boot 2.0.1 but when I run my test class, Spring tries to instantiate a Config class not from the test package.
Here is the test code:
TestConfig.class
#Configuration
#Import(value = {TestDatabaseConfig.class})
#Profile("local")
public class TestConfig {
}
TestDatabaseConfig.class
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "logEntityManagerFactory",
transactionManagerRef = "logTransactionManager",
basePackages = { "it.xxx.yyy.repository.log" })
#EntityScan(basePackages = {"it.xxx.yyy.model.log", "it.xxx.yyy.common"})
#Profile("local")
public class TestDatabaseConfig {
#Bean("logDataSourceProperties")
public DataSourceProperties logDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "logDataSource")
public DataSource dataSource(#Qualifier("logDataSourceProperties") DataSourceProperties properties) {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.build();
}
#Bean(name = "logEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean logEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("logDataSource") DataSource logDataSource) {
return builder.dataSource(logDataSource)
.packages("it.xxx.model.log")
.persistenceUnit("log")
.build();
}
#Bean(name = "logTransactionManager")
public PlatformTransactionManager logTransactionManager(#Qualifier("logEntityManagerFactory")EntityManagerFactory logEntityManagerFactory) {
return new JpaTransactionManager(logEntityManagerFactory);
}
}
When I run this class
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("local")
public class LogRepositoryTest {
#Autowired
private ResultLogRepository resultLogRepository;
#Test
public void init(){
}
}
it says :
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'kafkaProducer': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'kafka.topic.operation' in value "${kafka.topic.operation}"
[...]
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'kafka.topic.operation' in value "${kafka.topic.operation}"
But I cannot understand why it brings up my KafkaProducer.class from my main package (that has #Configuration annotation on it).
In your LogRepositoryTest test class you should indicate the alternate test configuration class that should be taken into account, in your case I think should be the TestConfig.
From Spring Boot documentation:
If you are familiar with the Spring Test Framework, you may be used to using #ContextConfiguration(classes=…​) in order to specify which Spring #Configuration to load. Alternatively, you might have often used nested #Configuration classes within your test.
So annotate LogRepositoryTest with #ContextConfiguration(classes = {TestConfig.class})
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("local")
#ContextConfiguration(classes = {TestConfig.class})
public class LogRepositoryTest {
#Autowired
private ResultLogRepository resultLogRepository;
#Test
public void init(){
}
}
UPDATE
Also annotate your configuration class with:
#EnableAutoConfiguration
Something like:
#Configuration
#EnableAutoConfiguration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "logEntityManagerFactory",
transactionManagerRef = "logTransactionManager",
basePackages = { "it.xxx.yyy.repository.log" })
#EntityScan(basePackages = {"it.xxx.yyy.model.log", "it.xxx.yyy.common"})
#Profile("local")
public class TestDatabaseConfig {
//...
}
UPDATE 2
For error:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' available: expected single matching bean but found 2: logDataSourceProperties,spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
Completely remove the method:
#Bean("logDataSourceProperties")
public DataSourceProperties logDataSourceProperties() {
return new DataSourceProperties();
}
and change your:
#Bean(name = "logDataSource")
public DataSource dataSource(#Qualifier("logDataSourceProperties") DataSourceProperties properties) {
// ...
}
to:
#Bean(name = "logDataSource")
public DataSource dataSource(DataSourceProperties properties) {
// ...
}

Spring: Cannot get bean by using #Component and #Bean

I'm new in Spring framework.
I try to config 2 beans with #Bean annotation within #Component.
After that, I try to getBean (by name), I got a NoSuchBeanDefinitionException.
Please help me to resolve it.
Here is my code:
- The component:
package com.example.component;
#Component
public class FactoryMethodComponent {
private static int i;
#Bean
#Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
#Bean
#Qualifier("tb1")
public TestBean1 publicInstanceTB1() {
return new TestBean1(publicInstance());
}
}
-The xml configuration file: app-context.xml.
<beans ...>
<context:component-scan base-package="com.example.*" />
</beans>
-The test code:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:app-context.xml" })
public class ComponentBeanTest {
#Test
public void test() {
System.out.println(((TestBean1)context.getBean("tb1")).getTestBean().getMethodName());
System.out.println(publicTestBean.getMethodName());
}
}
-Exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean
named 'tb1' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:577)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1111)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:276)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
at com.example.proxy.ComponentBeanTest.test(ComponentBeanTest.java:38)
Replace #Component with #Configuration which indicates that a class declares one or more #Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.
#Configuration
public class FactoryMethodComponent {
private static int i;
#Bean
#Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
#Bean
#Qualifier("tb1")
public TestBean1 publicInstanceTB1() {
return new TestBean1(publicInstance());
}
}

Can't make Spring's ImportAware to work

I am trying to create my own #EnableXxx-style annotation (namely #EnableCustomizedPropertySources). For this the annotation imports the class CustomizedPropertySourcesConfiguration which in turn implements ImportAware, in order to have access to attributes of the #EnableCustomizedPropertySources annotation.
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(CustomizedPropertySourcesConfiguration.class)
public #interface EnableCustomizedPropertySources {
String externalFolderName();
String propertiesFileName();
(...)
}
Imported configuration class:
#Configuration
public class CustomizedPropertySourcesConfiguration implements ImportAware {
protected AnnotationAttributes enableCustomizedPropertySourcesAttributes;
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableCustomizedPropertySources.class.getName(), false);
this.enableCustomizedPropertySourcesAttributes = AnnotationAttributes.fromMap(attributes);
}
#Bean
public PropertySourcesPlaceholderConfigurer propertySource() {
return (...);
}
}
The problem is, the method setImportMetadata is not invoked by Spring when I annotate some #Configuration class with my #EnableCustomizedPropertySources annotation, so I cannot access the annotations attributes.
The ImportAware class (CustomizedPropertySourcesConfiguration) needs both:
#Configuration
#Component

Resources