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.
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 setCLASSPATH
.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)@Service
marks 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 @PostConstruct
and @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 theref
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.
execution
(primary Spring PCD): matches method execution join pointswithin
: limits matching to join points of certain typesthis
: limits matching to join points where the bean reference is an instance of the given type (when Spring AOP creates a CGLIB-based proxy).target
: limits matching to join points where the target object is an instance of the given type (when a JDK-based proxy is created).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 (
*
) inexecution(*
is a wildcard character that matches any return type of the intercepted method, e.g.,void
,Integer
,String
, etc. - the second asterisk (
*
) incom.example.aop.*
is a wildcard character that matches any class in thecom.example.aop
package. - the dot and asterisk (
.*
) incom.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. (TheretVal
parameter contains the value returned by the target method.)afterThrowingAdvice(Exception exception)
method will be executed if the target method throws an exception. (Theexception
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...)