Wstrzykiwanie w tylko niektóre pola w konstruktorze

0
interface Processor {
    void do(); // only do, no setAuth method
}
@Component
class ProcessorImpl implements Processor {

    private Service service;
    private Authorization auth;

    // only service should be injected, and auth set by the client
    Processor (Service service, Authorization auth) {
        this.service = ...
        this.auth = ...
    }

    @Override
    void do() {
        ...
    }
    ...
}
@Component
class Service {
    ...
}
// no Spring component!
class Authorization {
    ...
}
@Component
class ProcessorFactory {
    private Processor processor;
    
    @Inject
    ProcessorContainer(Processor processor) {
        ...
    }
    ...
}

Jak kulturalnie ustawić Authorization dla ProcessorImpl w klasie ProcessorFactory? Tak by nie dopisywać do interfejsu Processor metody setAuthorization... i żeby wszystkie te klasy poza Authorization były magicznie tworzonymi komponentami przez Springa?

2

Hmm, nie wiem co dokładnie chcesz osiagnąć - ale przecież nie musisz dodwać metody setAuthorization do interfejsu i wszystkich klas implementujących. Możesz przecież mieć ją tylko w ProcessorImpl

0
mfabjanski napisał(a):

Hmm, nie wiem co dokładnie chcesz osiagnąć - ale przecież nie musisz dodwać metody setAuthorization do interfejsu i wszystkich klas implementujących. Możesz przecież mieć ją tylko w ProcessorImpl

@Component
class ProcessorFactory {
    private Processor processor;

    @Inject
    ProcessorContainer(Processor processor) {
        processor.setAuthorization(... // ERROR! There is no method setAuthorization in Processor interface
    }
    ...
}
2

Ogólnie @Autowired pozwala na 'magiczne' wstrzykiwanie zależności, ale nie jest o zalecana opcja (umieszczasz adnotacje nad polem i usuwasz argument z konstruktora). Jeżeli potrzebujesz coś takiego zrobić to całkiem możliwe, że próbujesz źle podejść do problemu. Może opiszesz co dokładnie chcesz osiągnąć - krok po kroku? :)

0

Mam pytanie w zasadzie co do takich przypadków: czy tutaj po prostu new Authorization() jest czymś złym? Czy zawsze powinniśmy lecieć w DI i wystrzegać się new?
@Shalom @jarekr000000 @Wibowit @Krolik @Koziołek @Kamil Żabiński @Charles_Ray

0
lavoholic napisał(a):

Mam pytanie w zasadzie co do takich przypadków: czy tutaj po prostu new Authorization() jest czymś złym? Czy zawsze powinniśmy lecieć w DI i wystrzegać się new?
@Shalom @jarekr000000 @Wibowit @Krolik @Koziołek @Kamil Żabiński @Charles_Ray

założmy, że to by było coś tkaiego: new Authorization(login, user, uri), gdzie login, user i uri podaje klient/użytkownik/plik z propertasami/baza danych itp.

2
  1. Nie rozumiem w ogóle twojego pytania.
  2. Ja sugeruje w ogóle nie używać nigdzie @Service. Zamiast tego zrób klasę @Configuration z metodami z @Bean gdzie ładnie sobie wszystko poskładasz. Nie ma wtedy dziwnych problemów z serii skąd się bierze XYZ ani jakichś nieoczkiwanych magicznych sytuacji. Tworzysz sobie tam wszystko normalnie przez new i nie masz takich dziwnych dylematów. Potem w takiej klasie od razu widać co się w ogóle dzieje w tej aplikacji.

@Configuration
public class MyAppConfig{
    @Bean
    MyService myService(){
        return new MyService();
    }
    @Bean
    MyService2 myService2(MyService myService, @Value("${some.property}") String property){
        return new MyService2(myService, property);
    }
}

Zaraz wpadnie Jarek i powie że w sumie od tego to jest już tylko jeden krok żeby w ogóle wyrzucić Springa z projektu, ale ja do takich drastycznych rzeczy nie namawiam.

Warto też zauważyć, że z tymi @Service bez IntelliJ to w ogóle nie ogarniesz co się dzieje w projekcie i skąd się biorą zależności ;] A nawet i z IntelliJ często nie wiadomo, bo nie wykryje ci że np. inny moduł ma takiego beana.

0

Dużo zależy od tego do czego będzie służyć klasa Authorization i jak wysoki poziom generyczności jest z tym związany. DI jest bardzo przydatne w czasie testów i pozwala na wprowadzenie wyższego poziomu abstrakcji do systemu, tzn. danego komponentu nie interesuje określony typ klasy, a jedynie interfejs - system może w miejsce interfejsu przekazać dowolną klasę implementującą, co np. pozwala bardzo prosto zmieniać źródło danych. W przypadku zastosowania dekompozycji dla klasy całkiem prawdopodobne jest postawnie kilku mniejszych artefaktów tworzonych bezpośrednio za pomocą konstruktora.

EDIT:

Jeżeli to faktycznie ma przechowywać dane wprowadzane przez użytkownika na starcie (zakładam to przez 'properties'), wtedy tej akcji nie powinna wykonywać ta klasa - to nie jej obszar odpowiedzialności. W przypadku Springa masz coś takiego jak @ConfigurationProperties, co automatycznie stworzy obiekt na podstawie pliku properties i udostępni go w kontenerze DI.

3

