Decorator Pattern w Spring – Rozszerzaj Funkcjonalność Bez Modyfikacji Kodu

TL;DR: Decorator pattern to wzorzec projektowy który pozwala dodawać nowe funkcjonalności do obiektów bez modyfikowania ich kodu źródłowego. W Spring wykorzystujemy go do rozszerzania komponentów poprzez proxy, interceptory i aspecty.

Dlaczego Decorator Pattern Jest Ważny w Spring

Decorator pattern to jeden z najważniejszych wzorców projektowych w ekosystemie Spring Framework. Pozwala nam dodawać nowe zachowania do istniejących obiektów w sposób przezroczysty dla klienta. W Spring ten wzorzec jest sercem mechanizmów takich jak AOP (Aspecty), transakcje czy cache’owanie.

W praktyce oznacza to, że możemy na przykład automatycznie dodać logowanie, obsługę transakcji czy walidację do naszych serwisów bez modyfikowania ich kodu źródłowego.

Co się nauczysz:

  • Jak działa Decorator pattern i dlaczego jest użyteczny
  • Praktyczne implementacje dekoratora w Spring
  • Wykorzystanie proxy i interceptorów
  • Rozszerzanie funkcjonalności bez modyfikacji kodu
  • Częste pułapki i jak ich unikać

Wymagania wstępne:

  • Podstawowa znajomość Java i OOP
  • Podstawy Spring Framework i Dependency Injection
  • Znajomość interfejsów i polimorfizmu

Czym Jest Decorator Pattern?

Decorator Pattern – wzorzec strukturalny który pozwala dodawać nowe zachowania do obiektów poprzez umieszczenie ich w specjalnych obiektach opakowujących (wrapper objects).

Wyobraź sobie, że masz podstawowy serwis użytkowników:

public interface UserService {
    User findById(Long id);
    void save(User user);
}

@Service
public class BasicUserService implements UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public User findById(Long id) {
        return userRepository.findById(id);
    }
    
    @Override
    public void save(User user) {
        userRepository.save(user);
    }
}

Teraz chcesz dodać logowanie. Zamiast modyfikować BasicUserService, tworzysz dekorator:

@Service
@Primary
public class LoggingUserServiceDecorator implements UserService {
    
    private final UserService userService;
    private final Logger logger = LoggerFactory.getLogger(LoggingUserServiceDecorator.class);
    
    public LoggingUserServiceDecorator(@Qualifier("basicUserService") UserService userService) {
        this.userService = userService;
    }
    
    @Override
    public User findById(Long id) {
        logger.info("Finding user with id: {}", id);
        User user = userService.findById(id);
        logger.info("Found user: {}", user.getName());
        return user;
    }
    
    @Override
    public void save(User user) {
        logger.info("Saving user: {}", user.getName());
        userService.save(user);
        logger.info("User saved successfully");
    }
}
Dekorator implementuje ten sam interfejs co obiekt, który opakowuje. Dzięki temu klient nie wie, że korzysta z dekoratora – wszystko działa transparentnie.

Decorator Pattern w Spring Framework

Spring Framework intensywnie wykorzystuje Decorator pattern, szczególnie w mechanizmie AOP (Aspect-Oriented Programming). Gdy definiujesz aspecty, Spring automatycznie tworzy proxy obiektów, które działają jako dekoratory.

Przykład z @Transactional

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional  // Spring tworzy proxy-dekorator
    public void processOrder(Order order) {
        // Spring automatycznie opakowuje tę metodę w transakcję
        orderRepository.save(order);
        // Jeśli wystąpi wyjątek, transakcja zostanie cofnięta
    }
}

Gdy Spring widzi @Transactional, automatycznie tworzy proxy-dekorator który:
1. Rozpoczyna transakcję przed wywołaniem metody
2. Wywołuje oryginalną metodę
3. Commituje transakcję przy sukcesie lub rollbackuje przy błędzie

To jak kelner w restauracji (dekorator) który bierze twoje zamówienie (wywołanie metody), przekazuje je do kuchni (oryginalna metoda), a następnie przynosi ci jedzenie wraz z rachunkiem (dodatkowa funkcjonalność).

Tworzenie Własnych Dekoratorów w Spring

Możesz łatwo tworzyć własne dekoratory. Oto przykład dekoratora cache’ującego:

@Component
public class CachingUserServiceDecorator implements UserService {
    
    private final UserService userService;
    private final Map cache = new ConcurrentHashMap<>();
    
    public CachingUserServiceDecorator(@Qualifier("loggingUserServiceDecorator") UserService userService) {
        this.userService = userService;
    }
    
    @Override
    public User findById(Long id) {
        return cache.computeIfAbsent(id, userService::findById);
    }
    
    @Override
    public void save(User user) {
        userService.save(user);
        cache.put(user.getId(), user); // Aktualizuj cache
    }
}
Pro tip: Możesz łączyć multiple dekoratory tworząc łańcuch. Każdy dekorator dodaje swoją funkcjonalność: BasicService → LoggingDecorator → CachingDecorator → ValidationDecorator.

Konfiguracja Dekoratorów

W Spring Boot 2.x możesz elegancko konfigurować dekoratory:

@Configuration
public class ServiceConfiguration {
    
    @Bean
    @Primary
    public UserService userService(
            @Qualifier("basicUserService") UserService basicService,
            @Value("${app.logging.enabled:true}") boolean loggingEnabled,
            @Value("${app.caching.enabled:false}") boolean cachingEnabled) {
            
        UserService service = basicService;
        
        if (loggingEnabled) {
            service = new LoggingUserServiceDecorator(service);
        }
        
        if (cachingEnabled) {
            service = new CachingUserServiceDecorator(service);
        }
        
        return service;
    }
}

Częste Problemy i Rozwiązania

Typowy błąd: Zapominanie o adnotacji @Primary gdy masz multiple implementacje tego samego interfejsu. Spring nie będzie wiedział, którą wybrać.
Uwaga: Dekoratory mogą wpływać na wydajność, szczególnie gdy łączysz ich wiele. Każda warstwa dodaje narzut – monitoruj performance.

Zalety i Wady Decorator Pattern

ZaletyWady
Rozszerzanie funkcjonalności bez modyfikacji koduMoże prowadzić do złożoności w debugowaniu
Przestrzeganie Open/Closed PrincipleWięcej obiektów w pamięci
Możliwość łączenia multiple dekoratorówPotencjalny narzut wydajnościowy
Elastyczna konfiguracja w runtimeTrudność w śledzeniu łańcucha wywołań
Czy mogę używać dekoratorów z Spring Data JPA?

Tak! Możesz tworzyć dekoratory dla repozytoriów JPA. Szczególnie przydatne do dodawania cache’owania, auditingu czy custom walidacji.

Jak debugować aplikację z wieloma dekoratorami?

Używaj logowania na poziomie DEBUG, IntelliJ debugger’a z step-into, oraz Spring Boot Actuator do monitorowania łańcucha wywołań.

Czy Decorator pattern to to samo co Proxy pattern?

Są podobne, ale Decorator dodaje funkcjonalność, podczas gdy Proxy kontroluje dostęp. W praktyce Spring często łączy oba wzorce.

Kiedy używać dekoratora zamiast dziedziczenia?

Gdy chcesz dynamicznie dodawać funkcjonalności w runtime, łączyć multiple zachowania, lub gdy klasa jest final/nie można jej dziedziczyć.

Czy dekoratory wpływają na wydajność?

Tak, każda warstwa dodaje minimalny narzut. W praktyce jest to zazwyczaj pomijalny koszt w porównaniu z korzyściami z czystszego kodu.

Przydatne zasoby:

🚀 Zadanie dla Ciebie

Stwórz własny dekorator dla serwisu produktów, który będzie:

  • Logował wszystkie operacje (czas wykonania, parametry)
  • Walidował czy cena produktu nie jest ujemna
  • Cache’ował wyniki wyszukiwania na 5 minut

Skonfiguruj go w Spring tak, żeby można było włączać/wyłączać poszczególne funkcjonalności przez application.properties.

Jakie inne wzorce projektowe chciałbyś poznać w kontekście Spring Framework? Podziel się w komentarzach!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry