I will continue with using the AlertService from part 2 of the series, to show how to utilise Dependency Injection in Spring with a more common situation. Let us start with the MessageServices
and bring them under control of the BeanFactory
in Spring by annotating them as a Component
.
@Component public class EmailMessageService implements MessageService { ... }
@Component public class SlackMessageService implements MessageService { ... }
One of these Components is the DiscFullAlertService
bean. We will inject the dependency to a MessageService
Bean via constructor injection.
package com.vividbreeze.designpattern.spring.di; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class DiscFullAlertService extends AlertService { public static final String ALERT_TOPIC = "Alert on disk /gdalogs1 almost full."; public static final String ALERT_MESSAGE = "disk /gdalogs1 has reached 90% of its capacity."; @Autowired public DiscFullAlertService(MessageService messageService) { super (messageService); } @Override public void processAlert(String receiver) { messageService.sendMessage(ALERT_TOPIC, ALERT_MESSAGE, receiver); } }
Let us now run the application
@ComponentScan public class FireAlert { public static void main (String args[]) { String receiver = "ops@vividbreeze.com"; ApplicationContext context = new AnnotationConfigApplicationContext(FireAlert.class); AlertService alertService = context.getBean(DiscFullAlertService.class); alertService.processAlert(receiver); } }
It looks like we might run into problems here, as we want to inject a MessageService
(the abstract super-class), but there are two beans that implement the MessageService: EmailMessageService and SlackMessageService. You might find the following message in the console:
... nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.vividbreeze.designpattern.spring.di.MessageService' available: expected single matching bean but found 2: emailMessageService,slackMessageService
Configurations
So the BeanFactory
is confused which implementation to inject into the DiscFullAlertService
. One solution would be to configure the BeanFactory
so that it knows which implementation to choose. In this configuration, you are able to instantiate and register a Bean with the BeanFactory
, so it later can be injected.
package com.vividbreeze.designpattern.spring.di; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class AlertConfiguration { @Bean public MessageService messageService() { return new EmailMessageService(); } }
The class is annotated as a class that contains the configuration (@Configuration
). The configuration is implemented in form of beans (or optionally an XML-file). The bean-name is the name of the method that is annotated with @Bean
(messageService, not getMessageService). This method returns the instance of the bean that is then registered with the BeanFactory. After the initialisation, a ComponentScan is performed. So when it reaches
@Autowired public DiscFullAlertService(MessageService messageService) { super (messageService); }
it the BeanFactory already has a registered Bean “messageService” and injects it in the messageService
attribute of DiscFullAlertService
. Notice that in our main-method, we pass the Configuration
class when we initialise the ApplicationContext
and we don’t perform a ComponentScan here.
public class FireAlert { public static void main (String args[]) { String receiver = "ops@vividbreeze.com"; ApplicationContext context = new AnnotationConfigApplicationContext(AlertConfiguration.class); AlertService alertService = context.getBean(DiscFullAlertService.class); alertService.processAlert(receiver); } }
The reason why I included the ComponentScan
in the Configuration
class will become more obvious when we work with a different Configuration for unit-tests. As mentioned in the previous blog-post where we might not be able to send e-mails or Slack-messages from our local computers. Thus we can use a MockMessageService
that just logs the message to the console.
@Component public class MockMessageService implements MessageService { private static final Logger LOG = LoggerFactory.getLogger(MockMessageService.class); @Override public void sendMessage(String subject, String message, String receiver) { LOG.debug("MockMessageService - subject: " + subject + ", receiver: " + receiver + ", message: " + message); } }
The instance of this class will also be annotated as a component so we can access it later via context.getBean(). The configuration will then look like this
@Configuration @ComponentScan public class AlertTestConfiguration { //@Bean public MessageService messageService() { return new MockMessageService(); } }
The unit test can now look like this
public class FireAlertTest { private static final Logger LOG = LoggerFactory.getLogger(FireAlertTest.class); ApplicationContext context = new AnnotationConfigApplicationContext(AlertTestConfiguration.class); @Test public void sendAlertMessage() { AlertService alertService = context.getBean(DiscFullAlertService.class); alertService.processAlert("vividbreeze"); } }
Debugging
The dependencies are resolved during run-time. If you have many beans with lots of dependencies your application might become more prone to errors. You can see which beans are initialised under which name during start-up (when the ApplicationContext
is instantiated and initialised). This log message might be a bit confusing. You can also list all beans that are registered with the BeanFactory
using the following snippet
ApplicationContext context = new AnnotationConfigApplicationContext(FireAlert.class); String[] beanNames = context.getBeanDefinitionNames(); for (String beanname: beanNames) { LOG.debug(">>> " + beanname + " - " + context.getBean(beanname).toString()); }
Remarks
This example should give an overview of how to use the Spring Framework in a simple real-life situation. I highly recommend to consult other tutorials and to experiment with small sample applications to gain a better understanding of Dependency Injection in Spring.
- Hackintosh – The Hardware - 22/02/2020
- Installing Artifactory with Docker and Ansible - 17/08/2018
- Docker Networking – Overlay Network - 15/08/2018
One Comment
Comments are closed.