Spring, Is it possible to decide on #Primary inside the #Bean method - spring-boot

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
}
}

Related

How to test conditional creation of a Bean with Spring Boot?

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

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

property value injection into spring beans

i want to know why #Value property injection works on classes with #Service annotation but not on classes with #Bean within #Configuration annotated class.
Works means that the property value is not null.
This value is also injected into two other service which i see during debugging in DefaultListableBeanFactory.doResolveDependency. But i dont see the bean WebserviceEndpoint.
Configuration
#Configuration
public class WebserviceConfig {
// do some configuration stuff
#Bean
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
}
Webservice interface
#WebService(targetNamespace = "http://de.example/", name = "IWebservice")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface IWebserviceEndpoint {
#WebMethod
#WebResult(name = "response", targetNamespace = "http://de.example/", partName = "parameters")
public Response callWebservice(#WebParam(partName = "parameters", name = "request", targetNamespace = "http://de.example/") Request request) throws RequestFault;
}
Webservice class
public class WebserviceEndpoint implements IWebserviceEndpoint {
#Value("${value.from.property}")
private String propertyValue;
}
application.yml
value:
from:
property: property-value
When does the injection of #Value happen in this case.
Basically propertyValue is null because Spring injects value after bean's creation.
So when you do:
#Bean
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
Spring creates a new instance with propertyValue=null.
You can initialize your instance attribue with #ConfigurationProperties
#Bean
#ConfigurationProperties(prefix=...)
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
Note that propertyValue should have a setter.
You have several ways to solve this problem, usually it's good to centralize properties in one utils class.
#Component
public class Configs {
#Value("${propery}"
String property;
String getProperty(){
return property;
}
}
And then:
#Bean
#ConfigurationProperties(prefix=...)
public IWebserviceEndpoint webserviceEndpoint() {
WebserviceEndpoint we = new WebserviceEndpoint();
we.setProperty(configs.getProperty())
return we;
}
Again there are many many different ways to solve this problem

Spring Java Configuration: overridden #Bean method invoked before autowiring

I have RepositoryConfig extending Neo4jConfiguration. The latter sets up a number of beans with #Bean annotated methods. RepositoryConfigoverrides getGraphDatabaseService which is invoked before any fields in RepositoryConfig are autowired. That is a problem since I want to use the autowired stuff inside the getGraphDatabaseServicemethod.
#ConfigurationProperties(prefix = "neo4j")
public class RepositoryProperties {
[...]
}
#Configuration
#EnableNeo4jRepositories("com.foo.bar")
#EnableConfigurationProperties(RepositoryProperties.class)
public class RepositoryConfig extends Neo4jConfiguration {
#Autowired
private RepositoryProperties properties;
#Override
#Bean(name = "graphDatabaseService", destroyMethod = "shutdown")
public GraphDatabaseService getGraphDatabaseService() {
[...] // properties is 'null' at this point
}
#PostContstruct
public void foo() {
[...] // properties is initiated OK here
}
}
Why is getGraphDatabaseServicebeing called before autowiring is complete? I guess it has to do with the inheritance... If I remove the inheritance then autowiring is complete at the time getGraphDatabaseServiceis called. I've also tried annotating the method with #DependsOn, with no luck.
Any ideas is much appreciated!
Yes, I have seen this too occasionally. I think there are two workarounds.
Option 1. Autowire the bean definition
#Override
#Bean(name = "graphDatabaseService", destroyMethod = "shutdown")
#Autowired
public GraphDatabaseService getGraphDatabaseService() {
[...] // properties is 'null' at this point
}
Option 2. Inject the bean
#Override
#Bean(name = "graphDatabaseService", destroyMethod = "shutdown")
public GraphDatabaseService getGraphDatabaseService(#Autowired RepositoryProperties properties) {
// can probably delete the Config member with this approach
[...] // properties is 'null' at this point
}

How do I autowire dependencies into Spring #Configuration instances?

I need to inject an object into my No XML Spring #Configuration object as follows:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "web.client")
public class WebApplicationConfiguration extends WebMvcConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(WebApplicationConfiguration.class);
#Inject
private MonitoringExceptionResolver resolver; // always null
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
log.debug("configuring exception resolvers");
super.configureHandlerExceptionResolvers(exceptionResolvers);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
exceptionResolvers.add(new AnnotationMethodHandlerExceptionResolver());
exceptionResolvers.add(new ResponseStatusExceptionResolver());
exceptionResolvers.add(resolver); // passing null ref here
}
}
Where MonitoringExceptionResolver is defined as follows:
#Service
public class MonitoringExceptionResolver implements HandlerExceptionResolver {
private final Counters counters;
#Inject
public MonitoringExceptionResolver(Counters counters) {
super();
this.counters = counters;
}
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Counter counter = counters.getCounterFor(ex.getClass());
if(counter != null) {
counter.increment();
}
return null;
}
}
However, I get NPE later in the execution chain because the "resolver" field above is null, even if I use #Autowired.
Other classes are being successfully wired in elsewhere using component scanning. Why is it always null in the above? Am I doing something wrong?
#Inject and #Autowired should work very similar in Spring.
Make sure that *BeanPostProcessor in use is aware of MonitoringExceptionResolver: mark it as #Component and make is subject of some #ComponentScan or make a #Bean factory method is some #Configuration class in use.

Resources