Make List of Spring Bean - java

I have interface EventService and class #Component Event implementing it. Class #Component BerlinEvent extends #Component Event and implements EventService.
On configuration class I have this:
#Configuration
public class Configuration {
//Country name
#Bean
#ConditionalOnProperty(name = "country", havingValue = "UK")
public Event defaultService(){return new Event();}
#Bean
#ConditionalOnProperty(name = "country", havingValue = "germany", matchIfMissing = true)
public Event germanyEventService(){return new BerlinEvent();}
}
And on main I make the bean:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(EventscraperApplication.class, args);
EventsManagerService eventsManager = context.getBean(EventsManager.class);
eventsManager.run(context.getBean(Event.class));
}
Now on class EventsManagerService I need to make a List with either BerlinEvent or Event objects depending on which bean was created and each object with different values but I cant figure out how to do it

Spring could autowire all beans that implement the same interface into the list like this
#Autowired
private List<Event> events;
By default, the autowiring fails whenever zero candidate beans are available; the default behavior is to treat annotated methods, constructors, and fields as indicating required dependencies. This behavior can be changed as demonstrated below. To avoid that you need to pass addtitonal parameter to annotation like this:
#Autowired(required = false)
private List<Event> events;
Here is the link to Spring documentation: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation

In you bean class, you can just do
#Service
public class EventsManagerService {
#Autowired
private ApplicationContext applicationContext;
private Map<String, Event> beans;
#PostConstruct
public void setMocks() {
beans = applicationContext.getBeansOfType(Event.class);
}
}
This will get you all the beans implementing Events class.

Related

Spring: Service Class with #Service annotation gets "required bean of type 'abcService' could not be found"

I have a spring batch application in which the writer has an #Autowired field (it is a service class). When running tests for the writer step, I am met with the error:
Field batchTrackingService in com.ally.cr.miscinfo.batch.writer.AdvantageClientItemWriter required a bean of type 'com.test.miscinfo.service.TestService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.test.miscinfo.service.batchTrackingService ' in your configuration.
I've looked at a few answers to related questions, and most of them are caused by the fact that the class being injected has not been annotated with #Component, #Service, #Repository, etc. However mine is. I also read questions where the supposed solution was to add the #ComponentScan() annotation to the Main class of my application. After trying this, it gave the same error. Can someone please help me? Any help is appreciated.
Here are the relevant classes:
Main class:
#SpringBootApplication
#EnableBatchProcessing
#EnableJpaRepositories("com.test.miscinfo.repository")
#EntityScan("com.test.miscinfo.entity")
#ComponentScan("com.test.miscinfo.service")
public class MiscInfoServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MiscInfoServiceApplication.class, args);
}
}
Writer class:
#Slf4j
#Component
#AllArgsConstructor
#NoArgsConstructor
public class AdvantageClientItemWriter implements ItemWriter<MiscInfo> {
#Autowired private AdvantageClientConfig advantageClientConfig;
#Autowired WebClient advantageClientWebClient;
#Autowired private BatchTrackingService batchTrackingService;
#Override
public void write(List<? extends MiscInfo> miscInfos) throws Exception {
/* some call to a method in the injected service */
}
}
Service class:
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
#Transactional
#Service
public class BatchTrackingService {
public void someMethod() {}
}
Please let me know if I am missing relevant info.
EDIT:
Adding test method:
#ExtendWith(SpringExtension.class)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#EnableConfigurationProperties(value = AdvantageClientConfig.class)
#SpringBootTest
#ActiveProfiles("test")
#ContextConfiguration(classes = { AdvantageClientItemWriter.class })
public class AdvantageClientItemWriterTest {
#MockBean RestTemplate advantageClientRestTemplate;
#MockBean WebClient advantageWebClient;
WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class);
WebClient.RequestBodySpec requestBodySpec = mock(WebClient.RequestBodySpec.class);
WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
WebClient.RequestHeadersSpec requestHeadersSpec = mock(WebClient.RequestHeadersSpec.class);
#Autowired AdvantageClientConfig advantageClientConfig;
#Autowired AdvantageClientItemWriter advantageClientItemWriter;
ArgumentCaptor<String> uriCaptor = ArgumentCaptor.forClass(String.class);
ArgumentCaptor<MediaType> mediaTypeCaptor= ArgumentCaptor.forClass(MediaType.class);
ArgumentCaptor<String> headerNameCaptor= ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> headerValueCaptor= ArgumentCaptor.forClass(String.class);
ArgumentCaptor<String> bodyCaptor= ArgumentCaptor.forClass(String.class);
private MemoryAppender memoryAppender;
#BeforeEach
public void init(){
Logger logger = (Logger) LoggerFactory.getLogger("com.test");
memoryAppender = new MemoryAppender();
memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
logger.setLevel(Level.DEBUG);
logger.addAppender(memoryAppender);
memoryAppender.start();
}
#Test
public void successfulAdvantageClientWrite() throws Exception {
setupMockReturns();
when(responseSpec.toBodilessEntity()).thenReturn(Mono.just(new ResponseEntity(null, HttpStatus.OK)));
List<MiscInfo> miscInfos = new ArrayList<>();
final MiscInfo miscInfo = createMiscInfo1();
miscInfos.add(miscInfo);
advantageClientItemWriter.write(miscInfos);
Assertions.assertEquals(advantageClientConfig.getEndpoint(), uriCaptor.getValue());
Assertions.assertEquals(MediaType.APPLICATION_JSON, mediaTypeCaptor.getValue());
Assertions.assertEquals( advantageClientConfig.getHeaderName(), headerNameCaptor.getValue());
Assertions.assertEquals(advantageClientConfig.getApiKey(), headerValueCaptor.getValue());
Assertions.assertEquals(new ObjectMapper().writer().withDefaultPrettyPrinter().writeValueAsString(miscInfos), bodyCaptor.getValue());
assertThat(memoryAppender.search("Write to Advantage status: ", Level.DEBUG).size()).isEqualTo(1);
}
}
This error means that Spring is trying to autowire of bean of type BatchTrackingService in your AdvantageClientItemWriter but it could not find one in the application context. In other words, your test context does not contain a bean definition of type BatchTrackingService, which could be due to one of the following causes:
Either the configuration class that defines that bean is not imported in the test class (in #ContextConfiguration(classes = { AdvantageClientItemWriter.class })
or the class of that bean is not in the package that is scanned by Spring (Boot).
Make sure that:
the class public class BatchTrackingService {} is defined in the package referenced in #ComponentScan("com.test.miscinfo.service")
the #ContextConfiguration annotation imports the class of that bean (something like #ContextConfiguration(classes = { AdvantageClientItemWriter.class, BatchTrackingService.class }), or that it imports a configuration class which defines an instance of that bean.

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

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

Changing a class annotated #Component to #Bean annotated method

I have a class that is annotated #Component that was then #Autowired into another class. However, I need to remove this #Component annotation and instead, create it with an #Bean annotated method in the class where its was previously autowired.
Where previously the classes looked like:
#Component
public class MyClass implements IMyClass
{
// Stuff
}
#Configuration
public class MyUsingClass
{
#Autowired
private IMyClass myClass;
private void methodUsingMyClass()
{
myClass.doStuff();
}
}
So now I have removed the #Component annotation and written a #Bean annotated method like this:
public class MyClass implements IMyClass
{
// Stuff
}
#Configuration
public class MyUsingClass
{
#Bean
public IMyClass getMyClass()
{
return new MyClass();
}
....
}
My question is around replacing the previous call of myClass.doStuff() to use the new bean. Do I now pass in a parameter of type MyClass to the private method:
private void methodUsingMyClass(final MyClass myClass)
{
myClass.doStuff();
}
... or do I call this method directly (doesn't seem the correct way to me):
private void methodUsingMyClass()
{
getMyClass().doStuff();
}
... or are neither of these correct?
I think you misunderstand the #Bean annotation. It can be used to create a Bean. So basically spring will scan all classes, will find your #Bean and create a Bean, not more. You can now use this bean, like if you would use one created with <bean></bean>. To actually use the bean you need to either get it from ApplicationContext or #Autowire it. Of course you can still use that function like any other function in your code, to create a new instance of that object, but that would contradict to what you want to achieve with beans
Using Annotations that solutions
public class MyClass implements IMyClass{
private OtherClassInjection otherClassInjection;
private OtherClassInjection2 otherClassInjection2;
MyClass(OtherClassInjection otherClassInjection, OtherClassInjection2 otherClassInjection2){
this.otherClassInjection=otherClassInjection;
this.otherClassInjection2=otherClassInjection2;
}
public void useObject(){
otherClassInjection.user();
}
}
#Bean(name = "myClass")
#Autowired
#Scope("prototype") //Define scope as needed
public MyClass getMyClass(#Qualifier("otherClassInjection") OtherClassInjection otherClassInjection,
OtherClassInjection2 otherClassInjection2) throws Exception
{
return new MyClass(otherClassInjection, otherClassInjection2);
}
that logical, it's work injection #Autowired when create a Bean if context are know that bean, that you will to want inject.
I'm use that way.

#Configuration not working for Bean creation

I have the following code, where in i am trying to create a bean out of the return type of the method.
It give error while starting the application as below:
Error creating bean with name 'myMap' defined in class path resource
[com/test/MyServiceImpl.class]: No matching factory method found:
factory bean 'MyServiceImpl'; factory method 'myMap()'. Check that a
method with the specified name exists and that it is non-static.
Code:
#Configuration
public class MyServiceImpl implements MyService
{
#Autowired
private MyDao myDao;
#Override
#Bean
#Scope("singleton")
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Map<String, String> myMap()
{
return myDao.getMapFromDB();
}
}
public interface MyService
{
Map<String, String> myMap()
}
My application is based spring mvc and I have added the relevant configuration in the xml.
<mvc:annotation-driven/>
<context:component-scan>
I'm not 100% sure what you try to do, but here's an approach which would work:
#Service
public class MyServiceImpl implements MyService {
#Autowired
private MyDao myDao;
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Map<String, String> myMap() {
return myDao.getMapFromDB();
}
}
interface MyService {
Map<String, String> myMap()
}
This would define your service implementation as a Spring-managed #Service. Your MyDao would automatically be injected and you can use it in your method.
If you want that your MyDao has a certain scope (singleton is already the default), than you would annotate the MyDao class.
If you want to write a configuration, you would need to do it like this:
#Configuration
class MyConfig {
public MyService myService() {
return new MyServiceImpl();
}
}

Categories

Resources