PolarSPARC

Spring Framework Core Essentials - Part 1


Bhaskar S 07/06/2019


Overview

Spring Framework is a lightweight, modular, open-source Java application platform that provides an Inversion of Control (IoC) or Dependency Injection (DI) container. In a IoC (or DI) container, application components (target) define their dependencies on other collaborator (dependee) components via metadata. The IoC container is then responsible for the instantiation and injection of the necessary collaborator objects into those target application components when they are referenced (or accessed) at runtime. This process is the inverse (hence the name, Inversion of Control) of the target application components themselves instantiating and/or looking up their dependencies.

In other words, the core principle of the IoC (or DI) pattern is that the application components should not be responsible for either the creation (via new) or the lookup of the collaborator components, rather have the IoC container manage the lifecycle and dependencies of the collaborator components at runtime. This results in a very flexible architecture (for example, one can choose different implementations of collaborator components at runtime).

Setup

The setup will be on a Ubuntu 18.04 LTS based Linux desktop. Ensure at least Java 8 or above is installed and setup. Also, ensure Apache Maven is installed and setup.

The current version of Spring Framework at the time of this article is 5.1.8.

The following is the listing for the Maven project file pom.xml that will be used:

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.polarsparc</groupId>
  <artifactId>springframework</artifactId>
  <version>1.0</version>
  <name>SpringFramework</name>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.4</version>
          <configuration>
            <fork>true</fork>
            <meminitial>128m</meminitial>
            <maxmem>512m</maxmem>
            <source>1.8</source>
            <target>1.8</target>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.1.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>    
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Hands-on with Spring Framework Core - 1

The core of Spring Framework is the IoC (or DI) container. It is responsible for managing the lifecycle (creation, initialization, usage, destruction) of the application objects (referred to as beans) and wiring up the dependencies (collaborator beans).

This brings two questions to mind:

To answer the first question - there is nothing special about the application objects (or beans) - they are just plain old Java objects (or POJOs).

To answer the second question - one must provide configuration information about the application beans and their dependencies to Spring Framework via configuration metadata. The configuration metadata can be provided via one of the following 3 approaches:

We will demonstrate each of the 3 approaches using a simple Hello greeter example.

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. The hello message (for a chosen language) is abstracted in the data package, while the greeter is abstracted in the service package.

The following is the interface WorldHelloDAO from the data package:

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

package com.polarsparc.springframework.data;

public interface WorldHelloDAO {
    public String fetch(final String lang);
}

The following is the POJO SimpleMemoryWorldHelloDAO from the data package that implements the interface WorldHelloDAO and stores 'Hello' for a predefined set of languages (English, French, German, Italian, and Spanish) in a java.util.Map:

SimpleMemoryWorldHelloDAO.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Hello 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 SimpleMemoryWorldHelloDAO implements WorldHelloDAO {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldHelloDAO.class);
    
    private Map helloByLang = null;
    
    public SimpleMemoryWorldHelloDAO() {
        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 SimpleMemoryWorldHelloDAO");
    }
    
    @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 following is the interface HelloGreeter from the service package:

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

package com.polarsparc.springframework.service;

public interface HelloGreeter {
    public String greetings(final String lang, final String name);
}

The following is the POJO SimpleHelloGreeter 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, separated by a desired separator:

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

package com.polarsparc.springframework.service;

import com.polarsparc.springframework.data.WorldHelloDAO;

public class SimpleHelloGreeter implements HelloGreeter {
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    
    private String separator = null;
    
    private WorldHelloDAO worldHello = null;
    
    public SimpleHelloGreeter() {
    }
    
    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) {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(name)
                      .toString();
        }
        else {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(STRANGER)
                      .toString();
        }
        
        return msg;
    }
}

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

sample1-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 name="simpleHelloGreeter" class="com.polarsparc.springframework.service.SimpleHelloGreeter">
        <property name="separator" value=":" />
        <property name="worldHello" ref="simpleWorldHello" />
    </bean>
</beans>

The root element <beans> indicates the XML namespaces as well as the XML Schema (XSD) entities used. For this example, we only need the spring-beans XSD. See Figure.1 below.

Beans XSD
Figure.1

Each application POJO (or bean) must be defined as a <bean> element under the root element. It must specify either an id or a name attribute and a class attribute. See Figure.2 below.

Bean id and class
Figure.2

