Spring Framework

Hi there!

In this blog, we talk about Spring Framework, a Java platform that provides comprehensive infrastructure support for developing Java applications. The content of this blog is shown below:

  • Architecture
  • Spring IoC Container
  • Spring Beans
  • Dependency Injection (DI)
  • Spring Annotations
  • Aspect Oriented Programming (AOP)

1. ARCHITECTURE

The Spring Framework provides about 20 modules which can be used based on an application requirement.

Figure 1. Spring Framework Architecture

Test layer supports the testing of Spring components with JUnit or TestNG frameworks.

Core Container layer consists of the Core, Beans, Context, and Spring Expression Language (SpEL) modules:

  • Core provides the fundamental parts of the framework, including the Inversion of Control (IoC) and Dependency Injection (DI).
  • Bean provides BeanFactory, an implementation of the factory pattern.
  • Context is a medium to access any objects defined and configured, e.g., the ApplicationContext interface.
  • SpEL provides Spring Expression Language for querying and manipulating an object graph at runtime.

AOP layer provides an aspect-oriented programming implementation, allowing you to define method-interceptors and pointcuts to decouple the code.

Aspects layer provides integration with AspectJ, an AOP framework.

Instrumentation layer provides class instrumentation support and class loader implementations.

Messaging layer provides support for STOMP as the WebSocket sub-protocol.

Data Access/Integration layer consists of JDBC, ORM, OXM, JMS and Transaction:

  • JDBC provides a JDBC-abstraction layer to simplify JDBC related coding.
  • ORM provides integration layers for object-relational mapping APIs, including JPA, JDO, Hibernate, and iBatis.
  • OXM provides an abstraction layer that supports Object/XML mapping implementations for JAXB, Castor, XMLBeans, JiBX and XStream.
  • Java Messaging Service (JMS) produces and consumes messages.
  • Transaction supports programmatic and declarative transaction management for classes that implement special interfaces and for all your POJOs.

Web layer consists of the Web, MVC, WebSocket, and Portlet:

  • MVC provides Model-View-Controller (MVC) implementation for Spring web applications.
  • WebSocket provides support for WebSocket-based, two-way communication between the client and the server in web applications.
  • Web provides basic web-oriented integration features such as multipart file-upload functionality and the initialization of the IoC container using servlet listeners and a web-oriented application context.
  • Portlet provides the MVC implementation to be used in a portlet environment and mirrors the functionality of Web-Servlet module.

2. IOC CONTAINER

Inversion of Control (IoC) is a design principle where the control of flow and dependencies in a program are inverted, meaning that the control is handed over to a container or framework which can manage dependencies (instead of allowing component to control its dependencies).

Dependency refers to an object that a class relies on to perform its functionality. Dependency Injection (DI) is a specific implementation of the IoC principle. DI injects the dependencies from outside the class (rather than having the class create them itself). Instead of hardcoding within the class, the dependencies are injected into it from an external source, usually a container or framework.

In Spring Framework, there are two types of IoC containers: BeanFactory and ApplicationContext. The ApplicationContext container includes all functionality of the BeanFactory container and thus is better; while BeanFactory is mostly used for lightweight applications where data volume and speed is significant.

2.1 BeanFactory

BeanFactory is the simplest container providing the basic support for DI. BeanFactory is defined by the org.springframework.beans.factory.BeanFactory interface.

Code 1-1 shows how to use BeanFactory:

Code 1-1(a). "Message.java"

 1package com.example;
 2
 3public class Message { 
 4   private String message;  
 5   
 6   public void setMessage(String message){ 
 7      this.message  = message; 
 8   }  
 9   public void getMessage(){ 
10      System.out.println("Message : " + message); 
11   } 
12}

Code 1-1(a) declares a class named Message, and it has a pair of getter/setter for class member named message.

Code 1-1(b). "Beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id = "demo" class = "com.example.Message">
 9      <property name = "message" value = "Hello World!"/>
10   </bean>
11
12</beans>

Code 1-1(b) is a XML configuration file tell that a bean called demo is defined, and its message is set to "Hello World!".

Code 1-1(c). "BeanFactoryDemoTest.java"

 1package com.example;
 2
 3import org.springframework.beans.factory.xml.XmlBeanFactory; 
 4import org.springframework.core.io.ClassPathResource;  
 5
 6public class BeanFactoryDemoTest { 
 7   public static void main(String[] args) { 
 8      XmlBeanFactory factory = new XmlBeanFactory (new ClassPathResource("Beans.xml")); 
 9      Message obj = (Message) factory.getBean("demo");    
10      obj.getMessage();    
11   }
12}   

Code 1-1(c) is a test program, and it uses ClassPathResource() API to load the bean configuration file, and it uses XmlBeanFactory() to create and initialize beans in the configuration file "Beans.xml".

Then, getBean() method uses bean ID ("demo") to return a generic object, which finally can be casted to the BeanFactoryDemo object. By invoking obj.getMessage(), the code 1-1(a) is executed, and shows:

1Message : Hello World!

Summary: this section uses Code 1-1(a, b, c) to show how to get bean by using BeanFactory.

2.2 ApplicationContext

ApplicationContext is similar to BeanFactory, but it adds enterprise-specific functionality.

ApplicationContext is defined by the org.springframework.context.ApplicationContext interface, with several implementations: FileSystemXmlApplicationContext, ClassPathXmlApplicationContext, and WebXmlApplicationContext.

  • FileSystemXmlApplicationContext loads the definitions of the beans, from the XML bean configuration file (full path to file) to the constructor.
  • ClassPathXmlApplicationContext loads the definitions of the beans from an XML file, and we need to set CLASSPATH.
  • WebXmlApplicationContext loads the XML file with definitions of all beans from within a web application.

Code 2-1, with Code 1-1(a, b), will show how to use FileSystemXmlApplicationContext of ApplicationContext:

Code 2-1. "FileSystemXmlApplicationContextDemoTest.java"

 1package com.example;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.FileSystemXmlApplicationContext;
 5
 6public class FileSystemXmlApplicationContextDemoTest {
 7   public static void main(String[] args) {
 8      ApplicationContext context = new FileSystemXmlApplicationContext
 9         ("C:/path/to/Beans.xml");
10
11      Message obj = (Message) context.getBean("demo");
12      obj.getMessage();
13   }
14}

Now we will reuse the codes defined in Code 1-1(a, b), and run the Code 2-1:

1Message : Hello World!

Summary: this section uses Code 1-1(a, b), Code 2-1 to show how to get bean by using ApplicationContext, especially the FileSystemXmlApplicationContext.

3. BEAN

Bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Bean definition contains the information called configuration metadata:

Table 3-1. Properties of Bean

Properties Description
id the bean identifier(unique)
class the bean class to create the bean
scope the scope of the objects created
constructor-arg to inject the dependencies
properties to inject the dependencies
autowiring to inject the dependencies
lazy-init let IoC container to create a bean instance at first requested
init-method executed after properties set by the container
destroy-method executed when the container is destroyed

3.1 Scope

The scope of a bean defines the life cycle and visibility of that bean in the contexts we use it (singleton, prototype, request, session, global-session). In pratice, we mainly use singleton, prototype:

singleton: Spring IoC container creates exactly one instance of the object defined by that bean definition. Shown in Code 3-1, if we execute getBean("demo") multiple times, the object will always be the same one.

Code 3-1. Snippet of "bean.xml"

1<bean id = "demo" 
2      class = "com.example.Message"
3      scope = "singleton">
4</bean>

prototype: Spring IoC container creates a new bean instance of the object every time a request for that specific bean is made. Shown in Code 3-2, if we execute getBean("demo") multiple times, there will be corresponsing multiple quite different objects.

Code 3-2. Snippet of "bean.xml"

1<bean id = "demo" 
2      class = "com.example.Message"
3      scope = "prototype">
4</bean>

3.2 Life Cycle

Bean life cycle is managed by the Spring container. The spring container gets started before creating the instance of a bean as per the request, and then dependencies are injected. And finally, the bean is destroyed when the spring container is closed.

Code 3-3(a). "LifeCycleDemo.java"

 1package com.example;
 2
 3public class LifeCycleDemo {
 4   public void init() {
 5      System.out.println("Bean initialized.");
 6   }
 7
 8   public void foo() {
 9      System.out.println("foo");
10   }
11
12   public void destroy() {
13      System.out.println("Bean destroyed.");
14   }
15}

In Code 3-3(a), a straightforward class named LifeCycleDemo is defined, comprising three methods: init(), foo(), and destroy(). Each of these methods prints out status information to indicate its current stage.

Code 3-3(b). "beans.java"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id = "life_cycle_demo"
 9         class = "com.example.LifeCycleDemo"
10         init-method = "init" 
11         destroy-method = "destroy">
12   </bean>
13
14</beans>

In Code 3-3(b), it defines a bean named "life_cycle_demo" of the class "com.example.LifeCycleDemo" with initialization(init) and destruction (destroy) methods.

Code 3-3(c). "LifeCycleDemoTest.java"

 1package com.example;
 2
 3import org.springframework.context.support.AbstractApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class LifeCycleDemoTest {
 7   public static void main(String[] args) {
 8      AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9
10      LifeCycleDemo obj = (LifeCycleDemo) context.getBean("life_cycle_demo");
11      obj.foo();
12      context.registerShutdownHook(); // display destroy info (registers a shutdown hook for the Spring application context)
13   }
14}

In Code 3-3(c), it demonstrates how to use the Spring Framework to initialize the Spring container, retrieve a bean from the container, and invoke a method on the bean. Additionally, it ensures that the Spring context is properly closed when the application exits by registering a shutdown hook.

When the Code 3-3(a, b, c) are executed, the following results should appear in the console:

1Bean initialized.
2foo
3Bean destroyed.

3.3 Postprocessors

BeanPostProcessor is an interface defined in org.springframework.beans.factory.config.BeanPostProcessor, and it allows for custom modification of new bean instance.

Code 3-4 shows how to use Postprocessor.

Code 3-4(a). "PostprocessorDemo.java"

 1package com.example;
 2
 3public class PostprocessorDemo {
 4   public void init(){
 5      System.out.println("init");
 6   }
 7
 8   public void foo() {
 9      System.out.println("foo...");
10   }
11
12   public void destroy(){
13      System.out.println("destroy");
14   }
15}

In Code 3-4(a), just like Code 3-3(a), a straightforward class named PostprocessorDemo is defined, comprising three methods: init(), foo(), and destroy(). Each of these methods prints out status information to indicate its current stage.

Code 3-4(b). "InitPostprocessorDemo.java"

 1package com.example;
 2
 3import org.springframework.beans.factory.config.BeanPostProcessor;
 4import org.springframework.beans.BeansException;
 5
 6public class InitPostprocessorDemo implements BeanPostProcessor {
 7   public Object postProcessBeforeInitialization(Object bean, String beanName) 
 8      throws BeansException {
 9      
10      System.out.println("Before init of " + beanName);
11      return bean;
12   }
13   public Object postProcessAfterInitialization(Object bean, String beanName) 
14      throws BeansException {
15      
16      System.out.println("After init of " + beanName);
17      return bean; 
18   }
19}

Code 3-4(b) is an example of implementing BeanPostProcessor, which prints a bean name before and after initialization of a bean. Note: the return type of postProcessBeforeInitialization and postProcessAfterInitialization is quite arbitrary, so they do not require bean as return values.

Code 3-4(c). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id = "demo" 
 9         class = "com.example.PostprocessorDemo"
10         init-method = "init"
11         destroy-method = "destroy" />
12
13   <bean class = "com.example.InitPostprocessorDemo" />
14
15</beans>

Code 3-4(c) defines two beans. The first bean with the ID "demo" associates itself with the class "com.example.PostprocessorDemo", and it specifies an initialization method called "init" as well as a destruction method called "destroy"; the second bean serves as a custom post-processor for "demo" in the Spring Application Context.

Code 3-4(d). "PostprocessorDemoTest.java"

 1package com.example;
 2
 3import org.springframework.context.support.AbstractApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class PostprocessorDemoTest {
 7   public static void main(String[] args) {
 8      AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9
10      PostprocessorDemo obj = (PostprocessorDemo) context.getBean("demo");
11      obj.foo();
12      context.registerShutdownHook();
13   }
14}

Code 3-4(d) demonstrates the usage of a Spring Framework postprocessor. It only load the bean with ID "demo" but not the Postprocessor class. And The expected output of Code 3-4 should be:

1Before init of demo
2init
3After init of demo
4foo...
5destroy

3.4 Definition Inheritance

Spring supports bean definition inheritance to promote reusability and minimize development effort.

Code 3-5 shows the basic usage of Bean definition inheritance:

Code 3-5(a). "Hello.java"

 1package com.example;
 2
 3public class Hello {
 4   private String name;
 5   private String type;
 6
 7   public void setName(String name){
 8      this.name = name;
 9   }
10   public void setType(String type){
11      this.type = type;
12   }
13   public void sayHello(){
14      System.out.println("Hello " + name + ", type = " + type);
15   }
16}

Code 3-5(a) shows a basic class called Hello, and Hello has two private instance variables, name and type, along with corresponding setter methods setName and setType to set their values. Additionally, the class contains a method sayHello() that prints a greeting message with the name and type values.

Code 3-5(b). "HelloStudent.java"

 1package com.example;
 2
 3public class HelloStudent {
 4   private String name;
 5   private String type;
 6   private String school;
 7
 8   public void setName(String name) {
 9      this.name = name;
10   }
11
12   public void setType(String type) {
13      this.type = type;
14   }
15
16   public void setSchool(String school) {
17      this.school = school;
18   }
19
20   public void sayHello(){
21      System.out.println("Hello " + name + ", type = " + type + ", from " + school);
22   }
23}

Code 3-5(b) introduces a new class called HelloStudent which extends the functionality of the previous Hello class by adding an additional private instance variable, school, and a corresponding setter method setSchool() to set its value. With this extension, the HelloStudent class now represents a student entity with a name, a type, and the school they attend.

Code 3-5(c). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id = "hello" class = "com.example.Hello">
 9      <property name = "name" value = "Tom"/>
10      <property name = "type" value = "student"/>
11   </bean>
12
13   <bean id ="helloStudent" class = "com.example.HelloStudent" parent = "hello">
14      <property name = "name" value = "Jerry"/>
15      <property name = "school" value = "MIT"/>
16   </bean>
17</beans>

Code 3-5(c) sets up two beans, hello and helloStudent, and helloStudent inherits bean definition from its parent called hello. Note the parent="hello" attribute in the "helloStudent" bean definition: This attribute indicates that "helloStudent" is a child bean of "hello," and it will inherit the properties defined in the "hello" bean (i.e., type is set to student).

Code 3-5(d). "HelloInheritanceTest.java"

 1package com.example;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class HelloInheritanceTest {
 7   public static void main(String[] args) {
 8      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9      
10      Hello tom = (Hello) context.getBean("hello");
11      tom.sayHello();
12
13      HelloStudent jerry = (HelloStudent) context.getBean("helloStudent");
14      jerry.sayHello();
15   }
16}

Code 3-5(d) demonstrates how to incorporate beans hello and helloStudent. And the expected output for Code 3-5 should be:

1Hello Tom, type = student
2Hello Jerry, type = student, from MIT

4. DI

Dependency injection (DI) is a pattern we can use to implement IoC. When writing a complex Java application, DI helps in gluing these classes together and keeping them independent at the same time.

There are two major variants for DI: Constructor-based DI, and Setter-based DI. It is recommended to use constructor arguments for mandatory dependencies and setters for optional dependencies.

In this section, we use two simple examples to show how DI works, and Code 4-1(a, b) are the generic parts for these two examples:

Code 4-1(a). "MessageService.java"

1package com.example.di;
2
3public interface MessageService {
4    String getMessage();
5}

Code 4-1(a) defines an interface MessageService that declares a method getMessage().

Code 4-1(b). "MessageServiceTest.java"

 1package com.example.di;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class MessageServiceTest {
 7    public static void main(String[] args) {
 8        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9        MessageService messageService = (MessageService) context.getBean("messageService");
10        String message = messageService.getMessage();
11        System.out.println("Message: " + message);
12    }
13}

Code 4-1(b) creates a class named MessageServiceTest that will load the Spring application context and retrieve the MessageService bean.

4.1 Constructor-based DI

Constructor-based DI is accomplished when the container invokes a class constructor with a number of arguments (each representing a dependency on the other class).

Code 4-1(a, b) and Code 4-2(a, b) demonstrate how to use Constructor-based DI:

Code 4-2(a). "MessageServiceImplConstructorBased.java"

 1package com.example.di;
 2
 3public class MessageServiceImplConstructorBased implements MessageService {
 4    private String message;
 5
 6    // Constructor for DI
 7    public MessageServiceImplConstructorBased(String message) {
 8        this.message = message;
 9    }
10
11    @Override
12    public String getMessage() {
13        return message;
14    }
15}

Code 4-2(a) defines the implementation of the MessageService interface as MessageServiceImplConstructorBased.

Code 4-2(b). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id="messageService" class="com.example.di.MessageServiceImplConstructorBased">
 9      <constructor-arg value="Hello, this is a constructor-based DI example!" />
10   </bean>
11
12</beans>

Code 4-2(b) defines a bean with the ID "messageService" and specifies the class com.example.di.MessageServiceImplConstructorBased. It also provides a constructor argument (value = "Hello, this is a constructor-based DI example!") for DI. This argument will be passed to the constructor of MessageServiceImplConstructorBased when the bean is created.

The expected output for Code 4-1(a, b) and Code 4-2(a, b) is:

1Message: Hello, this is a constructor-based DI example!

Now, let's dig it deeper. If we want to pass multiple objects into a constructor:

Code 4-2-extend(a). "Foo.java"

 1package  com.example.di;
 2
 3public class Foo {
 4   private int  id;
 5   private String name;
 6   private Bar bar;
 7   private Baz baz;
 8
 9   //Constructor for DI
10   public Foo(int id, String name, Bar bar, Baz baz) {
11        this.id = id;
12        this.name = name;
13        this.bar = bar;
14        this.baz = baz;
15    }
16
17   public show() {
18      // ...
19   }
20}

Code 4-2-extend(a) shows a more complex example of Constructor-based DI. Assuming the Bar and Baz classes in the package com.example.di, we will initialize Foo object with a four-parameter (id, name, bar, and baz) constructor.

Code 4-2-extend(b). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Define the bean for the Bar and Baz -->
 9   <bean id="bar" class="com.example.di.Bar" />
10   <bean id="baz" class="com.example.di.Baz" />
11
12   <!-- Define the bean for the Foo class with constructor-based Dependency Injection -->
13   <bean id="foo" class="com.example.di.Foo">
14       <constructor-arg value="1001" />  <!-- id -->
15       <constructor-arg value="Tommy" /> <!-- name -->
16       <constructor-arg ref="bar" />     <!-- bar -->
17       <constructor-arg ref="baz" />     <!-- baz -->
18   </bean>
19
20</beans>

Code 4-2-extend(b) shows how to pass different parameters into constructor. For simple types like int and String, use value; for complex types like Bar and Baz, define the separate beans and then use ref.

Code 4-2-extend(c). "FooTest.java"

 1package com.example.di;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class FooTest {
 7   public static void main(String[] args) {
 8      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9      Foo foo = (Foo) context.getBean("foo");
10      foo.show();  
11   }
12}

So, when passing a reference to an object, use ref attribute of <constructor-arg> tag; when passing a value directly, use value attribute.

4.2 Setter-based DI

Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor or no-argument static factory method to instantiate your bean.

Code 4-1(a, b) and Code 4-3(a, b) demonstrate how to use Setter-based DI:

Code 4-3(a). "MessageServiceImplSetterBased.java"

 1package com.example.di;
 2
 3public class MessageServiceImplSetterBased implements MessageService {
 4    private String message;
 5
 6    // Setter for DI
 7    public void setMessage(String message) {
 8        this.message = message;
 9    }
10
11    @Override
12    public String getMessage() {
13        return message;
14    }
15}

Code 4-3(a) defines the implementation of the MessageService interface using Setter setMessage() to pass values into bean.

Code 4-3(b). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <bean id="messageService" class="com.example.di.MessageServiceImplSetterBased">
 9      <property name="message" value="Hello, this is a setter-based DI example!" />
10   </bean>
11
12</beans>

Code 4-3(b) provides a <property> element with the name "message" and the value "Hello, this is a setter-based DI example!".

The expected output for Code 4-1(a, b) and Code 4-3(a, b) is:

1Message: Hello, this is a setter-based DI example!

Now, let's dig it deeper. If we want to use multiple setters:

Code 4-3-extend(a). "Foo.java"

 1package  com.example.di;
 2
 3public class Foo {
 4   private int  id;
 5   private String name;
 6   private Bar bar;
 7   private Baz baz;
 8
 9   // Setters for DI
10   public void setId(int id) {
11        this.id = id;
12   }
13
14   public void setName(String name) {
15        this.name = name;
16   }
17
18   public void setBar(Bar bar) {
19       this.bar = bar;
20   }
21
22   public void setBaz(Baz baz) {
23       this.baz = baz;
24   }
25
26   // other methods ...
27   public void show() {
28       // ...
29   }
30}

Code 4-3-extend(a) shows a more complex example of Constructor-based DI. Assuming the Bar and Baz classes in the package com.example.di, we will initialize Foo object with four setters (setId(), setName(), setBar(), and setBaz()).

Code 4-3-extend(b). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Define the bean for the Bar and Baz -->
 9   <bean id="bar" class="com.example.di.Bar" />
10   <bean id="baz" class="com.example.di.Baz" />
11
12   <!-- Define the bean for the Foo class with setter-based Dependency Injection -->
13   <bean id="foo" class="com.example.di.Foo">
14      <property name="id" value="1001" />
15      <property name="name" value="Tommy" />
16      <property name="bar" ref="bar" />
17      <property name="baz" ref="baz" />
18   </bean>
19
20</beans>

Code 4-3-extend(b) shows how to pass different parameters into setters. For simple types like int and String, use value; for complex types like Bar and Baz, define the separate beans and then use ref.

Code 4-3-extend(c). "FooTest.java"

 1package com.example.di;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class FooTest {
 7   public static void main(String[] args) {
 8      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9      Foo foo = context.getBean("foo", Foo.class);
10      foo.show();  
11   }
12}

In Setter-based DI, the Spring container will call the appropriate setter methods on the Foo instance after creating it, injecting the Bar and Baz dependencies into the Foo object foo.

4.3 Injecting Collection

Injecting collections refers to the process of providing a collection of objects (array, list, set, map, or properties) to a Spring bean during its initialization.

Code 4-4(a). "CollectionInjection.java"

 1package com.example.di;
 2
 3import java.util.List;
 4import java.util.Set;
 5import java.util.Map;
 6import java.util.Properties;
 7
 8public class CollectionInjection {
 9   private int[] array;
10   private List<String> list;
11   private Set<String> set;
12   private Map<String,String> map;
13   private Properties properties;
14
15   // Setters
16   public void setArray(int[] array) {
17       this.array = array;
18   }
19
20   public void setList(List<String> list) {
21       this.list = list;
22   }
23
24   public void setSet(Set<String> set) {
25       this.set = set;
26   }
27
28   public void setMap(Map<String, String> map) {
29       this.map = map;
30   }
31
32   public void setProperties(Properties properties) {
33       this.properties = properties;
34   }
35}

Code 4-4(a) shows the target class for Collection Injection.

Code 4-4(b). "beans.xml"

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5       http://www.springframework.org/schema/beans/spring-beans.xsd">
 6
 7    <!-- Define the CollectionInjection bean -->
 8    <bean id="collectionInjection" class="com.example.di.CollectionInjection">
 9        <!-- Inject an array -->
10        <property name="array">
11            <array>
12                <value>1</value>
13                <value>2</value>
14                <value>3</value>
15            </array>
16        </property>
17
18        <!-- Inject a list -->
19        <property name="list">
20            <list>
21                <value>First element</value>
22                <value>Second element</value>
23                <value>Third element</value>
24            </list>
25        </property>
26
27        <!-- Inject a set -->
28        <property name="set">
29            <set>
30                <value>Set element 1</value>
31                <value>Set element 2</value>
32                <value>Set element 3</value>
33            </set>
34        </property>
35
36        <!-- Inject a map -->
37        <property name="map">
38            <map>
39                <entry key="id" value="404"/>
40                <entry key="msg" value="Page Not Found"/>
41            </map>
42        </property>
43
44        <!-- Inject properties -->
45        <property name="properties">
46            <props>
47                <prop key="property1">Property Value 1</prop>
48                <prop key="property2">Property Value 2</prop>
49                <prop key="property3">Property Value 3</prop>
50            </props>
51        </property>
52    </bean>
53</beans>

Code 4-4(b) shows how to use XML file to inject array, list, set, map, and properties.

4.4 Autowire

Autowire is a specific feature of Spring DI that simplifies the process of injecting dependencies by automatically wiring beans together (without explicit configuration).

There are five autowiring modes:

Table 4-1. Autowiring Modes

Mode Description
no No autowiring (default mode)
byName Autowiring by property name
byType Autowiring by property data type, match exactly one
constructor Autowiring by constructor, match exactly one
autodetect first autowire by constructor, then autowire by byType

Note: to wire arrays and other typed-collections, use byType or constructor autowiring mode.

Now we will use the spell checker textEditor.spellCheck() to demonstrate autowiring modes, and partial codes are shown in Code 4-5(a, b, c):

Code 4-5(a). "TextEditorTest.java"

 1package com.example.di;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class TextEditorTest {
 7   public static void main(String[] args) {
 8      ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9      TextEditor textEditor = (TextEditor) context.getBean("textEditor");
10      textEditor.spellCheck();
11   }
12}

Code 4-5(a) is a test class to demonstrate how various autowire modes work.

Code 4-5(b). "SpellChecker.java"

1package com.example.di;
2
3public class SpellChecker {
4   public void checkSpelling() {
5      System.out.println("check Spelling...");
6   }
7}

Code 4-5(b) defines a class named SpellChecker, which is a simple Java class responsible for checking spellings. The SpellChecker class has a single method called checkSpelling() that prints the message "check Spelling..." to the console.

Code 4-5(c). "TextEditor.java"

 1package com.example.di;
 2
 3public class TextEditor {
 4   // autowire the `spellChecker` from Spring Container
 5   private SpellChecker spellChecker;
 6
 7   public void setSpellChecker( SpellChecker spellChecker ) {
 8      this.spellChecker = spellChecker;
 9   }
10
11   public SpellChecker getSpellChecker() {
12      return spellChecker;
13   }
14
15   public void spellCheck() {
16      spellChecker.checkSpelling();
17   }
18}

Code 4-5(c) defines a class named TextEditor, which is used to perform spell checking through the use of the SpellChecker defined in Code 4-5(b).

With Code 4-5(d, e, or f), the expected output for Code 4-5(a, b, c) should be:

1check Spelling...

4.4.1 Autowire byName

In XML configuration file, Spring container looks at the beans on which autowire attribute is set to byName, Spring container will then look for other beans with names that match the properties of the bean (the bean set to byName-autowiring). If matches are found, Spring will automatically inject those matching beans into the properties of the specified bean; otherwise, the bean's properties will remain unwired.

Code 4-5(a, b, c) and Code 4-5(d) demonstrate how autowire byName works:

Code 4-5(d) "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Definition for spellChecker bean -->
 9   <bean id = "spellChecker" class = "com.example.di.SpellChecker" />
10
11   <!-- Definition for textEditor bean -->
12   <bean id = "textEditor"
13         class = "com.example.di.TextEditor"
14         autowire = "byName" />
15</beans>

In Code 4-5(d), Spring will look for a bean with the name spellChecker in the Spring Container and inject it into spellChecker property of textEditor bean, due to autowire = "byName" on textEditor. And to enable the byName autowiring, TextEditor must have a class member whose type is SpellChecker.

4.4.2 Autowire byType

In the XML configuration file, when the autowire attribute is set to byType for a particular bean, the Spring container will attempt to find other beans in its context whose types match the property types of the bean being configured.

Code 4-5(a, b, c) and Code 4-5(e) demonstrate how autowire byType works:

Code 4-5(e). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Definition for spellChecker bean -->
 9   <bean id = "spellChecker" class = "com.example.di.SpellChecker" />
10
11   <!-- Definition for textEditor bean -->
12   <bean id = "textEditor"
13         class = "com.example.di.TextEditor"
14         autowire = "byType" />
15</beans>

In Code 4-5(e), Spring will automatically inject the spellChecker into spellChecker property of textEditor bean, because the SpellChecker class is defined as a Spring bean with the id spellChecker, and it matches the type of the spellChecker property in the TextEditor class.

4.4.3 Autowire constructor

In the XML configuration file, Spring container looks at the beans on which autowire attribute is set constructor. It then tries to match and wire its constructor's argument with exactly one of the beans name in the configuration file. If matches are found, it will inject those beans; otherwise, bean(s) will remain unwired.

Code 4-5(f). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Definition for spellChecker bean -->
 9   <bean id = "spellChecker" class = "com.example.di.SpellChecker" />
10
11   <!-- Definition for textEditor bean -->
12   <bean id = "textEditor"
13         class = "com.example.di.TextEditor"
14         autowire = "constructor" />
15</beans>

5. ANNOTATIONS

Annotations are a form of metadata, that applies to the Java classes, methods, or fields, to provide additional information and instructions to the Spring container. Annotations offer a straightforward alternative to XML files for efficient configuration and management of components and their dependencies.

5.1 Configuration Annotations

Below are some configuration annotations used to configure the Spring container, manage properties, and activate specific profiles.

5.1.1 @Bean

@Bean indicates that the return value of the annotated method should be registered as a bean in the Spring application context.

Code 5-1. Snippet of "Address.java"

1  @Bean
2  public Address getAddress(){
3    return new Address();
4  }

In Code 5-1, getAddress() is annotated with @Bean, meaning that Spring will register the Address object returned by that method as a bean.

5.1.2 @Configuration

@Configuration annotation is used to declare a class as a configuration class in Spring.

Code 5-2. Snippet of "DataConfig.java"

 1@Configuration
 2public class DataConfig{ 
 3  @Bean
 4  public DataSource source(){
 5    DataSource source = new OracleDataSource();
 6    source.setURL();
 7    source.setUser();
 8    return source;
 9  }
10}

In Code 5-2, @Configuration annotation declares the class DataConfig as a configuration class in Spring.

5.1.3 @ComponentScan

@ComponentScan annotation is used to enable component scanning in Spring.

Code 5-3(a). "AppConfig.java"

 1package com.example.annotation;
 2
 3import org.springframework.context.annotation.ComponentScan;
 4import org.springframework.context.annotation.Configuration;
 5
 6@Configuration
 7@ComponentScan(basePackages = "com.example.annotation")
 8public class AppConfig {
 9
10}

In Code 5-3(a): AppConfig uses @ComponentScan to specify the base package for component scanning. When Spring performs component scanning, it looks for classes annotated with stereotypes like @Component, within the specified package and its sub-packages. Spring will then automatically create Spring beans for these classes and add them to the application context.

Code 5-3(b). "HelloService.java"

 1package com.example.annotation;
 2
 3import org.springframework.stereotype.Component;
 4
 5@Component
 6public class HelloService {
 7    public void sayHello() {
 8        System.out.println("Hello World");
 9    }
10}

In Code 5-3(b): HelloService is annotated with @Component, indicating that it is a Spring bean that will be managed by the Spring container.

Code 5-3(c). "AppTest.java"

 1package com.example.annotation;
 2
 3import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 4
 5public class AppTest {
 6    public static void main(String[] args) {
 7        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 8        HelloService helloService = context.getBean(HelloService.class);
 9        helloService.sayHello();
10        context.close(); // !!! it is important to close the Annotation Config Application Context
11    }
12}

Code 5-3(c) creates an AnnotationConfigApplicationContext using AppConfig.class as the configuration class, retrieves the HelloService bean from the context, and then calls the sayHello() method.

The expected output for Code 5-3(a, b, c) is:

1Hello World

5.1.4 @PropertySource

@PropertySource annotation is used to specify the location of properties files containing configuration settings for the Spring application.

Code 5-4(a). "AppConfig.java"

 1package com.example.annotation.propertysource;
 2
 3import org.springframework.context.annotation.Configuration;
 4import org.springframework.context.annotation.PropertySource;
 5
 6@Configuration
 7@ComponentScan(basePackages = "com.example.annotation.propertysource")
 8@PropertySource("classpath:application.yml")
 9public class AppConfig {
10   
11}

Code 5-4(a) is a Java configuration class, and it specifies that it will define Spring beans and loads properties from the "application.yml" file.

Code 5-4(b). "application.yml"

1greeting:
2  message: "Hello, World!"

Code 5-4(b) is a YAML file that sets the property "greeting.message" with the value "Hello, World!" for the Spring application.

Code 5-4(c). "GreetingService.java"

 1package com.example.annotation.propertysource;
 2
 3import org.springframework.beans.factory.annotation.Value;
 4import org.springframework.stereotype.Component;
 5
 6@Component
 7public class GreetingService {
 8    @Value("${greeting.message}")
 9    private String message;
10
11    public void sayGreeting() {
12        System.out.println(message);
13    }
14}

Code 5-4(c) is a Spring component class, and it injects the value of the property "greeting.message" into the private field greetingMessage and provides a method to print the greeting message.

Code 5-4(d). "AppTest.java"

 1import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 2
 3public class AppTest {
 4    public static void main(String[] args) {
 5        // Create the application context using AppConfig
 6        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 7
 8        // Get the GreetingService bean from the context
 9        GreetingService greetingService = context.getBean(GreetingService.class);
10
11        // Call the sayGreeting() method to print "Hello, World!" on the console
12        greetingService.sayGreeting();
13
14        // Close the context
15        context.close();
16    }
17}

The expected output for Code 5-4(a, b, c, d) should be:

1Hello, World!

5.1.5 @Profile

@Profile annotation is used to define specific configurations for different application environments or scenarios.

Code 5-5(a). "DatabaseConfig.java"

 1package com.example.annotation.profile;
 2
 3import org.springframework.context.annotation.Bean;
 4import org.springframework.context.annotation.Configuration;
 5import org.springframework.context.annotation.Profile;
 6
 7@Configuration
 8public class DatabaseConfig {
 9
10    @Bean
11    @Profile("development")
12    public DataSource developmentDataSource() {
13        // Create and configure the H2 data source for development
14        return new H2DataSource();
15    }
16
17    @Bean
18    @Profile("production")
19    public DataSource productionDataSource() {
20        // Create and configure the MySQL data source for production
21        return new MySQLDataSource();
22    }
23}

Code 5-5(b). "DataSource.java"

 1package com.example.annotation.profile;
 2
 3public interface DataSource {
 4    // Define common data source methods here
 5}
 6
 7public class H2DataSource implements DataSource {
 8    // H2 data source implementation
 9}
10
11public class MySQLDataSource implements DataSource {
12    // MySQL data source implementation
13}

Code 5-5(c). "application.yml"

1spring:
2  profiles:
3    active: development

This will activate the @Profile("development") part of DataSource bean.

5.1.6 @Import

@Import annotation is used to import one or more configuration classes into the current configuration.

Code 5-6(a). "AppConfig.java"

 1import org.springframework.context.annotation.Bean;
 2import org.springframework.context.annotation.Configuration;
 3
 4@Configuration
 5public class AppConfig {
 6
 7    @Bean
 8    public MyBean myBean() {
 9        return new MyBean();
10    }
11}

Code 5-6(b). "AnotherAppConfig.java"

1import org.springframework.context.annotation.Configuration;
2import org.springframework.context.annotation.Import;
3
4@Configuration
5@Import(AppConfig.class)
6public class AnotherConfig {
7    // Additional configuration or beans can be defined here
8}
9

Code 5-6(b) makes all the beans defined in AppConfig (in this case, just MyBean) available in the current application context, when AnotherConfig is used.

5.1.7 @ImportResource

@ImportResource annotation is used to import XML-based Spring configurations into the current Java-based configuration class.

Code 5-7(a). "AppConfig.java"

 1package com.example.annotation.config;
 2
 3import org.springframework.context.annotation.Configuration;
 4import org.springframework.context.annotation.ImportResource;
 5
 6@Configuration
 7@ImportResource("classpath:config.xml") // Load the XML configuration file
 8public class AppConfig {
 9    // Java-based configuration can also be defined here if needed
10}

Code 5-7(a) indicates that it contains Spring bean definitions. It also uses @ImportResource to load the XML configuration file "config.xml."

Code 5-7(b). "config.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2
 3<beans xmlns = "http://www.springframework.org/schema/beans"
 4   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
 7
 8   <!-- Define a bean in the XML configuration -->
 9   <bean id="messageService" class="com.example.MessageService">
10      <property name="message" value="Hello, Spring!"/>
11   </bean>
12</beans>

Code 5-7(c). "MessageService.java"

 1package com.example.annotation.config;
 2
 3public class MessageService {
 4    private String message;
 5
 6    public String getMessage() {
 7        return message;
 8    }
 9
10    public void setMessage(String message) {
11        this.message = message;
12    }
13}

Code 5-7(d). "AppTest.java"

 1package com.example.annotation.config;
 2
 3import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 4
 5public class Main {
 6    public static void main(String[] args) {
 7        // Load the Java configuration class
 8        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 9
10        // Get the bean from the Spring context
11        MessageService messageService = context.getBean("messageService", MessageService.class);
12
13        // Use the bean
14        System.out.println(messageService.getMessage());
15
16        // Close the context
17        context.close();
18    }
19}

The expected output of running Code 5-7(a, b, c, d) should be:

1Hello, Spring!

5.2 Bean Annotations

Below are some bean annotations that are commonly used in Spring applications:

5.2.1 @Component, @Controller, @Repository, @Service

These are used to automatically detect and register beans with the Spring container during component scanning.

  • @Component indicates that the class is a general-purpose Spring component
  • @Controller marks the class as a Spring MVC controller
  • @Repository indicates that the class is a data repository (database operations)
  • @Servicemarks the class as a service bean dealing with business logic

For the reason of simplicity, I will reuse the Code 5-3(b) as the demo.

5.2.2 @Autowired

@Autowired annotation is used to automatically inject dependent beans into the target bean.

@Autowired can be applied on fields, setter methods, and constructors.

Code 5-8. "AutowiredField.java"

 1package com.example.autowired.field;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4
 5public class Customer {
 6   @Autowired
 7   private Person person;
 8
 9   // ...
10}

Code 5-8 shows how to use the @Autowired annotation to automatically inject a bean into the person field of Customer class.

Code 5-9. "AutowiredSetter.java"

 1package com.example.autowired.setter;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4
 5public class Customer {
 6    private Person person;
 7
 8    @Autowired
 9    public void setPerson(Person person) {
10        this.person = person;
11    }
12
13    // ...
14}

Code 5-9 shows how to use the @Autowired annotation to automatically inject a bean into the setter setPerson() of the Customer class. Spring tries to perform the byType autowiring on the method.

Code 5-10. "AutowiredConstructor.java"

 1package com.example.autowired.constructor;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4
 5public class Customer {
 6    private Person person;
 7
 8    @Autowired
 9    public Customer(Person person) {
10        this.person = person;
11    }
12
13    // ...
14}

Code 5-10 shows how to use the @Autowired annotation to automatically inject a bean into the constructor of the Customer class. Note: only one constructor of any bean class can carry the @Autowired annotation.

5.2.3 @Qualifier

The @Qualifier annotation is used in conjunction with @Autowired to resolve ambiguity when multiple beans of the same type are available for injection.

Code 5-11(a). "MessageService.java"

 1package com.example.annotation.qualifier;
 2
 3public interface MessageService {
 4   public void  sendMessage();
 5}
 6
 7@Component
 8public class MailService implements MessageService {
 9   @Override
10   public void sendMessage() {
11      System.out.println("Mail sent.");
12   }
13}
14
15@Component
16public class SmsService implements MessageService {
17   @Override
18   public void sendMessage() {
19      System.out.println("SMS sent.");
20   }
21}

Code 5-11(a) defines an interface MessageService, which declares a single method sendMessage(). The interface is then implemented by two classes, MailService and SmsService. These classes provide their own implementations of the sendMessage() method.

Code 5-11(b). "App.java"

 1package com.example.annotation.qualifier;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.beans.factory.annotation.Qualifier;
 5import org.springframework.stereotype.Component;
 6
 7@Component
 8public class App {
 9
10   @Autowired
11   @Qualifier("mailService")
12   private MessageService messageService;
13
14   public void action() {
15      messageService.sendMessage();
16   }
17}

