JavaRush /Blog Java /Random-PL /Wiosna. Lekcja 2. IoC/DI w praktyce
Umaralikhon
Poziom 3
Красноярск

Wiosna. Lekcja 2. IoC/DI w praktyce

Opublikowano w grupie Random-PL
I tak... W poprzedniej lekcji pokrótce omówiliśmy część teoretyczną IoC i DI. Dla naszego projektu utworzyliśmy także plik konfiguracyjny pom.xml. Dziś zaczynamy tworzyć główną część programu. Najpierw pokażę jak stworzyć program bez IoC/DI. A następnie bezpośrednio stworzymy program, który samodzielnie wprowadzi zależności. Oznacza to, że kontrola nad kodem przechodzi w ręce frameworka (brzmi przerażająco). Kiedy zarządzamy programem, wyobraźmy sobie, że istnieje pewna firma. A firma (na razie) ma dwa działy: Dział Rozwoju Java i Dział Zatrudnienia. Niech klasa opisująca „Dział Rozwoju Java” ma dwie metody: String getName() - zwracająca imię i nazwisko pracownika, String getJob() - zwracająca stanowisko pracownika. (Lista 1)
package org.example;

public class JavaDevelopment {

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

    public String getJob(){
        return "Middle Java developer";
    }
}
Niech klasa opisująca dział rekrutacji posiada konstruktor wejściowy akceptujący pracownika oraz metodę void displayInfo() wyświetlającą informacje o pracownikach. (Lista 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());
    }
}
Istnieje również Main - klasa zarządzająca wszystkimi działami. (Lista 3)
package org.example;

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

        hiringDepartment.displayInfo();
    }
}
Na razie stabilność. Kiedy uruchomimy klasę Main, otrzymamy następujący wynik:
Name: Alexa
Job: Middle Java developer
A teraz wyobraźmy sobie, że firma radzi sobie świetnie. Dlatego postanowili rozszerzyć zakres swojej działalności i otworzyli dział rozwoju Pythona. I tu pojawia się pytanie: Jak opisać ten dział na poziomie programu? Odpowiedź: musisz „skopiować i wkleić” wszędzie tam, gdzie chcesz opisać ten dział (stara, dobra metoda🙃). Najpierw utwórzmy samą klasę, która będzie opisywała dział „Pytoniści”. (Lista 4)
package org.example;

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

    public String getJob(){
        return "Middle Python developer";
    }
}
A potem przekażemy to do Działu Zatrudnienia. Dział Zatrudnienia nie mówi nic o tym dziale. Dlatego będziesz musiał utworzyć nowy obiekt klasy PythonDevelopment i konstruktor akceptujący programistów Pythona. Będziesz także musiał zmienić metodę displayInfo() tak, aby wyświetlała informacje poprawnie. (Lista 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());
        }
    }
}
Jak widzimy, objętość kodu podwoiła się, a nawet więcej. Przy dużej ilości kodu jego czytelność maleje. A najgorsze jest to, że wszystkie obiekty tworzymy ręcznie i tworzymy klasy, które są od siebie w dużym stopniu zależne. Ok, zgodziliśmy się z tym. Opisali tylko jeden dział. Nic na tym nie stracimy. A co jeśli dodamy kolejny dział? A co jeśli będzie ich dwóch? Trzy? Ale nikt nie zakazał „górnictwa i wypasu”. Wiosna.  Lekcja 2. IoC/DI w praktyce - 1 Tak, nikt nie zabraniał „Kopalni i Pastwiska”, ale to nie jest profesjonalne. Tyzh jest programistą. I tutaj możesz użyć DI. Oznacza to, że nie będziemy pracować na poziomie klasy, ale na poziomie interfejsu. Teraz stany naszych obiektów będą przechowywane w interfejsach. W ten sposób zależności pomiędzy klasami będą minimalne. W tym celu tworzymy najpierw interfejs Development, który posiada dwie metody opisu pracownika. (Lista 6)
package org.example;

public interface Development {
    String getName();
    String getJob();
}
Niech zatem dwie klasy JavaDevelopment i PythonDevelopment zaimplementują (dziedziczą) z tego interfejsu i zastąpią metody String getName() i 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";
    }
}
Następnie w klasie HiringDepartment można po prostu zdefiniować obiekt interfejsu typu Development, a także można taki obiekt przekazać do konstruktora. (Listing 9)
package org.example;

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

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

    public void displayInfo(){
        System.out.println("Name: " + development.getName());
        System.out.println("Job: " + development.getJob());
    }
}
Jak widać ilość kodu uległa zmniejszeniu. A co najważniejsze, zminimalizowano zależności. W jaki sposób wartości i zależności są faktycznie implementowane dla tych obiektów? Istnieją trzy sposoby wstrzykiwania zależności:
  • Korzystanie z konstruktora
  • Używanie seterów
  • Autowiring (automatyczne wiązanie)
Implementacja za pomocą konstruktora Porozmawiajmy teraz o implementacji za pomocą konstruktora. Spójrz na Listing 9. Konstruktor klasy HiringDepartment oczekuje na wejściu obiektu typu Development. Spróbujemy wstrzyknąć zależności poprzez ten konstruktor. Warto również zaznaczyć, że wstrzykiwanie zależności odbywa się przy użyciu tzw. kontenerów Spring. Istnieją trzy sposoby konfiguracji kontenerów Spring:
  • Korzystanie z plików XML (przestarzała metoda)
  • Korzystanie z adnotacji + plików XML (w nowoczesny sposób)
  • Korzystanie z kodu Java (w nowoczesny sposób)
Korzystamy teraz z konfiguracji przy użyciu plików XML. Pomimo tego, że metoda ta jest uważana za przestarzałą, wiele projektów wciąż jest pisanych w ten sposób. Dlatego musisz wiedzieć. Najpierw musisz utworzyć plik xml w folderze zasobów. Możesz nadać mu dowolną nazwę, ale najlepiej znaczącą. Nazwałem to „applicationContext.xml”. Wiosna.  Lekcja 2. IoC/DI w praktyce - 2 W pliku tym napiszemy następujący fragment kodu (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>
A teraz porządek. Pierwsze osiem linii kodu nas nie interesuje, są domyślne. Możesz je po prostu skopiować. Znacznik <bean> </bean> definiuje komponent Spring Bean. Komponent bean to obiekt tworzony i zarządzany przez kontener Spring. W prostych słowach kontener Spring sam tworzy dla nas nowy obiekt klasy (przykładowo: JavaDevelopment javaDevelopment = new JavaDevelopment();). Wewnątrz tego tagu znajdują się atrybuty id i class. id określa nazwę komponentu bean. Ten identyfikator będzie używany do uzyskania dostępu do obiektu. Jest odpowiednikiem nazwy obiektu w klasie Java. class - definiuje nazwę klasy, z którą powiązany jest nasz komponent bean (obiekt). Musisz podać pełną ścieżkę do klasy. Zwróć uwagę na komponent bean działu zatrudniania. Wewnątrz tego komponentu znajduje się kolejny tag <constructor-arg ref="javaDeveloper"/>. W tym miejscu następuje zastrzyk zależności (w naszym przypadku zastrzyk za pomocą konstruktora). <constructor-arg> - mówi Springowi, że kontener Spring powinien szukać zależności w konstruktorze klasy zdefiniowanym w atrybucie komponentu bean. Z którym obiektem należy powiązać, określa atrybut ref wewnątrz znacznika <constructor-arg>. ref - wskazuje identyfikator komponentu bean, z którym należy się skontaktować. Jeżeli w ref zamiast javaDeveloper podamy id pythonDeveloper, to połączenie nastąpi z klasą PythonDevelopmen. Teraz musimy opisać klasę Main. Będzie to wyglądać tak: (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(); //Контекст всегда должен закрываться
    }
}
Co jest tutaj?
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Ta linia łączy klasę Main z plikiem .xml opisującym nasze komponenty bean. Wartość przekazana do konstruktora musi być zgodna z nazwą pliku .xml. (W naszym przypadku applicationContext.xml).
HiringDepartment hiringDepartment = context.getBean("hiringDepartment", HiringDepartment.class);
Wskazuje, że chcemy pobrać komponent bean (obiekt) klasy HiringDepartment. Pierwszy argument wskazuje na identyfikator komponentu bean, który zapisaliśmy w pliku xml. Drugi argument wskazuje klasę, z którą chcemy się skontaktować. Proces ten nazywany jest refleksją .
hiringDepartment.displayInfo();
 context.close(); //Контекст всегда должен закрываться
Tutaj łatwo otrzymujemy metodę klasy HiringDepartment. Należy zauważyć, że do uzyskania obiektów nie użyliśmy słowa kluczowego new i nigdzie nie zdefiniowaliśmy obiektów zależnych typu JavaDevelopment lub PythonDevelopment. Zostały one po prostu opisane w pliku applicationContext.xml. Zwróć także uwagę na ostatnią linijkę. Zawsze należy zamknąć kontekst przed zamknięciem. W przeciwnym razie zasoby nie zostaną zwolnione i może wystąpić wyciek pamięci lub nieprawidłowe działanie programu. Jeśli masz pytania lub sugestie, napisz w komentarzach, na pewno odpowiem. Dziękuję za uwagę. Kod źródłowy pod linkiem Mój koszyk GitHub Treść kursu Ciąg dalszy...
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION