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
@Configurationannotation indicates that the class has@Beandefinition methods. So Spring container can process the class and generate Spring Beans to be used in the application. - The
@Beandeclares 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
@Beanannotation over methods in the configuration file (the one annotated with@Configurationfile). - Or using the
@Componentannotation 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
@PreDestroyannotated 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.
Top comments (0)