Code 5-11(b) injects mailService into messageService by @Qualifier annotation. Note: the MailService class is annotated with @Component, which makes it a Spring bean. So the default bean name for MailService class would be mailService (with the first letter converted to lowercase).

5.2.4 @Value

@Value annotation is used to inject values from properties files, environment variables, or other sources directly into bean fields or constructor parameters.

Code 5-12. "HelloService.java"

 1import org.springframework.beans.factory.annotation.Value;
 2import org.springframework.stereotype.Component;
 3
 4@Component
 5public class HelloService {
 6    @Value("Hello Spring Framework")
 7    private String message;
 8
 9    public void sayHello() {
10        System.out.println(message);
11    }
12}

Code 5-12 defines a Spring component class named HelloService with a field message that is initialized with the value "Hello Spring Framework" using the @Value annotation, and a method sayHello() to print the message to the console when called.

5.2.5 @Scope

@Scope annotation is used to specify the the scope of a @Component class or a @Bean definition (just like scope field in <bean> tag), defining the lifecycle and visibility of the bean instance.

The default scope for a bean is Singleton, and we can define the scope of a bean as a Prototype using the scope="prototype" attribute of the <bean> tag in the XML file or using @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) annotation, shown in Code 5-13.

Code 5-13. Snippet of "AppConfig.java"

1@Configuration
2public class AppConfig {
3   @Bean
4   @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
5   public MessageService messageService() {
6      return new EmailMessageService();
7   }
8}

5.2.6 @PostConstructand @PreDestroy

@PostConstruct annotation is used to indicate a method (init-method field in <bean> tag) that should be executed after the bean has been initialized by the Spring container.

@PreDestroy annotation is used to indicate a method (destroy-method field in <bean> tag) that should be executed just before the bean is destroyed by the Spring container.

Code 5-14(a). ""

 1package com.example.ctordtor;
 2
 3import javax.annotation.PostConstruct;
 4import javax.annotation.PreDestroy;
 5
 6import org.springframework.stereotype.Component;
 7
 8@Component
 9public class ExampleBean {
10
11   @PostConstruct
12   public void init() {
13      System.out.println("Initializing bean...");
14   }
15
16   @PreDestroy
17   public void cleanup() {
18      System.out.println("Destroying bean...");
19   }
20}

Code 5-14(b). "AppConfig.java"

 1package com.example.ctordtor;
 2
 3import org.springframework.context.annotation.ComponentScan;
 4import org.springframework.context.annotation.Configuration;
 5
 6@Configuration
 7@ComponentScan(basePackages = "com.example.springdemo")
 8public class AppConfig {
 9
10}

5.2.7 @Lazy

The @Lazy annotation is used to delay the initialization of a bean until the first time it is requested.

Code 5-15. "AppConfig.java"

 1package com.example.annotation.lazy;
 2
 3import org.springframework.context.annotation.Bean;
 4import org.springframework.context.annotation.Configuration;
 5import org.springframework.context.annotation.Lazy;
 6
 7@Configuration
 8public class AppConfig {
 9
10   @Lazy(value = true)
11   @Bean
12   public FirstBean firstBeanLazy() {
13      return new FirstBean();
14   }
15
16   @Lazy
17   @Bean
18   public SecondBean secondBeanLazy() {
19      return new SecondBean();
20   }
21
22   @Lazy(value = false)
23   @Bean
24   public ThirdBean thirdBeanNotLazy() {
25      return new ThirdBean();
26   }
27
28   @Bean
29   public FourthBean fourthBeanNotLazy() {
30      return new FourthBean();
31   }
32}

Code 5-15 defines 4 beans: firstBeanLazy and secondBeanLazy will be lazily initialized, while thirdBeanNotLazy and fourthBeanNotLazy will be eagerly initialized during the application startup.

5.2.8 @Primary

@Primary annotation is used to indicate a preferred bean when multiple beans of the same type are available for injection with @Autowired.

Code 5-16(a). Snippet of "AppConfig.java"

 1@Configuration
 2public class AppConfig {
 3
 4    @Bean
 5    public MessageService getEmailService() {
 6        return new MessageService("Email");
 7    }
 8
 9    @Bean
10    @Primary
11    public MessageService getSmsService() {
12        return new MessageService("SMS");
13    }
14}

Code 5-16(a) defines two beans (MessageService instances) with different type names ("Email" and "SMS") and marks the return value of getSmsService() as the primary bean using the @Primary annotation.

Code 5-16(b). Snippet of "MessageService.java"

1public class MessageService {
2   private String type;
3
4   public MessageService(String type) {
5      this.type = type;
6   }
7
8   // ...
9}

Code 5-16(b) declares the MessageService class with a constructor to set the type of MessageService when creating an instance.

6. AOP

Aspect-Oriented Programming (AOP) is a framework in Spring that allows breaking down program logic into separate concerns, which are conceptually independent from core business logic of the application, providing a way to decouple cross-cutting concerns from the objects they affect.

6.1 AOP Concepts

The concepts shown in the table below are general terms that are related to AOP in a broader sense beyond Spring Framework.

Table 6-1. General Terms of AOP

Terms Description
Aspect a module which has a set of APIs providing cross-cutting requirements
Target object The object being advised by one or more aspects
Join point a point in your application where you can plugin the AOP aspect
Pointcut a set of one or more join points where an advice should be executed
Advice the actual action to be taken either before or after the method execution
Introduction allows you to add new methods or attributes to the existing classes.
Weaving the process of linking aspects with other application types or objects to create an advised object

Spring AOP is a technique that modularizes cross-cutting concerns using aspects, which consist of advice and pointcuts. Aspects define specific behaviors, and pointcuts specify where these behaviors should be applied (e.g., method invocations).

During runtime weaving, the advice is applied to the target objects at the designated join points, effectively incorporating the desired functionalities into the application and improving code modularity.

Spring aspects can work with five kinds of advice mentioned:

Table 6-2. Types of Advice

Types of Advice Description
before run advice before the execution of the method
after run advice after the execution of the method
after-returning run advice after the a method only if its execution is completed successfully
after-throwing run advice after the a method only if its execution throws exception
around run advice before and after the advised method is invoked

6.2 XML Schema based AOP

Aspects can be implemented using the regular classes along with XML Schema based configuration. The basic structure for XML to config AOP looks like Code 6-0:

Code 6-0. Skeleton of AOP config in "beans.xml"

1<aop:config>
2   <aop:aspect id = "{AOP_ID}" ref = "{CONFIG_CLASS_lowerCammelNotation}">
3      <aop:pointcut id = "{POINTCUT_ID}" expression = "{POINTCUT_EXPRESSION}"/>
4         <aop:{ADVICE_NAME} pointcut-ref = "{POINTCUT_ID}" method = "{CONFIG_CLASS_CERTAIN_METHOD}"/>
5         <aop:after-returning pointcut-ref = "{POINTCUT_ID}" returning = "{RETURN_VAR_NAME}" method = "{CONFIG_CLASS_CERTAIN_METHOD}"/>
6         <aop:after-throwing  pointcut-ref = "{POINTCUT_ID}" throwing = "{EXCEPTION_NAME}" method = "{CONFIG_CLASS_CERTAIN_METHOD}"/>
7   </aop:aspect>
8</aop:config>

Code 6-0 shows how to config AOP:

  • An aspect is declared using the <aop:aspect> element, and the backing bean is referenced using the ref attribute.
  • A pointcut is declared using the <aop:pointcut> element to determine the join points (i.e., methods) of interest to be executed with different advices.
  • Advices can be declared inside <aop:aspect> tag using the element <aop:{ADVICE_NAME}>, such as <aop:before>, <aop:after>, <aop:after-returning>, <aop:after-throwing> and <aop:around>. (Please refer to Table 6-1).

PointCut Designator (PCD) is a keyword telling Spring AOP what to match.

  1. execution(primary Spring PCD): matches method execution join points
  2. within: limits matching to join points of certain types
  3. this: limits matching to join points where the bean reference is an instance of the given type (when Spring AOP creates a CGLIB-based proxy).
  4. target: limits matching to join points where the target object is an instance of the given type (when a JDK-based proxy is created).
  5. args: matches particular method arguments

Pointcut Expression looks like expression = "execution(* com.example.aop.*.*(..))", in expression field of <aop:pointcut> tag:

  • the execution is a Spring PCD
  • the first Asterisk Sign (*) in execution(* is a wildcard character that matches any return type of the intercepted method, e.g., void, Integer, String, etc.
  • the second asterisk (*) in com.example.aop.* is a wildcard character that matches any class in the com.example.aop package.
  • the dot and asterisk (.*) in com.example.aop.*.* is a wildcard character that matches any method with any name in the specified class.
  • (..)is another wildcard that matches any number of arguments in the method. (..) means the method can take zero or more arguments.

Code 6-1(a). "Logging.java"

 1package com.example.aop;
 2
 3public class Logging {
 4
 5    public void beforeAdvice(){
 6        System.out.println("`beforeAdvice()` invoked.");
 7    }
 8
 9    public void afterAdvice(){
10        System.out.println("`afterAdvice()` invoked.");
11    }
12
13    public void afterReturningAdvice(Object retVal) {
14        System.out.println("[Success] `afterReturningAdvice()` reads return value: " + retVal.toString() );
15        System.out.println("------");
16    }
17
18    public void afterThrowingAdvice(Exception exception){
19        System.out.println("[FAILURE] `afterThrowingAdvice()` detects Exception: " + exception.toString());
20        System.out.println("------");
21    }
22}

Code 6-1(a) represents an aspect in an AOP context, and it contains various advice methods that will be executed at specific points during the execution of the target methods in the application:

  • beforeAdvice() method will be executed before the target method is invoked.
  • afterAdvice() method will be executed after the target method has been invoked, regardless of whether it completed successfully or threw an exception.
  • afterReturningAdvice(Object retVal) method will be executed after the target method has successfully completed and returned a value. (The retVal parameter contains the value returned by the target method.)
  • afterThrowingAdvice(Exception exception) method will be executed if the target method throws an exception. (The exception parameter contains the exception thrown by the target method.)

Code 6-1(b). "Student.java"

 1package com.example.aop;
 2
 3public class Student {
 4    private Integer age;
 5    private String name;
 6
 7    public void setAge(Integer age) {
 8        this.age = age;
 9    }
10    public Integer getAge() {
11        System.out.println("Class method `getAge()` gets `age` = " + age );
12        return age;
13    }
14    public void setName(String name) {
15        this.name = name;
16    }
17    public String getName() {
18        System.out.println("Class method `getName()` gets `name` = " + name );
19        return name;
20    }
21    public void throwsException(){
22        System.out.println("Class method `throwsException()` will throw 'IllegalArgumentException'");
23        if (true)
24         throw new IllegalArgumentException(); // For Test
25    }
26}

In Code 6-1(b), Student class has getters/setters for age and name properties, and also has the throwsException() method, which will throw an IllegalArgumentException to demonstrate how AOP and exception handling work together.

Code 6-1(c). "AopDemoTest.java"

 1package com.example.aop;
 2
 3import org.springframework.context.ApplicationContext;
 4import org.springframework.context.support.ClassPathXmlApplicationContext;
 5
 6public class AopDemoTest {
 7    public static void main(String[] args) {
 8        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
 9
10        Student student = (Student) context.getBean("student");
11        student.getName();
12        student.getAge();
13        student.throwsException();
14    }
15}

Code 6-1(c) contains the main method that demonstrates the usage of AOP.

Code 6-1(d). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2<beans xmlns = "http://www.springframework.org/schema/beans"
 3   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
 4   xmlns:aop = "http://www.springframework.org/schema/aop"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
 7   http://www.springframework.org/schema/aop 
 8   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
 9
10    <!-- Bean definition for student -->
11    <bean id = "student" class = "com.example.aop.Student">
12        <property name = "name" value = "Tom" />
13        <property name = "age" value = "83"/>
14    </bean>
15
16    <!-- Bean definition for logging aspect -->
17    <bean id = "logging" class = "com.example.aop.Logging"/>
18
19    <!-- AOP Configurations -->
20    <aop:config>
21        <!--
22           `<aop:aspect id = "log">`: defines an aspect named "log" 
23            `ref = "logging"`:  refer to the bean named "logging", 
24                                   representing the "Logging.java" aspect
25        -->
26        <aop:aspect id = "log" ref = "logging">
27            <!-- 
28               A pointcut named "selectAll" is defined using an `expression` 
29                  to target *all methods* 
30                     within the package "com.example.aop" and its sub-packages. 
31            -->
32            <aop:pointcut id = "selectAll"
33                          expression = "execution(* com.example.aop.*.*(..))"/>
34
35            <!--
36               Associates the "beforeAdvice()" method 
37                  with the "selectAll" pointcut
38                     to be executed **before** the target methods
39            -->
40            <aop:before pointcut-ref = "selectAll" method = "beforeAdvice"/>
41            
42            <!--  
43               Associates the "afterAdvice()" method 
44                  with the "selectAll" pointcut 
45                     to be executed **after** the target methods.
46            -->
47            <aop:after pointcut-ref = "selectAll" method = "afterAdvice"/>
48
49            <!--
50               Associates the "afterReturningAdvice()" method 
51                  with the "selectAll" pointcut 
52                     to be executed after the **successful return** of the target methods.
53                     
54               The returning value will be the parameter for `afterReturningAdvice()`.
55            -->
56            <aop:after-returning pointcut-ref = "selectAll"
57                                 returning = "retVal" method = "afterReturningAdvice"/>
58
59            <!--
60               Associates the "afterThrowingAdvice()" method 
61                  with the "selectAll" pointcut
62                     to be executed if the target methods throw an exception.
63               The Exception object will be the parameter for `afterThrowingAdvice()`.
64            -->
65            <aop:after-throwing pointcut-ref = "selectAll"
66                                throwing = "exception" method = "afterThrowingAdvice"/>
67
68        </aop:aspect>
69    </aop:config>
70
71</beans>

Code 6-1(d) shows how to config Spring AOP.

The expected output for Code 6-1(a, b, c, d) is:

 1`beforeAdvice()` invoked.
 2Class method `getName()` gets `name` = Tom
 3`afterAdvice()` invoked.
 4[Success] `afterReturningAdvice()` reads return value: Tom
 5------
 6`beforeAdvice()` invoked.
 7Class method `getAge()` gets `age` = 83
 8`afterAdvice()` invoked.
 9[Success] `afterReturningAdvice()` reads return value: 83
10------
11`beforeAdvice()` invoked.
12Class method `throwsException()` will throw 'IllegalArgumentException'
13`afterAdvice()` invoked.
14[FAILURE] `afterThrowingAdvice()` detects Exception: java.lang.IllegalArgumentException
15------
16Exception in thread "main" java.lang.IllegalArgumentException
17	at com.example.aop.Student.throwsException(Student.java:23)
18	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
19	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
20   (Omit the rest 22-line-long Exception message...)

Code 6-1(e). "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2<beans xmlns = "http://www.springframework.org/schema/beans"
 3   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
 4   xmlns:aop = "http://www.springframework.org/schema/aop"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
 7   http://www.springframework.org/schema/aop 
 8   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
 9
10    <!-- Definition for student bean -->
11    <bean id = "student" class = "com.example.aop.Student">
12        <property name = "name" value = "Jerry" />
13        <property name = "age" value = "83"/>
14    </bean>
15
16    <!-- Definition for logging aspect -->
17    <bean id = "logging" class = "com.example.aop.Logging"/>
18
19    <!-- AOP Configurations -->
20    <aop:config>
21        <aop:aspect id = "log" ref = "logging">
22
23            <!--
24               A pointcut named "selectGetName" using an expression
25                  to target the `getName()` method of the `Student` class.
26
27               Note: `(..)` is a wildcard that 
28                        represents zero or more arguments of any type.
29            -->
30            <aop:pointcut id = "selectGetName"
31                          expression = "execution(* com.example.aop.Student.getName(..))"/>
32
33            <aop:before pointcut-ref = "selectGetName" method = "beforeAdvice"/>
34            <aop:after pointcut-ref = "selectGetName" method = "afterAdvice"/>
35            <aop:after-returning pointcut-ref = "selectGetName"
36                                 returning = "retVal" method = "afterReturningAdvice"/>
37            <aop:after-throwing pointcut-ref = "selectGetName"
38                                throwing = "exception" method = "afterThrowingAdvice"/>
39
40        </aop:aspect>
41    </aop:config>
42
43</beans>

Code 6-1(e) looks like Code 6-1(d), except for the element <aop:pointcut id = "selectGetName" expression = "execution(* com.example.aop.Student.getName(..))"/>, which targets only on the method Student.getName() rather than all methods in the Student class.

The expected output for Code 6-1(a, b, c, e) is:

 1`beforeAdvice()` invoked.
 2Class method `getName()` gets `name` = Tom
 3[Success] `afterReturningAdvice()` reads return value: Tom
 4------
 5`afterAdvice()` invoked.
 6Class method `getAge()` gets `age` = 83
 7Class method `throwsException()` will throw 'IllegalArgumentException'
 8Exception in thread "main" java.lang.IllegalArgumentException
 9	at com.example.aop.Student.throwsException(Student.java:23)
10	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
11	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
12	(Omit the rest 10-line-long Exception message...)

6.3 AspectJ based AOP

AspectJ refers declaring aspects as regular Java classes with Java 5 annotations.

First, the "beans.xml" need to be modified with <aop:aspectj-autoproxy/> tag, shown in Code 6-2.

Code 6-2. "beans.xml"

 1<?xml version = "1.0" encoding = "UTF-8"?>
 2<beans xmlns = "http://www.springframework.org/schema/beans"
 3   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
 4   xmlns:aop = "http://www.springframework.org/schema/aop"
 5   xsi:schemaLocation = "http://www.springframework.org/schema/beans
 6   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
 7   http://www.springframework.org/schema/aop 
 8   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
 9
10   <!-- AOP Configurations -->
11   <aop:aspectj-autoproxy/>
12
13   <!-- Bean definition for student -->
14   <bean id = "student" class = "com.example.aop.Student">
15      <property name = "name" value = "Tom" />
16      <property name = "age" value = "83"/>
17   </bean>
18
19   <!-- Bean definition for logging aspect -->
20   <bean id = "logging" class = "com.example.aop.Logging"/>
21   
22</beans>

Code 6-2 shows how to use <aop:aspectj-autoproxy/> tag to simplify AOP configuration.

Then I will rewrite the Code 6-1(a, c) to show how to use AspectJ. To declare Pointcuts and Advices, rewrite Code 6-1(a) to Code 6-1-AOP(a):

Code 6-1-AOP(a). "Logging.java"

 1package com.example.aop;
 2
 3import org.aspectj.lang.annotation.Aspect;
 4import org.aspectj.lang.annotation.Pointcut;
 5import org.aspectj.lang.annotation.Before;
 6import org.aspectj.lang.annotation.After;
 7import org.aspectj.lang.annotation.AfterThrowing;
 8import org.aspectj.lang.annotation.AfterReturning;
 9// import org.aspectj.lang.annotation.Around;
10
11@Aspect
12public class Logging {
13
14   /*
15      A pointcut named "selectAll" is defined using `@Pointcut` 
16                  to target *all methods* 
17                     within the package "com.example.aop" and its sub-packages. 
18      the method `selectAll()` is just a signature
19   */
20   @Pointcut("execution(* com.example.aop.*.*(..))")
21   private void selectAll(){}
22
23   @Before("selectAll()")
24   public void beforeAdvice(){
25      System.out.println("`beforeAdvice()` invoked.");
26   }
27
28   @After("selectAll()")
29   public void afterAdvice(){
30      System.out.println("`afterAdvice()` invoked.");
31   }
32
33   @AfterReturning(pointcut = "selectAll()", returning = "retVal")
34   public void afterReturningAdvice(Object retVal) {
35      System.out.println("[Success] `afterReturningAdvice()` reads return value: " + retVal.toString() );
36      System.out.println("------");
37   }
38
39   @AfterThrowing(pointcut = "selectAll()", throwing = "exception")
40   public void afterThrowingAdvice(Exception exception){
41      System.out.println("[FAILURE] `afterThrowingAdvice()` detects Exception: " + exception.toString());
42      System.out.println("------");
43   }
44}

Code 6-1-AOP(a) defines an AspectJ aspect named Logging, which contains advice methods (@Before, @After, @AfterReturning, @AfterThrowing) to log messages before and after the execution of all methods in the package "com.example.aop" and its sub-packages, as well as handling method return values and exceptions.

Note: in XML Schema based AOP, we use <aop:pointcut id = "POINTCUT_NAME" expression = "POINTCUT_EXPRESSION"; in AspectJ based AOP, we use @Pointcut("POINTCUT_EXPRESSION") annotation on an empty method called private void POINTCUT_NAME(){}.

The expected output for Code 6-1-AOP(a), Code 6-1(b, c), and Code 6-2 should be:

 1`beforeAdvice()` invoked.
 2Class method `getName()` gets `name` = Tom
 3[Success] `afterReturningAdvice()` reads return value: Tom
 4------
 5`afterAdvice()` invoked.
 6`beforeAdvice()` invoked.
 7Class method `getAge()` gets `age` = 83
 8[Success] `afterReturningAdvice()` reads return value: 83
 9------
10`afterAdvice()` invoked.
11`beforeAdvice()` invoked.
12Class method `throwsException()` will throw 'IllegalArgumentException'
13[FAILURE] `afterThrowingAdvice()` detects Exception: java.lang.IllegalArgumentException
14------
15`afterAdvice()` invoked.
16Exception in thread "main" java.lang.IllegalArgumentException
17	at com.example.aop.Student.throwsException(Student.java:26)
18   (Omit the rest Exception message...)

And if we want to target the Pointcut to Student.getName() method only, we can modify Code 6-1-AOP(a) to Code 6-1-AOP-selectGetName(a):

Code 6-1-AOP-selectGetName(a). "Logging.java"

 1package com.example.aop;
 2
 3import org.aspectj.lang.annotation.Aspect;
 4import org.aspectj.lang.annotation.Pointcut;
 5import org.aspectj.lang.annotation.Before;
 6import org.aspectj.lang.annotation.After;
 7import org.aspectj.lang.annotation.AfterThrowing;
 8import org.aspectj.lang.annotation.AfterReturning;
 9// import org.aspectj.lang.annotation.Around;
10
11@Aspect
12public class Logging {
13
14   /*
15      A pointcut named "selectGetName" using an expression
16         to target the `getName()` method of the `Student` class.
17
18      Note: `(..)` is a wildcard that 
19               represents zero or more arguments of any type.
20   */
21   @Pointcut("execution(* com.example.aop.Student.getName(..))")
22   private void selectGetName(){}
23
24   @Before("selectGetName()")
25   public void beforeAdvice(){
26      System.out.println("`beforeAdvice()` invoked.");
27   }
28
29   @After("selectGetName()")
30   public void afterAdvice(){
31      System.out.println("`afterAdvice()` invoked.");
32   }
33
34   @AfterReturning(pointcut = "selectGetName()", returning = "retVal")
35   public void afterReturningAdvice(Object retVal) {
36      System.out.println("[Success] `afterReturningAdvice()` reads return value: " + retVal.toString() );
37      System.out.println("------");
38   }
39
40   @AfterThrowing(pointcut = "selectGetName()", throwing = "exception")
41   public void afterThrowingAdvice(Exception exception){
42      System.out.println("[FAILURE] `afterThrowingAdvice()` detects Exception: " + exception.toString());
43      System.out.println("------");
44   }
45}

Code 6-1-AOP-selectGetName(a) changes pointcut to target only on method Student.getName().

The expected output for Code 6-1-AOP-selectGetName(a), Code 6-1(b, c), and Code 6-2 should be:

 1`beforeAdvice()` invoked.
 2Class method `getName()` gets `name` = Tom
 3[Success] `afterReturningAdvice()` reads return value: Tom
 4------
 5`afterAdvice()` invoked.
 6Class method `getAge()` gets `age` = 83
 7Class method `throwsException()` will throw 'IllegalArgumentException'
 8Exception in thread "main" java.lang.IllegalArgumentException
 9	at com.example.aop.Student.throwsException(Student.java:24)
10	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
11	(Omit the rest message...)

* This blog was last updated on 2023-07-19 00:00