From the POJO SimpleHelloGreeter, we can infer that there is a setter method for injecting a separator (setSeparator(String)). To inject a primitive value, use the <property> element under the <bean> element with a name and a value attribute.

Similarly, we can infer that there is a setter method for injecting the collaborator bean WorldHelloDAO (setWorldHello(WorldHelloDAO)). To inject a reference to an object, use the <property> element under the <bean> element with a name and a ref attribute. The ref attribute must point to a bean id or name. See Figure.3 below.

Bean Injection
Figure.3

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

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

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

Let us explain and understand the code from Sample01 listed above.

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

Output.1

Jul 05, 2019 8:58:22 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 05, 2019 8:58:22 PM com.polarsparc.springframework.Sample1 main
INFO: Hola: Bull
Jul 05, 2019 8:58:22 PM com.polarsparc.springframework.Sample1 main
INFO: Salve: Fox
Annotation based Approach

The interface WorldHelloDAO from the data package has no change and remains the same.

The following is the POJO SimpleMemoryWorldHelloDAO2 from the data package that implements the interface WorldHelloDAO and stores 'Hello' for a predefined set of languages (English, French, German, Italian, and Spanish) in a java.util.Map:

SimpleMemoryWorldHelloDAO2.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Memory World Hello 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 SimpleMemoryWorldHelloDAO2 implements WorldHelloDAO {
    private static Log LOG = LogFactory.getLog(SimpleMemoryWorldHelloDAO2.class);
    
    private Map helloByLang = null;
    
    public SimpleMemoryWorldHelloDAO2() {
        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 SimpleMemoryWorldHelloDAO2");
    }
    
    @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;
    }
}

Notice the use of the annotation @Repository in the code SimpleMemoryWorldHelloDAO2 above. This is a class level annotation and is typically used on persistence layer objects (or DAOs). This annotation is a specialized form of the annotation @Component, which indicates a POJO as an application component (or bean) to the Spring Framework IoC container. We could have used the annotation @Component instead and it would have been valid. See Figure.4 below.

Repository Annotation
Figure.4

The interface HelloGreeter from the service package has no change and remains the same.

The following is the POJO SimpleHelloGreeter2 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, separated by a desired separator:

SimpleHelloGreeter2.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 2
 * 
 * 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;

@Service("simpleHelloGreeter")
public class SimpleHelloGreeter2 implements HelloGreeter {
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    
    private String separator = null;
    
    private WorldHelloDAO worldHello = null;
    
    public SimpleHelloGreeter2() {
    }
    
    public String getSeparator() {
        return separator;
    }

    @Value("${env.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) {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(name)
                      .toString();
        }
        else {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(STRANGER)
                      .toString();
        }
        
        return msg;
    }
}

Notice the use of 3 new annotations @Service, @Value, and @Autowired in the code SimpleHelloGreeter2 above.

@Service is a class level annotation and is typically used on service layer objects. Just like the annotation @Repository, this annotation is a specialized form of the annotation @Component. The value inside the @Service annotation specifies the bean id. By default, the uncapitalized non-qualified class name is the bean id. We could have used the annotation @Component instead and it would have been valid. See Figure.5 below.

Service Annotation
Figure.5

@Value can be a member level or a method level annotation. The recommended approach is to use at the method level (on a setter method) for testability reasons. This annotation is used to inject a value into a member field. The value inside the @Value annotation could be a primitive String value or a reference to a system property name enclosed between "${" and "}". In this example, the system property name is env.greeter.sep. The colon after the system property name specifies a default value. In other words, it is of the form "${system-property-name:default-value}". See Figure.6 below.

Value Annotation
Figure.6

@Autowired can be a member level or a method level annotation. The recommended approach is to use at the method level (on a setter method) for testability reasons. This indicates to the IoC container to automatically inject (or auto wire) a reference to an instance of a bean of type that is similar to that of the method parameter. In this example, the IoC container will inject (or auto wire) an instance of a bean of type WorldHelloDAO via the setter method setWorldHello(WorldHelloDAO). See Figure.7 below.

Autowired Annotation
Figure.7

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

sample2-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:component-scan base-package="com.polarsparc.springframework.data" />
    <context:component-scan base-package="com.polarsparc.springframework.service" />                        
</beans>

Notice the addition of context namespace as well as the corresponding XML Schema XSD (spring-context).

The <context:component-scan> element specifies the Java package that needs to be scanned for annotations. Each Java package will have its own line in the XML file. In our example, it is the data and service packages.

The following is the Spring Framework application Sample2:

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

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext applicationContext =
            new GenericXmlApplicationContext("/sample2-beans.xml");
        
        HelloGreeter greeter = applicationContext.getBean("simpleHelloGreeter", HelloGreeter.class);
        
        LOG.info(greeter.greetings("English", "Cat"));
        LOG.info(greeter.greetings("German", "Tiger"));
    }
}

The class GenericXmlApplicationContext is another concrete implementations of the interface ApplicationContext that reads an XML based configuration file from the classpath and creates an instance of a Spring Framework IoC container. Since the XML file has no bean definitions and only specifies the context:component-scan element, this triggers scanning of POJOs (components) in the classpath (for the specified Java packages) for annotation processing and auto wiring.

Executing the Java program Sample02 listed above with the system property -Denv.greeter.sep="," should generate an output similar to the following:

Output.2

Jul 05, 2019 9:10:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO2 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO2
Jul 05, 2019 9:10:21 PM com.polarsparc.springframework.Sample2 main
INFO: Hello, Cat
Jul 05, 2019 9:10:21 PM com.polarsparc.springframework.Sample2 main
INFO: Guten Tag, Tiger
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 HelloGreeter from the service package has no change and remains the same.

The following is the POJO SimpleHelloGreeter3 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, separated by a desired separator:

SimpleHelloGreeter3.java
/*
 * Topic:  Spring Framework Core Essentials
 * 
 * Name:   Simple Hello Greeter 3
 * 
 * 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 com.polarsparc.springframework.data.WorldHelloDAO;

public class SimpleHelloGreeter3 implements HelloGreeter {
    private static String SPACE = " ";
    private static String STRANGER = "Stranger";
    
    private String separator = null;
    
    private WorldHelloDAO worldHello = null;
    
    public SimpleHelloGreeter3() {
    }
    
    public String getSeparator() {
        return separator;
    }

    @Value("${env.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) {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(name)
                      .toString();
        }
        else {
            msg = new StringBuilder()
                      .append(worldHello.fetch(lang.toLowerCase()))
                      .append(separator)
                      .append(SPACE)
                      .append(STRANGER)
                      .toString();
        }
        
        return msg;
    }
}

Notice we only use 2 of the annotations @Value and @Autowired in the code SimpleHelloGreeter3 above.

We need a Java class that will be used to specify the configuration metadata about the different application components (or beans). This configuration class will be abstracted in the config package.

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

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

package com.polarsparc.springframework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO;
import com.polarsparc.springframework.data.WorldHelloDAO;
import com.polarsparc.springframework.service.HelloGreeter;
import com.polarsparc.springframework.service3.SimpleHelloGreeter3;

@Configuration
public class Sample3Config {
    @Bean
    public WorldHelloDAO simpleWorldHello() {
        return new SimpleMemoryWorldHelloDAO();
    }
    
    @Bean
    public HelloGreeter simpleHelloGreeter() {
        return new SimpleHelloGreeter3();
    }
}

Notice the use of 2 new annotations @Configuration and @Bean in the code Sample3Config above.

@Configuration is a class level annotation and is used to tag a class as the source of bean definitions. It is similar to the XML element <beans> from the XML based configuration metadata file. See Figure.8 below in red.

@Bean is a method level annotation and correspond to the XML element <bean> from the XML based configuration metadata file. By default, the method name is used as the name of the bean. See Figure.8 below in blue.

Configuration and Bean
Figure.8

The following is the Spring Framework application Sample3:

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

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

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

The class AnnotationConfigApplicationContext is another concrete implementations of the interface ApplicationContext that parses the configuration metadata from the specified configuration class and creates an instance of a Spring Framework IoC container. Internally it scans of POJOs (components) in the classpath for annotation processing and auto wiring.

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

Output.3

Jul 05, 2019 10:12:17 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO 
INFO: Initialized a new instance of SimpleMemoryWorldHelloDAO
Jul 05, 2019 10:12:17 PM com.polarsparc.springframework.Sample3 main
INFO: Bonjour- Dog
Jul 05, 2019 10:12:17 PM com.polarsparc.springframework.Sample3 main
INFO: Salve- Snake

We did not pass the system property -Denv.greeter.sep="," when executing the Java program Sample03 and hence it used the default separator "-".

More to be covered in the next part of this series ...

References

[1] Spring Framework

[2] Spring Framework Core

[3] Spring Framework API



© PolarSPARC