From the previous episode:
In the previous episode, we explored different ways to configure and instantiate the Spring Context, which is the IoC container responsible for the creation and lifecycle of the Spring Beans.
What is a spring Bean?
Spring Beans are the backbone of the Spring Framework, serving as the building blocks for creating applications in a modular and efficient manner.
Spring Bean = Any class that we want to be managed by Spring
In this episode, we will see how we can declare beans using Java annotations. To make it easier to understand, let’s build a simple project. The final version of the project is available here.
Creating a new project
We will start with a blank Maven project.
package com.cristianrita.springbeans;
import com.cristianrita.springbeans.config.MyApplicationConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyApplicationConfig.class);
}
}
Then we will create a new AnnotationConfigApplicationContext
instance. As you can see, I am passing a class reference to the constructor. MyApplicationConfig
is a configuration class that Spring will use to configure the context. Let's create it.
We need spring-context
dependency added to the pom.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring-beans-example</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.4</version>
</dependency>
</dependencies>
</project>
Configuring the context
package com.cristianrita.springbeans.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyApplicationConfig {
}
A configuration class has to be annotated with @Configuration
. For now, we will leave it like this.
Do you remember the demo we did in the first episode? Let's try to implement the EmailSender using Spring. Because it contains business logic, we will rename the class to EmailSerivce. The primary purpose of a service is to encapsulate and organize business-related functionality, making it reusable and maintainable, and to allow for separation of concerns in a typical 3-tier architecture.
It is good practice to make it clear that this class is a service that contains business logic.
package com.cristianrita.springbeans.services;
public interface Sender {
void send(String to, String message);
}
package com.cristianrita.springbeans.services;
import org.springframework.stereotype.Component;
@Component
public class EmailService implements Sender {
public EmailService() {
System.out.println("EmailService created!");
}
public void send(String to, String message) {
System.out.println("Sending email to " + to + " with message " + message);
}
}
To tell Spring that EmailService
should be registered as a bean, we annotate it with @Component
. This is all we need to make EmailService a bean.
Component scanning
In a complex app that has thousands of classes, it would not be efficient if Spring scans each one to look for the @Component
annotations. We need to instruct Spring where to search for beans. This is where @ComponentScan
annotation comes into the picture.
package com.cristianrita.springbeans.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.cristianrita.springbeans.services"})
public class MyApplicationConfig {
}
We annotated the config class with @ComponentScan
. @ComponentScan
takes a list of packages that Spring will scan for Components. It can also take a list of individual classes using the basePackageClasses
property, but in practice, we don't need such granularity.
Let's go back to the main method and get an instance of the EmailService bean.
package com.cristianrita.springbeans;
import com.cristianrita.springbeans.config.MyApplicationConfig;
import com.cristianrita.springbeans.services.EmailService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyApplicationConfig.class);
EmailService emailService = applicationContext.getBean(EmailService.class);
emailService.send("test@email.com", "Hello World!");
}
}
We can now run the app. We should see the following message printed on the STDOUT:
EmailService created!
Sending email to test@email.com with message Hello World!
Quick recap:
We created an instance of
AnnotationConfigApplicationContext
;We specified that
MyApplicationConfig
is a configuration class that Spring will use to config the context;We annotated the EmailService class with
@Component
, telling Spring that this class should be registered as a bean;Using
@ComponentScan
annotation, we specified the packages where Sping should look for components;When we ran the application, Spring created the EmailService bean by calling the
EmailService()
constructor.
Eager vs. Lazy initialization
By default, all the beans are eagerly initialized at startup. This is helpful because if there is any error in the configuration, we will get an exception when the application starts and the context is created.
The drawback? If Spring has to create a lot of beans, eager initialization will affect the startup time. To overcome this, we can annotate a component with @Lazy
. This way, Spring will create an instance of the bean only when we need it.
To see how it works let's do the following experiment:
package com.cristianrita.springbeans;
import com.cristianrita.springbeans.config.MyApplicationConfig;
import com.cristianrita.springbeans.services.EmailService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyApplicationConfig.class);
}
}
We removed any reference to the EmailService
bean. If we run the application now we will see this:
EmailService created!
So Spring initialized the bean eagerly, although we didn't use it.
On the other hand, we can annotate the EmailService
with @Lazy
.
@Component
@Lazy
public class EmailService implements Sender {
public EmailService() {
System.out.println("EmailService created!");
}
@Override
public void send(String to, String message) {
System.out.println("Sending email to " + to + " with message " + message);
}
}
This time nothing will be printed to STDOUT. The bean is not created until we need it.
package com.cristianrita.springbeans;
import com.cristianrita.springbeans.config.MyApplicationConfig;
import com.cristianrita.springbeans.services.EmailService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApplication {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyApplicationConfig.class);
System.out.println("Application started!");
Thread.sleep(5000);
EmailService emailService = applicationContext.getBean(EmailService.class);
emailService.send("test@email.com", "Hello World!");
}
}
The moment we ask for the bean, Spring will initialize it.
Stereotype annotations
Stereotype annotations are a set of annotations that provide a convenient way to categorize and describe the components in a Spring application. They are used to indicate the role of a component in the application.
The following are the most commonly used stereotype annotations in Spring:
@Component
: a generic stereotype annotation that can be used for any component in the application;@Service
: used to indicate that a class is a service component, responsible for implementing the business logic;@Repository
: used to indicate that a class is a repository component, responsible for accessing the data store;@Controller
: used to indicate that a class is a controller component, responsible for handling user requests and returning the response.
So instead of using a generic @Component
annotation, we can use the @Service
annotation for EmailService. From Spring's point of view, there is no difference between the two. It's just a way to make it clear to other developers that the bean is a service or a controller.
Programming to an interface
Because the EmailService
implements the Sender
interface, we can modify the code and ask Spring for a bean that implements it.
package com.cristianrita.springbeans;
import com.cristianrita.springbeans.config.MyApplicationConfig;
import com.cristianrita.springbeans.services.Sender;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApplication {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyApplicationConfig.class);
System.out.println("Application started!");
Thread.sleep(5000);
Sender emailService = applicationContext.getBean(Sender.class);
emailService.send("test@email.com", "Hello World!");
}
}
Spring is smart enough to figure out that there is a bean that implements the Sender interface and returns an instance of the EmailService. This way, we don't depend on an implementation but on an interface.
However, if more than one bean implements the interface, Spring doesn't know which bean to instantiate, so it returns an exception. We will see how to handle this case in a future episode.
Conclusion
In conclusion, creating beans using Java annotations in the Spring framework is a convenient and efficient way to manage components in a Spring application. The use of annotations, such as @Component
, @Service
, @Repository
, and @Controller
, allows for automatic detection and registration of components in the application context, reducing the amount of manual configuration required. Additionally, the use of stereotype annotations makes it easier to categorize and describe the role of components in the application, promoting a clear separation of concerns. Overall, using Java annotations is a powerful tool for managing and maintaining the components in a Spring application and is a must-know for any Spring developer.
waiting for next episode