Używam Dependency Injection na wiosnę już od jakiegoś czasu i rozumiem, jak to działa oraz jakie są zalety i wady korzystania z niego. Kiedy jednak tworzę nową klasę, często zastanawiam się - czy tą klasą powinien zarządzać Spring IOC Container?
I nie chcę mówić o różnicach między adnotacją @Autowired, konfiguracją XML, iniekcją seterów, iniekcją konstruktora itp. Moje pytanie jest ogólne.
Załóżmy, że mamy usługę z konwerterem:
@Service
public class Service {
@Autowired
private Repository repository;
@Autowired
private Converter converter;
public List<CarDto> getAllCars() {
List<Car> cars = repository.findAll();
return converter.mapToDto(cars);
}
}
@Component
public class Converter {
public CarDto mapToDto(List<Car> cars) {
return new ArrayList<CarDto>(); // do the mapping here
}
}
Najwyraźniej konwerter nie ma żadnych zależności, więc nie jest konieczne, aby został on automatycznie napisany. Ale dla mnie wygląda to lepiej jako autowired. Kod jest bardziej przejrzysty i łatwy do przetestowania. Jeśli napiszę ten kod bez DI, usługa będzie wyglądać następująco:
@Service
public class Service {
@Autowired
private Repository repository;
public List<CarDto> getAllCars() {
List<Car> cars = repository.findAll();
Converter converter = new Converter();
return converter.mapToDto(cars);
}
}
Teraz znacznie trudniej to przetestować. Co więcej, nowy konwerter zostanie utworzony dla każdej operacji konwersji, nawet jeśli zawsze jest w tym samym stanie, co wydaje się narzutem.
Istnieją pewne dobrze znane wzorce w Spring MVC: Kontrolery korzystające z usług i Usługi korzystające z repozytoriów. Następnie, jeśli repozytorium jest autowiredowane (którym zwykle jest), to usługa również musi być autowiredowana. I to jest całkiem jasne. Ale kiedy używamy adnotacji @Component? Jeśli masz jakieś statyczne klasy użytkowania (takie jak konwertery, mapery) - czy je autoryzujesz?
Czy starasz się, aby wszystkie klasy były automatycznie programowane? Wtedy wszystkie zależności klasowe są łatwe do wstrzyknięcia (ponownie, łatwe do zrozumienia i przetestowania). A może próbujesz uruchomić się automatycznie tylko wtedy, gdy jest to absolutnie konieczne?
Spędziłem trochę czasu na szukaniu ogólnych zasad dotyczących korzystania z automatycznego okablowania, ale nie mogłem znaleźć żadnych konkretnych wskazówek. Zwykle ludzie mówią o „czy używasz DI? (Tak / nie)” lub „jaki typ zastrzyku zależności preferujesz”, co nie odpowiada na moje pytanie.
Byłbym wdzięczny za wszelkie wskazówki dotyczące tego tematu!
źródło
Odpowiedzi:
Zgadzam się z komentarzem @ ericW i chcę tylko dodać, pamiętaj, że możesz użyć inicjatorów, aby zachować kompaktowy kod:
lub
lub, jeśli klasa naprawdę nie ma żadnego stanu
Jednym z kluczowych kryteriów decydujących o tym, czy Spring powinna utworzyć instancję i wstrzyknąć fasolę, jest to, że fasola jest tak skomplikowana, że chcesz się z niej kpić podczas testowania? Jeśli tak, to wstrzyknij. Na przykład, jeśli konwerter wykonuje podróż w obie strony do dowolnego systemu zewnętrznego, należy zamiast tego uczynić go komponentem. Lub jeśli zwracana wartość ma duże drzewo decyzyjne z dziesiątkami możliwych odmian w oparciu o dane wejściowe, to wyśmiewaj je. I tak dalej.
Udało ci się już dobrze rozwinąć tę funkcjonalność i ją zamknąć, więc teraz jest tylko kwestia, czy jest wystarczająco złożona, aby uznać ją za oddzielną „jednostkę” do testowania.
źródło
Nie sądzę, że musisz @Autowiredować wszystkie swoje klasy, powinno to zależeć od rzeczywistego użycia, w twoich scenariuszach lepiej jest używać metody statycznej zamiast @Autowired. Nie widziałem żadnej korzyści użycia @Autowired dla tych prostych klas utils, a to absolutnie zwiększy koszt kontenera Spring, jeśli nie użyje go poprawnie.
źródło
Moja ogólna zasada opiera się na czymś, co powiedziałeś: testowalność. Zadaj sobie pytanie „ Czy mogę to łatwo przetestować? ”. Jeśli odpowiedź brzmi „tak”, to bez jakiegokolwiek innego powodu byłbym z tym w porządku. Jeśli więc opracujesz test jednostkowy w tym samym czasie, w którym się rozwijasz, zaoszczędzisz wiele bólu.
Jedynym potencjalnym problemem jest to, że jeśli konwerter zawiedzie, test usługi również się nie powiedzie. Niektórzy twierdzą, że należy kpić z wyników konwertera w testach jednostkowych. W ten sposób można szybciej zidentyfikować błędy. Ale ma swoją cenę: musisz wyśmiewać wszystkie wyniki konwerterów, gdy prawdziwy konwerter mógł wykonać pracę.
Zakładam również, że nie ma żadnego powodu, aby używać różnych konwerterów dto.
źródło
TL; DR: Hybrydowe podejście automatycznego okablowania dla DI i przekazywanie konstruktora dla DI może uprościć przedstawiony kod.
Patrzyłem na podobne pytania ze względu na pewne weblogiki z błędami / komplikacjami podczas uruchamiania frameworka wiosennego, obejmującymi zależności inicjalizacji @autowired bean. Zacząłem mieszać z innym podejściem DI: przekazywaniem konstruktorów. Wymaga warunków takich jak ty („Oczywiście, konwerter nie ma żadnych zależności, więc nie jest konieczne, aby był on automatycznie zapisany”). Niemniej jednak bardzo mi się podoba ta elastyczność i wciąż jest całkiem prosta.
lub nawet jako rif z odpowiedzi Roba
Może nie wymagać zmiany interfejsu publicznego, ale ja bym tego zrobił. Nadal
może być chroniony lub prywatny w celu ograniczenia zakresu tylko do celów testowych / rozszerzenia.
Kluczową kwestią jest to, że DI jest opcjonalny. Zapewniona jest wartość domyślna, którą można zastąpić w prosty sposób. Ma swoją słabość wraz ze wzrostem liczby pól, ale dla 1, a może 2 pól, wolę to pod następującymi warunkami:
Ostatni punkt (trochę OT, ale związany z tym, jak decydujemy, co / gdzie @autowired): klasa konwertera, jak przedstawiono, jest metodą użytkową (bez pól, bez konstruktora, może być statyczna). Może rozwiązaniem byłoby zastosowanie metody mapToDto () w klasie Cars? To znaczy, przenieś zastrzyk konwersji do definicji samochodów, gdzie prawdopodobnie jest już ściśle związany:
źródło
Myślę, że dobrym tego przykładem jest SecurityUtils lub UserUtils. Do każdej aplikacji Spring, która zarządza użytkownikami, potrzebujesz klasy Util z całą masą metod statycznych, takich jak:
getCurrentUser ()
isAuthenticated ()
isCurrentUserInRole (organ władzy)
itp.
i nigdy nie stworzyłem ich automatycznie. JHipster (którego używam jako niezłą ocenę najlepszych praktyk) nie robi tego.
źródło
Możemy oddzielić klasy na podstawie funkcji tej klasy, takich jak kontrolery, usługa, repozytorium, byt. Możemy wykorzystywać inne klasy do naszej logiki, nie tylko do celów kontrolerów, usług itp., Przy tej okazji możemy adnotować te klasy za pomocą @component. Spowoduje to automatyczne zarejestrowanie tej klasy w pojemniku sprężynowym. Jeśli jest zarejestrowany, będzie zarządzany przez kontener wiosenny. Pojęcie iniekcji zależności i inwersji kontroli może być dostarczone do tej klasy z adnotacjami.
źródło