Avoid duplication using Spring and Java Config beans - java

I am having in Class A the following beans:
#Bean
public AsyncItemProcessor OneUploadAsyncItemProcessor() {
// ...
asyncItemProcessor.setDelegate(processor(OVERRIDDEN_BY_EXPRESSION, OVERRIDDEN_BY_EXPRESSION));
// ...
return asyncItemProcessor;
}
#Scope(value = "step", proxyMode = ScopedProxyMode.INTERFACES)
#Bean
public ItemProcessor<MyItem, MyItem> processor(#Value("#{jobParameters[pushMessage]}") String pushMessage, #Value("#{jobParameters[jobId]}") String jobId) {
return new PushItemProcessor(pushMessage, jobId);
}
Now I have in a class B the following:
#Bean
public AsyncItemProcessor TwpUploadAsyncItemProcessor() {
// ...
asyncItemProcessor.setDelegate(processor(OVERRIDDEN_BY_EXPRESSION, OVERRIDDEN_BY_EXPRESSION));
return asyncItemProcessor;
}
How I can Inject into class B the bean processor (which defined on class A) without duplicate it.

You just need to "autowire" it in class B. Something like:
class B {
#Autowire
//#Qualifier(value = "OneUploadAsyncItemProcessor")
[modifier] AsyncItemProcessor OneUploadAsyncItemProcessor;
}
I'm assuming you do not want –and hence going to delete- the bean TwpUploadAsyncItemProcessor from class B and include/autowire the (already defined) bean OneUploadAsyncItemProcessor from class A. If so, the #Qualifier annotation is not needed.
On the other hand if you want to autowire OneUploadAsyncItemProcessor without deleting TwpUploadAsyncItemProcessor and/or define (in the future) another bean of type AsyncItemProcessor you are going to need the #Qualifier annotation.

Related

How to execute a runnable instead of bean creation in Spring configuration?

There is a usual configuration creating beans and performing an action using some of them.
The intention of initialAction is just execute print("Hello...") if certain conditions are in place (MyCondition.class). Obviously this return null is only required to keep the ceremony of beans creation and can be misleading for developers reading the code.
Is there a more elegant and convenient way to express the intention "run code after certain beans created if a condition is true"?
#Configuration
class App{
#Bean
#Conditional(MyCondition.class)
Object initialAction() {
var a = firstBean();
var b = secondBean();
print("Hello beans: $s, %s".formatted(a, b))
return null;
}
MyEntity firstBean(){
...
}
MyEntity secondBean(){
...
}
// other beans
}
There is an annotation #PostConstruct from javax or jakarta packages that are invoked once the dependency injection is done. Sadly, this cannot be combined with the #Conditional annotation.
You can, however, create a dedicated #Configuration + #Conditional class using #PostConstruct:
#Configuration
#Conditional(MyCondition.class)
public InitialActionConfiguration {
#Autowired
#Qualifier("firstBean")
private MyEntity firstBean;
#Autowired
#Qualifier("secondBean")
private MyEntity secondBean;
#PostConstruct
public void postConstruct() {
var a = firstBean;
var b = secondBean;
print("Hello beans: $s, %s".formatted(a, b));
return null;
}
// initializations in other #Configuration class
}
Once you do this, both MyEntity beans got initialized (if properly defined as beans) and this configuration class autowires them and prints them out only if the condition defined by MyCondition is satisfied.

Why do we use a qualifier when we can have a name for the bean?

