In
Part 1 of this series, we introduced the concept of Inversion of Control
(IoC) or Dependency Injection (DI)
using Spring Framework and got our hands dirty in setter injection using the 3
different approaches.
In this part, we will explore the constructor injection using the 3 different approaches.
This may raise a question in our minds - why Constructor Injection ???
There will be situations when we want the collaborator (dependee) object(s) to be immutable. In other words, we
want the injected bean references to be immutable (not change) once initialized. This is where
constructor injection comes in handy. With the setter injection, one can change the collaborator bean(s)
later at runtime.
Hands-on with Spring Framework Core - 2
We will demonstrate each of the 3 approaches using a simple Hello greeter (in different
languages) example with a little twist - we will also display the sales tax corresponding to the country (associated
with the language).
XML based Approach
The simple Hello greeter standalone application will output a greetings message for a
chosen language (English, French, etc) and a specified name. In addition, it will tag the sales tax for the country of
the chosen language. The hello message (for a chosen language) and the sales tax (for the country of the chosen
language) are abstracted in the data package, while the greeter is abstracted in the service package.
The interface WorldHelloDAO as well as its implementation POJO
SimpleMemoryWorldHelloDAO from the data package have no changes and remains the same.
The following is the interface WorldSalesTax from the data package:
The following is the POJO SimpleMemoryWorldSalesTaxDAO from the data package that
implements the interface WorldSalesTax and stores the sales tax rates for a predefined
set of languages (English, French, German, Italian, and Spanish) in a java.util.Map:
The interface HelloGreeter from the service package has no change and remains the same.
The following is the POJO SimpleHelloGreeter4 from the service package that
implements the interface HelloGreeter. It returns a greetings message that consists
of the 'Hello' for the chosen language and the specified name along with the sales tax rate of the country corresponding
to the chosen language:
The following XML file sample3-beans.xml specifies the configuration metadata
for the Spring Framework IoC container:
To inject a constructor argument, use the <constructor-arg> element under
the corresponding <bean> element. To specify primitive values, use the
value attribute and to inject object references, use the ref
attribute.
The index attribute is used to identify the position of the constructor argument.
For example, the first argument is at index 0, the second argument is at index 1 and so on.
The type attribute is used to identify the type of the constructor argument.
Both the index and type attributes are
optional. They will be needed in situations when there are many contructors and one needs to resolve which one
to use.
See Figure.1 below.
Figure.1
Now that we have got the application POJOs and the configuration metadata defined in an XML file, it is time
to bring them together into the Spring Framework IoC container as a standalone
Java application.
The following is the Spring Framework application Sample4:
Executing the Java program Sample04 listed above should generate an output similar to
the following:
Output.1
Jul 12, 2019 8:47:41 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 12, 2019 8:47:41 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO
Jul 12, 2019 8:47:41 PM com.polarsparc.springframework.Sample4 main
INFO: Hola, Duck >>> Sales Tax: 21.0
Jul 12, 2019 8:47:41 PM com.polarsparc.springframework.Sample4 main
INFO: Bonjour, Snake >>> Sales Tax: 20.0
Annotation based Approach
The interface WorldHelloDAO as well as its implementation POJO
SimpleMemoryWorldHelloDAO2 from the data package have no changes and remains the same.
The interface WorldSalesTax from the data package has no change and remains the same.
The following is the POJO SimpleMemoryWorldSalesTaxDAO2 from the data package that
implements the interface WorldSalesTax and stores the sales tax rates for a predefined
set of languages (English, French, German, Italian, and Spanish) in a java.util.Map:
The interface HelloGreeter from the service package has no change and remains the same.
The following is the POJO SimpleHelloGreeter5 from the service package that implements
the interface HelloGreeter. It returns a greetings message that consists of the 'Hello' for
the chosen language and the specified name along with the sales tax rate of the country corresponding to the chosen
language:
Notice the use of the @Value annotations alongside the first two constructor arguments
in the code SimpleHelloGreeter5 above.
@Value can also be applied to method arguments. This annotation is used to inject a value
for the method arguments. See Figure.2 below.
Figure.2
The following XML file sample4-beans.xml indicates to the
Spring Framework IoC container that it perform component scanning at the specified package level for
annotation processing:
The <context:property-placeholder> element is used to specify a properties file
that will contains the values for all the property names referred inside the various @Value
annotations.
The location attribute indicates the name and where the properties file can be found.
Notice the use of the prefix classpath:. This indicates that the file will be in the
Java classpath.
The following are the contents of the properties file sample5.properties:
The following is the Spring Framework application Sample5:
Executing the Java program Sample05 listed above should generate an output similar
to the following:
Output.2
Jul 12, 2019 9:35:15 PM com.polarsparc.springframework.data2.SimpleMemoryWorldHelloDAO2
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO2
Jul 12, 2019 9:35:15 PM com.polarsparc.springframework.data3.SimpleMemoryWorldSalesTaxDAO2
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO2
Jul 12, 2019 9:35:15 PM com.polarsparc.springframework.Sample5 main
INFO: Bonjour, Parrot >>> Sales Tax: 20.0
Jul 12, 2019 9:35:15 PM com.polarsparc.springframework.Sample5 main
INFO: Guten Tag, Leopard >>> Sales Tax: 19.0
JavaConfig based Approach
The interface WorldHelloDAO as well as its implementation POJO
SimpleMemoryWorldHelloDAO from the data package have no changes and remains the same.
The interface WorldSalesTax as well as its implementation POJO
SimpleMemoryWorldSalesTaxDAO from the data package have no changes and remains the same.
The interface HelloGreeter from the service package has no change and remains the
same.
The following is the POJO SimpleHelloGreeter6 from the service package that
implements the interface HelloGreeter. It returns a greetings message that consists
of the 'Hello' for the chosen language and the specified name along with the sales tax rate of the country
corresponding to the chosen language:
The following is the JavaConfig POJO Sample6Config from
the config package:
The interface org.springframework.core.env.Environment encapsulates the current
application environment and its associated configuration information. Configuration information can come from
properties file(s).
@PropertySource annotation is used in conjunction with the
@Configuration annotation and is used to specify application properties file(s). In our example, the
properties file is sample5.properties (located in the classpath). The configuration
information from the specified properties file will be injected into the Environment
variable env (via @Autowired) and made available for use
in the application via the method env.getProperty(String). See
Figure.3 below.
Figure.3
The following is the Spring Framework application Sample6:
Executing the Java program Sample06 listed above should generate an output similar to
the following:
Output.3
Jul 12, 2019 10:03:17 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 12, 2019 10:03:17 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO
Jul 12, 2019 10:03:17 PM com.polarsparc.springframework.Sample6 main
INFO: Bonjour, Panther >>> Sales Tax: 20.0
Jul 12, 2019 10:03:17 PM com.polarsparc.springframework.Sample6 main
INFO: Hello, Lion >>> Sales Tax: 20.0
An interesting question to ask at this point - what would happen if there was one more implementation the interface
WorldHelloDAO with annotation ???
The following is the POJO SimpleMemoryWorldHelloDAO3 from the data package that
implements the interface WorldHelloDAO and stores 'HELLO' (uppercase) for a predefined set
of languages (English, French, German, Italian, and Spanish) in a java.uitl.Map:
We now have two classes in the data package, namely, SimpleMemoryWorldHelloDAO2 and
SimpleMemoryWorldHelloDAO3 that implements the WorldHelloDAO
interface. Both of the classes have been annotated with @Repository.
Executing the Java program Sample05 listed above should generate an output similar
to the following:
Output.4
Jul 12, 2019 10:23:07 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO2
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO2
Jul 12, 2019 10:23:07 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO3
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO3
Jul 12, 2019 10:23:07 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO2
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO2
Jul 12, 2019 10:23:07 PM org.springframework.context.support.AbstractApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'simpleHelloGreeter': Unsatisfied dependency expressed through method 'setWorldHello' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.polarsparc.springframework.data.WorldHelloDAO' available: expected single matching bean but found 2: simpleMemoryWorldHelloDAO2,simpleMemoryWorldHelloDAO3
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'simpleHelloGreeter': Unsatisfied dependency expressed through method 'setWorldHello' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.polarsparc.springframework.data.WorldHelloDAO' available: expected single matching bean but found 2: simpleMemoryWorldHelloDAO2,simpleMemoryWorldHelloDAO3
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:676)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:845)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.GenericXmlApplicationContext.(GenericXmlApplicationContext.java:71)
at com.polarsparc.springframework.Sample5.main(Sample5.java:26)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.polarsparc.springframework.data.WorldHelloDAO' available: expected single matching bean but found 2: simpleMemoryWorldHelloDAO2,simpleMemoryWorldHelloDAO3
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1229)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1171)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:668)
... 14 more
If we look towards the end of the output, we see the following as shown in Figure.4 below.
Figure.4
To resolve this, we need to explicitly provide an id for the SimpleMemoryWorldHelloDAO3
class via the @Repository annotation.
The following is the updated POJO SimpleMemoryWorldHelloDAO3 from the data package that
implements the interface WorldHelloDAO and stores 'HELLO' (uppercase) for a predefined set
of languages (English, French, German, Italian, and Spanish) in a java.uitl.Map:
The value inside the @Repository annotation specifies the bean id. This *
MUST* match the JavaBean name for the setter method
setWorldHello(WorldHelloDAO worldHello) from the class SimpleHelloGreeter5 that is
decorated with the @Autowired annotation. See Figure.5 below.
Figure.5
Once again, executing the Java program Sample05 listed above should generate an output similar
to the following:
Output.5
Jul 12, 2019 10:43:54 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO2
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO2
Jul 12, 2019 10:43:54 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO3
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO3
Jul 12, 2019 10:43:54 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO2
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO2
Jul 12, 2019 10:43:54 PM com.polarsparc.springframework.Sample5 main
INFO: BONJOUR, Parrot >>> Sales Tax: 20.0
Jul 12, 2019 10:43:54 PM com.polarsparc.springframework.Sample5 main
INFO: GUTEN TAG, Leopard >>> Sales Tax: 19.0
More to be covered in the next part of this series ...