PolarSPARC

Spring Framework Core Essentials - Part 2


Bhaskar S 07/12/2019


Overview

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:

WorldSalesTax.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   World Sales Tax
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.data;

public interface WorldSalesTax {
    public float fetch(final String lang);
}

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:

SimpleMemoryWorldSalesTaxDAO.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Sales Tax DAO
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.data;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SimpleMemoryWorldSalesTaxDAO implements WorldSalesTax {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldSalesTaxDAO.class);
    
    private Map salesTaxByState = null;
    
    public SimpleMemoryWorldSalesTaxDAO() {
        
        salesTaxByState = Stream.of(new String[][] {
            { "english", "20.0" },
            { "french", "20.0" },
            { "german", "19.0" },
            { "italian", "22.0" },
            { "spanish", "21.0" }
        }).collect(Collectors.toMap(str -> str[0], str -> Float.valueOf(str[1])));
        
        LOG.info("Initialized a new instance of SimpleMemoryWorldSalesTaxDAO");
    }
    
    @Override
    public float fetch(String lang) {
        if (lang != null && lang.trim().isEmpty() == false) {
            return salesTaxByState.getOrDefault(lang.toLowerCase(), 10.0f);
        }
        
        return 15.0f;
    }
}

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:

SimpleHelloGreeter4.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 4
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.service;

import com.polarsparc.springframework.data.WorldHelloDAO;
import com.polarsparc.springframework.data.WorldSalesTax;

public class SimpleHelloGreeter4 implements HelloGreeter {
    private static int MAX_COUNT = 3;
    
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    private static String SALES_TAX = "Sales Tax:";
    
    private int count = 0;
    
    private String separator = null;
    private String sep = null;
    
    private WorldHelloDAO worldHello = null;
    private WorldSalesTax worldSalesTax = null;
    
    public SimpleHelloGreeter4(Integer count, String sep, WorldSalesTax worldSalesTax) {
        if (count < 1) {
            this.count = count;
        }
        if (count > MAX_COUNT) {
            this.count = MAX_COUNT;
        }
        this.count = count;
        this.sep = sep;
        this.worldSalesTax = worldSalesTax;
    }
    
    public String getSeparator() {
        return separator;
    }

    public void setSeparator(String separator) {
        this.separator = separator;
    }

    public WorldHelloDAO getWorldHello() {
        return worldHello;
    }

    public void setWorldHello(WorldHelloDAO worldHello) {
        this.worldHello = worldHello;
    }

    @Override
    public String greetings(String lang, String name) {
        String msg = null;
        
        if (name != null && name.trim().isEmpty() == false) {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(name)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        else {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(STRANGER)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        
        return msg;
    }
}

The following XML file sample3-beans.xml specifies the configuration metadata for the Spring Framework IoC container:

sample3-beans.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
                        
    <bean id="simpleWorldHello" class="com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO" />
    
    <bean id="simpleWorldSalesTax" class="com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO" />
    
    <bean name="simpleHelloGreeter" class="com.polarsparc.springframework.service.SimpleHelloGreeter4">
        <constructor-arg index="0" type="java.lang.Integer" value="3" />
        <constructor-arg index="1" type="java.lang.String" value=">" />
        <constructor-arg index="2" ref="simpleWorldSalesTax" />
        <property name="separator" value="," />
        <property name="worldHello" ref="simpleWorldHello" />
    </bean>
</beans>

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.

Constructor Injection
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:

Sample4.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 4
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.polarsparc.springframework.service.HelloGreeter;

public class Sample4 {
    private static Log LOG = LogFactory.getLog(Sample4.class);

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("/sample3-beans.xml");
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeter", HelloGreeter.class);
        
        LOG.info(greeter.greetings("spanish", "Duck"));
        LOG.info(greeter.greetings("french", "Snake"));
    }
}

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:

SimpleMemoryWorldSalesTaxDAO2.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Sales Tax DAO 2
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.data;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Repository;

@Repository
public class SimpleMemoryWorldSalesTaxDAO2 implements WorldSalesTax {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldSalesTaxDAO2.class);
    
    private Map salesTaxByState = null;
    
    public SimpleMemoryWorldSalesTaxDAO2() {
        
        salesTaxByState = Stream.of(new String[][] {
            { "english", "20.0" },
            { "french", "20.0" },
            { "german", "19.0" },
            { "italian", "22.0" },
            { "spanish", "21.0" }
        }).collect(Collectors.toMap(str -> str[0], str -> Float.valueOf(str[1])));
        
        LOG.info("Initialized a new instance of SimpleMemoryWorldSalesTaxDAO2");
    }
    
    @Override
    public float fetch(String lang) {
        if (lang != null && lang.trim().isEmpty() == false) {
            return salesTaxByState.getOrDefault(lang.toLowerCase(), 10.0f);
        }
        
        return 15.0f;
    }
}

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:

SimpleHelloGreeter5.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 5
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.polarsparc.springframework.data.WorldHelloDAO;
import com.polarsparc.springframework.data.WorldSalesTax;

@Service("simpleHelloGreeterWithTax")
public class SimpleHelloGreeter5 implements HelloGreeter {
    private static int MAX_COUNT = 3;
    
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    private static String SALES_TAX = "Sales Tax:";
    
    private int count = 0;
    
    private String separator = null;
    private String sep = null;
    
    private WorldHelloDAO worldHello = null;
    private WorldSalesTax worldSalesTax = null;
    
    @Autowired
    public SimpleHelloGreeter5(@Value("${prop.sep.count:1}") Integer count,
                               @Value("${prop.sep.char:#}") String sep,
                               WorldSalesTax worldSalesTax) {
        if (count < 1) {
            this.count = count;
        }
        if (count > MAX_COUNT) {
            this.count = MAX_COUNT;
        }
        this.count = count;
        this.sep = sep;
        this.worldSalesTax = worldSalesTax;
    }
    
    public String getSeparator() {
        return separator;
    }

    @Value("${prop.greeter.sep:-}")
    public void setSeparator(String separator) {
        this.separator = separator;
    }

    public WorldHelloDAO getWorldHello() {
        return worldHello;
    }

    @Autowired
    public void setWorldHello(WorldHelloDAO worldHello) {
        this.worldHello = worldHello;
    }

    @Override
    public String greetings(String lang, String name) {
        String msg = null;
        
        if (name != null && name.trim().isEmpty() == false) {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(name)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        else {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(STRANGER)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        
        return msg;
    }
}

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.

Value Annotation
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:

sample4-beans.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:sample5.properties" />
    
    <context:component-scan base-package="com.polarsparc.springframework.data" />
    <context:component-scan base-package="com.polarsparc.springframework.service" />
</beans>

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:

sample5.properties
#
### Sample5 properties
#

prop.sep.count=3
prop.sep.char=>
prop.greeter.sep=,

The following is the Spring Framework application Sample5:

Sample5.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 5
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.polarsparc.springframework.service.HelloGreeter;

public class Sample5 {
    private static Log LOG = LogFactory.getLog(Sample5.class);

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new GenericXmlApplicationContext("/sample4-beans.xml");
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeterWithTax", HelloGreeter.class);
        
        LOG.info(greeter.greetings("French", "Parrot"));
        LOG.info(greeter.greetings("German", "Leopard"));
    }
}

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:

SimpleHelloGreeter6.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 6
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.service;

import org.springframework.beans.factory.annotation.Autowired;

import com.polarsparc.springframework.data.WorldHelloDAO;
import com.polarsparc.springframework.data.WorldSalesTax;

public class SimpleHelloGreeter6 implements HelloGreeter {
    private static int MAX_COUNT = 3;
    
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    private static String SALES_TAX = "Sales Tax:";
    
    private int count = 0;
    
    private String separator = null;
    private String sep = null;
    
    private WorldHelloDAO worldHello = null;
    private WorldSalesTax worldSalesTax = null;
    
    public SimpleHelloGreeter6(Integer count, String sep, WorldSalesTax worldSalesTax) {
        if (count < 1) {
            this.count = count;
        }
        if (count > MAX_COUNT) {
            this.count = MAX_COUNT;
        }
        this.count = count;
        this.sep = sep;
        this.worldSalesTax = worldSalesTax;
    }
    
    public String getSeparator() {
        return separator;
    }

    public void setSeparator(String separator) {
        this.separator = separator;
    }

    public WorldHelloDAO getWorldHello() {
        return worldHello;
    }

    @Autowired
    public void setWorldHello(WorldHelloDAO worldHello) {
        this.worldHello = worldHello;
    }

    @Override
    public String greetings(String lang, String name) {
        String msg = null;
        
        if (name != null && name.trim().isEmpty() == false) {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(name)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        else {
            StringBuilder sb = new StringBuilder()
                                   .append(worldHello.fetch(lang.toLowerCase()))
                                   .append(separator)
                                   .append(SPACE)
                                   .append(STRANGER)
                                   .append(SPACE);
            for (int i = 0; i < count; i++) {
                sb.append(sep);
            }
            msg = sb.append(SPACE)
                    .append(SALES_TAX)
                    .append(SPACE)
                    .append(worldSalesTax.fetch(lang.toLowerCase()))
                    .toString();
        }
        
        return msg;
    }
}

The following is the JavaConfig POJO Sample6Config from the config package:

Sample6Config.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 6 Config
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

import com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO;
import com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO;
import com.polarsparc.springframework.data.WorldHelloDAO;
import com.polarsparc.springframework.data.WorldSalesTax;
import com.polarsparc.springframework.service.HelloGreeter;
import com.polarsparc.springframework.service.SimpleHelloGreeter6;

@Configuration
@PropertySource("classpath:sample5.properties")
public class Sample6Config {
    @Autowired
    private Environment env;
    
    @Bean
    public WorldHelloDAO simpleWorldHello() {
        return new SimpleMemoryWorldHelloDAO();
    }
    
    @Bean
    public HelloGreeter simpleHelloGreeter() {
        Integer sepCount = env.getProperty("prop.sep.count", Integer.class);
        
        String sepChar = env.getProperty("prop.sep.char");
        String greeterSep = env.getProperty("prop.greeter.sep");
        
        WorldSalesTax worldSalesTax = new SimpleMemoryWorldSalesTaxDAO();
        
        SimpleHelloGreeter6 greeter = new SimpleHelloGreeter6(sepCount, sepChar, worldSalesTax);
        greeter.setSeparator(greeterSep);
        
        return greeter;
    }
}

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.

Configuration and Bean
Figure.3

The following is the Spring Framework application Sample6:

Sample6.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 6
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.polarsparc.springframework.config.Sample6Config;
import com.polarsparc.springframework.service.HelloGreeter;

public class Sample6 {
    private static Log LOG = LogFactory.getLog(Sample6.class);

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(Sample6Config.class);
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeter", HelloGreeter.class);
        
        LOG.info(greeter.greetings("french", "Panther"));
        LOG.info(greeter.greetings("English", "Lion"));
    }
}

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:

SimpleMemoryWorldHelloDAO3.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Hello DAO 3
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.data;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Repository;

@Repository
public class SimpleMemoryWorldHelloDAO3 implements WorldHelloDAO {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldHelloDAO3.class);
    
    private Map helloByLang = null;
    
    public SimpleMemoryWorldHelloDAO3() {
        helloByLang = Stream.of(new String[][] {
            { "english", "HELLO" },
            { "french", "BONJOUR" },
            { "german", "GUTEN TAG" },
            { "italian", "SALVE" },
            { "spanish", "HOLA" }
        }).collect(Collectors.toMap(str -> str[0], str -> str[1]));
        
        LOG.info("Initialized a new instance of SimpleMemoryWorldHelloDAO3");
    }
    
    @Override
    public String fetch(String lang) {
        String msg = null;
        
        if (lang != null && lang.trim().isEmpty() == false) {
            msg = helloByLang.get(lang.toLowerCase());
        }
        
        if (msg == null) {
            msg = helloByLang.get("english");
        }
        
        return msg;
    }
}

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.

No Unique Bean
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:

SimpleMemoryWorldHelloDAO3.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Hello DAO 3
 * 
 * Author: Bhaskar S
 * 
 * URL:    https://www.polarsparc.com
 */

package com.polarsparc.springframework.data;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Repository;

@Repository("worldHello")
public class SimpleMemoryWorldHelloDAO3 implements WorldHelloDAO {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldHelloDAO3.class);
    
    private Map helloByLang = null;
    
    public SimpleMemoryWorldHelloDAO3() {
        helloByLang = Stream.of(new String[][] {
            { "english", "HELLO" },
            { "french", "BONJOUR" },
            { "german", "GUTEN TAG" },
            { "italian", "SALVE" },
            { "spanish", "HOLA" }
        }).collect(Collectors.toMap(str -> str[0], str -> str[1]));
        
        LOG.info("Initialized a new instance of SimpleMemoryWorldHelloDAO3");
    }
    
    @Override
    public String fetch(String lang) {
        String msg = null;
        
        if (lang != null && lang.trim().isEmpty() == false) {
            msg = helloByLang.get(lang.toLowerCase());
        }
        
        if (msg == null) {
            msg = helloByLang.get("english");
        }
        
        return msg;
    }
}

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.

Bean Id Value
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 ...

References

[1] Spring Framework Core Essentials - Part 1

[2] Spring Framework

[3] Spring Framework Core

[4] Spring Framework API



© PolarSPARC