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:
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:
Beans ⇨ Is there anything special about the application beans ?
Dependencies ⇨ How does the Spring Framework know about the bean dependencies
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:
XML based ⇨ Configuration metadata is specified via XML files. This is how it all started and still supported, but however, they are not the preferred method at the current times
Annotation based ⇨ Configuration metadata is specified using annotations in the application POJOs. There is still an external XML file to enable annotation scanning and processing
JavaConfig based ⇨ Configuration metadata is specified using POJO classes and some annotations without the need for any external XML file for scanning and processing annotations. This is the preferred method currently
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:
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:
The following is the interface HelloGreeter from the service package:
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:
The following XML file sample1-beans.xml specifies the configuration metadata for the Spring Framework IoC container:
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.
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.
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.
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:
Let us explain and understand the code from Sample01 listed above.
The interface ApplicationContext is the core of Spring Framework and represents the IoC container. It provides access to the configuration information about all the beans being managed by the container as well as references to their instances.
The class ClassPathXmlApplicationContext is one of the concrete implementations of the interface ApplicationContext that reads an XML based configuration metadata file from the classpath and creates an instance of a Spring Framework IoC container.
The method getBean(String, Class) on an instance of ApplicationContext returns the reference to the bean with String id and of type Class. In this example, we are looking for a bean with id simpleHelloGreeter of type HelloGreeter.class
Executing the Java program Sample01 listed above should generate an output similar to the following:
Jul 05, 2019 8:58:22 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAOINFO: 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:
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.
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:
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.
@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.
@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.
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:
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:
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:
Jul 05, 2019 9:10:21 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAO2INFO: 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:
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:
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.
The following is the Spring Framework application Sample3:
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:
Jul 05, 2019 10:12:17 PM com.polarsparc.springframework.data.SimpleMemoryWorldHelloDAOINFO: 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