Why do we use qualifiers with #Bean when we can have different names for different beans of the same type (class)?
#Bean
#Qualifier("fooConfig")
public Baz method1() {
}
Isn't the following code more clean?
#Bean("fooConfig")
public Baz method1() {
}
If I create two beans of the same type with different names (using #Bean annotation), then can we inject them specifically using the #Qualifier annotation(can be added on field/constructor parameter/setter) in another bean?
#Bean("fooConfig")
public Baz method1(){
}
#Bean("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
If the above is true, then where do we use #Qualifier (with #Bean or #Component) instead of giving the bean a name as shown below?
#Bean
#Qualifier("fooConfig")
public Baz method1(){
}
#Bean
#Qualifier("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
Beans have names. They don't have qualifiers. #Qualifier is annotation, with which you tell Spring the name of Bean to be injected.
No.
Default Qualifier is the only implementation of the interface(example is below, 4th question) or the only method with a particular return type. You don't need to specify the #Qualifier in that case. Spring is smart enough to find itself.
For example:
#Configuration
public class MyConfiguration {
#Bean
public MyCustomComponent myComponent() {
return new MyCustomComponent();
}
}
If you will try to inject myComponent somewhere, Spring is smart enough to find the bean above. Becaude there is only one Bean with return type MyCustomComponent. But if there was a couple of methods, that would return MyCustomComponent, then you would have to tell Spring which one to inject with #Qualifier annotation.
SIDENOTE: #Bean annotation by default Spring uses the method name as a bean name. You can also assign other name like #Bean("otherComponent").
You have one Interface, and a couple of Classes implementing it. You inject bean of your interface. How can Spring know which Class should be used?
This is you interface:
public interface TestRepository{}
This is your implementation 1:
#Repository
public class Test1Repository implements TestRepository{}
Your implementation 2:
#Repository
public class Test2Repository implements TestRepository{}
Now you are injecting it like:
private final TestRepository testRepository;
public TestServiceImpl(TestRepository testRepository) {
this.testRepository= testRepository;
}
QUESTION! How is Spring supposed to know which class to inject? Test1 or Test2? That's why you tell it with #Qualifier which class.
private final TestRepository testRepository;
public TestServiceImpl(#Qualifier("test1Repository") TestRepository testRepository) {
this.testRepository= testRepository;
}
I Prefer different method to not using #Qualifier
Create common Interface
public interface CommonFooBar{
public String commonFoo();
public String commonBar();
}
Extends to each service
public interface FooService extends CommonFooBar {
}
public interface BarService extends CommonFooBar {
}
Then using it to your class
#Autowired
FooService fooService;
or
#Autowired
BarService barService;
so, we can defined the single responsibility to each interface and This kind of segregation is more readable to every junior.
I quite like a different way of working. Surely if you provide a unique name for your bean, then that is all you need?
Given the example below, its easy to see that Spring will name the beans based on the method name used to create the beans. In other words, if you give your beans sensible names, then the code should become self-explanatory. This also works when injecting beans into other classes.
The end result of this is:
Spring will name your beans based on the method used to create them.
If you import a bean, Spring will try to match on the bean name.
If you try to import a bean that does not match the name, Spring will attempt to match the class.
If your injected field name does not match the bean name and there are more than one instance of your bean, Spring will throw an exception on startup as it won't know which one to inject.
Lets not over-complicate Spring.
#Bean
mqConnectionFactory() {
ConnectionFactory connectionFactory = new MQXAConnectionFactory();
return connectionFactory;
}
#Bean
public ConnectionFactory pooledConnectionFactory(ConnectionFactory mqconnectionFactory) {
JmsPoolConnectionFactory connectionFactory = new JmsPoolConnectionFactory();
connectionFactory.setConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public ConnectionFactory cachingConnectionFactory(ConnectionFactory mqConnectionFactory) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setTargetConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(cachingConnectionFactory);
return jmsTemplate;
}
#Bean
public DefaultMessageListenerContainer messageListenerContainer(ConnectionFactory pooledConnectionFactory) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(pooledConnectionFactory);
...
return container;
}

How do I extend a named Spring bean when using a #Qualifier specified bean injection point?

How do I extend a named bean when using a #Qualifier specified bean injection point?
I have project 1 consisting of 3 spring beans:
#Component("bean1")
public class Bean1 implements Bean {
}
#Component("bean2")
public class Bean2 implements Bean {
}
#Component("bean3")
public class Bean3 {
private Bean bean;
public void setBean(#Qualifier("bean1") final Bean bean) {
this.bean = bean;
}
}
This project is bundled into a jar and included as a dependency on my 2nd project:
#Component("bean1")
#Primary
public class Bean1Child extends Bean1 {
}
What I want to happen is for the bean, Bean1Child, to be injected into Bean3's setter method. Unfortunatly I am receiving an error.
org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'bean1' for bean class [Bean1Child]
conflicts with existing, non-compatible bean definition of same name
and class [Bean1]
I needed to use #Qualifier so that Bean2 is not injected into Bean3 Using the #Primary annotation did not help. How can I have Bean1Child injected into Bean3 when running from my 2nd project?
If this is possible, you can change the way the beans are created by removing the #Component annotations:
In the first project, the BeanChild3 would be refactored to get the bean in the constructor
public class Bean3 {
private final Bean bean;
public Bean3(final Bean bean) {
this.bean = bean;
}
}
Then we can create the beans in a BeansConfig class
#Configuration
class BeansConfig {
#ConditionalOnMissingBean
#Bean("bean1")
Bean bean1(){
return new Bean1();
}
#Bean("bean2")
Bean bean2(){
return new Bean2();
}
#Bean("bean3")
Bean bean3(#Autowired #Qualifier("bean1") Bean){
return new Bean3(bean);
}
}
The #ConditionalOnMissingBean allows us to provide another bean with the same name to be used instead. If no such bean exists, then the default one would be used.
You will then need to create a beanChild1 bean in your second project and it should be picked up.
#Bean("bean1")
Bean bean1(){
return new Bean1Child();
}
You can easily achieve this using #ConditionalOnMissingBean feature.
Modify your Bean1 class as below
#Component("bean1")
#ConditionalOnMissingBean(type = "bean1")
public class Bean1 implements Bean {
}
modify Bean1Child class as below
#Component
#Qualifier("bean1")
#Primary
public class Bean1Child extends Bean1 {
}
How it works?
Spring will try to load a bean named "bean1". If it doesn't find any bean by the same name that is marked as #Primary it will fall back to Bean1 class and load it as "bean1".
Bean1Child has to be marked as primary because spring is going to find 2 beans by the same name. We need to tell which to load.
You have multiple beans of the same type and want to prevent Bean2 from injecting. In some project inject Bean1 and in others Bean1Child.
There are multiple options.
Override bean definition with #Bean
Make Bean1Child bean definition the same as Bean1 has using #Bean
#Configuration
public class Config {
#Primary
#Bean
public Bean1 bean1() { //return type must be Bean1
return new Bean1Child();
}
}
and set the property spring.main.allow-bean-definition-overriding=true
Create custom #Qualifier annotation
#Qualifier
#Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface BeanType {
}
#BeanType
public #interface Bean1Type {
}
#Bean1Type
#Component("bean1")
public class Bean1 implements Bean {
}
#Component("bean2")
public class Bean2 implements Bean {
}
#Component("bean3")
public class Bean3 {
private final Bean bean;
public Bean3(#Bean1Type Bean bean) {
this.bean = bean;
}
}
#Bean1Type
#Primary
#Component("bean1Child")
public class Bean1Child extends Bean1 {
}
You cannot have two beans specified with the same qualified name, as the error indicates:
Annotation-specified bean name 'bean1' for bean class [Bean1Child]
conflicts with existing, non-compatible bean definition of same name and class [Bean1]
Giving a different qualifier name to Bean1Child should work.
#Component("bean1child")
#Primary
public class Bean1Child extends Bean1 {
}
and in Bean3 , public void setBean(#Qualifier("bean1child") final Bean bean) {

Spring: Autowire bean that does not have qualifier

Is it possible to autowire a bean that does NOT have the given qualifier in spring? The use case would be to have a list of all beans, but exclude one:
#Autowired
#NotQualifier("excludedBean") // <-- can we do something like this?
List<SomeBean> someBeanList;
public class Bean1 implements SomeBean {}
public class Bean2 implements SomeBean {}
#Qualifier("excludedBean")
public class Bean3 implements SomeBean {}
In the example above someList should contain an instance of Bean1 and Bean2 but not Bean3.
(Remark: I'm aware that the opposite would work, i.e. add some qualifier to Bean1 and Bean2 and then autowire with that qualifier.)
EDIT: Some further clarifications:
All beans are in the spring context (also the one being excluded).
Configuration needs to be annotation-based, not xml-based. Therefore, e.g. turning off autowired-candidate does not work.
Autowire capability of the bean must remain in general. In other words, I want to exclude the bean from the injection point List<SomeBean> someBeanList;, but I want to autowire it somewhere else.
You can introduce you own annotation with meta annotations #Conditional and #Qualifier
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
#Conditional(MyCondition.class)
public #interface ExcludeBean {
and then introduce class where you can do your conditional logic
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !metadata.equals(ExcludeBean.class);
}
}
In your Configuration class
#Bean
#ExcludeBean
public BeanA beanA() {
return new BeanA();
}
You can also exclude bean from being candidate for autowiring by setting autowire-candidate on particular bean or by specifying default-autowire-candidates="list of candidates here"
main point it's bean for exclude is in context but not injected into some cases with exclude condition .
you can do exclude bean with qualifier with custom annotaion and BeanPostProcessor. (I did as example for simple case , for collection of bean type , but you can extend it)
annotaion for exclude :
#Component
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ExcludeBeanByQualifierForCollectionAutowired {
String qualifierToExcludeValue();
Class<?> aClass();
}
bean post processor with injection
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
#Component
public class ExcludeAutowiredBeanPostProcessor implements BeanPostProcessor {
#Autowired
private ConfigurableListableBeanFactory configurableBeanFactory;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
ExcludeBeanByQualifierForCollectionAutowired myAutowiredExcludeAnnotation = field.getAnnotation(ExcludeBeanByQualifierForCollectionAutowired.class);
if (myAutowiredExcludeAnnotation != null) {
Collection<Object> beanForInjection = new ArrayList<>();
String[] beanNamesOfType = configurableBeanFactory.getBeanNamesForType(myAutowiredExcludeAnnotation.aClass());
for (String injectedCandidateBeanName : beanNamesOfType) {
Object beanCandidate = configurableBeanFactory.getBean(injectedCandidateBeanName);
Qualifier qualifierForBeanCandidate = beanCandidate.getClass().getDeclaredAnnotation(Qualifier.class);
if (qualifierForBeanCandidate == null || !qualifierForBeanCandidate.value().equals(myAutowiredExcludeAnnotation.qualifierToExcludeValue())) {
beanForInjection.add(beanCandidate);
}
}
try {
field.set(bean, beanForInjection);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
and example:
public class ParentBean {}
public class Bean1Included extends ParentBean {}
public class Bean2Included extends ParentBean {}
public class Bean3Included extends ParentBean {}
#Qualifier("excludedBean")
public class BeanExcluded extends ParentBean {}
configuration
#Configuration
public class BeanConfiguration {
#Bean
public Bean1Included getBean1(){
return new Bean1Included();
}
#Bean
public Bean2Included getBean2(){
return new Bean2Included();
}
#Bean
public Bean3Included getBean3(){
return new Bean3Included();
}
#Bean
public BeanExcluded getExcludedBean(){
return new BeanExcluded();
}
#Bean
public ExcludeAutowiredBeanPostProcessor excludeAutowiredBeanPostProcessor(){
return new ExcludeAutowiredBeanPostProcessor();
}
}
and result:
#ExtendWith(SpringExtension.class) // assumes Junit 5
#ContextConfiguration(classes = BeanConfiguration.class)
public class ExcludeConditionTest {
#Autowired
private ApplicationContext context;
#Autowired
private BeanExcluded beanExcluded;
#ExcludeBeanByQualifierForCollectionAutowired(qualifierToExcludeValue = "excludedBean" , aClass = ParentBean.class)
private List<ParentBean> beensWithoutExclude;
#Test
void should_not_inject_excluded_bean() {
assertThat(context.getBeansOfType(ParentBean.class).values())
.hasOnlyElementsOfTypes(Bean1Included.class,
Bean2Included.class,
Bean3Included.class,
BeanExcluded.class);
assertThat(beansWithoutExclude)
.hasOnlyElementsOfTypes(Bean1Included.class,
Bean2Included.class,
Bean3Included.class)
.doesNotHaveAnyElementsOfTypes(BeanExcluded.class);
assertThat(beanExcluded).isNotNull();
}
}
There might be two cases :
case 1 : Bean3 in not in spring context;
case 2 : Bean3 is in spring context but not injected in some cases with #Autowired ,
if you need to exclude bean with Qualifier from context at all ,use
Condition. This bean is not registered in application conxtet if matches returns false. as result :
#Autowired List someBeanList; -- here injected all beans instanceof SomeBean and registered in application context.
from spring api
Condition A single condition that must be matched in order for a
component to be registered. Conditions are checked immediately before
the bean-definition is due to be registered and are free to veto
registration based on any criteria that can be determined at that
point.
autowired with qualifier :
2.1 if you want to exclude bean with the some qualifier from autowired
value in some bean/beans and in xml configuration you can use
autowire-candidate
2.2 also you can get
all autowired values by Setter Injection and filter only
beans that you need.
//no Autowired. Autowired in method
private List<ParentBean> someBeen = new ArrayList<>();
#Autowired
public void setSomeBeen(List<ParentBean> beens){
// if you use java 8 use stream api
for (ParentBean bean:beens) {
Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class);
if(qualifier == null ||!qualifier.value().equals("excludedBean")){
someBeen.add(bean);
}
}
}
2.3 you can use custome AutowiredAnnotationBeanPostProcessor :) and customise #Autowired for you requirements if you need something realy custom.
from spring api AutowiredAnnotationBeanPostProcessor :
Note: A default AutowiredAnnotationBeanPostProcessor will be
registered by the "context:annotation-config" and
"context:component-scan" XML tags. Remove or turn off the default
annotation configuration there if you intend to specify a custom
AutowiredAnnotationBeanPostProcessor bean definition.
Another way to do this, is creating a custom component with #Qualifier
#Component
#Qualifier
public #interface MyComponent {
public boolean isMock() default false;
}
#Autowired
#MyComponent(true)
List<SomeBean> mockList; // will inject just component with "isMock = true"
#Autowired
#MyComponent(false)
List<SomeBean> notMockList; // will inject just component with "isMock = false"
#MyComponent
public class Bean1 implements SomeBean {}
#MyComponent
public class Bean2 implements SomeBean {}
#MyComponent(isMock = true)
public class Bean3 implements SomeBean {}
Obs: this code is not tested, just giving an idea

