How to declare an optional #Bean in Spring? - spring

I want to provide an optional #Bean in my #Configuration file like:
#Bean
public Type method(Type dependency) {
// TODO
}
when dependency can't be found, the method should not be called.
How to do that?

You need to use ConditionalOnClass If using SpringBoot and Conditional in Spring since 4.0 See If using Spring
Example of SpringBoot :-
#Bean
#ConditionalOnClass(value=com.mypack.Type.class)
public Type method() {
......
return ...
}
Now the method() will be called only when com.mypack.Type.class is in classpath.

In addition to the accepted answer, you have to check if the dependency is initialized before calling any method which requires that dependency.
#Autowired(required = false)
Type dependency;
public Type methodWhichRequiresTheBean() {
...
}
public Type someOtherMethod() {
if(dependency != null) { //Check if dependency initialized
methodWhichRequiresTheBean();
}
}

Related

How to create notes/description based on custom Annotation to Swagger

I have a Springboot Rest application where I have annotation which is automatically converted API's and Params.
I have custom annotation where i include some notes to it, How can i get that generated to my swagger page in OpenAPI 3?
Ex:
#RestController
Class Controller {
#GetMapping(/test/result/)
#CustomAnnotation(value = "This description should come in swagger")
void method() {
}
}
SpringDoc allows you to customize the generated OpenAPI specification by implementing your own Customizer bean.
There are plenty of Customizer interfaces that you can use for customization, but the most usable are OperationCustomizer, ParameterCustomizer, and PropertyCustomizer.
Below is an example of Operation Customizer for your use case.
#Component
public class OperationCustomizer implements org.springdoc.core.customizers.OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
CustomAnnotation annotation = handlerMethod.getMethodAnnotation(CustomAnnotation.class);
if (annotation != null) {
operation.description(annotation.value());
}
return operation;
}
}
Here you can find an example of the project that uses custom annotations and Customizers for them.
And here an example of the project that modifies the generated specification based on the #NonNull annotation.
As #VadymVL pointed out a component extending OperationCustomizer is necessary:
#Component
public class CustomOperationCustomizer implements OperationCustomizer {
#Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
/* your code */
}
Just don't forget to register it:
#Bean
public GroupedOpenApi publicApi(CustomOperationCustomizer operationCustomizer) {
return GroupedOpenApi.builder()
.group(/*your group*/)
.pathsToMatch(/*your endpoint*/)
.
/* anything else you need */
.
.addOperationCustomizer(operationCustomizer)
.build();
}
And you're good to go!

Spring #Bean(name ="name") vs #Bean #Qualifier("name")

Is there any differences between the following 2 bean declaration?
#Bean(name = "bean1")
public A getA() {
return new A();
}
#Bean
#Qualifier("bean1")
public A getA() {
return new A();
}
Both can be autowired using #Qualifier
#Autowire
public void test(#Qualifier("bean1") A a) {
...
}
With value() you don't have to specify attribute name, like #Qualifier("bean1"). Attribute name() reference the same value as value() because of custom annotation #AliasFor(..) from Spring, therefore they are just different names with the same behavior.
You can use
#Autowire
public void test(A bean1) {
...
}
if you use
#Bean(name = "bean1")
not with
#Bean
#Qualifier("bean1")
The first part is fundamentally the same, the second part is what you basically need when two or more beans of same type exist. The first part is just the preference one might have.
You can have multiple beans with same Qualifier name but bean name in spring application context needs to be unique
#Bean
#Qualifier("qualifier1")
Foo foo()
{
return new Foo();
}
#Bean
#Qualifier("qualifier1")
Bar bar()
{
return new Bar();
}
The above code is acceptable. but in the case of bean, it is not.

#Bean-method providing a list is ignored, if there is a #Bean-method proving a single bean

