JavaRush /Java Blog /Random EN /Spring. Lesson 2. IoC / DI in practice
Umaralikhon
Level 3
Красноярск

Spring. Lesson 2. IoC / DI in practice

Published in the Random EN group
And so... In the previous lesson, we briefly reviewed the theoretical part of IoC and DI. We also set up the pom.xml configuration file for our project. Today we start creating the main part of the program. First, I will show how a program is created without IoC / DI. And then we will directly create a program that independently injects dependencies. That is, code control passes into the hands of the framework (sounds creepy). While we manage the program Let's imagine that there is a certain company. And the company (so far) has two departments: the Java Development department and the HiringDepartment. Let the class describing "Java Development Department" have two methods: String getName() - returning the name of the employee, String getJob() - returning the position of the employee. (Listing 1)
package org.example;

public class JavaDevelopment {

    public String getName(){
        return "Alexa";
    }

    public String getJob(){
        return "Middle Java developer";
    }
}
Let the class describing the hiring department have an input constructor that accepts an employee, and a void displayInfo() method that displays information about employees. (Listing 2)
package org.example;

public class HiringDepartment {
    private JavaDevelopment javaDevelopment;

    public HiringDepartment(JavaDevelopment javaDevelopment) {
        this.javaDevelopment = javaDevelopment;
    }

    public void displayInfo() {
        System.out.println("Name: " + javaDevelopment.getName());
        System.out.println("Job: " + javaDevelopment.getJob());
    }
}
And there is also Main - a class that manages all departments. (Listing 3)
package org.example;

public class Main {
    public static void main(String ... args){
        JavaDevelopment javaDevelopment = new JavaDevelopment();
        HiringDepartment hiringDepartment = new HiringDepartment(javaDevelopment);

        hiringDepartment.displayInfo();
    }
}
While stability. When running the Main - class, we get the following result:
Name: Alexa
Job: Middle Java developer
Now imagine that the company is doing great. Therefore, they decided to expand the scope of activities, and opened a Python development department. And here the question arises: How to describe this department at the program level? Answer: you need to "copy-paste" wherever you need to describe this department (good old method 🙃). To begin with, let's create the class itself, which would describe the "Pythonists" department. (Listing 4)
package org.example;

public class PythonDevelopment {
    public String getName(){
        return "Mike";
    }

    public String getJob(){
        return "Middle Python developer";
    }
}
And then we will transfer it to the hiring department (HiringDepartment). And in HiringDepartment'e nothing is said about this department. Therefore, you will have to create a new object of the PythonDevelopment class and a constructor that accepts Python developers. And you will also have to change the displayInfo () method - so that it correctly displays information. (Listing 5)
package org.example;

public class HiringDepartment {
    private JavaDevelopment javaDevelopment;

    public HiringDepartment(JavaDevelopment javaDevelopment) {
        this.javaDevelopment = javaDevelopment;
    }


    //Тут создается отдел найма для Python - разработчиков
    private PythonDevelopment pythonDevelopment;

    public HiringDepartment(PythonDevelopment pythonDevelopment) {
        this.pythonDevelopment = pythonDevelopment;
    }

    //Тогда придется изменить метод displayInfo()
    public void displayInfo() {
        if(javaDevelopment != null) {
            System.out.println("Name: " + javaDevelopment.getName());
            System.out.println("Job: " + javaDevelopment.getJob());
        } else if (pythonDevelopment != null){
            System.out.println("Name: " + pythonDevelopment.getName());
            System.out.println("Job: " + pythonDevelopment.getJob());
        }
    }
}
As we can see, the amount of code has doubled, or even more. With a large amount of code, its readability decreases. And the worst thing is that we create all objects manually and make classes highly dependent on each other. Ok, we agreed with that. Just described one section. We won't lose anything from this. Well, what if another department is added? What if there are two? Three? And no one forbade "copy - pastit". Spring.  Lesson 2. IoC / DI in practice - 1 Yes, no one forbade "Kop - pasture", but this is not professional. You are a programmer. And here you can use DI. That is, we will work not at the class level, but at the interface level. Now the states of our objects will be stored in interfaces. Thus, the dependencies between classes will be minimal. To do this, we first create the Development interface, which has two methods for describing an employee.
package org.example;

public interface Development {
    String getName();
    String getJob();
}
Then let the two classes JavaDevelopment and PythonDevelopment implement (inherit) from this interface and override the String getName() and String getJob() methods. (Listing 7, 8)
package org.example;

public class JavaDevelopment implements Development {
    @Override
    public String getName(){
        return "Alexa";
    }

