A lot of ground will be covered in these posts, but it will all center around Spring Framework. I will show you the most important design patterns used in Spring, thanks to which you will understand this framework better.

Requirements:

Java knowledge

Basic Spring knowledge

Object-Oriented Programming

What are Design Patterns?

Design patterns are solutions to common problems. You can say that Design Patterns are guidelines on how to tackle problems in certain situations. There are three types of patterns:

Creational Design Patterns

Creational patterns are focused on how to instantiate an object or group of related objects.

Structural Design Patterns

Structural patterns are mostly concerned with object composition or in other words how the entities can use each other.

Behavioral Design Patterns

Behavioral Patterns are concerned with providing solutions regarding object interaction - how they communicate, how are some dependent on others.

Inversion of Control

Inversion of Control is one of the most important patterns that is used by Spring. This is the main principle of Spring. The main advantages of IoC are:

Decreases coupling

Improves testability

Greater modularity of a program

How it works? The control of application objects is transferred to a container or framework. A central container constructs and manages all objects references. When other components within the application need an object - IoC container hands it to them.

How it works in Spring Framework? In Spring, the IoC container manages all of the application dependencies. Instead of writing “new Class()” on your code, the object is created by someone else. This ‘someone else’ is referred to as the IoC Container.

In addition to creating new objects, Spring IoC container manages their entire life cycle. That includes creating objects, destroying them and invoking certain methods of the object (callbacks). So, what are the benefits of Spring IoC? Firstly, it reduces noise in the code (Imagine creating a JDBC connection object in dozens of classes). It also reduces object coupling, because you are handed a dependency into your object, you are not as tightly coupled to it (you actually don`t need to know how it is constructed).

Dependency Injection

Dependency injection is a software design pattern that implements inversion of control for resolving dependencies.

Let’s start with a simple example:

interface Screen { void print ( String text ); }

class MainScreen implements Screen { @Override public void print ( String text ) { System . out . println ( text ); } } }

class PrintOnScreen { private final Screen screen = new MainScreen (); void showMessage ( String text ) { screen . print ( "Text:" + text ); } }

public static void printHelloWorldMessage () { PrintOnScreen printOnScreen = new PrintOnScreen (); printOnScreen . showMessage ( "Hello World" ); }

Writing a unit test for the PrintOnScreen class is non-trivial. Additionally, in the PrintOnScreen class there is initialized the printer field with a specific implementation. If we would like to change the implementation of the Screen interface, it will be necessary to modify the code of the MainScreen class. We can get rid of these disadvantages using the DI approach. We just need to modify the MainScreen class to accept the Screen , for example - injected by the constructor:

class PrintOnScreen { private final Screen screen = new Screen (); PrintOnScreen ( Screen screen ) { this . screen = screen ; } void showMessage ( String text ) { screen . print ( "Text:" + text ); } }

public static void printHelloWorldMessage () { Screen screen = new MainScreen (); PrintOnScreen printOnScreen = new PrintOnScreen ( screen ); printOnScreen . showMessage ( "Hello World" ); }

This implementation of the PrintOnScreen class will be easier to test. The test in Mockito might look like this:

class PrintOnScreenTest { @Test void shouldPrintHelloWorld () { // given Screen screen = mock ( Screen . class ); PrintOnScreen printOnScreen = new PrintOnScreen ( screen ); String text = "Hello World" ; // when printOnScreen . showMessage ( text ); // then verify ( screen ). print ( argThat ( arg -> arg . contains ( "Hello World" ))); } }

By using DI, the PrintOnScreen class has become easier to test, but initialization is more complicated. Fortunately, Spring Framework allows us to use the benefits of DI in an easier way.

Dependency Injection in Spring Framework

One way to create a Spring bean is to add the @Component annotation to the class that you want to become a bean. The container will create an instance of this class and inject the required dependencies into it.

There are 3 ways to inject dependencies:

Constructor Dependency Injection:

class DependencyInjectionExample { private final MainScreen mainScreen ; DependencyInjectionExample ( MainScreen mainScreen ) { this . mainScreen = mainScreen ; } public void printMessage () { mainScreen . showMessage ( "Heeeellooo !" ); } }

Setter Dependency Injection:

class PrintOnScreen { private Screen screen ; @Autowired void seetScreen ( Screen screen ) { this . screen = screen ; } }

Field Dependency Injection:

class PrintOnScreen { @Autowired private Screen screen ; }

Which way of dependency injection is the best?

Injection through the field seems to be the most convenient, but this approach has several disadvantages:

You can not add a final modifier to such a field, which makes our class mutable.

This makes it difficult to initialize dependencies in tests.

If we use injecting by the constructor and our application will have a cyclic relationship between the beans, we’ll get an exception just after starting the application. If injected through the field, the exception will not fly and the application will work.

Spring developers recommend injecting by the constructor.

What if more than one bean implements the same interface?

Sometimes several classes - beanes implements the same interface:

@Bean Screen messageScreen () { return message -> System . out . println ( message ); } @Bean Screen warnScreen () { return message -> System . out . println ( message ); }

Then we need to indicate the implementation that is to be injected. We have several options:

Using the bean name as the name of the method parameter

@Bean ( "messageScreenBean" ) Screen messageScreen () {

If we do not give the name of the bean in the annotation parameter, it will be the name of the method by default.

@Qualifier annotation

@Qualifier ( "messageScreen" ) @Bean Screen messageScreen ()

@Bean MessagePrinter printer ( @Qualifier ( "messageScreen" ) Screen screen )

We can mark one of the beans with @Primary annotation. If there is no qualifier for injection, this implementation will be selected by default.

Summary

In this post, I demonstrated basic examples of Dependency Injection in the Spring Framework. I hope you can see how simple Spring makes Dependency Injection.

If you liked it, I invite you to read the next part in which I described the most popular creational design patterns: Design Patterns used in Spring Framework Part 2 - Creational Patterns.