In module A I have this class hierarchy:
public interface Sanitizer<C extends DocumentContext> {
CommonWorkflowResult<C> sanitizeForInbound(C documentContext);
CommonWorkflowResult<C> sanitizeForOutbound(C documentContext);
}
An abstract base class implements this interface:
public abstract class BaseSanitizer<C extends DocumentContext> implements Sanitizer<C> { ... }
The first subclass extending the base class:
#Component
public class SaxXmlSanitizer<C extends DocumentContext> extends BaseSanitizer<C> { ... }
The second class extending the base class:
#Component
public class RegExXmlSanitizer<C extends DocumentContext> extends BaseSanitizer<C> { ... }
In an different module B I import the dependency (module A) containing the classes above and here I have a configuration defining the beans conditionally on a property being set via application.properties:
#Bean
#ConditionalOnProperty(name = "sanitizer.type", havingValue = "REGEX", matchIfMissing = true)
public Sanitizer regExSanitizer() {
return new RegExXmlSanitizer();
}
#Bean
#ConditionalOnProperty(name = "sanitizer.type", havingValue = "SAX", matchIfMissing = false)
public Sanitizer xmlSanitizer() {
return new SaxXmlSanitizer();
}
How can I write a Spring Boot test to see that a RegExXmlSanitizer Bean exists in the application context if the property sanitizerType=REGEX is set (and the SaxXmlSanitizer Bean does not exist) and vice versa if the property sanitizerType=SAX is set?
I tried:
#Test
void testSanitizerExists() {
this.contextRunner//.withPropertyValues("sanitizerType=SAX")
.run(context -> Assertions.assertNotNull(context.getBean(SaxXmlSanitizer.class)));
}
but I always get a
NoSuchBeanDefinitionException: No qualifying bean of type
'SaxXmlSanitizer' available: expected at least 1 bean
which qualifies as autowire candidate.
I got it running doing it this way:
#SpringBootTest(classes = {RegExXmlSanitizer.class}, properties = {"sanitizerType=REGEX"})
#ActiveProfiles("test")
class RegExXmlSanitizerTest {
#Autowired
private RegExXmlSanitizer<DocumentContext> sanitizer;
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("sanitizerType=REGEX")
.withConfiguration(AutoConfigurations.of(SanitizerTestConfig.class));
#TestConfiguration
static class SanitizerTestConfig {
#Bean
#ConditionalOnProperty(name = "sanitizerType", havingValue = "REGEX", matchIfMissing = true)
public Sanitizer regExXmlSanitizer() {
return new RegExXmlSanitizer();
}
}
#Test
void testSanitizerExists() {
this.contextRunner.withPropertyValues("sanitizerType=SAX")
.run(context -> Assertions.assertNotNull(context.getBean(RegExXmlSanitizer.class)));
}
...
}
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 Springboot2 and java8.
I've a #Configuration class, that will instantiate a bean depending on some properties, and depending on those properties, the bean instantiated should be Primary or not.
#Configuration
public class MyConfClass {
#Autowired
private MyProperties myProperties;
#Bean
#ConditionalOnProperty(name = "property.use-default", havingValue = "false", matchIfMissing = true)
public MySpringBean buildMySpringBean() {
MySpringBean bean = new MySpringBean();
if (myProperties.isPrimary()) {
// Should be primary like if annotated with #Primary
} else {
// should not
}
return bean;
}
}
In general You might try to create your own BeanFactoryPostProcessor that will
set Primary parameter to bean definition based on configuration, however it means that you'll dive pretty deep into spring internals.
If you don't want to fiddle with this pretty advanced concept,
Probably you can go with following approach:
#Configuration
public class MyConfClass {
#Bean
#Primary
#ConditionalOnProperty(name = "shouldBeDefault", havingValue = "true", matchIfMissing = true)
public MySpringBean buildMySpringBeanPrimary() {
return new MySpringBean();
}
#Bean
#ConditionalOnProperty(name = "shouldBeDefault", havingValue = "false", matchIfMissing = false)
public MySpringBean buildMySpringBeanNotPrimary() {
return new MySpringBean();
}
Frankly I didn't understand what is property.use-default property, but if you also have to be dependent on this condition, then probably you'll have to prepare "compound conditional" that will evaluate to "true" only if both "underlying" conditions are true.
This can be done easily as explained Here
Update
Since it looks like you're going to use BeanFactoryPostProcessor here, this is the example that should work (probably with minor changes):
#Component // or register it in #Configuration as if its a regular bean
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private final Environment env;
public MyBeanFactoryPostProcessor(Envrionment env) {this.env = env;}
public void postProcessBeanFactory(ConfiguratbleListableBeanFactory beanFactory) throws BeansException {
boolean shouldBePrimary = resolveShouldBePrimary();
if(shouldBePrimary) {
BeanDefinition bd = beanFactory.getBeanDefinition("yourBeanName");
bd.setPrimary(true);
}
}
private boolean resolveShouldBePrimary() {
// here you can read properies directly or if you work with #ConfigurationProperties annotated class you can do:
MyConfigProperties myConfigProperties = Binder.get(env).bind("prefix.in.config", MyConfigProperties.class).get()
// now resolve from the mapped class
}
}
When upgrading spring from 2.0.8 to 2.1.2 (using JDK 8) the application starts and runs fine but tests fail due to java.lang.IllegalStateException: Failed to load ApplicationContext.
I am using an abstract class which some tests do extend.
#SpringBootTest
#RunWith(SpringRunner.class)
public abstract class AbstractTestkonfiguration {
#TestConfiguration
static class TestEnvironmentConfiguration {
#Component
#PropertySource(value = "classpath:my-test.properties")
#ConfigurationProperties(prefix = "my")
public static class MyTestProperties extends EnvironmentalProperties {
}
}
}
The class EnvironmentalProperties is a class for type-safe configuration properties (Doc)
Before the upgrade that worked and a class of EnvironmentalProperties was provided but now I am getting a
[...]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.abc.EnvironmentalProperties' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#javax.inject.Inject()}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1210)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1164)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
... 90 more
Could it be related to a change in Nested Configuration Class Detection (Upgrading to Spring Framework 5.x)? If so, how can I configure a bean of EnvironmentalProperties only for tests?
Update: Even if used as follows it doesn't work (same result).
#SpringBootTest
#RunWith(SpringRunner.class)
public abstract class AbstractTestkonfiguration {
#Configuration
public static class TestEnvironmentConfiguration {
#Bean
public MyTestProperties environmentalProperties(){
return new EnvironmentalProperties() {
// manual creation of an instance
}
}
}
}
There are a few changes that you have to make.
You haven't enabled the configuration properties via #EnableConfigurationProperties
The property source needs to be injected at test class
Remove #Component annotation
Here is a working example;
src/test/resources/my-test.properties
my.server.name=foo
my.server=test
And
src/main/resources/application.properties
my.name=production
The production configuration.
#ConfigurationProperties(prefix = "my")
#PropertySource(value = "classpath:application.properties")
public class EnvironmentalProperties {
private String name;
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
#SpringBootTest
#TestPropertySource(value = {"classpath:my-test.properties", "classpath:application.properties"})
#RunWith(SpringRunner.class)
public class AbstractTestkonfiguration {
#Autowired
private MyTestProperties myTestProperties;
#TestConfiguration
#EnableConfigurationProperties(MyTestProperties.class)
public static class TestEnvironmentConfiguration {
#ConfigurationProperties(prefix = "my")
public static class MyTestProperties extends EnvironmentalProperties {
private String server;
public String getServer() {
return server;
}
public void setServer(final String server) {
this.server = server;
}
}
}
#Test
public void check_configuration () {
Assert.assertEquals(myTestProperties.getServer(), "test");
Assert.assertEquals(myTestProperties.getName(), "production");
}
This works on Java 11 & spring-boot 2.1.2.RELEASE. Please note, this is only an example. You will have to adapt it to your project properly.
This super class DAO:
public class CrudDAO{
}
This child class:
#Repository
public class JnsTimeDao extends CrudDAO {
}
#Repository
public class BatchDAO extends CrudDAO {
}
this super service class
#Transactional(readOnly = true)
public abstract class CrudService<D extends CrudDAO> {
#Autowired
protected D dao;
}
startup error:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type [com.gp.dao.CrudDAO] is defined: expected
single matching bean but found 2: batchDAO,jnsTimeDao
There are 2 beans of type CrudDAO. So, Spring won't be able to understand which bean to inject. Can be solved as follows
#Repository("jnsTimeDao")
public class JnsTimeDao extends CrudDAO {
}
#Repository("batchDao")
public class BatchDAO extends CrudDAO {
}
While injecting use #Qualifier
#Transactional(readOnly = true)
public abstract class CrudService<D extends CrudDAO> {
#Autowired
#Qualifier("batchDao")
protected D dao;
}
I'm writing a specification class in spock framework.
#ContextConfiguration(classes = [MyServiceImplementation.class])
class MyServiceSpecification extends Specification {
#Autowired
private MyService myServiceImplementation
def " " {
//code
}
}
The class MyServiceImplementation is annotated #Service. I'm not using XML configuration. MyServiceImpl is an implementation of the interface: MyService.
Why is the autowired object myServiceImplementation null?
I tried using ComponentScan and it still didn't work.
First, you need to have both spock-core and spock-spring on the classpath. Second, #ContextConfiguration(classes= takes a list of configuration classes, not bean classes.
#ContextConfiguration(classes = [MyConfig.class])
class MyServiceSpecification extends Specification {
#Autowired
private MyService myServiceImplementation
def " " {
//code
}
}
// You could also define #ComponentScan here
class MyConfig {
#Bean
MyService myService() {
return new MyServiceImplementation();
}
}