    @Override
    public String getJob(){
        return "Middle Java developer";
    }
}
package org.example;

public class PythonDevelopment implements Development {
    @Override
    public String getName(){
        return "Mike";
    }

    @Override
    public String getJob(){
        return "Middle Python developer";
    }
}
Then, in the HiringDepartment class, you can simply define an interface object of the Development type, and you can also pass such an object to the constructor. (Listing 9)
package org.example;

public class HiringDepartment {
    private Development development; //Определяем интерфейс

    //Конструктор принимает an object интерфейса
    public HiringDepartment(Development development){
        this.development = development;
    }

    public void displayInfo(){
        System.out.println("Name: " + development.getName());
        System.out.println("Job: " + development.getJob());
    }
}
As we can see, the amount of code has decreased. And most importantly, dependencies are minimized. How are values ​​and dependencies actually injected into these objects? There are three ways to inject dependencies:
  • Using the constructor
  • Using Setters
  • Autowiring (automatic binding)
Constructor Injection Now let's talk about constructor injection. Look at Listing 9. The constructor of the HiringDepartment class takes an object of type Development as input. We will try to inject dependencies through this constructor. It is also worth noting that dependency injection is performed using the so-called Spring containers (Spring container). There are three ways to configure Spring containers:
  • Using XML Files (Legacy)
  • Using annotations + XML files (Modern way)
  • With Java code(Modern way)
We now use the configuration using XML files. Despite the fact that this method is considered obsolete, yet many projects are written in this way. Therefore, you need to know. To begin with, an xml file must be created in the resources folder. It can be given any name, but preferably meaningful. I named "applicationContext.xml". Spring.  Lesson 2. IoC / DI in practice - 2 In this file, we will write the following code snippet (Listing 10):
<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="javaDeveloper" class="org.example.JavaDevelopment"/>
    <bean id="pythonDeveloper" class="org.example.PythonDevelopment"/>

    <bean id="hiringDepartment" class="org.example.HiringDepartment">
        <constructor-arg ref="javaDeveloper"/>
    </bean>

</beans>
Now in order. The first eight lines of code are not interesting to us, they are default. You can just copy them. The <bean> </bean> tag defines a Spring bean. A bean is an object that is created and managed by the Spring container. In simple words, Spring container itself creates a new class object for us (for example: JavaDevelopment javaDevelopment = new JavaDevelopment();). Inside this tag there are id and class attributes. id specifies the name of the bean. By this id it will be possible to access the object. It is equivalent to the name of an object in a Java class. class- defines the name of the class to which our bean (object) is attached. You must specify the full path to the class. Notice the hiringDepartment bean. Inside this bean, there is another <constructor-arg ref="javaDeveloper"/> tag. This is where dependency injection takes place (in our case, injection using a constructor). <constructor-arg> - tells Spring that the Spring container should look for dependencies in the constructor of the class defined in the bean attribute. The ref attribute , inside the <constructor-arg> tag, determines which object to bind to . ref - points to the id of the bean to bind to. If instead of javaDeveloper we specify the id pythonDeveloper in the ref, then the link occurs with the PythonDevelopmen class. And now we need to describe the Main class. It will look like this:
package org.example;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String ... args){
        //Определяем контекст файл в котором содержатся прописанные нами бины
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //Получем бины, которые были определены в файле applicationContext.xml
        HiringDepartment hiringDepartment = context.getBean("hiringDepartment", HiringDepartment.class);

        hiringDepartment.displayInfo();

        context.close(); //Контекст всегда должен закрываться
    }
}
What is here?
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
This line links the Main class to the .xml file that describes our beans. The value passed to the constructor must match the name of the .xml file. (In our case applicationContext.xml).
HiringDepartment hiringDepartment = context.getBean("hiringDepartment", HiringDepartment.class);
Indicates that we want to get a bean (object) of the HiringDepartment class. The first argument points to the id of the bean that we wrote in the xml file. The second argument points to the class we want to communicate with. This process is called reflection .
hiringDepartment.displayInfo();
 context.close(); //Контекст всегда должен закрываться
Here we calmly get the HiringDepartment class method. Notice that we didn't use the new keyword to get the objects, and we didn't define dependent JavaDevelopment or PythonDevelopment type objects anywhere. They are simply described in the context file applicationContext.xml. Also pay attention to the last line. You should always close the context before shutting down. Otherwise, the resources will not be freed, memory leaks or incorrect operation of the program may occur. If you have any questions or suggestions, write in the comments, I will definitely answer. Thank you for attention. Source Code Link My GitHub Cart Course Content To be continued...
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION