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 begin to create the main part of the program. First, I'll show you how to create a program without IoC / DI. And then we will directly create a program that independently introduces dependencies. That is, control of the code passes into the hands of the framework (sounds creepy). While we are managing the program, imagine that there is a certain company. And the company (for now) has two departments: Java Development and Hiring Department. Let the class describing the “Java Development Department” have two methods: String getName() - returning the employee’s name, String getJob() - returning the employee’s position. (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());
    }
}
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();
    }
}
Stability for now. When we run the Main class we get the following result:
Name: Alexa
Job: Middle Java developer
Now let’s imagine that the company is doing great. Therefore, they decided to expand the scope of their 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 and paste” wherever you need to describe this department (the good old method🙃). First, 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 HiringDepartment. And HiringDepartment says nothing about this department. Therefore, you will have to create a new object of the PythonDevelopment class and a constructor that accepts Python developers. You will also have to change the displayInfo() method so that it displays information correctly. (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 volume 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 that are highly dependent on each other. Ok, we agreed with this. They just described one department. We won't lose anything from this. Well, what if we add another department? What if there are two? Three? But no one forbade “mining and grazing.” Spring.  Lesson 2. IoC / DI in practice - 1 Yes, no one forbade “Mine and Pasture”, but it is not professional. Tyzh is 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. This way, dependencies between classes will be minimal. To do this, we first create the Development interface, which has two methods for describing an employee. (Listing 6)
package org.example;

public interface Development {
    String getName();
    String getJob();
}
Let then two classes JavaDevelopment and PythonDevelopment implement (inherit) from this interface and override the methods String getName() and String getJob(). (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 type Development 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 were minimized. How are values ​​and dependencies actually implemented for these objects? There are three ways to do dependency injection:
  • Using the constructor
  • Using setters
  • Autowiring (automatic binding)
Implementation using a constructor Now let's talk about implementation using a constructor. Look at Listing 9. The constructor of the HiringDepartment class expects 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. There are three ways to configure Spring containers:
  • Using XML files (Outdated method)
  • Using annotations + XML files (Modern way)
  • Using Java code (Modern way)
We are now using the configuration using XML files. Despite the fact that this method is considered outdated, many projects are still written in this way. Therefore you need to know. First, you must create an xml file in the resources folder. You can give it any name, but preferably a meaningful one. I called it "applicationContext.xml". Spring.  Lesson 2. IoC / DI in practice - 2 In this file we will write the following piece of code (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 simply copy them. The <bean> </bean> tag defines a Spring bean. A bean is an object that is created and managed by a 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. This id will be used 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 bound. You must specify the full path to the class. Pay attention to the hiringDepartment bean. Inside this bean there is another <constructor-arg ref="javaDeveloper"/> tag. This is where dependency injection occurs (in our case, injection using a constructor). <constructor-arg> - tells Spring that the Spring container should look for dependencies in the class constructor defined in the bean attribute. And which object needs to be associated with is determined by the ref attribute , inside the <constructor-arg> tag. ref - indicates the id of the bean to contact. If in ref instead of javaDeveloper we specify the id pythonDeveloper, then the connection occurs with the PythonDevelopmen class. Now we need to describe the Main class. It will look like this: (Listing11)
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's 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 bean id that we wrote in the xml file. The second argument points to the class we want to contact. This process is called reflection .
hiringDepartment.displayInfo();
 context.close(); //Контекст всегда должен закрываться
Here we easily get a method of the HiringDepartment class. Note that we did not use the new keyword to obtain the objects, and we did not define dependent objects of type JavaDevelopment or PythonDevelopment anywhere. They were simply described in the applicationContext.xml file. Also pay attention to the last line. You should always close the context before shutting down. Otherwise, resources will not be freed, and a memory leak or incorrect operation of the program may occur. If you have questions or suggestions, write in the comments, I will definitely answer. Thank you for attention. Source code at the link My GitHub Cart Course content To be continued...
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION