Abdulcelil Cercenazi
Posted on February 22, 2022
Meet Mr. Beans 🥸
A Bean is a Java object. That's it.
Java objects are units that hold data and logic which make our application do its work. Objects can depend on other objects (dependencies) to complete a certain task.
In Java, creating dependencies is usually done in the constructor of the class using the new
keyword.
Example of Traditional Bean Creation 🧑🔧
Say we have a DownloadService
that depends on a ConnectionService
.
The ConnectionService
needs an address and a password to set up its connection.
public class ConnectionService {
public ConnectionService(String address, String password)
{
// use address and password to set up the connection
}
public void connect()
{
// connecting things
}
}
Now, if the DownloadService
wanted to use the ConnectionService
it has to know how to construct it. It has to get the password, the address and call the ConnectionService
constructor.
public class DownloadService {
ConnectionService connectionService;
public DownloadService()
{
String password = getPassword();
String address = getAddress();
connectionService = new ConnectionService(address, password);
}
public void download()
{
connectionService.connect();
// download things
}
private String getAddress()
{
return "address";
}
private String getPassword()
{
return "pass";
}
}
This is all fine and dandy, but what if we had 10 or more classes that use the ConnnectionService
? Then they all have to know how to get the password, the address and call the constructor.
This is certainly not maintainable and hard to follow around.
Inversion of Control (IoC) to the Rescue 🏋️♂️
In the software development world, there is a well-known rule called the Single Responsibility Principle. It urges developers to assign a single responsibility to a method or a class.
So in our above example, the DownloadService
should not know how to construct the complicated ConnectionService
, it should just use it.
What the IoC pattern suggests is that the DownloadService
service should invert the responsibility of creating the ConnectionService
to some other entity.
This entity could be, for example, a factory class.
public class ConnectionFactory {
public ConnectionService createConnectionService()
{
String password = getPassword();
String address = getAddress();
return new ConnectionService(address, password);
}
private String getAddress()
{
return "address";
}
private String getPassword()
{
return "pass";
}
}
Then in the DownloadService
, we call the ConnectionFactoy
which knows how to construct a ConnectionService
object.
public class DownloadService {
ConnectionService connectionService;
public DownloadService()
{
connectionService = new ConnectionFactory().createConnectionService();
}
public void download()
{
connectionService.connect();
// download things
}
}
IoC in Spring ☘️
Spring offers a helping hand using the IoC Spring Container. All we have to do is describe our beans to the container in a configuration class and it will give them to us whenever we want.
Let's test this with our above code:
public class ConnectionService {
public ConnectionService(String address, String password)
{
// use address and password to setup the connection
}
public void connect()
{
System.out.println("Connecting...");
// connecting things
}
}
Now, let's introduce the configuration class which will provide us with the ConnectionService
bean.
@Configuration
public class AppConfiguration {
@Bean
ConnectionService connectionService()
{
String address = getAddress();
String password = getPassword();
return new ConnectionService(address, password);
}
private String getAddress()
{
return "address";
}
private String getPassword()
{
return "pass";
}
}
Two new things to note here:
- The
@Configuration
annotation indicates that the class has@Bean
definition methods. So Spring container can process the class and generate Spring Beans to be used in the application. - The
@Bean
declares the object returned from the method as a Bean to be managed by Spring IoC container.
Finally, let's check out our DownloadService
class which will have the ConnectionService
Bean injected in.
@Component
public class DownloadService {
private final ConnectionService connectionService;
@Autowired
public DownloadService(ConnectionService connectionService)
{
this.connectionService = connectionService;
}
public void download()
{
connectionService.connect();
// download things
}
}
An important detail to pay attention to here is the @Component
and @Autowired
annotations.
The @Component
will tell Spring to manage the DownloadService
as a Bean. Doing this, Spring will create the class and inject any Beans it depends on (ConnectionService
in this example) into the class.
The @Autowired
will tell Spring to get the objects declared in the constructor parameters from the Spring IoC container, which in our case was declared in the AppConfiguration
class.
Now, that was a good move to have the IoC principle implemented in our system. Spring, however, offers an even more convenient way to handle IoC.
We can remove the AppConfiguration
class and simply have the logic of instantiating the ConnectionService
inside of it and have Spring create the Bean and make it ready for use.
@Component
public class ConnectionService {
public ConnectionService()
{
// use address and password to setup the connection
String address = getAddress();
String password = getPassword();
}
public void connect()
{
// connecting things
}
private String getAddress()
{
return "address";
}
private String getPassword()
{
return "pass";
}
}
Note the @Component
annotation which tells Spring to treat this class as a Bean and have it in the Spring IoC ready for the DownloadService
to use.
Beans' Life Cycle 🌕 🌖 🌗 🌘 🌑
Spring Bean factory manages the lifecycle of beans created through the Spring container and controls the creation and destruction of the beans.
Let's explore the phases of a Spring Bean's life cycle:
-
Bean definition.
- It's defined by the
@Bean
annotation over methods in the configuration file (the one annotated with@Configuration
file). - Or using the
@Component
annotation and its derivatives (@Service
,@Controller
, etc..) over classes.
- It's defined by the
-
Bean Instantiation.
- Spring instantiate bean objects and loads the objects into the ApplicationContext.
-
Populating Bean properties.
- Spring scans the beans and sets relevant properties as id, scope, and the default values.
-
Pre-Initialization
- Spring’s BeanPostProcessors do their work.
-
AfterPropertiesSet
- Spring executes the
afterPropertiesSet()
methods of the beans which implement InitializingBean.
- Spring executes the
-
Custom Initialization
- Spring triggers any initialization methods that we define.
-
Post-initialization
- Spring’s BeanPostProcessors do their work for one more time.
-
Ready
- The Bean is ready and injected into all other dependencies.
-
Pre-Destroy
- Spring triggers
@PreDestroy
annotated methods in this phase
- Spring triggers
-
Destroy
- Spring executes the destroy() methods of DisposableBean implementations
-
Custom Destruction
- Spring triggers any custom destruction methods that we've defined.
Conclusion ✍️
The principle of Inversion of Control helps us write clean, maintainable code.
This can be achieved by separating the logic of creating complex objects into a class of their own (separation of concerns).
Spring offers a neat solution using Spring's ApplicationContext
and Spring's IoC container. The container treats our objects as Beans which have life cycles managed by Spring and custom methods that we can introduce.
Posted on February 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.