Simplifying application development using Spring and its pattern
Developing an enterprise application using the traditional Java platform has a lot of limitations when it comes to organizing the basic building blocks as individual components for reusability in your application. Creating reusable components for basic and common functionality is best design practice, so you cannot ignore it. To address the reusability problem in your application, you can use various design patterns, such as the Factory pattern, Abstract Factory pattern, Builder pattern, Decorator pattern, and Service Locator pattern, to compose the basic building blocks into a coherent whole, such as class and object instances, to promote the reusability of components. These patterns address the common and recursive application problems. Spring Framework simply implements these patterns internally, providing you with an infrastructure to use in a formalized way.
There are lots of complexities in enterprise application development, but Spring was created to address these, and makes it possible to simplify the process for developers. Spring isn't only limited to server-side development--it also helps simplifies things regarding building projects, testability, and loose coupling. Spring follows the POJO pattern, that is, a Spring component can be any type of POJO. A component is a self-contained piece of code that ideally could be reused in multiple applications.
Since this book is focused on all design patterns that are adopted by the Spring Framework to simplify Java development, we need to discuss or at least provide some basic implementation and consideration of design patterns and the best practices to design the infrastructure for enterprise application development. Spring uses the following strategies to make java development easy and testable:
- Spring uses the power of the POJO pattern for lightweight and minimally invasive development of enterprise applications
- It uses the power of the dependency injection pattern (DI pattern) for loose coupling and makes a system interface oriented
- It uses the power of the Decorator and Proxy design pattern for declarative programming through aspects and common conventions
- It uses the power of the Template Design pattern for eliminating boilerplate code with aspects and templates
In this chapter, I'll explain each of these ideas, and also show concrete examples of how Spring simplifies Java development. Let's start with exploring how Spring remains minimally invasive by encouraging POJO-oriented development by using the POJO pattern.
Using the power of the POJO pattern
There are many other frameworks for Java development that lock you in by forcing you to extend or implement one of their existing classes or interfaces; Struts, Tapestry, and earlier versions of EJB had this approach. The programming model of these frameworks is based on the invasive model. This makes it harder for your code to find bugs in the system, and sometimes it will render your code unintelligible. However, if you are working with Spring Framework, you don't need to implement or extend its existing classes and interfaces, so this is simply POJO-based implementation, following a non-invasive programming model. It makes it easier for your code to find bugs in the system, and keeps the code understandable.
Spring allows you to do programming with very simple non Spring classes, which means there is no need to implement Spring-specific classes or interfaces, so all classes in the Spring-based application are simply POJOs. That means you can compile and run these files without dependency on Spring libraries; you cannot even recognize that these classes are being used by the Spring Framework. In Java-based configuration, you will use Spring annotations, which is the worst case of the Spring-based application.
Let's look at this with the help of the following example:
package com.packt.chapter1.spring; public class HelloWorld { public String hello() { return "Hello World"; } }
The preceding class is a simple POJO class with no special indication or implementation related to the framework to make it a Spring component. So this class could function equally well in a Spring application as it could in a non-Spring application. This is the beauty of Spring's non-invasive programming model. Another way that Spring empowers POJO is by collaborating with other POJOs using the DI pattern. Let's see how DI works to help decouple components.
Injecting dependencies between POJOs
The term dependency injection is not new-it is used by PicoContainer. Dependency injection is a design pattern that promotes loose coupling between the Spring components--that is, between the different collaborating POJOs. So by applying DI to your complex programming, your code will become simpler, easier to understand, and easier to test.
In your application, many objects are working together for a particular functionality as per your requirement. This collaboration between the objects is actually known as dependency injection. Injecting dependency between the working components helps you to unit test every component in your application without tight coupling.
In a working application, what the end user wants is to see the output. To create the output, a few objects in the application work together and are sometimes coupled. So when you are writing these complex application classes, consider the reusability of these classes and make these classes as independent as possible. This is a best practice of coding that will help you in unit testing these classes independently.
How DI works and makes things easy for development and testing
Let's look at DI pattern implementation in your application. It makes things easy to understand, loosely coupled, and testable across the application. Suppose we have a simple application (something more complex than a Hello World example that you might make in your college classes). Every class is working together to perform some business task and help build business needs and expectations. That means that each class in the application has its measure of responsibility for a business task, together with other collaborating objects (its dependencies). Let's look at the following image. This dependency between the objects can create complexity and tight coupling between the dependent objects:
The TransferService component is traditionally dependent on two other components: TransferRepository and AccountRepository
A typical application system consists of several parts working together to carry out a use case. For example, consider the TransferService
class, shown next.
TransferService
using direct instantiation:
package com.packt.chapter1.bankapp.transfer; public class TransferService { private AccountRepository accountRepository; public TransferService () { this.accountRepository = new AccountRepository(); } public void transferMoney(Account a, Account b) { accountRepository.transfer(a, b); } }
The TransferService
object needs an AccountRepository
object to make money transfer from account a
to account b
. Hence, it creates an instance of the AccountRepository
object directly and uses it. But direct instantiation increases coupling and scatters the object creation code across the application, making it hard to maintain and difficult to write a unit test for TransferService
, because, in this case, whenever you want to test the transferMoney()
method of the TransferService
class by using the assert
to unit test, then the transfer()
method of the AccountRepository
class is also called unlikely by this test. But the developer is not aware about the dependency of AccountRepository
on the TransferService
class; at least, the developer is not able to test the transferMoney()
method of the TransferService
class using unit testing.
In enterprise applications, coupling is very dangerous, and it pushes you to a situation where you will not be able to do any enhancement in the application in the future, where any further changes in such an application can create a lot of bugs, and where fixing these bugs can create new bugs. Tightly coupled components are one of the reasons for major problems in these applications. Unnecessary tightly coupled code makes your application non-maintainable, and as time goes by, its code will not be reused, as it cannot be understood by other developers. But sometimes a certain amount of coupling is required for an enterprise application because completely uncoupled components are not possible in real-world cases. Each component in the application has some responsibility for a role and business requirement, to the extent that all components in the application have to be aware of the responsibility of the other components. That means coupling is necessary sometimes, but we have to manage the coupling between required components very carefully.
Using factory helper pattern for dependent components
Let's try another method for dependent objects using the Factory pattern. This design pattern is based on the GOF factory design pattern to create object instances by using a factory method. So this method actually centralizes the use of the new operator. It creates the object instances based on the information provided by the client code. This pattern is widely used in the dependency injection strategy.
TransferService
using factory helper:
package com.packt.chapter1.bankapp.transfer; public class TransferService { private AccountRepository accountRepository; public TransferService() { this.accountRepository = AccountRepositoryFactory.getInstance("jdbc"); } public void transferMoney(Account a, Account b) { accountRepository.transfer(a, b); } }
In the preceding code, we use the Factory pattern to create an object of AccountRepository
. In software engineering, one of the best practices of application design and development is program-to-interface (P2I). According to this practice, concrete classes must implement an interface that is used in the client code for the caller rather than using a concrete class. By using P2I, you can improve the preceding code. Therefore, we can easily replace it with a different implementation of the interface with little impact on the client code. So programming-to-interface provides us with a method involving low coupling. In other words, there is no direct dependency on a concrete implementation leading to low coupling. Let's look at the following code. Here, AccountRepository
is an interface rather than a class:
public interface AccountRepository{ void transfer(); //other methods }
So we can implement it as per our requirement, and it is dependent upon the client's infrastructure. Suppose we want an AccountRepository
during the development phase with JDBC API. We can provide a JdbcAccountRepositry
concrete implementation of the AccountRepositry
interface, as shown here:
public class JdbcAccountRepositry implements AccountRepositry{ //...implementation of methods defined in AccountRepositry // ...implementation of other methods }
In this pattern, objects are created by factory classes to make it easy to maintain, and this avoids scattering the code of object creation across other business components. With a factory helper, it is also possible to make object creation configurable. This technique provides a solution for tight coupling, but we are still adding factory classes to the business component for fetching collaborating components. So let's see the DI pattern in the next section and look at how to solve this problem.
Using DI pattern for dependent components
According to the DI pattern, dependent objects are given their dependencies at the time of the creation of the objects by some factory or third party. This factory coordinates each object in the system in such a way that each dependent object is not expected to create their dependencies. This means that we have to focus on defining the dependencies instead of resolving the dependencies of collaborating objects in the enterprise application. Let's look at the following image. You will learn that dependencies are injected into the objects that need them:
Dependency injection between the different collaborating components in the application
To illustrate this point, let's look at TransferService
in the next section--a TransferService
has dependency with AccountRepository
and TransferRepository
. Here, TransferService
is capable of transferring money by any kind implementation of TransferRepository
, that is, we can either use JdbcTransferRepository
or JpaTransferRepository
, depending on whichever comes along according to the deployment environment.
TransferServiceImpl
is flexible enough to take on any TransferRepository
it's given:
package com.packt.chapter1.bankapp; public class TransferServiceImpl implements TransferService { private TransferRepository transferRepository; private AccountRepository accountRepository; public TransferServiceImpl(TransferRepository transferRepository, AccountRepository accountRepository) { this.transferRepository = transferRepository;//TransferRepository is injected this.accountRepository = accountRepository; //AccountRepository is injected } public void transferMoney(Long a, Long b, Amount amount) { Account accountA = accountRepository.findByAccountId(a); Account accountB = accountRepository.findByAccountId(b); transferRepository.transfer(accountA, accountB, amount); } }
Here you can see that TransferServiceImpl
doesn't create its own repositories implementation. Instead, we have given the implementation of repositories at the time of construction as a constructor argument. This is a type of DI known as constructor injection. Here we have passed the repository interface type as an argument of the constructor. Now TransferServiceImpl
could use any implementation of repositories, either JDBC, JPA, or mock objects. The point is that TransferServiceImpl
isn't coupled to any specific implementation of repositories. It doesn't matter what kind of repository is used to transfer an amount from one account to another account, as long as it implements the repositories interfaces. If you are using the DI pattern of the Spring Framework, loose coupling is one of the key benefits. The DI pattern always promotes P2I, so each object knows about its dependencies by their associated interface rather than associated implementation, so the dependency can easily be swapped out with another implementation of that interface instead of changing to its dependent class implementation.
Spring provides support for assembling such an application system from its parts:
- Parts do not worry about finding each other
- Any part can easily be swapped out
The method for assembling an application system by creating associations between application parts or components is known as wiring. In Spring, there are many ways to wire collaborating components together to make an application system. For instance, we could use either an XML configuration file or a Java configuration file.
Now let's look at how to inject the dependencies of TransferRepository
and AccountRepository
into a TransferService
with Spring:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="transferService" class="com.packt.chapter1.bankapp.service.TransferServiceImpl"> <constructor-arg ref="accountRepository"/> <constructor-arg ref="transferRepository"/> </bean> <bean id="accountRepository" class="com. packt.chapter1.bankapp.repository.JdbcAccountRepository"/> <bean id="transferRepository" class="com. packt.chapter1.bankapp.repository.JdbcTransferRepository"/> </beans>
Here, TransferServiceImpl
, JdbcAccountRepository
, and JdbcTransferRepository
are declared as beans in Spring. In the case of the TransferServiceImpl
bean, it's constructed, passing a reference to the AccountRepository
and TransferRepository
beans as constructor arguments. You might like to know that Spring also allows you to express the same configuration using Java.
Spring offers Java-based configuration as an alternative to XML:
package com.packt.chapter1.bankapp.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.packt.chapter1.bankapp.repository.AccountRepository; import com.packt.chapter1.bankapp.repository.TransferRepository; import com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository; import com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository; import com.packt.chapter1.bankapp.service.TransferService; import com.packt.chapter1.bankapp.service.TransferServiceImpl; @Configuration public class AppConfig { @Bean public TransferService transferService(){ return new TransferServiceImpl(accountRepository(), transferRepository()); } @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(); } @Bean public TransferRepository transferRepository() { return new JdbcTransferRepository(); } }
The benefits of the dependency injection pattern are the same whether you are using an XML-based or a Java-based configuration:
- Dependency injection promotes loose coupling. You can remove hard-coded dependencies with best practice P2I, and you could provide dependencies from outside the application by using the Factory pattern and its built-in swappable and pluggable implementation
- The DI pattern promotes the composition design of object-oriented programming rather than inheritance programming
Although TransferService
depends on an AccountRepository
and TransferRepository
, it doesn't care about what type (JDBC or JPA) of implementations of AccountRepository
and TransferRepository
are used in the application. Only Spring, through its configuration (XML- or Java-based), knows how all the components come together and are instantiated with their required dependencies using the DI pattern. DI makes it possible to change those dependencies with no changes to the dependent classes--that is, we could use either a JDBC implementation or a JPA implementation without changing the implementation of AccountService
.
In a Spring application, an implementation of the application context (Spring offers AnnotationConfigApplicationContext
for Java-based and ClassPathXmlApplicationContext
for XML-based implementations) loads bean definitions and wires them together into a Spring container. The application context in Spring creates and wires the Spring beans for the application at startup. Look into the implementation of the Spring application context with Java-based configuration--It loads the Spring configuration files (AppConfig.java
for Java and Sprig.xml
for XML) located in the application's classpath. In the following code, the main()
method of the TransferMain
class uses a AnnotationConfigApplicationContext
class to load the configuration class AppConfig.java
and get an object of the AccountService
class.
Spring offers Java-based configuration as an alternative to XML:
package com.packt.chapter1.bankapp; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation .AnnotationConfigApplicationContext; import com.packt.chapter1.bankapp.config.AppConfig; import com.packt.chapter1.bankapp.model.Amount; import com.packt.chapter1.bankapp.service.TransferService; public class TransferMain { public static void main(String[] args) { //Load Spring context ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); //Get TransferService bean TransferService transferService = applicationContext.getBean(TransferService.class); //Use transfer method transferService.transferAmmount(100l, 200l, new Amount(2000.0)); applicationContext.close(); } }
Here we have a quick introduction to the dependency injection pattern. You'll learn a lot more about the DI pattern in the coming chapters of this book. Now let's look at another way of simplifying Java development using Spring's declarative programming model through aspects and proxy patterns.
Applying aspects for cross cutting concerns
In a Spring application, the DI pattern provides us with loose coupling between collaborating software components, but Aspect-Oriented Programming in Spring (Spring AOP) enables you to capture common functionalities that are repetitive throughout your application. So we can say that Spring AOP promotes loose coupling and allows cross-cutting concerns, listed as follows, to be separated in a most elegant fashion. It allows these services to be applied transparently through declaration. With Spring AOP, it is possible to write custom aspects and configure them declaratively.
The generic functionalities that are needed in many places in your application are:
- Logging and tracing
- Transaction management
- Security
- Caching
- Error handling
- Performance monitoring
- Custom business rules
The components listed here are not part of your core application, but these components have some additional responsibilities, commonly referred to as cross-cutting concerns because they tend to cut across multiple components in a system beyond their core responsibilities. If you put these components with your core functionalities, thereby implementing cross-cutting concerns without modularization, it will have two major problems:
- Code tangling: A coupling of concerns means that a cross-cutting concern code, such as a security concern, a transaction concern, and a logging concern, is coupled with the code for business objects in your application.
- Code scattering: Code scattering refers to the same concern being spread across modules. This means that your concern code of security, transaction, and logging is spread across all modules of the system. In other words, you can say there is a duplicity of the same concern code across the system.
The following diagram illustrates this complexity. The business objects are too intimately involved with the cross-cutting concerns. Not only does each object know that it's being logged, secured, and involved in a transactional context, but each object is also responsible for performing those services assigned only to it:
Cross-cutting concerns, such as logging, security and transaction, are often scattered about in modules where those tasks are not their primary concern
Spring AOP enables the modularization of cross-cutting concerns to avoid tangling and scattering. You can apply these modularized concerns to the core business components of the application declaratively without affecting the aforementioned the above components. The aspects ensure that the POJOs remain plain. Spring AOP makes this magic possible by using the Proxy Design Pattern. We will discuss the Proxy Design pattern more in the coming chapters of this book.
How Spring AOP works
The following points describe the work of Spring AOP:
- Implement your mainline application logic: Focusing on the core problem means that, when you are writing the application business logic, you don't need to worry about adding additional functionalities, such as logging, security, and transaction, between the business codes-Spring AOP takes care of it.
- Write aspects to implement your cross-cutting concerns: Spring provides many aspects out of the box, which means you can write additional functionalities in the form of the aspect as independent units in Spring AOP. These aspects have additional responsibilities as cross-cutting concerns beyond the application logic codes.
- Weave the aspects into your application: Adding the cross-cutting behaviors to the right places, that is, after writing the aspects for additional responsibilities, you could declaratively inject them into the right places in the application logic codes.
Let's look at an illustration of AOP in Spring:
AOP-based system evolution--this leaves the application components to focus on their specific business functionalities
In the preceding diagram, Spring AOP separates the cross-cutting concerns, for example, security, transaction, and logging, from the business modules, that is, BankService
, CustomerService
, and ReportingService
. These cross-cutting concerns are applied to predefined points (stripes in the preceding diagram) of the business modules at the running time of the application.
Suppose that you want to log the messages before and after calling the transferAmmount()
method of TransferService
using the services of a LoggingAspect
. The following listing shows the LoggingAspect
class you might use.
LoggingAspect
call is used for logging the system for TransferService
:
package com.packt.chapter1.bankapp.aspect; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class LoggingAspect { @Before("execution(* *.transferAmount(..))") public void logBeforeTransfer(){ System.out.println("####LoggingAspect.logBeforeTransfer() method called before transfer amount####"); } @After("execution(* *.transferAmount(..))") public void logAfterTransfer(){ System.out.println("####LoggingAspect.logAfterTransfer() method called after transfer amount####"); } }
To turn LoggingAspect
into an aspect bean, all you need to do is declare it as one in the Spring configuration file. Also, to make it an aspect, you have to add the @Aspect
annotation to this class. Here's the updated AppConfig.java
file, revised to declare LoggingAspect
as an aspect.
Declaring LoggingAspect
as an aspect and enabling the Apsect
proxy feature of Spring AOP:
package com.packt.chapter1.bankapp.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import com.packt.chapter1.bankapp.aspect.LoggingAspect; import com.packt.chapter1.bankapp.repository.AccountRepository; import com.packt.chapter1.bankapp.repository.TransferRepository; import com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository; import com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository; import com.packt.chapter1.bankapp.service.TransferService; import com.packt.chapter1.bankapp.service.TransferServiceImpl; @Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public TransferService transferService(){ return new TransferServiceImpl(accountRepository(), transferRepository()); } @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(); } @Bean public TransferRepository transferRepository() { return new JdbcTransferRepository(); } @Bean public LoggingAspect loggingAspect() { return new LoggingAspect(); } }
Here, we're using Spring's AOP configuration based on Java to declare the LoggingAspect
bean as an aspect. First, we declare LoggingAspect
as a bean. Then we annotate that bean with the @Aspect
annotation.
We annotate logBeforeTransfer()
of LoggingAspect
with the @Before
annotation so that this method is called before the transferAmount()
is executed. This is called before advice. Then, we annotate another method of LoggingAspect
with the @After
annotation to declare that the logAfterTransfer()
method should be called after transferAmount()
has executed. This is known as after advice.
@EnableAspectJAutoProxy
is used to enable Spring AOP features in the application. This annotation actually forces you to apply proxy to some of the components that are defined in the spring configuration file. We'll talk more about Spring AOP later, in Chapter 4, Spring Aspect Oriented Programming with Proxy and Decorator Pattern. For now, it's enough to know that you've asked Spring to call logBeforeTransfer()
and logAferTransfer()
of LoggingAspect
before and after the transferAmount()
method of the TransferService
class. For now, there are two important points to take away from this example:
LoggingAspect
is still a POJO (if you ignore the@Aspect
annotation or are using XML-based configuration)--nothing about it indicates that it's to be used as an aspect.- It is important to remember that
LoggingAspect
can be applied toTransferService
withoutTransferService
needing to explicitly call it. In fact,TransferService
remains completely unaware of the existence ofLoggingAspect
.
Let's move to another way that Spring simplifies Java development.
Applying the template pattern to eliminate boilerplate code
At one point in the enterprise application, we saw some code that looked like code we had already written before in the same application. That is actually boilerplate code. It is code that we often have to write again and again in the same application to accomplish common requirements in different parts of the application. Unfortunately, there are a lot of places where Java APIs involve a bunch of boilerplate code. A common example of boilerplate code can be seen when working with JDBC to query data from a database. If you've ever worked with JDBC, you've probably written something in code that deals with the following:
- Retrieving a connection from the connection pool
- Creating a
PreparedStatement
object - Binding SQL parameters
- Executing the
PreparedStatement
object - Retrieving data from the
ResultSet
object and populating data container objects - Releasing all database resources
Let's look at the following code, it contains boilerplate code with the JDBC API of the Java:
public Account getAccountById(long id) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); stmt = conn.prepareStatement( "select id, name, amount from " + "account where id=?"); stmt.setLong(1, id); rs = stmt.executeQuery(); Account account = null; if (rs.next()) { account = new Account(); account.setId(rs.getLong("id")); account.setName(rs.getString("name")); account.setAmount(rs.getString("amount")); } return account; } catch (SQLException e) { } finally { if(rs != null) { try { rs.close(); } catch(SQLException e) {} } if(stmt != null) { try { stmt.close(); } catch(SQLException e) {} } if(conn != null) { try { conn.close(); } catch(SQLException e) {} } } return null; }
In the preceding code, we can see that the JDBC code queries the database for an account name and amount. For this simple task, we have to create a connection, then create a statement, and finally query for the results. We also have to catch SQLException
, a checked exception, even though there's not a lot you can do if it's thrown. Lastly, we have to clean up the mess, closing down the connection statement and result set. This could also force it to handle JDBC's exception, so you must catch SQLException
here as well. This kind of boilerplate code seriously hurts reusability.
Spring JDBC solves the problem of boilerplate code by using the Template Design pattern, and it makes life very easy by removing the common code in templates. This makes the data access code very clean and prevents nagging problems, such as connection leaks, because the Spring Framework ensures that all database resources are released properly.
The Template Design pattern in Spring
Let's see how to go about using the Template Design pattern in spring:
- Define the outline or skeleton of an algorithm
- Leave the details for specific implementations until later.
- Hide away large amounts of boilerplate code.
- Spring provides many template classes:
JdbcTemplate
JmsTemplate
RestTemplate
WebServiceTemplate
- Most hide low-level resource management
Let's look at the same code that we used earlier with Spring's JdbcTemplate
and how it removes the boilerplate code.
Use JdbcTemplates
to let your code the focus on the task:
public Account getAccountById(long id) { return jdbcTemplate.queryForObject( "select id, name, amoount" + "from account where id=?", new RowMapper<Account>() { public Account mapRow(ResultSet rs, int rowNum) throws SQLException { account = new Account(); account.setId(rs.getLong("id")); account.setName(rs.getString("name")); account.setAmount(rs.getString("amount")); return account; } }, id); }
As you can see in the preceding code, this new version of getAccountById()
is much simpler as compared to the boiler plate code, and here the method is focused on selecting an account from the database rather than creating a database connection, creating a statement, executing the query, handling the SQL exception, and finally closing the connection as well. With the template, you have to provide the SQL query and a RowMapper
used for mapping the resulting set data to the domain object in the template's queryForObject()
method. The template is responsible for doing everything for this operation, such as database connection and so on. It also hides a lot of boilerplate code behind the framework.
We have seen in this section how Spring attacks the complexities of Java development with the power of POJO-oriented development and patterns such as the DI pattern, the Aspect-using Proxy pattern, and the Template method design pattern.
In the next section, we will look at how to use a Spring container to create and manage the Spring beans in the application.