Framework a niezależność

Niespodzianka w spring'u

Ostatnio coraz więcej używam javowego springa, który codziennie mnie czymś zaskakuje. Przy okazji webcomponentowego szaleństwa tworzyłem prosty serwis do autoryzacji i chciałem obsłużyć go w innym serwisie. Wszystko było super, lecz potrzebowałem stworzyć takiego specjalnego beana w kontenerze DI – beana który będzie tworzony przy każdym requeście, a później w middlewarze czy innym interceptorze odpowiednio wypełniony i użyty w dalszej interpretacji żądania.

Brzmi prosto. Przecież mamy scope typu request, można go bez problemu użyć. Jak się okazuje nie w moim przypadku – ponieważ stwierdziłem że w mojej aplikacji getterów i setterów nie będzie. Dlaczego? To już osobny „gruby” temat. Przyjmijmy że taki mam kaprys, nawet jeśli jest głupi.


A zatem. Moim obiektem do wypełnienia jest UserContext który wygląda następujaco:

1
2
3
4
5
6
7
8
public class UserContext {
    private static Logger LOG = LoggerFactory.getLogger(UserContext.class);
    public String userId;
    public String name;
    public UserContext() {
        LOG.info(String.format("new %s initialized", UserContext.class.getName()));
    }
}

Niektórzy mogliby powiedzieć że dziwnie to wygląda w javie 🙂

Oraz rejestracja beana:

1
2
3
4
5
@Bean
@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserContext context(){
    return new UserContext();
}

Powinno działać prawda? Sprawdźmy. Przed obsługą każdego requestu przez kontroler, stworzyłem warstwę pośrednią która ma wypełniać kontekst. Dodałem tam prosty licznik który po trzecim requeście już nic nie ustawi. Wygląda to następująco:

1
2
3
4
5
6
7
@Autowired
private UserContext context;
private static int requestCount = 0;

// And inside method few lines later:
if (requestCount < 3)
    context.userId = String.valueOf(requestCount++)

Generuję kilka requestów. Teraz kontekst ma wartość „2”, a adres obiektu to: „com.orchowskia.stenografisto.management.api.middleware.UserContext@76a73ae9”. Identyczny obiekt jest wstrzyknięty do kontrolera.

Idąc o krok dalej, w następnym requeście adres obiektu powinien się zmienić, a userId powinien być nullem.
Tak się jednak nie dzieje. Log z konstruktora wyświetla:

2018-12-31 19:26:04.397 INFO 4386 --- [nio-8080-exec-9] c.o.s.m.api.middleware.UserContext : new com.orchowskia.stenografisto.management.api.middleware.UserContext initialized
Zaś adres to „com.orchowskia.stenografisto.management.api.middleware.UserContext@72284450” który różni się od poprzedniego, więc mamy do czynienia z nowym obiektem.
Jednak wartość dalej wynosi „2”!!! Dlaczego?! Nie mam pojęcia. Mój debugger ewidentnie twierdzi że wartość nie została nigdzie ustawiona. Jedynym wytłumaczeniem jest jakiś automagiczny mechanizm springa o którym nie mam pojęcia. Który w pewnym sensie zmarnował godzinę mojego życia. A tak wygląda cykl życia beana, może ktoś z was ma pomysł skąd takie zachowanie?

Spring w akcji - Craig Walls
Spring w akcji - Craig Walls

Dodajmy teraz gettery i settery do klasy beana:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserContext {
    private static Logger LOG = LoggerFactory.getLogger(UserContext.class);
    private String userId;
    private String name;
   
    public UserContext() {
      LOG.info(String.format("new %s initialized",UserContext.class.getName()));
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Po tych poprawkach wszystko działa, tak jak tego oczekiwałem. Wartość jest ustawiana przy każdym requeście, a jeśli nie – pozostaje pusta.

Czego się nauczyłem?

Po tego typu sytuacji, nie chcę powiedzieć że Spring jest zły, chociaż to najprowdopodobniej jest jakieś niedociągnięcie. Nie chcę też się wdawać w dyskusje dotyczące mojego podejścia. Chcę jedynie wspomnieć o tym że każdy framework wymusza na nas „coś” z czym musimy żyć, ograniczyć może nawet taką decyzję jak niestosowanie getterów i setterów.

Przypomniała mi się prelekcja Jarosława Pałki, który stwierdził że często:

framework rozwiązuje problemy programistów którzy go tworzą, a nie nasze

Zastanówmy się więc dodatkowy – szósty raz, czy warto zaimportować kolejny framework w realnym projekcie.

Zastanówmy się czy nie warto odizolować logikę biznesową od jakiegokolwiek frameworku, czy biblioteki.

Zastanówmy się czy warto stosować „indywidualistyczne” konwencje pisania kodu, tak jak w opisanym przypadku, gdyż metody get i set są naturalne dla kodu java.

Na koniec – jeszcze jedno rzucone w eter pytanie. Jak często twórcy „wielkich” frameworków faktycznie rozwiązują realne problemy? Nie śledzę dokładnie postępowań Pivotal od springa, wiem tylko że prowadzą support przy wdrażaniu swoich produktów. Tylko czy to wystarcza żeby podejmować dobre decyzje?



A co jeśli łyżeczka nie istnieje?
Close Menu