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”. 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)
- 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)
<?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...
GO TO FULL VERSION