I have a configuration providing a single bean and a configuration providing a list of beans. All these beans have the same type.
When I start up an application context with these configurations, I see that an autowired list of the bean type only contains the single bean. I want it to include all beans of that type. I use Spring 5.2.0.
I have boiled it down to one configuration: if I provide a single bean and a list of beans, only the single bean will be used.
This is reproduced in the following test. It fails, because the list only contains "A" and "D" (which shows it did not autowire the list of beans):
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = { TestConfiguration.class })
class AutowiringListsTest {
#Autowired
private List<TestBean> testBeanList;
#Test
void testThatBothConfigurationsContributeToBeanList() {
final List<String> idList = testBeanList.stream().map(TestBean::getId).sorted().collect(Collectors.toList());
assertThat(idList, hasItems("A", "B", "C", "D"));
}
#Configuration
public static class TestConfiguration {
#Bean
public TestBean someBean() {
return new TestBean("A");
}
#Bean
public List<TestBean> someMoreBeans() {
return Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBean anotherBean() {
return new TestBean("D");
}
}
public static class TestBean {
private final String id;
public TestBean(final String id) {
this.id = id;
}
private String getId() {
return id;
}
}
}
I want to get this to run so that multiple modules can provide beans of a certain type.
Some modules want to provide multiple beans and their number depends on a property.
Some modules will always provide one bean.
The module using the beans (autowiring them as list) should autowire all beans.
How can I get this to run? In what scenario does Spring's behavior make sense?
I can work around the issue by introducing a TestBeanFactory. Each configuration that wants to contribute to the list of TestBeans instead provides a factory.
#Configuration
public static class TestConfiguration {
/** Implemented once in the configuration that defines <code>TestBean</code>. */
#Bean
public List<TestBean> testBeansFromFactory(Collection<TestBeanFactory> factories) {
return factories.stream().map(TestBeanFactory::createTestBeans).flatMap(Collection::stream)
.collect(toList());
}
// Further methods can be defined in various configurations that want to add to the list of TestBeans.
#Bean
public TestBeanFactory someBean() {
return () -> Arrays.asList(new TestBean("A"));
}
#Bean
public TestBeanFactory someMoreBeans() {
return () -> Arrays.asList(new TestBean("B"), new TestBean("C"));
}
#Bean
public TestBeanFactory anotherBean() {
return () -> Arrays.asList(new TestBean("D"));
}
}
public static class TestBean { ... }
public static interface TestBeanFactory {
public Collection<TestBean> createTestBeans();
}
That works and is only slightly more code.
M.Deinum makes a point in the comments for Spring's behavior being consistent:
As you defined a bean of type List it will be used. Autowiring is based on type, it will try to detect the bean of the certain type. The collection (and map as well) is a special one looking up all dependencies of the given type.

Switch bean by changing properties in Spring boot

I have one interface MyInterface, and 2 implementation beans: FirstImpl & SeconImpl. I want to switch between using these 2 implementations while program is running without restarting it, by only changing a property in application.properties file, e.g: interface.bean.default=FirstImpl change to interface.bean.default=SecondImpl.
Anyone knows how to do that with Spring boot?
You could try to use #ConditionalOnProperty:
#Configuration
public class MyInterfaceConfiguration {
#Bean
#ConditionalOnProperty(value = "my.interfacte.impl", havingValue="firstImpl")
public MyInterface firstImpl(){
return new FirstImpl();
}
#Bean
#ConditionalOnProperty(value = "my.interfacte.impl", havingValue="secondImpl")
public MyInterface secondImpl(){
return new SecondImpl();
}
}
and when you update your property in application.properties with actuator/refresh to:
my.interfacte.impl=firstImpl
you will have your FirstImpl instance. When you have:
my.interfacte.impl=secondImpl
you will have your SecondImpl.
#Hasan, your update only works if I customize it a little bit as below:
#Configuration
#RefreshScope
public class MyInterfaceConfiguration {
#Value("${my.interfacte.impl}")
String impl;
#Bean
#RefreshScope
public MyInterface getBean(){
if ("firstImpl".equals(impl)) {
return new FirstImpl();
} else if ("secondImpl".equals(impl)) {
return new SecondImpl();
}
return null;
}
}
I have to use 2 #RefreshScope at class level and bean creation method level!

Transitive inclusion of #Configuration classes from a .xml-based spring config

Suppose we start with an xml-based config, say main.xml, that imports a java config FullConfig.java via:
<context:annotation-config/>
<bean class="test.FullConfig"/>
This java config has the form:
#Configuration
#Import(value = {IncludeConfig.class})
public class FullConfig {
#Autowired
#Qualifier(value = "tmpBean")
private DataClazz autowired;
#Bean
public DataClazz someOtherBean() {
System.out.println("Using autowired tmpBean:" + autowired);
return new DataClazz();
}
}
so it imports a further java config, which contains a definition of the tmpBean of DataClazz type,
#Configuration
public class IncludeConfig {
#Bean
public DataClazz tmpBean() {
return new DataClazz();
}
}
Now two questions:
Is this "transitive inclusion" guaranteed to work in spring (i.e. is someOtherBean() guaranteed not to thrown a NPE)?
IntelliJ up to version 2017.2 does mark #Qualifier(value = "tmpBean") red with a message "Cannot find bean with qualifier 'tmpBean'". Should that be considered a bug?
Note: I have checked that an application using ClassPathXmlApplicationContext("main.xml") does work correctly, i.e. no NPE is thrown (and all relevant beans are visible).
You need to return DataClazz:
#Bean
public DataClazz someOtherBean() {
System.out.println("Using autowired tmpBean:" + autowired);
return autowired;
}
Probably yes but try to test it.
IDEA-82844 (Bug)

Resources