PolarSPARC

Spring Framework Core Essentials - Part 4


Bhaskar S 07/20/2019


Overview

In Part 3 of this series, we got our hands dirty on injecting application beans that contained initialization and clean-up methods using the 3 different approaches.

In this part, we will explore the use-case where we get the reference to a singleton collaborator (dependee) bean via a factory method. We will demonstrate this use-case using the 3 different approaches.

Hands-on with Spring Framework Core - 4

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 interface WorldSalesTax from the data package has no change and remains the same.

The following is the POJO SimpleMemoryWorldSalesTaxDAO3 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:

SimpleMemoryWorldSalesTaxDAO3.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Sales Tax 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;

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

Notice the use of enum in the code SimpleMemoryWorldSalesTaxDAO3 above to force a singleton instance.

See Figure.1 below.

Singleton Factory
Figure.1

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

sample8-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.SimpleMemoryWorldSalesTaxDAO3"
          factory-method="getInstance" />
    
    <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 create an instance of a bean via its factory method, use the factory-method attribute on the corresponding <bean> element.

See Figure.2 below.

Factory Method
Figure.2

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 Sample10:

Sample10.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 10
 * 
 * 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 Sample10 {
    private static Log LOG = LogFactory.getLog(Sample10.class);

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("/sample8-beans.xml");
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeter", HelloGreeter.class);
        
        LOG.info(greeter.greetings("german", "Eagle"));
        LOG.info(greeter.greetings("italian", "Goose"));
    }
}

Executing the Java program Sample10 listed above should generate an output similar to the following:

Output.1

Jul 20, 2019 11:28:49 AM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 20, 2019 11:28:49 AM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO3
Jul 20, 2019 11:28:49 AM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 getInstance
INFO: Fetching the instance of WorldSalesTax
Jul 20, 2019 11:28:49 AM com.polarsparc.springframework.Sample10 main
INFO: Guten Tag, Eagle >>> Sales Tax: 20.0
Jul 20, 2019 11:28:49 AM com.polarsparc.springframework.Sample10 main
INFO: Salve, Goose >>> Sales Tax: 25.0
Annotation based Approach

The interface WorldHelloDAO as well as its implementation POJO SimpleFileWorldHelloDAO2 from the data package have no changes and remains the same.

The interface WorldSalesTax as well as its implementation POJO SimpleMemoryWorldSalesTaxDAO3 from the data package has 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 SimpleHelloGreeter8 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:

SimpleHelloGreeter8.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 8
 * 
 * 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.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

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

@Lazy
@Service("simpleHelloGreeterWithTax8")
public class SimpleHelloGreeter8 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 SimpleHelloGreeter8(@Value("${prop.sep.count:1}") Integer count,
                               @Value("${prop.sep.char:#}") String sep,
                               @Qualifier("worldSalesTax3") 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
    @Qualifier("fileWorldHello")
    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 @Lazy annotation on the class SimpleHelloGreeter8 above. By default, the Spring Framework IoC container creates and initializes the application bean(s) eagerly on startup. This annotation indicates to the Spring Framework IoC container that it initialize the application bean(s) only when they are referenced at runtime.

The following XML file sample9-beans.xml indicates to the Spring Framework IoC container that it perform component scanning at the specified package level for annotation processing:

sample9-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" />
    
    <bean id="worldSalesTax3" class="com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3"
          factory-method="getInstance" primary="true" />
          
    <context:component-scan base-package="com.polarsparc.springframework.data" />
    <context:component-scan base-package="com.polarsparc.springframework.service" />
</beans>

Spring Framework does not have any annotation to indicate the use of factory method. Hence, we define the application bean (with id worldSalesTax3) in the XML configuration metadata file and specify the factory-method attribute.

The primary attribute with the value of true on the associated <bean> element indicates this bean will be preferred (primary) over the other instances of the same type.

⚠ ATTENTION ⚠

Not using the primary attribute will cause the following exception:

No qualifying bean of type 'com.polarsparc.springframework.data.WorldSalesTax' available: expected single matching bean but found 2: worldSalesTax3,simpleMemoryWorldSalesTaxDAO2

The following is the Spring Framework application Sample11:

Sample11.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 11
 * 
 * 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 Sample11 {
    private static Log LOG = LogFactory.getLog(Sample11.class);

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new GenericXmlApplicationContext("/sample9-beans.xml");
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeterWithTax8", HelloGreeter.class);
        
        LOG.info(greeter.greetings("English", "Monkey"));
        LOG.info(greeter.greetings("Spanish", "Chimp"));
    }
}

Executing the Java program Sample11 listed above should generate an output similar to the following:

Output.2

Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO3
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 getInstance
INFO: Fetching the instance of WorldSalesTax
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleFileWorldHelloDAO2 
INFO: Created new instance of SimpleFileWorldHelloDAO
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleFileWorldHelloDAO2 init
INFO: Initializing cache from the file sample7.csv
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleFileWorldHelloDAO2 init
INFO: Cache successfully initialized with {german=Gut3n T4g, spanish=H0l4, english=H3ll0, italian=S4lv3, french=B0nj0ur}
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO2 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO2
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO3 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO3
Jul 20, 2019 12:52:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO2 
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO2
Jul 20, 2019 12:52:22 PM com.polarsparc.springframework.Sample11 main
INFO: H3ll0, Monkey >>> Sales Tax: 10.0
Jul 20, 2019 12:52:22 PM com.polarsparc.springframework.Sample11 main
INFO: H0l4, Chimp >>> Sales Tax: 30.0
JavaConfig based Approach

The interface WorldHelloDAO as well as its implementation POJO SimpleFileWorldHelloDAO from the data package have no changes and remains the same.

The interface WorldSalesTax as well as its implementation POJO SimpleMemoryWorldSalesTaxDAO3 from the data package have no changes and remains the same.

The interface HelloGreeter as well as its implementation POJO SimpleHelloGreeter6 from the service package have no changes and remains the same.

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

Sample12Config.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 12 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.SimpleMemoryWorldSalesTaxDAO3;
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 Sample12Config {
    @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 = SimpleMemoryWorldSalesTaxDAO3.getInstance();
        
        SimpleHelloGreeter6 greeter = new SimpleHelloGreeter6(sepCount, sepChar, worldSalesTax);
        greeter.setSeparator(greeterSep);
        
        return greeter;
    }
}

The following is the Spring Framework application Sample12:

Sample12.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Sample 12
 * 
 * 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.Sample12Config;
import com.polarsparc.springframework.service.HelloGreeter;

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

    public static void main(String[] args) {
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(Sample12Config.class);
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeter", HelloGreeter.class);
        
        LOG.info(greeter.greetings("German", "Dragon"));
        LOG.info(greeter.greetings("Italian", "Mongoose"));
        
        ((AnnotationConfigApplicationContext)applicationContext).close();
    }
}

Executing the Java program Sample12 listed above should generate an output similar to the following:

Output.3

Jul 20, 2019 1:07:48 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 20, 2019 1:07:48 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 
INFO: Initialized a new instance of SimpleMemoryWorldSalesTaxDAO3
Jul 20, 2019 1:07:48 PM com.polarsparc.springframework.data.SimpleMemoryWorldSalesTaxDAO3 getInstance
INFO: Fetching the instance of WorldSalesTax
Jul 20, 2019 1:07:48 PM com.polarsparc.springframework.Sample12 main
INFO: Guten Tag, Dragon >>> Sales Tax: 20.0
Jul 20, 2019 1:07:48 PM com.polarsparc.springframework.Sample12 main
INFO: Salve, Mongoose >>> Sales Tax: 25.0

With this, we wrap this series on Spring Framework Core Essentials.

References

[1] Spring Framework Core Essentials - Part 1

[2] Spring Framework Core Essentials - Part 2

[3] Spring Framework Core Essentials - Part 3

[4] Spring Framework

[5] Spring Framework Core

[6] Spring Framework API



© PolarSPARC