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?
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"); } }
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
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 Mapcache = 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 } }
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
Zalety i Wady Decorator Pattern
Zalety | Wady |
---|---|
Rozszerzanie funkcjonalności bez modyfikacji kodu | Może prowadzić do złożoności w debugowaniu |
Przestrzeganie Open/Closed Principle | Więcej obiektów w pamięci |
Możliwość łączenia multiple dekoratorów | Potencjalny narzut wydajnościowy |
Elastyczna konfiguracja w runtime | Trudność w śledzeniu łańcucha wywołań |
Tak! Możesz tworzyć dekoratory dla repozytoriów JPA. Szczególnie przydatne do dodawania cache’owania, auditingu czy custom walidacji.
Używaj logowania na poziomie DEBUG, IntelliJ debugger’a z step-into, oraz Spring Boot Actuator do monitorowania łańcucha wywołań.
Są podobne, ale Decorator dodaje funkcjonalność, podczas gdy Proxy kontroluje dostęp. W praktyce Spring często łączy oba wzorce.
Gdy chcesz dynamicznie dodawać funkcjonalności w runtime, łączyć multiple zachowania, lub gdy klasa jest final/nie można jej dziedziczyć.
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!