How does spring resolve method calls as beans?

Consider this code:
public class Bean1 {}
public class Bean2 {
private final Bean1 bean1;
public Bean2(Bean1 bean1){
this.bean1 = bean1;
}
}
#Configuration
public class MyConfiguration {
#Bean
public Bean1 bean1(){
return new AImpl();
}
#Bean
public Bean2 bean2() {
return new BImpl(bean1());
}
#Bean
public Bean3 bean3() {
return new BImpl(bean1());
}
}
My knowledge of Java dicates, that two references of bean1 in bean2 and bean3 should be different, that, since I call the bean1() method twice, two different objects should be created.
However, under Spring, in the same ApplciationContext, etc. etc., both bean2 and bean3 will have the same reference to the same object of class Bean1.
How is that possible in Java? What mechanism does Spring use that allows it to somehow intercept method calls and put beans as result of those calls?
Class with the #Configurable annotation are treated in a special way. They are parsed using ASM and from the scanning special bean definitions are created. Basically each #Bean annotation is a special kind of factory bean.
Because the methods are treated as factory beans they are only invoked once (unless the scope isn't singleton of course).
Your configuration class is not executed as it.
Your class is first read by org.springframework.asm.ClassReader
The class org.springframework.context.annotation.ConfigurationClassParser parses your configuration class. Each method annoted by #Bean is associated to a org.springframework.context.annotation.BeanMethod.

Categories

Resources