Spring Framework Beans: Full Mode and Lite Mode
Introduction
In this article, we are going to delve deep into the details of full-mode and lite-mode beans. As we delve into these details, we will also explore Proxy mechanisms directly associated with full mode and lite mode. This exploration will provide us with valuable information not only about bean modes but also about mechanisms such as transaction management and security.
To gain a comprehensive understanding of Full mode and Lite mode, we must begin with fundamental knowledge.
Firstly, we will delve into the specifics of Inversion of Control (IoC). Secondly, we’ll proceed with defining a bean, exploring the common methods used to define them in Java code. Following this, our focus will shift to Proxy mechanisms and their applications within the Spring Framework. As we gather the necessary fundamental information, our journey will then progress to Full mode and Lite mode.
Let’s start with some fundamental knowledge;
What is IoC?
According to Spring Framework’s documentation;
It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
In essence, a container component manages the life cycle of our dependencies. We declare our required dependencies using various injection methods. Notably, the Spring Framework offers support for three distinct injection methods: constructor injection, setter injection, and field injection. For more detailed information about these methods, please refer to this page.
The IoC mechanism scans the source code and identifies the potential bean candidates immediately after we execute the program. Essentially, it comprehensively manages this process.
What is a Bean?
Let’s look at Spring’s official bean definition;
A bean is an object that is instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application.
Put simply, a bean is an object created by the IoC mechanism and stored within the IoC container. In the Spring Framework, this IoC container is referred to as the Application Context.
How does IoC mechanism create beans?
Upon execution, the IoC mechanism within the Spring Framework scans a specific section of the code base, identifying potential beans and storing their class-level information as Bean Definitions.
Actually, it’s quite a complex process, but this level of understanding should suffice for now.
However, the IoC mechanism requires certain identifiers to recognize a class as a bean source. There are various methods to designate a class as a bean source. You can use an XML file for this purpose, or programmatically specify to the IoC mechanism that certain classes are indeed beans. Nevertheless, the most prevalent approach in Spring Framework to declare a class as a bean involves using specific annotations. Let’s take a look at the following code snippet:
@Component
public class Audi {}
This is a very simple Java class with no properties or methods. However, by using the @Component annotation at the top of the class, we effectively designate it as a bean. Thus, the IoC mechanism extracts necessary information from its source code to include it as a bean in the Application Context. Additionally, there are other annotations such as @Service, @Controller, or @Repository that can be utilized to mark a class as a bean source. These annotations are all extensions of the @Component annotation. Essentially, everything begins with the @Component annotation when declaring a class as a bean source.
Additionally, a single class has the potential to serve as the source for multiple beans. Now, let’s examine the following code snippet:
@Configuration
public class CarFactory {
@Bean
public Audi audi() {
return new Audi();
}
@Bean
public MercedesBenz mercedesBenz() {
return new MercedesBenz();
}
@Bean
public Renault renault() {
return new Renault();
}
}
Let’s assume we’ve already written Audi, MercedesBenz, and Renault as straightforward Java classes without using @Component or any of its derivatives. However, the CarFactory class serves as a source for multiple beans.
This method is the most commonly used approach to define beans within the source code using the Spring Framework. Now that we’ve covered adequate fundamental knowledge, let’s proceed to the core of this article: Proxy Mechanisms.
What is a Proxy?
Actually, we’re not discussing web proxies here. Don’t let the previous image divert your attention.
Although it’s not explicitly mentioned in the official Spring documentation, Proxy Mechanisms are an implementation of a software engineering design pattern. This pattern is employed to add an additional layer of control, security, or functionality to objects without altering their inherent behavior.
This design pattern is extensively used within various areas of the Spring Framework, such as web security and transaction management. As they constitute critical components of software, understanding this design pattern is valuable, even beyond comprehending Full mode and Lite mode.
Let’s proceed to examine the upcoming code snippet.
public class Pojo {
public void foo() {
// some logic
}
}
Now, here we have a very simple vanilla Java class. Let’s consider creating an object from this class and storing it in a reference variable. Take a look at the following code snippet:
public class Start {
public static void main(String[] args) {
Pojo pojo = new Pojo();
pojo.foo();
}
}
Without applying the Proxy Design Pattern, it involves a direct method call. In simpler terms, we directly invoke the foo() method on the ‘pojo’ reference. The sequence of this call can be observed in the forthcoming figure.
However, when we apply the Proxy design pattern, the process takes an interesting turn. Let’s assume that we’ve effectively implemented the Proxy design pattern — this can be achieved through various methods. In the Spring Framework, it’s commonly accomplished via the Aspect-Oriented Programming (AOP) approach.
If we were to call the foo() method on the same ‘pojo’ reference after implementing the Proxy design pattern, the sequence of this call would appear as depicted in the forthcoming figure.
As we intercept method calls through a proxy class, we actually incorporate additional functionality to the original Pojo class. This proxy class is dynamically added at runtime. Consequently, we gain the ability to manipulate method arguments, modify return values, or even block the caller under specific circumstances. Furthermore, we have the capability to suppress any exceptions that might arise during the execution flow of the actual foo() method. Remarkably, this is accomplished without altering any part of the original Pojo class — it remains unchanged.
As mentioned earlier, there are multiple methods to implement this design pattern. Now, let’s proceed to explore the available proxies in Spring.
What are the available Proxies in Spring?
As per the official Spring Documentation, there exist two approaches to implement this design pattern. Spring employs Aspect-Oriented Programming (AOP) for proxy mechanisms, and Spring AOP utilizes either JDK dynamic proxies or CGLIB to generate the proxy for a specified target object.
JDK Dynamic Proxy
It’s the official proxy mechanism provided by Java. Using the JDK Reflection API, it allows dynamic implementations of interfaces at runtime. In essence, to proxy a class using this mechanism, the class must implement at least one interface. Moreover, the methods that can be proxied are constrained by the structure of the interface(s) — only methods defined in the interface(s) can be proxied. To illustrate this concept better, take a look at the following figure:
CGLIB Proxy
It’s worth noting that while JDK Dynamic Proxy is an inherent feature in Java, CGLIB is an open-source project offering proxies for classes. In essence, there’s no need to implement an interface to manage a class with a proxy using CGLIB. However, similar to JDK Dynamic Proxy, it does come with certain limitations. For instance, it cannot intercept classes that are declared as final. The same limitation applies to final methods as well. To grasp this concept better, take a look at the following figure:
As you recall, we have learned how to define beans using annotations like @Bean and @Component. The @Bean annotation holds particular importance, especially concerning Full Mode and Lite Mode. Let’s get started!
What is Full Mode?
It is when you define your beans as methods in a class that is annotated by @Configuration annotation. See the following code snippet;
@Configuration
public class CarFactory {
@Bean
public Car car() {
return new Car(engine());
}
@Bean
public V12Engine v12Engine() {
return new V12Engine(engineQualityEngineer());
}
@Bean
public V6Engine v6Engine() {
return new V6Engine(engineQualityEngineer());
}
@Bean
public EngineQualityEngineer engineQualityEngineer() {
return new EngineQualityEngineer();
}
}
In this scenario, a quality engineer is required to validate the assembling stage for a fully configured engine, and an engine is necessary for a car to be built. Therefore, there are inter-dependencies among these beans. In this particular situation, the aim is to avoid assigning an engineer to each assembly process, opting instead to assign all assembly processes to the same engineer. This strategy aims to save the company money and streamline work efficiency.
As you can see, all methods annotated with the @Bean annotation reside within a @Configuration class, contributing to the creation of full mode beans. There are several implications in this scenario.
It’s important to recall that a bean is generated, assembled, and managed by the IoC container. Therefore, IoC handles the creation of all beans and subsequently injects them into their respective methods.
A crucial point here is that even if we invoke the engineQualityEngineer() method from both the v12Engine() and v6Engine() methods, the resulting EngineQualityEngineer object remains the same. This object is managed by IoC, and it achieves this by utilizing the CGLIB Proxy mechanism. Essentially, in Full Mode, it becomes possible to declare interdependent bean dependencies, and IoC reliably provides the necessary dependencies. This approach helps prevent many problems that could lead to elusive bugs, making it the recommended way.
What is Lite Mode?
You can indeed declare beans within a class annotated with @Component or even in a plain Java class. However, it’s crucial to exercise caution with this mode, as it’s beneficial for only very specific scenarios — such as adding external functionality to a class that originally lacks this functionality. It’s worth noting that this approach contradicts the Single Responsibility Principle (SRP) of SOLID. An additional point to highlight is that in this mode, there’s no CGLIB sub-classing.
In this particular scenario, the method annotated with @Bean is a simple factory method. Each invocation of this method returns a new instance. Let’s take a final look at the following code:
public class CarFactory {
@Bean
public Car car() {
return new Car(engine());
}
@Bean
public V12Engine v12Engine() {
return new V12Engine(engineQualityEngineer());
}
@Bean
public V6Engine v6Engine() {
return new V6Engine(engineQualityEngineer());
}
@Bean
public EngineQualityEngineer engineQualityEngineer() {
return new EngineQualityEngineer();
}
}
In the given scenario, since there is no @Configuration annotation at the class level, managing inter-dependencies becomes impossible as we lack a proxy mechanism. Consequently, our company will assign a dedicated quality engineer for each engine, leading to a significant waste of time and money.
Conclusion
In this article, we delved deeply into the details of Full Mode and Lite Mode for beans. Throughout our exploration, we gained fundamental knowledge about Inversion of Control, Beans, Proxy Mechanism, and more. It was a worthwhile journey.
Thank you for taking the time to read my article, and I hope to see you soon in another one! Goodbye for now!