@Julian_: w momencie kiedy trafiasz w ProcessorFactory to już istnieje instancja Processor, bo coś do ProcessorContainer musi być przekazane. Takie trochę odwórcone DI. Użyj klas konfiguracyjnych i new

0
Aitwar napisał(a):

Ogólnie @Autowired pozwala na 'magiczne' wstrzykiwanie zależności, ale nie jest o zalecana opcja (umieszczasz adnotacje nad polem i usuwasz argument z konstruktora). Jeżeli potrzebujesz coś takiego zrobić to całkiem możliwe, że próbujesz źle podejść do problemu. Może opiszesz co dokładnie chcesz osiągnąć - krok po kroku? :)

Gdyby Service nie wymagał Authorization to zrobiłbym tak:

class App {
    
    private Processor processor;   
   
    @Inject
    App(Processor processor) {
        this.processor = ...
    }

    void do() {
        ...
        processor.do();
        ...
    }
    ...
}

ale wymaga, więc jak to zrobić? Jak chciałbym tak:

class App {
    
    private Processor processor;   
   
    @Inject
    App(Processor processor) {
        Properties props = initProps();
        Authorization auth = new Authorization(props.login, props.password, props.uri);
        processor.setAuth(auth);        
        this.processor = processor;
    }

    void do() {
        ...
        processor.do();
        ...
    }
    ...
}

ale wtedy muszę dodać do interfejsu Processor to brzydkie setProcessor(...

2
lavoholic napisał(a):

Mam pytanie w zasadzie co do takich przypadków: czy tutaj po prostu new Authorization() jest czymś złym? Czy zawsze powinniśmy lecieć w DI i wystrzegać się new?
@Shalom @jarekr000000 @Wibowit @Krolik @Koziołek @Kamil Żabiński @Charles_Ray

Na początku, jako antyspringowiec, chciałem powiedzieć, że to pytanie jest IHMO odwrotnie zadane i powinno brzmieć:

Czy zawsze powinniśmy lecieć w new i wystrzegać się DI?

Ale zdałem sobie sprawę, że pytanie jest bez sensu, ponieważ po odrzuceniu Springa lub Guice dalej jest to DI. Jedyne z czego rezygnujemy to Kontener DI.
Jednak dalej jak ludzie możemy wybrać sobie implementacje w main() lub w testach. Są nawet firmy które tak piszą mikroserwisy produkcyjne. Ponieważ mikroserwisy są względnie małe ten main() nie jest olbrzymi.
Niestety z niewiadomych powodów większość programistów boi się, że po odrzuceniu Springa ich kod magicznie zmieni się w Latającego Potwora Spaghetti zawierającego new w losowych miejscach

Więc poprawne pytanie powinno brzmieć:

Czy zawsze powinniśmy lecieć w new i wystrzegać się Kontenera DI?

I jeśli mamy w zespole panikarzy to możemy używać Springa, ale najlepiej tak, żeby łatwo można było go zastąpić Guice. A Guice używać tak, żeby łatwo móc go zastąpić czystym new

0

Zawsze, kiedy w kodzie klasy A robisz new B() tworzysz już na etapie kompilacji sztywne powiązanie między tymi klasami, a więc nie da rady podmienić implementacji. Teraz pytanie czy to dobrze, czy to źle. Jeśli B jest komponentem, to słabo, bo nie jesteś w stanie testować A samodzielnie bez B oraz nie możesz łatwo rozszerzać swojego kodu. W takim wypadku lepiej użyć DI, czyli instancje B dostarczyć z zewnątrz, najlepiej przez konstruktor. Oczywiście ktoś musi gdzieś zrobić new B(), ale z punktu widzenia klasy A masz rozszerzalność i testowalność.

2

@Julian_:
Nie rozumiem jaki problem chcesz rozwiązać. Kiedy się pojawia to Authorization?
Przy inicjalizacji aplikacji, czy per request/sesja?

Jak chcesz w Processorze odciąć się od inicjalizacji usługi, to może sobie zrobić jakąś abstrakcję,
np. ServiceProvider i z niego korzystać w implementacji Procesora. Interfejs nic nie musi wiedzieć na ten temat. ServiceProvider dostarczałby Ci już przygotowaną usługę, coś w stylu:

interace Processor {
  void doSth();
}

interface ServiceProvider {
  Service getService();
}


class ProcessorImpl implements Processor {
	...
	ProcessorImpl(ServiceProvider provider) {
		service = provider.getService()
	}
	
	void doSth() {
		...
		service.foobar();
		...
	}
}

class AuthenticationServiceProvider implements ServiceProvider {
	AuthenticationService service;
	
	AuthenticationServiceProvider(Authentication auth) {
		this.service = auth;
	}
	
	AuthenticationServiceProvider(String login, String pass, String uri) {
		this.service = new ... 
	}
	
	Service getService() { 
		return service; 
	} 
}

Bez Springa miałbyś coś takiego:

Processor proc = new ProcessorImpl( new AuthenticationServiceProvider("foo","bar","baz") ); 

W Springu nie chce mi się dumać, bo nie wiem czy zrozumiałem o co Ci chodzi i preferuję XMLe niż adnotacje ;-)

1

Jeśli chcesz, żeby Spring wstrzyknął Ci obiekt Authorization, możesz zrobić tak (pseudokod Javo-Kotlinowy):

@Configuration
class Config {
   @Bean
   Authorization auth() = new Autorization()
}

Wtedy Spring tworzy Ci komponent-singleton i wstrzykuje tam, gdzie każesz. Czy to odpowiada na Twoje pytanie? :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1