SlideShare a Scribd company logo
IDZ DO
         PRZYK£ADOWY ROZDZIA£

                           SPIS TREŒCI
                                         Spring Framework.
                                         Profesjonalne tworzenie
           KATALOG KSI¥¯EK               oprogramowania w Javie
                      KATALOG ONLINE     Autorzy: Rod Johnson, Juergen Hoeller, Alef
                                         Arendsen, Thomas Risberg, Colin Sampaleanu
                                         T³umaczenie: Piotr Rajca, Miko³aj Szczepaniak
       ZAMÓW DRUKOWANY KATALOG           ISBN: 83-246-0208-9
                                         Tytu³ orygina³u: Professional Java Development
              TWÓJ KOSZYK                with the Spring Framework
                                         Format: B5, stron: 824
                    DODAJ DO KOSZYKA
                                         Spring to szkielet wytwarzania aplikacji (framework), dziêki któremu proces budowania
                                         oprogramowania w jêzyku Java dla platformy J2EE staje siê znacznie prostszy
         CENNIK I INFORMACJE             i efektywniejszy. Spring oferuje us³ugi, które mo¿na z powodzeniem u¿ywaæ
                                         w wielu œrodowiskach — od apletów po autonomiczne aplikacje klienckie, od aplikacji
                                         internetowych pracuj¹cych w prostych kontenerach serwletów po z³o¿one systemy
                   ZAMÓW INFORMACJE      korporacyjne pracuj¹ce pod kontrol¹ rozbudowanych serwerów aplikacji J2EE.
                     O NOWOŒCIACH
                                         Spring pozwala na korzystanie z mo¿liwoœci programowania aspektowego, znacznie
                                         sprawniejsz¹ obs³ugê relacyjnych baz danych, b³yskawiczne budowanie graficznych
                       ZAMÓW CENNIK      interfejsów u¿ytkownika oraz integracjê z innymi szkieletami takimi, jak Struts czy JSF.
                                         Ksi¹¿ka „Spring Framework. Profesjonalne tworzenie oprogramowania w Javie” odkryje
                 CZYTELNIA               przed Tob¹ wszystkie tajniki stosowania Springa w procesie wytwarzania systemów
                                         informatycznych dla platformy J2EE. Dowiesz siê, jak efektywnie korzystaæ
          FRAGMENTY KSI¥¯EK ONLINE       z najwa¿niejszych sk³adników szkieletu Spring i poznasz ich rolê w budowaniu aplikacji.
                                         Nauczysz siê nie tylko tego, co mo¿na zrealizowaæ za pomoc¹ poszczególnych us³ug,
                                         ale tak¿e tego, w jaki sposób zrobiæ to najlepiej. W kolejnych æwiczeniach
                                         przeanalizujesz proces tworzenie kompletnej aplikacji w oparciu o Spring.
                                         W ksi¹¿ce poruszono m.in. tematy:
                                             • Struktura szkieletu Spring
                                             • Tworzenie komponentów i definiowanie zale¿noœci pomiêdzy nimi
                                             • Testowanie aplikacji i testy jednostkowe
                                             • Programowanie aspektowe w Spring
                                             • Po³¹czenia z relacyjnymi bazami danych za pomoc¹ JDBC
                                             • Zarz¹dzanie transakcjami
Wydawnictwo Helion                           • Korzystanie z mechanizmu Hibernate
ul. Chopina 6                                • Zabezpieczanie aplikacji
44-100 Gliwice                               • Stosowanie szkieletu Web MVC
tel. (32)230-98-63                          Przekonaj siê, jak Spring mo¿e zmieniæ Twoj¹ pracê nad tworzeniem aplikacji J2EE
e-mail: helion@helion.pl
Wstęp .........................................................................................................................................................19

Rozdział 1. Wprowadzenie do Springa .................................................................................................... 27
     Dlaczego Spring? ........................................................................................................ 27
         Problemy związane z tradycyjnym podejściem do programowania dla platformy J2EE ... 27
         Lekkie frameworki .................................................................................................. 31
         Podstawowe składniki Springa ................................................................................ 32
     Zalety Springa ............................................................................................................ 34
     Kontekst Springa ........................................................................................................ 37
         Technologie ........................................................................................................... 37
         Techniki ................................................................................................................ 51
         Związki z pozostałymi frameworkami ....................................................................... 52
     Budowa architektury aplikacji opartej na frameworku Spring ........................................... 56
         Szerszy kontekst ................................................................................................... 57
         Trwałość i integracja .............................................................................................. 58
         Obiekty usług biznesowych ..................................................................................... 62
         Prezentacja ........................................................................................................... 63
     Przyszłość .................................................................................................................. 65
         Harmonogram wydawania kolejnych wersji ............................................................... 66
         Ewolucja Javy i platformy J2EE ................................................................................ 66
         Współczesny kontekst technologiczny ...................................................................... 69
         Standardy i otwarty dostęp do kodu źródłowego ....................................................... 69
     Projekt Spring i społeczność programistów .................................................................... 70
         Historia ................................................................................................................. 71
         Krótkie omówienie modułów Springa ....................................................................... 73
         Obsługiwane środowiska ........................................................................................ 78
     Podsumowanie ........................................................................................................... 78

Rozdział 2. Fabryka komponentów i kontekst aplikacji ......................................................................... 81
     Odwracanie kontroli i wstrzykiwanie zależności .............................................................. 82
        Różne formy wstrzykiwania zależności ..................................................................... 85
        Wybór pomiędzy wstrzykiwaniem przez metody ustawiające
         a wstrzykiwaniem przez konstruktory ..................................................................... 89
4        Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

         Kontener .................................................................................................................... 91
            Fabryka komponentów ........................................................................................... 91
            Kontekst aplikacji .................................................................................................. 93
            Uruchamianie kontenera ........................................................................................ 95
            Korzystanie komponentów uzyskiwanych z fabryki ..................................................... 97
            Konfiguracja komponentów w formacie XML ............................................................. 98
            Podstawowa definicja komponentu .......................................................................... 99
            Definiowanie zależności komponentów .................................................................. 102
            Zarządzanie cyklem życia komponentu ................................................................... 113
            Abstrakcja dostępu do usług i zasobów ................................................................. 116
            Wielokrotne wykorzystywanie tych samych definicji komponentów ............................ 122
            Stosowanie postprocesorów do obsługi niestandardowych komponentów i kontenerów .. 126
         Podsumowanie ......................................................................................................... 133

    Rozdział 3. Zaawansowane mechanizmy kontenera ...........................................................................135
         Abstrakcje dla niskopoziomowych zasobów ................................................................. 136
             Kontekst aplikacji jako implementacja interfejsu ResourceLoader ............................ 136
             Kontekst aplikacji jako źródło komunikatów ........................................................... 139
         Zdarzenia aplikacji .................................................................................................... 142
         Zarządzanie kontenerem ............................................................................................ 145
             Ścieżki lokalizacji zasobów przekazywane do konstruktorów implementacji
               interfejsu ApplicationContext .............................................................................. 145
             Deklaratywne korzystanie z kontekstów aplikacji .................................................... 147
             Podział definicji kontenera na wiele plików ............................................................. 149
             Strategie obsługi modułów ................................................................................... 151
             Singletony obsługujące dostęp do kontenera ......................................................... 154
         Pomocnicze komponenty fabrykujące .......................................................................... 155
             Komponent PropertyPathFactoryBean .................................................................... 155
             Komponent FieldRetrievingFactoryBean ................................................................. 156
             Komponent MethodInvokingFactoryBean ................................................................ 157
         Edytory właściwości oferowane w ramach Springa ........................................................ 158
         Strategie testowania ................................................................................................. 159
             Testy jednostkowe ............................................................................................... 160
             Testy wykorzystujące kontener Springa .................................................................. 163
         Rozwiązania alternatywne względem konfiguracji w formacie XML ................................. 166
             Definicje konfiguracji w plikach właściwości ........................................................... 166
             Programowe definicje komponentów ...................................................................... 168
             Pozostałe formaty ................................................................................................ 168
         Materiały dodatkowe ................................................................................................. 169
         Podsumowanie ......................................................................................................... 169

    Rozdział 4. Spring i AOP ...........................................................................................................................171
         Cele ......................................................................................................................... 171
         Założenia ................................................................................................................. 173
         Przykład ................................................................................................................... 173
         Framework programowania aspektowego Springa ........................................................ 177
             Łańcuch przechwytywania ..................................................................................... 178
             Zalety i wady ....................................................................................................... 178
             Rada .................................................................................................................. 180
             Przecięcia ........................................................................................................... 186
Spis treści            5

        Doradcy .............................................................................................................. 193
        Integracja z kontenerem IoC Springa ..................................................................... 195
        Analizowanie i zmiana stanu pośrednika w czasie wykonywania programu ................ 212
        Programowe tworzenie pośredników ...................................................................... 213
    Korzystanie z zaawansowanych funkcji Spring AOP ....................................................... 214
        Źródła obiektów docelowych ................................................................................. 214
        Wczesne kończenie łańcucha przechwytywania ....................................................... 221
        Stosowanie wprowadzeń ...................................................................................... 221
        Udostępnianie bieżącego pośrednika .................................................................... 224
        Udostępnianie bieżącego egzemplarza interfejsu MethodInvocation ......................... 225
        Zrozumienie typów pośredników ............................................................................ 226
        Diagnozowanie i testowanie .................................................................................. 228
        Rozmaitości ........................................................................................................ 231
    Integracja z innymi frameworkami programowania aspektowego ................................... 234
        Cele ................................................................................................................... 235
        Integracja z narzędziem AspectJ ............................................................................ 235
        AspectWerkz ....................................................................................................... 242
    Materiały dodatkowe ................................................................................................. 243
    Podsumowanie ......................................................................................................... 243

Rozdział 5. Obiekty DAO i framework Spring JDBC .............................................................................245
    Wzorzec obiektów dostępu do danych ......................................................................... 246
    Wprowadzenie do Spring JDBC ................................................................................... 248
       Motywacja — problemy bezpośredniego korzystania z interfejsu JDBC ...................... 248
       W czym może pomóc Spring? ................................................................................ 251
       Prosty przykład .................................................................................................... 251
    Budowa warstwy dostępu do danych dla przykładowej aplikacji ..................................... 253
       Model danych stosowany w przykładowej aplikacji .................................................. 253
       Źródło danych ...................................................................................................... 254
       Tłumaczenie wyjątków .......................................................................................... 256
    Operacje klasy JdbcTemplate ..................................................................................... 259
       Stosowanie metod zwrotnych ................................................................................ 259
       Metody pomocnicze klasy JdbcTemplate ................................................................ 261
       Wykonywanie prostych zapytań za pośrednictwem klasy JdbcTemplate ..................... 262
       Wykonywanie prostych aktualizacji za pośrednictwem klasy JdbcTemplate ................ 263
       Zaawansowane zastosowania klasy JdbcTemplate ................................................. 264
       Obsługa interfejsu RowSet ................................................................................... 266
    Klasy obsługujące operacje na relacyjnych systemach zarządzania bazami danych ......... 267
       Klasy SqlQuery i MappingSqlQuery ........................................................................ 268
       Operacje wstawiania i aktualizacji realizowane za pomocą klasy SqlUpdate .............. 271
       Aktualizowanie zbioru wynikowego za pomocą klasy UpdateSqlQuery ....................... 272
       Generowanie kluczy głównych ............................................................................... 273
       Uzyskiwanie kluczy wygenerowanych przez bazę danych .......................................... 274
       Wywoływanie procedur składowanych .................................................................... 275
    Zagadnienia zaawansowane ....................................................................................... 278
       Uruchamianie Spring JDBC na serwerze aplikacji .................................................... 278
       Stosowanie niestandardowych mechanizmów tłumaczenia wyjątków ........................ 280
       Odczytywanie i zapisywanie danych obiektów LOB .................................................. 284
       Aktualizacje wsadowe .......................................................................................... 289
       Zaawansowane techniki korzystania z procedur składowanych ................................. 290
6       Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

        Zagadnienia dodatkowe ............................................................................................. 295
           Wydajność .......................................................................................................... 295
           Kiedy należy używać biblioteki JDBC,
             a kiedy narzędzi odwzorowań obiektowo-relacyjnych? ........................................... 296
           Wersje biblioteki JDBC i platformy J2EE ................................................................. 296
        Podsumowanie ......................................................................................................... 297

    Rozdział 6. Zarządzanie transakcjami i źródłami danych .................................................................299
        Pojęcia podstawowe .................................................................................................. 299
            Czym jest transakcja? .......................................................................................... 300
            Właściwości ACID ................................................................................................ 300
            Sterowanie współbieżnością ................................................................................. 303
        Transakcje i platforma J2EE ....................................................................................... 303
            Transakcje lokalne ............................................................................................... 304
            Transakcje globalne (rozproszone) ........................................................................ 304
            Propagowanie transakcji ....................................................................................... 305
            Wyznaczanie granic pomiędzy transakcjami ............................................................ 305
        Przykład obsługi transakcji w Springu .......................................................................... 306
        Wprowadzenie do oferowanej przez Spring warstwy abstrakcji ponad transakcjami ......... 308
            Przegląd możliwych opcji sterowania transakcjami .................................................. 310
            Definicja transakcji .............................................................................................. 312
            Status transakcji ................................................................................................. 314
            Strategie wyznaczania granic transakcji ................................................................. 314
            Strategie zarządzania transakcjami ....................................................................... 328
        Deklaracje źródeł danych ........................................................................................... 338
            Źródła lokalne bez puli ......................................................................................... 338
            Źródła lokalne z pulą ............................................................................................ 339
            JNDI ................................................................................................................... 340
            Wybór pomiędzy lokalnym źródłem danych a źródłem danych JNDI ........................... 341
        Podsumowanie ......................................................................................................... 341

    Rozdział 7. Odwzorowania obiektowo-relacyjne ................................................................................343
        Pojęcia podstawowe .................................................................................................. 344
           Podstawy odwzorowań obiektowo-relacyjnych ......................................................... 345
           Obiektowe języki zapytań ...................................................................................... 346
           Przezroczyste utrwalanie ....................................................................................... 347
           Kiedy należy korzystać z narzędzi odwzorowań obiektowo-relacyjnych? ..................... 348
        Obsługa odwzorowań obiektowo-relacyjnych w Springu ................................................. 349
           Obiekty dostępu do danych (DAO) ......................................................................... 349
           Zarządzanie transakcjami ..................................................................................... 350
        iBATIS SQL Maps ...................................................................................................... 351
           Plik odwzorowania ............................................................................................... 352
           Implementacja interfejsu DAO ............................................................................... 354
           Konfiguracja w kontekście Springa ........................................................................ 356
           Zarządzanie transakcjami ..................................................................................... 357
           Podsumowanie analizy narzędzia iBATIS ................................................................ 359
        Hibernate ................................................................................................................. 360
           Plik odwzorowań .................................................................................................. 362
           Implementacja interfejsu DAO ............................................................................... 363
           Konfiguracja w kontekście Springa ........................................................................ 366
           Zarządzanie transakcjami ..................................................................................... 370
Spis treści             7

        Otwarta sesja w widoku ........................................................................................ 377
        Obsługa obiektów BLOB i CLOB ............................................................................ 381
        Hibernate: podsumowanie .................................................................................... 383
     JDO ......................................................................................................................... 385
        Cykl życia trwałego obiektu ................................................................................... 386
        Implementacje interfejsów DAO ............................................................................ 387
        Konfiguracja kontekstu aplikacji Springa ................................................................ 389
        Zarządzanie transakcjami ..................................................................................... 391
        Cykl życia egzemplarza PersistenceManager .......................................................... 392
        Otwarty egzemplarz PersistenceManager w widoku ................................................. 393
        Dialekt JDO ......................................................................................................... 396
        Podsumowanie analizy technologii JDO .................................................................. 397
     Pozostałe narzędzia odwzorowań obiektowo-relacyjnych ............................................... 399
        Apache OJB ......................................................................................................... 399
        TopLink ............................................................................................................... 401
        Cayenne ............................................................................................................. 403
        Specyfikacja JSR-220 ........................................................................................... 403
     Podsumowanie ......................................................................................................... 404

Rozdział 8. Lekki framework zdalnego dostępu ..................................................................................407
     Pojęcia podstawowe i zakres tematyczny rozdziału ....................................................... 408
     Jednolity styl konfiguracji ........................................................................................... 410
     Hessian i Burlap ....................................................................................................... 412
        Uzyskiwanie dostępu do usługi ............................................................................. 414
        Eksportowanie usługi ........................................................................................... 416
     Obiekt wywołujący HTTP ............................................................................................. 417
        Uzyskiwanie dostępu do usługi ............................................................................. 419
        Eksportowanie usługi ........................................................................................... 420
        Opcje konfiguracyjne ............................................................................................ 421
     RMI ......................................................................................................................... 422
        Uzyskiwanie dostępu do usługi ............................................................................. 424
        Strategie wyszukiwania pieńków ........................................................................... 426
        Eksportowanie usługi ........................................................................................... 428
        Opcje konfiguracyjne ............................................................................................ 429
        RMI-IIOP .............................................................................................................. 429
     Usługi sieciowe przez JAX-RPC ................................................................................... 430
        Uzyskiwanie dostępu do usługi ............................................................................. 432
        Eksportowanie usługi ........................................................................................... 435
        Niestandardowe odwzorowania typów .................................................................... 437
     Podsumowanie ......................................................................................................... 439

Rozdział 9. Usługi wspomagające .........................................................................................................443
     JMS ......................................................................................................................... 443
        Wprowadzenie ..................................................................................................... 444
        Cele obsługi JMS w Springu .................................................................................. 445
        Dostęp do JMS za pomocą szablonu ..................................................................... 446
        Obsługa wyjątków ................................................................................................ 449
        Zarządzanie obiektem ConnectionFactory .............................................................. 449
        Konwertery komunikatów ...................................................................................... 450
        Zarządzanie miejscami docelowymi ....................................................................... 451
8        Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

            Zarządzanie transakcjami ..................................................................................... 453
            Klasa JmsGatewaySupport ................................................................................... 453
            W przyszłości ....................................................................................................... 455
         Planowanie w Springu ............................................................................................... 455
            Czasomierze a Quartz .......................................................................................... 456
            Czasomierze ....................................................................................................... 457
            Framework Quartz ................................................................................................ 459
         Wysyłanie poczty elektronicznej w Springu ................................................................... 466
            Od czego zacząć? ................................................................................................ 466
            Wielokrotne stosowanie tej samej sesji pocztowej .................................................. 467
            Wysyłanie wiadomości przy użyciu COS .................................................................. 468
            Menedżer poczty elektronicznej ogólnego zastosowania .......................................... 468
         Zastosowanie języków skryptowych ............................................................................ 473
            Spójny model ...................................................................................................... 474
            Inne języki skryptowe ........................................................................................... 479
         Podsumowanie ......................................................................................................... 479

    Rozdział 10. System bezpieczeństwa Acegi dla Springa ......................................................................481
         Sposoby zabezpieczania aplikacji korporacyjnych ......................................................... 481
             Typowe wymagania .............................................................................................. 481
             System bezpieczeństwa Acegi w zarysie ................................................................ 483
             Usługa uwierzytelniania i autoryzacji Javy ............................................................... 488
             Specyfikacja serwletów ........................................................................................ 491
         Podstawy systemu bezpieczeństwa Acegi .................................................................... 493
             Uwierzytelnianie ................................................................................................... 493
             Przechowywanie obiektów Authentication ............................................................... 498
             Autoryzacja .......................................................................................................... 500
             Bezpieczeństwo obiektów dziedziny ....................................................................... 503
         Przykład ................................................................................................................... 505
             Wprowadzenie do przykładowej aplikacji ................................................................ 505
             Implementacja pozbawiona mechanizmów bezpieczeństwa ..................................... 507
             Rozwiązanie wykorzystujące zabezpieczenia ........................................................... 509
             Uwierzytelnianie ................................................................................................... 509
             Autoryzacja .......................................................................................................... 510
         Podsumowanie ......................................................................................................... 514

    Rozdział 11. Spring i EJB ..........................................................................................................................517
         Określanie, czy stosowanie komponentów EJB jest potrzebne ....................................... 518
         Dostęp do komponentów EJB ..................................................................................... 519
            Typowe rozwiązanie .............................................................................................. 520
            Rozwiązanie z wykorzystaniem Springa .................................................................. 521
         Tworzenie komponentów EJB w Springu ...................................................................... 529
            Bezstanowe komponenty sesyjne .......................................................................... 529
            Stanowe komponenty sesyjne ............................................................................... 532
            Komponenty sterowane komunikatami .................................................................. 534
            Kilka słów o XDoclet ............................................................................................ 535
         Dostęp na zasadzie singletonu — rozwiązanie dobre czy złe? ....................................... 536
            ContextSingletonBeanFactoryLocator i SingletonBeanFactoryLocator ........................ 537
            Wspólny kontekst jako „rodzic” kontekstu aplikacji sieciowej .................................. 540
            Stosowanie wspólnego kontekstu w komponentach EJB ......................................... 543
Spis treści             9

     Zagadnienia związane z testowaniem ......................................................................... 543
        Implementacja funkcjonalności biznesowej w zwyczajnych obiektach Javy ................. 544
        Zastosowanie imitacji kontenera EJB ..................................................................... 544
        Testy integracyjne w środowisku serwera aplikacji .................................................. 545
     Podsumowanie ......................................................................................................... 546

Rozdział 12. Framework Web MVC .......................................................................................................547
     Prosty przykład ......................................................................................................... 548
     Ogólna architektura ................................................................................................... 550
         Pojęcia związane z Web MVC ................................................................................ 550
         Ogólne działanie Web MVC wykorzystujące serwlet dyspozytora oraz kontrolery ......... 551
         Wymagania dobrego sieciowego frameworku MVC .................................................. 552
         Elementy Spring Web MVC ................................................................................... 554
     Komponenty infrastruktury ......................................................................................... 556
         DispatcherServlet ................................................................................................ 557
         WebApplicationContext ......................................................................................... 560
     Przepływ sterowania związanego z przetwarzaniem żądań ............................................. 563
     Typowy układ aplikacji Spring MVC ............................................................................. 566
     Odwzorowania HandlerMapping .................................................................................. 568
         BeanNameUrlHandlerMapping .............................................................................. 568
         SimpleUrlHandlerMapping .................................................................................... 569
         CommonsPathMapUrlHandlerMapping ................................................................... 571
         Więcej niż jedno odwzorowanie HandlerMapping ..................................................... 572
     HandlerExecutionChain oraz obiekty przechwytujące .................................................... 573
         WebContentInterceptor ........................................................................................ 575
         UserRoleAuthorizationInterceptor .......................................................................... 576
         Inne obiekty przechwytujące udostępniane przez Spring MVC .................................. 577
     Obiekty obsługi oraz ich adaptery ............................................................................... 577
     ModelAndView oraz ViewResolver ............................................................................... 577
         UrlBasedViewResolver ......................................................................................... 578
         BeanNameViewResolver oraz XmlViewResolver ...................................................... 579
         ResourceBundleViewResolver ............................................................................... 579
         Tworzenie łańcucha obiektów ViewResolver ........................................................... 580
     Zmiana i wybór ustawień lokalnych ............................................................................. 582
     Obiekty HandlerExceptionResolver .............................................................................. 584
     Kontrolery ................................................................................................................ 587
         WebContentGenerator .......................................................................................... 587
         AbstractController ................................................................................................ 587
         UrlFilenameViewController .................................................................................... 588
         ParametrizableViewController ................................................................................ 589
         MulitActionController ............................................................................................ 590
     Wiązanie danych ....................................................................................................... 591
         Przydatne możliwości używane podczas wiązania danych ........................................ 592
     Praktyczne przykłady zastosowania kontrolerów ........................................................... 593
         Przeglądanie listy przedstawień przy użyciu kontrolera AbstractController ................. 594
         Rezerwacja miejsc ............................................................................................... 596
     Kreatory ................................................................................................................... 606
         Podstawowa konfiguracja ..................................................................................... 606
         Metody szablonowe ............................................................................................. 607
         Przepływ sterowania kreatora ................................................................................ 608
         Zmiany stron, numeracja oraz inne akcje ............................................................... 609
10        Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

          Rozszerzanie infrastruktury obiektów obsługi ............................................................... 610
          Przesyłanie plików na serwer ..................................................................................... 611
              Konfiguracja resolvera .......................................................................................... 611
              Tworzenie formularza do przesyłania plików na serwer ............................................ 612
              Obsługa przesłanych danych ................................................................................. 612
          Testowanie kontrolerów ............................................................................................. 614
              Testowanie bez kontekstu aplikacji ....................................................................... 614
              Bardziej szczegółowe testowanie przy użyciu obiektów pozornych ............................. 615
              Testowanie przy wykorzystaniu pełnego kontekstu aplikacji ..................................... 616
              Inne sposoby testowania aplikacji ......................................................................... 618
          Podsumowanie ......................................................................................................... 618

     Rozdział 13. Technologie widoków .........................................................................................................621
          Przykład ................................................................................................................... 622
              Ogólna konfiguracja ............................................................................................. 623
              JavaServer Pages ................................................................................................ 623
              FreeMarker ......................................................................................................... 624
              Generacja dokumentów PDF przy użyciu biblioteki iText ........................................... 624
          Czynniki mające wpływ na wybór technologii generacji widoków ..................................... 625
          Obiekty widoków i modeli ........................................................................................... 626
          Możliwości klasy AbstractView ................................................................................... 628
              Zgłaszanie nowych żądań przy użyciu widoków RedirectView .................................... 629
              Użycie prefiksu widoku do generacji przekazań i przekierowań ................................. 631
          JavaServer Pages ...................................................................................................... 631
              Konfiguracja aplikacji korzystającej z JSP ............................................................... 632
              Tworzenie formularzy przy użyciu znaczników niestandardowych ............................... 633
              Wykorzystanie plików znaczników do tworzenia elementów nadających się
                do wielokrotnego zastosowania .......................................................................... 640
          Velocity oraz FreeMarker ............................................................................................ 642
              Konfiguracja resolvera widoków ............................................................................ 642
              Stosowanie makr ułatwiających tworzenie formularzy .............................................. 645
          Tiles ........................................................................................................................ 648
          Widoki bazujące na dokumentach XML i XSLT ............................................................. 652
          Widoki generujące arkusze Excela lub inne dokumenty ................................................. 654
              Generacja arkusza kalkulacyjnego na podstawie listy przedstawień .......................... 654
              Wykorzystanie szablonów jako podstawy do generacji arkuszy kalkulacyjnych ........... 656
              Inne widoki generujące dokumenty ........................................................................ 656
              Zastosowanie obiektów przechwytujących HandlerInterceptor w celu rozróżniania
                wybranego typu odpowiedzi ................................................................................ 657
          Implementacja widoków niestandardowych ................................................................. 659
              Interfejs View i klasa AbstractView ........................................................................ 659
              Implementacja widoku generującego dane XML na podstawie obiektu danych .......... 660
              Czynniki, jakie należy uwzględniać podczas tworzenia widoków niestandardowych ..... 662
          Podsumowanie ......................................................................................................... 662

     Rozdział 14. Integracja z innymi frameworkami sieciowymi .............................................................665
          Czynniki wpływające na wybór używanego frameworka MVC .......................................... 666
              Porównanie tradycyjnych frameworków Web MVC .................................................... 666
          Integracja z frameworkiem Spring. Pojęcia podstawowe ............................................... 682
          Integracja z frameworkiem WebWork .......................................................................... 684
              Przygotowanie obiektu ObjectFactory ..................................................................... 684
Spis treści              11

     Integracja z frameworkiem Struts ............................................................................... 685
         Stosowanie obiektów ActionSupport ...................................................................... 686
         Stosowanie klas DelegatingRequestProcessor oraz DelegatingActionProxy ............... 687
         Stosowanie bazowej akcji dysponującej możliwością automatycznego wiązania ......... 691
     Integracja z frameworkiem Tapestry ............................................................................ 692
         Pobieranie komponentów na potrzeby Tapestry ...................................................... 692
         Klasa strony ........................................................................................................ 693
         Definicja strony ................................................................................................... 693
         Szablon strony ..................................................................................................... 695
         Ostatnie informacje o integracji z frameworkiem Tapestry ....................................... 696
     Integracja z JavaServer Faces .................................................................................... 697
     Podsumowanie ......................................................................................................... 698

Rozdział 15. Aplikacja przykładowa ......................................................................................................701
     Wybór technologii serwera ......................................................................................... 702
     Warstwy aplikacji ...................................................................................................... 702
     Warstwa trwałości ..................................................................................................... 704
        Model danych ...................................................................................................... 704
        Model obiektów dziedziny ..................................................................................... 705
        Odwzorowania obiektowo-relacyjne ........................................................................ 707
        Implementacja DAO ............................................................................................. 712
        Konfiguracja dostępu do danych ........................................................................... 714
     Warstwa usług biznesowych ....................................................................................... 715
        Usługi ................................................................................................................. 715
        Kontekst aplikacji ................................................................................................ 716
     Warstwa sieciowa ..................................................................................................... 718
        Przepływ sterowania w aplikacji ............................................................................. 718
        Konfiguracja aplikacji przy użyciu pliku web.xml ...................................................... 720
        Kontrolery sieciowe .............................................................................................. 721
        Technologia widoków ........................................................................................... 723
     Porównanie z implementacją przedstawioną w książce J2EE Design and Development ... 725
        Prostsza technologia ............................................................................................ 725
        Zmiany związane z bazą danych ............................................................................ 725
     Konfiguracja serwera ................................................................................................. 726
        MySQL ................................................................................................................ 726
        Tomcat ............................................................................................................... 727
     Kompilacja aplikacji i jej wdrożenie ............................................................................. 727
        Utworzenie tabel bazy danych i zapisanie w nich informacji ..................................... 727
        Kompilacja aplikacji i wdrożenie jej na serwerze Tomcat ......................................... 728
     Podsumowanie ......................................................................................................... 728

Rozdział 16. Wnioski ...............................................................................................................................729
     Problemy, jakie rozwiązuje Spring ............................................................................... 729
     Rozwiązania przyjęte w Springu .................................................................................. 730
     Porady związane ze stosowaniem Springa ................................................................... 733
         Wybór technologii ................................................................................................ 733
         Warstwy aplikacji ................................................................................................. 736
         Struktura aplikacji ................................................................................................ 744
         Testowanie aplikacji ............................................................................................. 749
     Projekty związane z frameworkiem Spring .................................................................... 752
         System bezpieczeństwa Acegi Security .................................................................. 752
         Inne projekty ....................................................................................................... 753
12        Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

          Spring poza środowiskiem J2EE ................................................................................. 754
          Poszukiwanie informacji dodatkowych ......................................................................... 755
              Książki i artykuły .................................................................................................. 755
              Zasoby dostępne na stronach WWW ..................................................................... 756
              Przykładowe aplikacje .......................................................................................... 757
          Przyszłość ................................................................................................................ 758

     A Wymagania dla przykładowej aplikacji .............................................................................................761
          Przegląd ................................................................................................................... 761
          Grupy użytkowników .................................................................................................. 762
              Publiczni użytkownicy internetowi .......................................................................... 762
              Kasjerzy .............................................................................................................. 763
              Administratorzy .................................................................................................... 763
          Założenia ................................................................................................................. 764
          Ograniczenia zakresu aplikacji .................................................................................... 765
          Terminarz prac .......................................................................................................... 765
          Interfejs użytkowników internetowych .......................................................................... 766
              Podstawowy schemat działania ............................................................................. 766
              Obsługa błędów ................................................................................................... 767
              Ekrany aplikacji ................................................................................................... 767
          Wymagania niefunkcjonalne ....................................................................................... 780
          Środowisko sprzętowe i programowe .......................................................................... 782

     Skorowidz ..............................................................................................................................................785
Sercem Springa jest funkcjonalność lekkiego kontenera IoC (ang. Inversion of Control). Do
skonfigurowania i właściwego powiązania obiektów aplikacji z obiektami frameworka (oraz
zarządzania ich cyklami życia) można użyć jednego lub wielu egzemplarzy tego kontenera.
Podstawowe cechy kontenera IoC dają nam pewność, że zdecydowana większość tych
obiektów nie będzie zawierała zależności wiążących je z samym kontenerem, zatem relacje
pomiędzy obiektami można wyrażać za pomocą tak naturalnych rozwiązań (języka Java)
jak interfejsy czy abstrakcyjne klasy bazowe, co niemal w stu procentach uniezależnia
nas od sposobu implementacji tych obiektów oraz lokalizacji ich zależności. Okazuje się,
że kontener IoC jest podstawą dla bardzo wielu funkcji i mechanizmów, które będziemy
analizowali w tym i kolejnych rozdziałach.

Z tego rozdziału dowiesz się, jak konfigurować i korzystać z fabryk komponentów Springa
oraz kontekstów aplikacji, czyli dwóch podstawowych składników decydujących o kształcie
i funkcjonalności kontenera IoC tego frameworka. Poznasz interfejsy BeanFactory i Appli-
cationContext wraz ze wszystkimi ważnymi interfejsami i klasami pokrewnymi, które są
wykorzystywane zawsze wtedy, gdy należy programowo utworzyć lub uzyskać dostęp do
kontenera IoC. W niniejszym rozdziale skupimy się na tych odmianach interfejsów Bean-
Factory i ApplicationContext, które można deklaratywnie konfigurować za pośrednictwem
odpowiednich dokumentów XML. Wspomniane interfejsy stanowią podstawową funkcjo-
nalność w zakresie konfigurowania i korzystania z kontenera IoC i są stosowane przez
użytkowników Springa w zdecydowanej większości przypadków. Warto jednak pamiętać, że
Spring oddziela konfigurację kontenera od mechanizmów jego użytkowania. Podczas lektury
kolejnego rozdziału przekonasz się, że dostęp do wszystkich możliwości kontenera można
uzyskiwać zarówno za pośrednictwem konfiguracji programowej, jak i alternatywnych for-
matów deklaratywnych.
82   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

     W rozdziale zajmiemy się następującymi zagadnieniami:
       n   odwracaniem kontroli (IoC) i wstrzykiwaniem zależności,
       n   podstawowymi technikami konfigurowania obiektów i definiowania relacji
           łączących obiekty w ramach kontenera Springa,
       n   sposobem interpretowania zależności i różnicami pomiędzy jawnym
           a automatycznym wprowadzaniem zależności,
       n   obsługą cyklu życia obiektów w kontenerze Springa,
       n   abstrakcją technik dostępu do usług i zasobów,
       n   postprocesorami fabryki komponentów i samych komponentów, które
           odpowiadają za dostosowywanie zachowania kontenera i komponentów,
       n   technikami programowego korzystania z interfejsów BeanFactory
           i ApplicationContext.




     W rozdziale 1. opisano koncepcję odwracania kontroli (IoC) i wyjaśniono, dlaczego jest
     ona tak ważna dla procesu wytwarzania oprogramowania. W pierwszej kolejności krótko
     przypomnimy, co to pojęcie rzeczywiście oznacza, by zaraz potem przystąpić do analizy
     kilku przykładów jego praktycznego stosowania dla kontenera Springa.

     Kod oprogramowania rozbija się zwykle na logiczne, współpracujące ze sobą komponenty
     lub usługi. W Javie takie komponenty mają przeważnie postać egzemplarzy klas, czyli
     obiektów. Każdy obiekt realizuje swoje zadania z wykorzystaniem lub we współpracy
     z pozostałymi obiektami. Przypuśćmy, że mamy dany obiekt A; można powiedzieć, że po-
     zostałe obiekty, z których korzysta obiekt A, są jego zależnościami (ang. dependencies).
     Odwracanie kontroli (IoC) jest w wielu sytuacjach pożądanym wzorcem architekturalnym,
     który przewiduje, że obiekty są ze sobą wiązane przez byt zewnętrzny (w tym przypadku
     kontener), który — tym samym — odpowiada za obsługę ich zależności (eliminując ko-
     nieczność bezpośredniego tworzenia egzemplarzy jednych obiektów w drugich).

     Przyjrzyjmy się teraz kilku przykładom.

        Większość przykładów prezentowanych w tym rozdziale (nawet tych, które nie są pre-
        zentowane w formie kompletnych klas czy interfejsów) jest dostępna także w postaci
        gotowej do kompilacji, z którą możesz swobodnie eksperymentować (patrz ftp://ftp.
        åhelion.pl/przyklady/sprifr.zip).

     Przypuśćmy, że dysponujemy usługą obsługującą historyczne dane pogodowe, którą zako-
     dowano w tradycyjny sposób (bez korzystania z kontenera IoC):
        public class WeatherService {

           WeatherDao weatherDao = new StaticDataWeatherDaoImpl();
Rozdział 2. n Fabryka komponentów i kontekst aplikacji       83

       public Double getHistoricalHigh(Date date) {
         WeatherData wd = weatherDao.find(date);
         if (wd != null)
           return new Double(wd.getHigh());
         return null;
       }
   }

   public interface WeatherDao {

       WeatherData find(Date date);
       WeatherData save(Date date);
       WeatherData update(Date date);
   }

   public class StaticDataWeatherDaoImpl implements WeatherDao {

       public WeatherData find(Date date) {
         WeatherData wd = new WeatherData();
         wd.setDate((Date) date.clone());
         ...
         return wd;
       }

       public WeatherData save(Date date) {
         ...
       }

       public WeatherData update(Date date) {
         ...
       }
   }

Zgodnie z dobrymi praktykami napisano też przypadek testowy, który weryfikuje prawi-
dłowe funkcjonowanie tego kodu. Jeśli użyjemy popularnego narzędzia JUnit, kod takiego
testu może mieć następującą postać:
   public class WeatherServiceTest extends TestCase {
     public void testSample1() throws Exception {
       WeatherService ws = new WeatherService();
       Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime());
       // … w tym miejscu powinieneś umieścić dodatkowy kod sprawdzający zwracaną wartość…
     }
   }

Nasza usługa pogodowa wykorzystuje dane pogodowe w formie obiektu DAO (obiektu do-
stępu do danych; ang. Data Access Object), który jest niezbędny do uzyskania danych histo-
rycznych. Do obsługi obiektu DAO program wykorzystuje co prawda interfejs WeatherDao,
jednak w przedstawionym przykładzie usługa pogodowa bezpośrednio tworzy egzemplarz
konkretnego, znanego typu DAO, który implementuje ten interfejs: StaticDataWeather-
åDaoImpl. Dodatkowo nasza aplikacja testowa, WeatherServiceTest, bezpośrednio wyko-
rzystuje konkretną klasę WeatherService (eliminuje możliwość specjalizacji). Prezentowany
przykład zakodowano bez korzystania z technik IoC. O ile usługa pogodowa współpracuje
z odpowiednim obiektem DAO za pośrednictwem interfejsu, konkretny egzemplarz obiektu
84   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

     DAO (określonego typu) jest przez tę usługę tworzony bezpośrednio i właśnie usługa po-
     godowa steruje jego cyklem życia; oznacza to, że wspomniana usługa jest obarczona zależ-
     nościami zarówno od interfejsu DAO, jak i konkretnej klasy implementującej ten interfejs.
     Co więcej, przykładowa aplikacja testowa reprezentuje klienta tej usługi, który bezpośred-
     nio tworzy egzemplarz konkretnego typu usługi pogodowej, zamiast korzystać z pośred-
     nictwa właściwego interfejsu. W rzeczywistej aplikacji bardzo prawdopodobne byłoby wy-
     stępowanie innych niejawnych zależności (np. od konkretnego frameworku utrwalania
     danych), a prezentowane do tej pory podejście wymagałoby kodowania tych zależności na
     stałe w kodzie źródłowym programu.

     Spójrzmy teraz na prosty przykład tego, jak kontener Springa może obsługiwać mechanizm
     odwracania kontroli. W pierwszej kolejności przebudujemy naszą usługę pogodową, aby
     prezentowała właściwy podział na interfejs i implementację oraz umożliwiała definiowanie
     (konfigurowanie) dla tej implementacji konkretnego egzemplarza obiektu DAO w formie
     właściwości komponentu JavaBean:
        public interface WeatherService {
           Double getHistoricalHigh(Date date);
        }

        public class WeatherServiceImpl implements WeatherService {

            private WeatherDao weatherDao;

            public void setWeatherDao(WeatherDao weatherDao) {
              this.weatherDao = weatherDao;
            }

            public Double getHistoricalHigh(Date date) {
              WeatherData wd = weatherDao.find(date);
              if (wd != null)
                return new Double(wd.getHigh());
              return null;
            }
        }

        // ta sama klasa co w poprzednim przykładzie
        public class StaticDataWeatherDaoImpl implements WeatherDao {
              ...
        }

     Do zarządzania egzemplarzem usługi pogodowej użyjemy kontekstu aplikacji Springa, a kon-
     kretnie klasy ClassPathXmlApplicationContext, i upewnimy się, że kontekst aplikacji otrzymał
     obiekt DAO naszej usługi, z którym będzie mógł współpracować. W pierwszej kolejności
     musimy zdefiniować plik konfiguracyjny w formacie XML, applicationContext.xml:
        <?xml version="1.0" encoding="UTF-8"?<
        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
            "http://www.springframework.org/dtd/spring-beans.dtd"<

        <beans<
          <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
            <property name="weatherDao"<
              <ref local="weatherDao"/<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji        85

           </property<
         </bean<
         <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"<
         </bean<
       </beans<

    Obiekt weatherService konfigurujemy za pomocą elementu bean, w którym określamy, że
    jego właściwość, weatherDao, powinna być ustawiona na egzemplarz komponentu weatherDao
    (który także definiujemy jako element bean). Pozostaje nam już tylko zmodyfikowanie klasy
    testowej, aby tworzyła odpowiedni kontener i uzyskiwała dostęp do znajdującej się w tym
    kontenerze usługi pogodowej:
       public class WeatherServiceTest extends TestCase {
         public void testSample2() throws Exception {
           ApplicationContext ctx = new ClassPathXmlApplicationContext(
               "ch02/sample2/applicationContext.xml");
           WeatherService ws = (WeatherService)ctx.getBean("weatherService");

               Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime());
               // … w tym miejscu powinieneś umieścić dodatkowy kod sprawdzający zwracaną wartość…
           }
       }

    Wszystkie analizowane klasy zostały już zakodowane i wdrożone zgodnie z zaleceniami
    IoC. Usługa pogodowa nie ma i nie potrzebuje żadnej wiedzy o szczegółach implementacji
    wykorzystywanego obiektu DAO, ponieważ zależność pomiędzy tymi składnikami aplikacji
    bazuje wyłącznie na interfejsie WeatherDao. Co więcej, podział oryginalnej klasy Weather-
    åService na interfejs WeatherService i implementującą go klasę WeatherServiceImpl chro-
    ni klientów usługi pogodowej przed takimi szczegółami implementacyjnymi jak sposób loka-
    lizowania przez tę usługę odpowiedniego obiektu DAO (w tym konkretnym przypadku wy-
    korzystano metodę ustawiającą znaną z komponentów JavaBeans). Takie rozwiązanie powoduje,
    że teraz implementacja usługi pogodowej może być zmieniana w sposób przezroczysty.
    Okazuje się, że na tym etapie możemy wymieniać implementacje interfejsu WeatherDao
    i (lub) interfejsu WeatherService, i że tego rodzaju zmiany będą wymagały wyłącznie od-
    powiedniego dostosowania zapisów pliku konfiguracyjnego (bez najmniejszego wpływu na
    funkcjonowanie klientów zmienianych komponentów). Przedstawiony przykład dobrze ilu-
    struje zastosowanie interfejsów w sytuacji, gdy kod jednej z warstw wykorzystuje kod innej
    warstwy (wspominano o tym w rozdziale 1.).


Różne formy wstrzykiwania zależności
    Termin wstrzykiwanie zależności (ang. Dependency Injection) opisuje proces dostarczania
    komponentowi niezbędnych zależności z wykorzystaniem techniki IoC — można więc po-
    wiedzieć, że zależności są w istocie wstrzykiwane do komponentów. Wersja wstrzykiwania
    zależności, którą mieliśmy okazję analizować w poprzednim przykładzie, jest nazywana
    wstrzykiwaniem przez metody ustawiające (ang. Setter Injection), ponieważ do wprowa-
    dzania (wstrzykiwania) zależności do właściwych obiektów wykorzystuje się znane z kom-
    ponentów JavaBeans metody ustawiające. Więcej informacji na temat komponentów Java-
    Beans oraz specyfikację tej technologii znajdziesz na stronie internetowej http://java.sun.
    åcom/products/javabeans.
86   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

     Przeanalizujmy teraz nieco inny wariant wstrzykiwania zależności — wstrzykiwanie przez
     konstruktor (ang. Constructor Injection), gdzie zależności są dostarczane do obiektu za
     pośrednictwem jego własnego konstruktora:
        public interface WeatherService {
           Double getHistoricalHigh(Date date);
        }
        public class WeatherServiceImpl implements WeatherService {

            private final WeatherDao weatherDao;

            public WeatherServiceImpl(WeatherDao weatherDao) {
              this.weatherDao = weatherDao;
            }

            public Double getHistoricalHigh(Date date) {
              WeatherData wd = weatherDao.find(date);
              if (wd != null)
                return new Double(wd.getHigh());
              return null;
            }
        }

        // niezmieniony interfejs WeatherDao
        public interface WeatherDao {
           ...
        }

        // niezmieniona klasa StaticDataWeatherDaoImpl
        public class StaticDataWeatherDaoImpl implements WeatherDao {
           ...
        }

     Klasa WeatherServiceImpl zawiera teraz konstruktor, który pobiera w formie argumentu
     egzemplarz interfejsu WeatherDao (zamiast — jak w poprzednim przykładzie — odpowied-
     niej metody ustawiającej, która także pobierała na wejściu egzemplarz tego interfejsu).
     Oczywiście także konfiguracja kontekstu aplikacji wymaga odpowiedniej modyfikacji.
     Okazuje się jednak, że zmiany w żaden sposób nie dotyczą klasy testowej, która nie wymaga
     żadnych dodatkowych czynności:
        <beans<
          <bean id="weatherService" class="ch02.sample3.WeatherServiceImpl"<
            <constructor-arg<
              <ref local="weatherDao"/<
            </constructor-arg<
          </bean<
          <bean id="weatherDao" class="ch02.sample3.StaticDataWeatherDaoImpl"<
          </bean<
        </beans<

        // niezmieniona klasa WeatherServiceTest
        public class WeatherServiceTest extends TestCase {
           ...
        }
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   87

Wstrzykiwanie metod jest ostatnią formą wstrzykiwania zależności, którą się zajmiemy
(jest stosowana zdecydowanie rzadziej od dwóch poprzednich technik). Wstrzykiwanie za-
leżności w tej formie przenosi na kontener odpowiedzialność za implementowanie metod
w czasie wykonywania programu. Przykładowo, obiekt może definiować chronioną metodę
abstrakcyjną, a kontener może tę metodę implementować w czasie wykonywania programu
w taki sposób, aby zwracała obiekt zlokalizowany podczas przeszukiwania środowiska wy-
konywania aplikacji. Celem wstrzykiwania metod jest oczywiście wyeliminowanie zależ-
ności od interfejsów API kontenera i tym samym ograniczanie niepożądanych związków.

Jednym z podstawowych i jednocześnie najlepszych zastosowań techniki wstrzykiwania
metod jest obsługa przypadków, w których bezstanowy obiekt, singleton, musi współpra-
cować z niesingletonem, z obiektem stanowym lub takim, który nie zapewnia bezpieczeń-
stwa przetwarzania wielowątkowego. Przypomnij sobie naszą usługę pogodową, w której
potrzebujemy tylko jednego egzemplarza, ponieważ nasza implementacja jest bezstanowa
i jako taka może być wdrażana za pomocą domyślnego kontenera Springa, który traktuje ją
jak singleton (tworzy tylko jeden egzemplarz, który można przechowywać w pamięci pod-
ręcznej i wykorzystywać wielokrotnie). Warto się jednak zastanowić, co by było, gdyby
usługa pogodowa musiała korzystać z klasy StatefulWeatherDao, implementacji interfejsu
WeatherDao niezapewniającej bezpieczeństwa wątków. W każdym wywołaniu metody
WeatherService.getHistoricalHigh() usługa pogodowa musiałaby wykorzystywać świeży
egzemplarz obiektu DAO (lub przynajmniej musiałaby się upewniać, że w danej chwili ża-
den inny egzemplarz samej usługi nie wykorzystuje tego samego obiektu DAO). Jak się za
chwilę przekonasz, można w prosty sposób zasygnalizować kontenerowi konieczność
traktowania DAO jak obiektu, który nie jest singletonem — wówczas każde żądanie doty-
czące tego obiektu spowoduje zwrócenie nowego egzemplarza. Problem w tym, że poje-
dynczy egzemplarz usługi pogodowej jest przedmiotem wstrzykiwania zależności tylko raz.
Jednym z rozwiązań jest oczywiście umieszczenie w kodzie tej usługi odwołań do interfejsu
API kontenera Springa i — w razie potrzeby — żądanie nowego obiektu DAO. Takie podejście
ma jednak jedną zasadniczą wadę: wiąże naszą usługę ze Springiem, co jest sprzeczne
z dążeniem do jak największej izolacji obu struktur. Zamiast tego możemy zastosować ofe-
rowany przez Springa mechanizm wstrzykiwania metody wyszukującej (ang. Lookup
Method Injection), gdzie usługa pogodowa będzie miała dostęp do odpowiedniego obiektu
DAO za pośrednictwem metody JavaBean getWeatherDao(), która — w zależności od po-
trzeb — może być albo konkretną implementacją albo metodą abstrakcyjną. Następnie, już
w definicji fabryki, nakazujemy kontenerowi przykrycie tej metody i zapewnienie imple-
mentacji zwracającej nowy egzemplarz DAO w formie innego komponentu:
   public abstract class WeatherServiceImpl implements WeatherService {

       protected abstract WeatherDao getWeatherDao();

       public Double getHistoricalHigh(Date date) {
         WeatherData wd = getWeatherDao().find(date);
         if (wd != null)
           return new Double(wd.getHigh());
         return null;
       }
   }

   <beans<
     <bean id="weatherService" class="ch02.sample4.WeatherServiceImpl"<
88   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

            <lookup-method name="getWeatherDao" bean="weatherDao"/<
          </bean<
          <bean id="weatherDao" singleton="false"
                class="ch02.sample4.StatefulDataWeatherDaoImpl"<
          </bean<
        </beans<

     Zauważ, że nakazaliśmy kontenerowi, aby nie traktował obiektu DAO jak singleton, zatem
     każde wywołanie metody getWeatherDao() może zwrócić nowy egzemplarz tego obiektu.
     Gdybyśmy tego nie zrobili, metoda za każdym razem zwracałaby ten sam egzemplarz sin-
     gletonu (składowany w pamięci podręcznej). Takie rozwiązanie jest co prawda poprawne,
     jednak jest mało prawdopodobne, byś kiedykolwiek chciał uzyskać właśnie taki efekt, po-
     nieważ podstawową zaletą techniki wstrzykiwania metod wyszukujących jest możliwość
     wstrzykiwania prototypów (niesingletonów), jak choćby w analizowanym przykładzie. W tym
     przypadku klasa WeatherServiceImpl i metoda getWeatherDao() są abstrakcyjne, możemy
     jednak przykryć dowolną metodę zwracającą każdego komponentu JavaBean (w praktyce
     metody kandydujące nie muszą pobierać żadnych argumentów, a ich nazwy nie muszą
     nawet być zgodne z konwencją nazewnictwa JavaBeans, choć ze względu na klarowność
     kodu takie rozwiązanie jest zalecane). Warto pamiętać, że także pozostały kod aplikacji
     musi korzystać z naszej metody zwracającej (a nie z odpowiedniego pola) do uzyskiwania
     dostępu do obiektu DAO. Wywoływanie metod zamiast bezpośredniego odwoływania się
     do pól obiektów jest jeszcze jedną dobrą praktyką przetwarzania właściwości komponentów
     JavaBeans.

     Stosując techniki wstrzykiwania metod, warto odpowiedzieć sobie na pytanie, jak można
     testować kod (np. wykonywać testy jednostkowe) aplikacji bez kontenera, który wstrzykuje
     daną metodę. Podobne wątpliwości są szczególnie istotne w przypadku aplikacji podob-
     nych do analizowanej usługi pogodowej, gdzie klasa implementacji jest abstrakcyjna. Sto-
     sunkowo prostą i jednocześnie możliwą do zrealizowania strategią przeprowadzania testów
     jednostkowych w podobnych przypadkach jest stworzenie dla wspomnianej klasy abstrak-
     cyjnej podklasy zawierającej testową implementację metody, która w normalnych warun-
     kach jest wstrzykiwana, przykładowo:
        ...
        WeatherService ws = new WeatherServiceImpl() {
          protected WeatherDao getWeatherDao() {
            // zwraca obiekt DAO dla danego testu
            ...
          }
        };

     Stosowanie przez użytkowników Springa tak zaawansowanych mechanizmów jak wstrzy-
     kiwanie metod zawsze powinno być poprzedzone dogłębną analizą — należy zdecydować,
     które rozwiązanie w danej sytuacji jest najbardziej uzasadnione i które nie będzie stanowiło
     niepotrzebnego utrudnienia dla programistów. Niezależnie od wyników tej analizy naszym
     zdaniem przedstawione powyżej podejście jest pod wieloma względami lepsze od umiesz-
     czania w kodzie aplikacji zależności od interfejsów API kontenera.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji        89


Wybór pomiędzy wstrzykiwaniem
przez metody ustawiające a wstrzykiwaniem przez konstruktory
    Podczas korzystania z istniejących klas może się zdarzyć, że nie będziesz nawet miał wy-
    boru pomiędzy użyciem techniki wstrzykiwania przez metody ustawiające a zastosowaniem
    wstrzykiwania przez konstruktor. Jeśli dana klasa zawiera wyłącznie wieloargumentowy
    konstruktor i żadnych właściwości komponentu JavaBean lub jednoargumentowy kon-
    struktor i proste właściwości JavaBean, wybór metody wstrzykiwania nie zależy od Ciebie
    — dokonano go już wcześniej. Co więcej, niektóre spośród istniejących klas mogą wymu-
    szać stosowanie kombinacji obu form wstrzykiwania zależności, gdzie oprócz wieloargu-
    mentowego konstruktora będziesz zobligowany do ustawienia kilku opcjonalnych właści-
    wości JavaBean.

    Jeśli masz wybór odnośnie wersji wstrzykiwania zależności, której chcesz użyć lub dla której
    chcesz zaprojektować architekturę swojej aplikacji, przed podjęciem ostatecznej decyzji
    powinieneś wziąć pod uwagę kilka czynników:
      n   Stosowanie właściwości JavaBean generalnie ułatwia obsługę domyślnych lub
          opcjonalnych wartości w sytuacji, gdy nie wszystkie wartości faktycznie są wymagane.
          W przypadku wstrzykiwania przez konstruktor takie podejście musiałoby prowadzić
          do stworzenia wielu wariantów konstruktora, gdzie jeden konstruktor wywoływałby
          w swoim ciele inny. Wiele wersji konstruktora i długie listy argumentów mogą być
          zbyt rozwlekłe i utrudniać konserwację kodu.
      n   W przeciwieństwie do konstruktorów właściwości JavaBean (jeśli nie są prywatne)
          są automatycznie dziedziczone przez podklasy. Ograniczenie związane z dziedziczeniem
          konstruktorów często zmusza programistów do tworzenia w kodzie podklas
          szablonowych konstruktorów, których jedynym zadaniem jest wywoływanie
          odpowiednich konstruktorów nadklasy. Warto jednak pamiętać, że większość
          środowisk IDE oferuje obecnie rozmaite mechanizmy uzupełniania kodu, które
          czynią proces tworzenia konstruktorów lub właściwości JavaBean niezwykle łatwym.
      n   Właściwości JavaBean są zdecydowanie łatwiejsze w interpretacji (nawet bez
          stosowania dodatkowych komentarzy dokumentujących) na poziomie kodu
          źródłowego niż argumenty konstruktora. Właściwości JavaBean wymagają też
          mniejszego wysiłku podczas dodawania komentarzy JavaDoc, ponieważ
          (w przeciwieństwie do konstruktorów) nie powtarzają się w kodzie.
      n   W czasie działania, właściwości JavaBean mogą być dopasowywane według nazw,
          które są widoczne z poziomu mechanizmu refleksji Javy. Z drugiej strony,
          w skompilowanym pliku klasy użyte w kodzie źródłowym nazwy argumentów
          konstruktora nie są zachowywane, zatem automatyczne dopasowywanie według
          nazw jest niemożliwe.
      n   Właściwości JavaBean oferują możliwość zwracania (i ustawiania) bieżącego stanu,
          jeśli tylko zdefiniowano odpowiednią metodę zwracającą (i ustawiającą). Takie
          rozwiązanie jest w wielu sytuacjach niezwykle przydatne, np. kiedy należy zapisać
          bieżący stan w innymi miejscu.
90   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

       n   Interfejs PropertyPritor dla właściwości JavaBean umożliwia wykonywanie
           automatycznej konwersji typów tam, gdzie jest to konieczne. Właśnie to wygodne
           rozwiązanie jest wykorzystywane i obsługiwane przez Springa.
       n   Właściwości JavaBean mogą być zmienne, ponieważ odpowiednie metody ustawiające
           można wywoływać wiele razy. Oznacza to, że jeśli wymaga tego realizowany
           przypadek testowy, można w prosty sposób modyfikować istniejące zależności.
           Inaczej jest w przypadku zależności przekazywanych za pośrednictwem konstruktorów,
           które — jeśli nie są dodatkowo udostępniane w formie właściwości — pozostają
           niezmienne. Z drugiej strony, jeśli jednym z wymagań jest bezwzględna niezmienność
           zależności, właśnie technika wstrzykiwania przez konstruktor umożliwia zadeklarowanie
           pola ustawianego w ciele konstruktora ze słowem kluczowym final (metoda
           ustawiająca w najlepszym razie może odpowiedzieć wyjątkiem na próbę drugiego
           lub kolejnego wywołania; nie ma też możliwości zadeklarowania pola ustawianego
           w takiej metodzie ze słowem kluczowym final, które zabezpieczyłoby to pole
           przed ewentualnymi modyfikacjami przez pozostały kod danej klasy).
       n   Argumenty konstruktora dają programiście pewność, że poprawny obiekt zostanie
           skonstruowany, ponieważ zadeklarowanie wszystkich wymaganych wartości
           wymusi ich przekazanie, a sama klasa w procesie inicjalizacji może je wykorzystywać
           w wymaganej kolejności w procesie inicjalizacji. W przypadku właściwości
           JavaBean nie można wykluczyć sytuacji, w której część właściwości nie została
           ustawiona przed użyciem danego obiektu, który — tym samym — znajdował się
           w niewłaściwym stanie. Co więcej, jeśli użyjesz właściwości JavaBean, nie będziesz
           mógł w żaden sposób wymusić kolejności wywołań metod ustawiających, co może
           wymagać dodatkowych zabiegów inicjalizujących już po ustawieniu właściwości
           (za pomocą metody init()). Taka dodatkowa metoda może też sprawdzać, czy
           wszystkie właściwości zostały ustawione. Spring oferuje mechanizmy automatycznego
           wywoływania dowolnej zadeklarowanej metody inicjalizującej bezpośrednio
           po ustawieniu wszystkich właściwości; alternatywnym rozwiązaniem jest
           zaimplementowanie interfejsu InitializingBean, który deklaruje automatycznie
           wywoływaną metodę afterPropertiesSet().
       n   Stosowanie kilku konstruktorów może być bardziej zwięzłym rozwiązaniem
           niż korzystanie z wielu właściwości JavaBean wraz z odpowiednimi metodami
           ustawiającymi i zwracającymi. Większość środowisk IDE oferuje jednak funkcje
           uzupełniania kodu, które bardzo ułatwiają i skracają proces tworzenia zarówno
           konstruktorów, jak i właściwości JavaBean.

     Ogólnie, zespół Springa w większości praktycznych zastosowań preferuje stosowanie techniki
     wstrzykiwania przez metody ustawiające zamiast wstrzykiwania przez konstruktory, choć
     tego rodzaju decyzje powinny być dobrze przemyślane i w żadnym razie nie należy upra-
     wiać doktrynerstwa. Wymienione powyżej aspekty obejmują większość czynników, które
     należy brać pod uwagę w każdej z sytuacji. Przyjmuje się, że wstrzykiwanie przez kon-
     struktory lepiej się sprawdza w przypadku prostszych scenariuszy inicjalizacji, gdzie kon-
     struktor otrzymuje na wejściu zaledwie kilka argumentów, które dodatkowo powinny re-
     prezentować złożone, a więc łatwiejsze do dopasowania, i unikatowe typy. Wraz ze wzrostem
     złożoności całej konfiguracji rośnie przewaga techniki wstrzykiwania przez metody
     ustawiające (zarówno w zakresie możliwości konserwacji, jak i nakładów pracy samego
     programisty). Warto pamiętać, że także inne popularne kontenery IoC obsługują zarówno
Rozdział 2. n Fabryka komponentów i kontekst aplikacji        91

    wstrzykiwanie przez konstruktory, jak i wstrzykiwanie przez metody ustawiające, zatem
    obawa przed nadmiernym związaniem aplikacji z mechanizmami Springa nie powinna być
    uwzględniana podczas dokonywania tego wyboru.

    Niezależnie od wybranej formy wstrzykiwania zależności jest oczywiste, że należy ten me-
    chanizm stosować w taki sposób, aby odpowiednie konstrukcje były wprowadzane wyłącz-
    nie do klasy implementacji oraz właściwej konfiguracji tej klasy. Pozostały kod aplikacji
    powinien współpracować z pozostałymi modułami (komponentami) za pośrednictwem in-
    terfejsów i w żaden sposób nie powinien być uzależniony od problemów konfiguracyjnych.




    Podstawowy kontener IoC Springa jest często nazywany fabryką komponentów (ang. bean
    factory). Każda fabryka komponentów umożliwia logicznie spójne konfigurowanie i wią-
    zanie wielu obiektów za pomocą odpowiednich mechanizmów wstrzykiwania zależności.
    Fabryka komponentów oferuje też pewne funkcje w zakresie zarządzania obiektami, a w szcze-
    gólności ich cyklem życia. Podejście bazujące na technice odwracania kontroli umożliwia
    daleko idące oddzielanie kodu poszczególnych składników aplikacji. Co więcej, poza ko-
    rzystaniem z mechanizmu refleksji podczas uzyskiwania dostępu do obiektów odwracanie
    kontroli uniezależnia kod aplikacji od samej fabryki komponentów i — tym samym — eli-
    minuje konieczność dostosowywania kodu do współpracy z funkcjami Springa. Kod aplikacji
    potrzebny do konfigurowania obiektów (i do uzyskiwania obiektów z wykorzystaniem ta-
    kich wzorców jak singletony czy fabryki obiektów specjalnych) można albo w całości wy-
    eliminować, albo przynajmniej w znacznym stopniu zredukować.

       W typowej aplikacji bazującej na Springu tylko bardzo niewielka ilość kodu scalającego
       będzie świadoma do współpracy z kontenerem Springa lub korzystania z interfejsów
       tego kontenera. Nawet tę skromną ilość kodu w wielu przypadkach udaje się wyelimi-
       nować, korzystając z istniejącego kodu frameworka do załadowania fabryki komponen-
       tów w sposób deklaratywny.



Fabryka komponentów
    Istnieją różne implementacje fabryki komponentów, z których każda oferuje nieco inną
    funkcjonalność (w praktyce różnice mają związek z działaniem mechanizmów konfiguracji,
    a najczęściej stosowaną reprezentacją jest format XML). Wszystkie fabryki komponentów
    implementują interfejs org.springframework.beans.factory.BeanFactory — programowe
    zarządzanie egzemplarzami wymaga dostępności właśnie za pośrednictwem tego interfejsu.
    Dodatkową funkcjonalność udostępniają ewentualne podinterfejsy interfejsu BeanFactory.
    Poniższa lista zawiera część tej hierarchii interfejsów:
      n BeanFactory   — podstawowy interfejs wykorzystywany do uzyskiwania dostępu do
         wszystkich fabryk komponentów. Przykładowo wywołanie metody getBean(String
         name) umożliwia uzyskanie komponentu z kontenera na podstawie nazwy. Inny
         wariant tej metody, getBean(String name, Class requirerType), dodatkowo daje
92   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

         programiście możliwość określenia wymaganej klasy zwracanego komponentu
         i generuje wyjątek, jeśli taki komponent nie istnieje. Inne metody umożliwiają nam
         odpytywanie fabryki komponentów w zakresie istnienia poszczególnych komponentów
         (według nazw), odnajdywanie typów komponentów (także według nazw) oraz
         sprawdzanie, czy w danej fabryce istnieją jakieś aliasy danego komponentu (czy
         dany komponent występuje w fabryce pod wieloma nazwami). I wreszcie można
         określić, czy dany komponent skonfigurowano jako singleton (czy pierwszy
         utworzony egzemplarz komponentu będzie wykorzystywany wielokrotnie dla
         wszystkich kolejnych żądań czy fabryka za każdym razem będzie tworzyła nowy
         egzemplarz).
       n HierarchicalBeanFactory    — większość fabryk komponentów może być tworzona
         jako część większej hierarchii — wówczas żądanie od fabryki dostępu do komponentu,
         który w tej konkretnej fabryce nie istnieje, spowoduje przekazanie żądania do jego
         fabryki nadrzędnej (przodka), która może z kolei zażądać odpowiedniego komponentu
         od swojej fabryki nadrzędnej itd. Z perspektywy aplikacji klienta cała hierarchia
         powyżej bieżącej fabryki (włącznie z tą fabryką) może być traktowana jak jedna,
         scalona fabryka. Jedną z zalet takiej hierarchicznej struktury jest możliwość jej
         dopasowania do rzeczywistych warstw architekturalnych lub modułów aplikacji.
         O ile uzyskiwanie komponentu z fabryki będącej częścią hierarchii odbywa się
         w sposób całkowicie przezroczysty, interfejs HierarchicalBeanFactory jest
         niezbędny, jeśli chcemy mieć możliwość dostępu do komponentów składowanych
         w jej fabryce nadrzędnej.
       n ListableBeanFactory    — metody tego podinterfejsu interfejsu BeanFactory
         umożliwiają generowanie rozmaitych list komponentów dostępnych w bieżącej
         fabryce, włącznie z listą nazw komponentów, nazwami wszystkich komponentów
         określonego typu oraz liczbą komponentów w danej fabryce. Kilka metod tego
         interfejsu umożliwia dodatkowo tworzenie egzemplarza Map zawierającego
         wszystkie komponenty określonego typu. Warto pamiętać, że o ile metody interfejsu
         BeanFactory automatycznie są udostępniane na wszystkich poziomach hierarchii
         fabryk komponentów, metody interfejsu ListableBeanFactory są stosowane tylko
         dla jednej, bieżącej fabryki. Klasa pomocnicza BeanFactoryUtils oferuje niemal
         identyczne metody jak interfejs ListableBeanFactory, tyle że w przeciwieństwie
         do metod tego interfejsu uwzględniają w generowanych wynikach całą hierarchię
         fabryk komponentów. W wielu przypadkach stosowanie metod klasy BeanFactoryUtils
         jest lepszym rozwiązaniem niż korzystanie z interfejsu ListableBeanFactory.
       n AutowireCapableBeanFactory    — interfejs AutowireCapableBeanFactory umożliwia
         (za pośrednictwem metod autowireBeanProperties() oraz applyBeanPropertyValues())
         konfigurowanie istniejącego, zewnętrznego obiektu i dostarczanie zależności
         z poziomu fabryki komponentów. Oznacza to, że wspomniane metody pozwalają
         pominąć normalny krok tworzenia obiektu będący częścią uzyskiwania komponentu
         za pośrednictwem metody BeanFactory.getBean(). Podczas pracy z zewnętrznym
         kodem, który wymaga tworzenia egzemplarzy obiektów jako takich, tworzenie
         komponentów za pomocą odpowiednich mechanizmów Springa nie zawsze jest
         możliwe, co nie oznacza, że należy rezygnować z oferowanych przez ten framework
         cennych rozwiązań w zakresie wstrzykiwania zależności. Inna metoda, autowire(),
         umożliwia określanie na potrzeby fabryki komponentów nazwy klasy, wykorzystanie
         tej fabryki do utworzenia egzemplarza tej klasy, użycie refleksji do wykrycia
         wszystkich zależności tej klasy i wstrzyknięcia tych zależności do danego
Rozdział 2. n Fabryka komponentów i kontekst aplikacji      93

           komponentu (w efekcie powinieneś otrzymać w pełni skonfigurowany obiekt).
           Warto pamiętać, że fabryka nie będzie zawierała i zarządzała obiektami
           konfigurowanymi za pomocą tych metod (inaczej niż w przypadku tworzonych
           przez siebie egzemplarzy singletonów), choć możemy sami dodać do tej fabryki
           nasze egzemplarze jako singletony.
       n ConfigurableBeanFactory  — ten interfejs wprowadza do podstawowej fabryki
           komponentów dodatkowe opcje konfiguracyjne, które mogą być stosowane
           w fazie inicjalizacji.

     Ogólnie Spring próbuje stosować tzw. wyjątki nieweryfikowalne (ang. non-checked excep-
     tions), czyli podklasy klasy RuntimePxception dla nieweryfikowalnych błędów. Interfejsy
     fabryki komponentów, w tym BeanFactory i jego podinterfejsy, nie są pod tym względem
     wyjątkiem. W większości przypadków błędy konfiguracji są nieweryfikowalne, zatem
     wszystkie wyjątki zwracane przez opisywane interfejsy API są podklasami klasy niewery-
     fikowalnych wyjątków BeansPxception. Decyzja o tym, gdzie i kiedy należy przechwytywać
     i obsługiwać te wyjątki, należy do programisty — jeśli istnieje odpowiednia strategia obsługi
     sytuacji wyjątkowej, można nawet podejmować próby odtwarzania stanu sprzed wyjątku.


Kontekst aplikacji
     Spring obsługuje też mechanizm znacznie bardziej zaawansowany od fabryki komponentów
     — tzw. kontekst aplikacji (ang. application context).

        Warto podkreślić, że kontekst aplikacji jest fabryką komponentów, a interfejs org.sp-
        åringframework.context.ApplicationContext jest podinterfejsem interfejsu BeanFactory.
        Kompletną hierarchię interfejsów przedstawiono na rysunku 2.1.

     Ogólnie wszystkie zadania, które można realizować za pomocą fabryki komponentów,
     można też wykonywać z wykorzystaniem kontekstu aplikacji. Po co więc wprowadzono takie
     rozróżnienie? Przede wszystkim po to, aby podkreślić zwiększoną funkcjonalność i nieco
     inny sposób używania kontekstu aplikacji:
       n   Ogólny styl pracy z frameworkiem — pewne operacje na komponentach
           w kontenerze lub na samym kontenerze, które w fabryce komponentów muszą
           być obsługiwane programowo, w kontekście aplikacji mogą być realizowane
           deklaratywnie. Chodzi między innymi o rozpoznawanie i stosowanie specjalnych
           postprocesorów dla komponentów oraz postprocesorów dla fabryki komponentów.
           Co więcej, istnieje wiele mechanizmów Springa, które ułatwiają automatyczne
           wczytywanie kontekstów aplikacji — przykładowo, w warstwie MVC aplikacji
           internetowych większość fabryk komponentów będzie tworzona przez kod
           użytkownika, natomiast konteksty aplikacji będą wykorzystywane najczęściej za
           pomocą technik deklaratywnych i tworzone przez kod Springa. W obu przypadkach
           znaczna część kodu użytkownika będzie oczywiście zarządzana przez kontener,
           zatem nie będzie dysponowała żadną wiedzą o samym kontenerze.
       n   Obsługa interfejsu MessageSource — kontekst aplikacji implementuje interfejs
           MessageSource, którego zadaniem jest uzyskiwanie zlokalizowanych komunikatów
           i którego implementacja może być w prosty sposób włączana do aplikacji.
94     Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

Rysunek 2.1.




          n    Obsługa zdarzeń aplikacji i frameworka — kontekst aplikacji oferuje możliwość
               wywoływania zdarzeń frameworka lub aplikacji, które są następnie przechwytywane
               i obsługiwane przez zarejestrowane obiekty nasłuchujące.
          n    Obsługa interfejsu ResourceLoader — interfejs ResourceLoarer Springa jest
               elastyczną i uniwersalną abstrakcją obsługującą niskopoziomowe zasoby. Sam
               kontekst aplikacji jest implementacją tego interfejsu i — tym samym — zapewnia
               aplikacji dostęp do egzemplarzy Resource właściwych dla danego wdrożenia.

           Być może zastanawiasz się, kiedy lepszym rozwiązaniem jest tworzenie i korzystanie
           z fabryki komponentów, a kiedy lepiej użyć kontekstu aplikacji. Niemal we wszystkich
           przypadkach warto rozważyć użycie kontekstu aplikacji, który bez żadnych dodatkowych
           kosztów poszerza zakres funkcjonalności. Prawdopodobnie jedynym wyjątkiem od tej
           reguły są aplikacje bazujące na apletach, gdzie każdy zajmowany (lub przesyłany) bajt
           pamięci może mieć ogromne znaczenie — fabryka komponentów pozwala na pewne
           oszczędności w tym zakresie, ponieważ umożliwia korzystanie z pakietu bibliotek
           Springa, które zawierają wyłącznie funkcjonalność fabryki (bez rozbudowanej funkcjo-
           nalności kontekstu aplikacji). W tym i innych rozdziałach poświęconych funkcjonalno-
           ści fabryki komponentów możesz bezpiecznie zakładać, że wszystkie analizowane
           elementy funkcjonalności są wspólne zarówno dla fabryki komponentów, jak i dla kon-
           tekstu aplikacji.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji    95

    W tym i w większości pozostałych rozdziałów tej książki analiza konfiguracji i funkcjonal-
    ności kontenera będzie ilustrowana przykładami w wersji deklaratywnej, opartej na forma-
    cie XML (w tym XmlBeanFactory lub ClassPathXmlApplicationContext) fabryki kompo-
    nentów i kontekstu aplikacji. Warto zdać sobie sprawę z tego, że funkcjonalność kontenera
    w żadnym razie nie jest tożsama z formatem jego konfiguracji. Konfiguracja oparta na
    XML jest co prawda wykorzystywana przez zdecydowaną większość użytkowników Springa,
    jednak istnieje też kompletny interfejs API dla mechanizmów konfiguracji i dostępu do
    kontenerów, w tym obsługa innych formatów konfiguracji (które mogą być konstruowane
    i obsługiwane w bardzo podobny sposób jak odpowiednie dokumenty XML). Przykładowo,
    istnieje klasa PropertiesBeanDefinitionRearer, która odpowiada za załadowanie do fabryki
    komponentów definicji zapisanych w plikach właściwości Javy.


Uruchamianie kontenera
    Poprzednie przykłady pokazały, jak można programowo uruchamiać kontener z poziomu
    kodu użytkownika. W tym punkcie przeanalizujemy kilka ciekawych rozwiązań w tym zakresie.

    Egzemplarz (implementację) interfejsu ApplicationContext można wczytać, wskazując
    ścieżkę do odpowiedniej klasy (pliku):
       ApplicationContext appContext =
            new ClassPathXmlApplicationContext("ch03/sample2/applicationContext.xml");
       // Uwaga: ApplicationContext jest egzemplarzem BeanFactory, to oczywiste!
       BeanFactory factory = (BeanFactory) appContext;

    Można też wskazać konkretną lokalizację w systemie plików:
       ApplicationContext appContext =
           new FileSystemXmlApplicationContext("/some/file/path/applicationContext.xml");

    Istnieje też możliwość łączenia dwóch lub większej liczby fragmentów prezentowanych już
    dokumentów XML. Takie rozwiązanie umożliwia nam lokalizowanie definicji komponen-
    tów w ramach logicznych modułów, do których należą, i jednocześnie tworzenie jednego
    kontekstu na bazie połączonych definicji. Przykład prezentowany w następnym rozdziale
    pokaże, że opisane podejście może być bardzo użyteczne także podczas testów. Poniżej
    przedstawiono jeden z wcześniejszych przykładów konfiguracji, tyle że tym razem złożony
    z dwóch fragmentów kodu XML:

    applicationContext-dao.xml:
       <?xml version="1.0" encoding="UTF-8"?<
       <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
           "http://www.springframework.org/dtd/spring-beans.dtd"<

       <beans<
         <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"<
         </bean<
       </beans<
96   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

     applicationContext-services.xml:
        <?xml version="1.0" encoding="UTF-8"?<
        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
            "http://www.springframework.org/dtd/spring-beans.dtd"<

        <beans<
          <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
            <property name="weatherDao"<
              <ref bean="weatherDao"/<
            </property<
          </bean<
        </beans<

        Uważni Czytelnicy zapewne zauważyli, że w porównaniu z wcześniejszymi przykłada-
        mi nieznacznie zmieniono sposób odwoływania się do komponentu weatherDao, który
        wykorzystujemy jako właściwość komponentu usługi pogodowej; odwołania do kom-
        ponentów szczegółowo omówimy w dalszej części rozdziału.

     Aby wczytać i połączyć oba fragmenty (teoretycznie może ich być więcej), musimy je tylko
     wymienić w tablicy nazw (łańcuchów) przekazywanej do konstruktora klasy ClassPathXml-
     åApplicationContext:
        ApplicationContext appContext = new ClassPathXmlApplicationContext(
                new String[] {"applicationContext-services.xml",
                              "applicationContext-dao.xml"});

     Abstrakcja Resource Springa (którą szczegółowo omówimy nieco później) umożliwia sto-
     sowanie prefiksu classpath*: do wyszczególniania tych wszystkich zasobów pasujących
     do konkretnej nazwy, które są widoczne z perspektywy classloadera i jego classloaderów
     nadrzędnych. Przykładowo, gdyby nasza aplikacja została rozproszona pomiędzy wiele pli-
     ków JAR, wszystkich należących do ścieżki do klas, z których każdy zawierałby własny
     fragment kontekstu aplikacji nazwany applicationContext.xml, moglibyśmy w prosty spo-
     sób określić, że chcemy utworzyć kontekst złożony ze wszystkich istniejących fragmentów:
        ApplicationContext appContext =
                new ClassPathXmlApplicationContext("classpath*:applicationContext.xml"});

     Okazuje się, że tworzenie i wczytywanie fabryki komponentów skonfigurowanych za po-
     mocą odpowiednich zapisów języka XML jest bardzo łatwe. Najprostszym rozwiązaniem
     jest użycie abstrakcji Resource Springa, która umożliwia uzyskiwanie dostępu do zasobów
     według ścieżki klas:
        ClassPathResource res =
                new ClassPathResource("org/springframework/prospering/beans.xml"});
        XmlBeanFactory factory = new XmlBeanFactory(res);

     lub:
        FilesystemResource res = new FilesystemResource("/some/file/path/beans.xml");
        XmlBeanFactory factory = new XmlBeanFactory(res);
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   97

    Równie dobrze moglibyśmy użyć egzemplarza InputStream:
       InputStream is = new FileInputStream("/some/file/path/beans.xml");
       XmlBeanFactory factory = new XmlBeanFactory(is);

    Aby wyczerpać ten temat, musimy wspomnieć o możliwości prostego oddzielenia operacji
    tworzenia fabryki komponentów od procesu przetwarzania definicji komponentów. Nie bę-
    dziemy się zajmować tym zagadnieniem w szczegółach, warto jednak pamiętać, że takie
    wyodrębnienie zachowania fabryki komponentów od mechanizmów przetwarzania definicji
    komponentów upraszcza stosowanie innych formatów konfiguracji:
       ClassPathResource res = new ClassPathResource("beans.xml"});
       DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
       XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
       reader.loadBeanDefinitions(res);

    Stosując klasę GenericApplicationContext dla kontekstu aplikacji, możemy w podobny
    sposób oddzielić kod tworzący ten kontekst od kodu przetwarzającego definicje kompo-
    nentów. Wiele aplikacji Springa w ogóle nie tworzy kontenera programowo, ponieważ bazuje
    na odpowiednim kodzie tego frameworka (który wykonuje odpowiednie działania w ich
    imieniu). Przykładowo, możemy deklaratywnie skonfigurować mechanizm ContextLoarer
    Springa w taki sposób, aby automatycznie wczytywał kontekst aplikacji w momencie
    uruchamiania aplikacji internetowej. Odpowiednie techniki konfiguracji omówimy w na-
    stępnym rozdziale.


Korzystanie komponentów uzyskiwanych z fabryki
    Kiedy już fabryka komponentów lub kontekst aplikacji zostaną wczytane, uzyskanie dostępu
    do komponentów sprowadza się do wywołania metody getBean() interfejsu BeanFactory:
       WeatherService ws = (WeatherService)ctx.getBean("weatherService");

    lub jednej z metod któregoś z bardziej zaawansowanych interfejsów:
       Map allWeatherServices = ctx.getBeansOfType(WeatherService.class);

    Żądanie od kontenera dostępu do komponentu wywołuje proces jego tworzenia i inicjaliza-
    cji, który obejmuje omówioną już fazę wstrzykiwania zależności. Krok wstrzykiwania za-
    leżności może z kolei zainicjować proces tworzenia pozostałych komponentów (zależności
    pierwszego komponentu) itd., aż do utworzenia kompletnego grafu wzajemnie powiąza-
    nych egzemplarzy obiektów.

    W związku z opisaną procedurą nasuwa się dość oczywiste pytanie: co należałoby zrobić
    z samą fabryką komponentów lub kontekstem aplikacji, aby pozostały kod, który tego wy-
    maga, mógł uzyskać do nich dostęp? Obecnie skupiamy się na analizie sposobu konfiguro-
    wania i funkcjonowania kontenera, zatem przedstawienie i wyjaśnienie odpowiedzi na to
    pytanie odłóżmy na dalszą część tego rozdziału.
98   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie


        Pamiętaj, że z wyjątkiem bardzo niewielkiej ilości kodu scalającego zdecydowana więk-
        szość kodu aplikacji pisanego i łączonego zgodnie z założeniami IoC nie musi zawierać
        żadnych operacji związanych z uzyskiwaniem dostępu do fabryki, ponieważ za zarzą-
        dzanie zależnościami pomiędzy obiektami i samymi obiektami odpowiada kontener.
        Najprostszą strategią zapewniania odpowiednich proporcji pomiędzy kodem scalają-
        cym (niezbędnym do inicjowania pewnych działań) a właściwym kodem aplikacji jest
        umieszczenie fabryki komponentów w znanym miejscu, które będzie odpowiadało nie
        tylko oczekiwanym zastosowaniom, ale też tym elementom kodu, które będą potrze-
        bowały dostępu do tej fabryki. Sam Spring oferuje mechanizm deklaratywnego wczyty-
        wania kontekstu dla aplikacji internetowych i składowania tego kontekstu w obiekcie
        ServletContext. Co więcej, Spring zawiera klasy pomocnicze przypominające single-
        tony, które mogą być z powodzeniem stosowane do składowania i wydobywania z fa-
        bryki komponentów (jeśli takie rozwiązanie z jakiegoś powodu wydaje Ci się lepsze lub
        jeśli nie istnieje strategia składowania fabryki komponentów w ramach konkretnej
        aplikacji).



Konfiguracja komponentów w formacie XML
     Mieliśmy już okazję przejrzeć przykładowe pliki z definicjami fabryk komponentów w formacie
     XML, jednak do tej pory nie poddaliśmy zawartych tam zapisów szczegółowej analizie.
     W największym uproszczeniu definicja fabryki komponentów składa się z elementu beans
     (na najwyższym poziomie) oraz jednego lub wielu elementów bean:
        <?xml version="1.0" encoding="UTF-8"?<
        <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
            "http://www.springframework.org/dtd/spring-beans.dtd"<

        <beans<
          <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
            <property name="weatherDao"<
              <ref local="weatherDao"/<
            </property<
          </bean<
          <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"<
          </bean<
        </beans<

     Prawidłowe elementy i atrybuty pliku definicji szczegółowo opisano w pliku XML DTD
     (ang. Document Type Definition) nazwanym spring-beans.dtd. Wspomniany plik DTD
     w połączeniu z podręcznikiem użytkownika Springa powinien być traktowany jak ostateczne
     źródło informacji o technikach konfiguracji aplikacji. Ogólnie, opcjonalne atrybuty ele-
     mentu beans (na najwyższym poziomie elementów XML definicji komponentów) mają
     wpływ na zachowanie całego pliku konfiguracji i zawierają domyślne wartości dla rozma-
     itych aspektów wszystkich definiowanych komponentów, natomiast większość opcjonalnych
     atrybutów i podelementów potomnych elementów bean opisuje konfigurację i cykl życia
     poszczególnych komponentów. Plik spring-beans.dtd jest co prawda dołączany do Springa,
     jednak z jego zawartością można się zapoznać także w internecie (patrz www.springframe-
     åwork.org/dtd/spring-beans.dtd).
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   99


Podstawowa definicja komponentu
      Definicja pojedynczego komponentu zawiera wszystkie informacje, których kontener po-
      trzebuje do jego utworzenia, w tym trochę szczegółowych danych o cyklu życia kompo-
      nentu oraz jego zależnościach. Przyjrzyjmy się dwóm pierwszym składnikom elementu bean.


Identyfikator

      W definicji komponentów na najwyższym poziomie hierarchii niemal we wszystkich przy-
      padkach definiujemy jeden lub wiele identyfikatorów (lub nazw) komponentów, aby pozo-
      stałe komponenty mogły się do nich odwoływać podczas programowego korzystania z da-
      nego kontenera. Do definiowania identyfikatorów (lub głównych identyfikatorów)
      komponentów służy atrybut ir. Zaletą tego atrybutu jest zgodność z typem XML IDREF —
      kiedy pozostałe komponenty będą się odwoływały do komponentu za pośrednictwem tak
      zdefiniowanego identyfikatora, sam parser dokumentu XML może pomóc w sprawdzeniu,
      czy takie odwołanie jest poprawne (czy w tym samym pliku zdefiniowano odpowiedni
      identyfikator), zatem atrybut ir ułatwia wczesną weryfikację prawidłowości konfiguracji.
      Warto jednak pamiętać, że typ XML IDREF wprowadza pewne ograniczenia w kwestii ak-
      ceptowanych znaków — identyfikatory muszą się rozpoczynać od litery, a na kolejnych
      pozycjach mogą zawierać dowolne znaki alfanumeryczne i znaki podkreślenia (ale nie mo-
      gą zawierać spacji). W większości przypadków opisane ograniczenia nie stanowią co
      prawda żadnego problemu, jeśli jednak z jakiegoś powodu chcesz je ominąć, możesz zdefi-
      niować identyfikator w atrybucie name. Przykładowo, takie rozwiązanie jest uzasadnione,
      jeśli identyfikator komponentu jest poza kontrolą użytkownika i reprezentuje ścieżkę URL.
      Co więcej, atrybut name dopuszcza możliwość stosowania listy identyfikatorów oddzielo-
      nych przecinkami. Kiedy definicja komponentu definiuje więcej niż jeden identyfikator, np.
      w formie kombinacji atrybutu ir i (lub) atrybutu name, wszystkie dodatkowe identyfikatory
      (poza pierwszym) należy traktować jak aliasy. Odwołania do wszystkich identyfikatorów
      są równie poprawne. Przeanalizujmy kilka przykładów:
         <beans<

           <bean id="bean1" class="ch02.sample .TestBean"/<

           <bean name="bean2" class="ch02.sample .TestBean"/<

           <bean name="/myservlet/myaction" class="ch02.sample .TestBean"/<

           <bean id="component1-dataSource"
                 name="component2-dataSource,component3-dataSource"
                 class="ch02.sample .TestBean"/<

         </beans<

      Ponieważ trzeci definiowany komponent wymaga identyfikatora rozpoczynającego się od
      znaku ukośnika (/), użycie atrybutu ir jest niemożliwe — musimy użyć atrybutu name. Za-
      uważ, że czwarty komponent zawiera aż trzy identyfikatory, wszystkie równie poprawne.
      Być może zastanawiasz się, po co w ogóle miałbyś definiować więcej niż jeden identyfikator
      dla komponentu. Dobrą praktyką jest takie dzielenie konfiguracji według komponentów lub
100   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      modułów, aby dla każdego modułu istniał fragment pliku XML zawierający komponenty
      powiązane z tym modułem (wraz z ich zależnościami). Nazwy tych zależności mogą (jak
      w przedstawionym przykładzie) być definiowane z prefiksem właściwym dla danego kom-
      ponentu (w tym przypadku rataSource). Kiedy fabryka komponentów lub kontekst aplika-
      cji jest ostatecznie budowany z wielu przygotowanych wcześniej fragmentów (lub kiedy
      tworzona jest hierarchia kontekstów — patrz dalsza część tego rozdziału), każdy z tych
      komponentów będzie się odwoływał do tego samego, fizycznego bytu. Takie rozwiązanie
      należy traktować jak techniczną próbę rozwiązania problemu izolacji komponentów.


Mechanizm tworzenia komponentów

      Może też zaistnieć konieczność wskazania kontenerowi sposobu, w jaki ten powinien two-
      rzyć lub uzyskiwać egzemplarze komponentu, kiedy będzie tego potrzebował. Najczęściej
      stosowanym mechanizmem jest tworzenie komponentu za pośrednictwem jego konstruktora.
      Do określania nazwy klasy komponentu służy atrybut class. Za każdym razem, gdy kontener
      potrzebuje nowego egzemplarza komponentu, wewnętrznie wykonuje odpowiednik znanego
      z języka Java operatora new. Wszystkie prezentowane do tej pory przykłady wykorzysty-
      wały właśnie ten mechanizm.

      Innym mechanizmem tworzenia komponentów jest sygnalizowanie kontenerowi konieczno-
      ści użycia statycznej metody fabrykującej (ang. factory method), której jedynym zadaniem jest
      zwracanie nowego egzemplarza komponentu. Istniejący kod, nad którym nie masz kontroli,
      będzie Cię czasami zmuszał do korzystania z takiej statycznej metody fabrykującej. Nazwę
      klasy zawierającej tę metodę można określić za pośrednictwem atrybutu class, natomiast
      nazwę samej metody farykującej należy zdefiniować w atrybucie factory-methor:
         ...
         <bean id="testBeanObtainedViaStaticFactory"
               class="ch02.sample4.StaticFactory" factory-method="getTestBeanInstance"/<
         ...

         public class StaticFactory {
           public static TestBean getTestBeanInstance() {
             return new TestBean();
           }
         }

      Tak zdefiniowana statyczna metoda fabrykująca może zwracać egzemplarze obiektów do-
      wolnego typu; klasa zwracanego egzemplarza nie musi być tożsama z klasą zawierającą
      samą metodę fabrykującą.

      Trzecim mechanizmem tworzenia nowych egzemplarzy komponentu jest wywoływanie
      niestatycznych metod fabrykujących innych egzemplarzy komponentów w ramach tego
      samego kontenera:
         ...
         <bean id="nonStaticFactory" class="ch02.sample4.NonStaticFactory"/<

         <bean id="testBeanObtainedViaNonStaticFactory"
               factory-bean="nonStaticFactory" factory-method="getTestBeanInstance"/<
         ...
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   101

        public class NonStaticFactory {
          public TestBean getTestBeanInstance() {
            return new TestBean();
          }
        }

     Kiedy będzie potrzebny nowy egzemplarz komponentu testBeanObtainerViaNonStaticFactory,
     kontener w pierwszej kolejności utworzy egzemplarz fabryki nonStaticFactory i wywoła
     udostępnianą przez tę fabrykę metodę getTestBeanInstance(). Zauważ, że w tym przypadku
     w ogóle nie określiliśmy wartości atrybutu class.

     Kiedy już uda się uzyskać nowy egzemplarz obiektu, kontener traktuje go dokładnie tak
     samo jak wszystkie pozostałe egzemplarze, niezależnie od tego, czy został utworzony za
     pośrednictwem konstruktora, statycznej metody fabrykującej czy metody fabrykującej in-
     nego egzemplarza. Oznacza to, że każdy z tych egzemplarzy może być przedmiotem
     wstrzykiwania zależności przez metody ustawiające i podlega normalnemu cyklowi życia
     (w tym odpowiednim wywołaniom zwrotnym).


Komponenty singletonowe kontra komponenty niesingletonowe (prototypowe)

     Ważnym aspektem cyklu życia komponentu jest to, czy kontener traktuje go jak singleton
     czy jak zwykłą klasę z wieloma egzemplarzami. Domyślne komponenty singletonowe są
     tworzone przez kontener tylko raz. Kontener następnie przechowuje i wykorzystuje ten sam
     egzemplarz komponentu za każdym razem, gdy następuje odwołanie do danego kompo-
     nentu. Takie rozwiązanie może oznaczać znaczne oszczędności zasobów (w szczególności
     pamięci, ale także obciążenia procesora) w porównaniu z tworzeniem nowego egzemplarza
     komponentu w odpowiedzi na każde kolejne żądanie. Komponenty singletonowe są więc
     najlepszym rozwiązaniem zawsze wtedy, gdy tylko implementacja właściwych klas do-
     puszcza taką możliwość; czyli wtedy, gdy komponent jest bezstanowy lub gdy jego stan
     jest ustawiany tylko raz, w czasie inicjalizacji, i — tym samym — zapewnia bezpieczeń-
     stwo wątków (może być wykorzystywany jednocześnie przez więcej niż jeden wątek).
     Komponenty singletonowe są rozwiązaniem domyślnym, ponieważ zdecydowana więk-
     szość praktycznych usług, kontrolerów i zasobów konfigurowanych w ramach kontenera
     i tak jest implementowana w formie klas gwarantujących bezpieczną pracę w środowisku
     wielowątkowym, zatem nie modyfikują swojego stanu po wykonaniu fazy inicjalizacji.

     Niesingletonowe, prototypowe komponenty, jak sama nazwa wskazuje, powinny być defi-
     niowane z atrybutem singleton równym false. Warto pamiętać, że cykl życia komponentu
     prototypowego często różni się od cyklu życia komponentu singletonowego. Kiedy konte-
     ner otrzymuje żądanie dostarczenia komponentu prototypowego, następuje oczywiście ini-
     cjalizacja i przekazanie odpowiedniego egzemplarza, ale na tym rola kontenera się kończy
     (od tego momentu kontener nie przechowuje zwróconego egzemplarza). O ile więc istnieje
     możliwość wymuszenia na kontenerze Springa wykonania pewnych operacji kończących
     cykl życia komponentów singletonowych (patrz dalsza część tego rozdziału), w przypadku
     komponentów prototypowych te same operacje będzie trzeba zrealizować w kodzie użyt-
     kownika, ponieważ kontener nie będzie już miał żadnej wiedzy o zwróconych egzemplarzach
     tych komponentów:
102   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

         <bean id="singleton1" class="ch02.sample4.TestBean"/<

         <bean id="singleton2" singleton="true" class="ch02.sample .TestBean"/<

         <bean id="prototype1" singleton="false" class="ch02.sample .TestBean"/<



Definiowanie zależności komponentów
      Spełnianie zależności komponentów (czy to w formie innych komponentów, czy tylko pro-
      stych wartości potrzebnych do działania danego komponentu) jest prawdziwym jądrem,
      kluczowym elementem funkcjonalności kontenera, warto więc dobrze zrozumieć rzeczywiste
      działanie tego procesu. Miałeś już okazję zapoznać się z przykładami podstawowych typów
      wstrzykiwania zależności, wstrzykiwaniem przez konstruktory i wstrzykiwaniem przez
      metody ustawiające, wiesz też, że Spring obsługuje obie formy tej techniki. Przekonałeś
      się również, jak Spring może wykorzystać odpowiednią metodę fabrykującą — zamiast
      używać konstruktora do uzyskiwania początkowego egzemplarza obiektu. Właśnie z uwagi
      na konieczność dostarczenia zależności do komponentu stosowanie metody fabrykującej do
      uzyskiwania egzemplarza komponentu można de facto uznać za odpowiednik tworzenia
      egzemplarza za pośrednictwem konstruktora. W przypadku użycia konstruktora to kontener
      odpowiada za zapewnienie wartości (opcjonalnych) argumentów konstruktora (które repre-
      zentują zależności). Podobnie, w przypadku metody fabrykującej kontener dostarcza warto-
      ści (opcjonalnych) argumentów do metody fabrykującej (także reprezentujące zależności).
      Niezależnie od tego, czy początkowy egzemplarz obiektu jest tworzony przez konstruktor czy
      przez metodę fabrykującą, od tego momentu wszystkie egzemplarze są traktowane jednakowo.

      Techniki wstrzykiwania przez konstruktory i wstrzykiwania przez metody ustawiające nie
      wykluczają się wzajemnie. Nawet jeśli Spring uzyskuje początkowy egzemplarz kompo-
      nentu za pośrednictwem konstruktora lub metody fabrykującej i dostarcza wartości argu-
      mentów do konstruktora lub metody fabrykującej (wstrzykuje zależności), nadal może sto-
      sować mechanizm wstrzykiwania przez metody ustawiające do wprowadzania kolejnych
      zależności. Takie rozwiązanie może być szczególnie przydatne np. wtedy, gdy będziemy
      musieli użyć i zainicjalizować istniejącą klasę, która z jednej strony zawiera konstruktor
      pobierający jeden lub wiele argumentów i generujący komponent w znanym (prawidłowym)
      stanie początkowym, ale z drugiej strony bazuje na metodach ustawiających JavaBeans dla
      części opcjonalnych właściwości. Gdyby nie obsługa obu form wstrzykiwania zależności,
      nie byłbyś w stanie prawidłowo inicjalizować tego rodzaju obiektów w sytuacji, gdyby któraś
      z nich wymagała ustawienia opcjonalnych właściwości.

      Przeanalizujmy teraz sposób, w jaki kontener inicjalizuje i realizuje zależności komponentów:
        n   Kontener w pierwszej kolejności inicjalizuje definicję komponentu bez tworzenia
            jego egzemplarza — zwykle następuje to w czasie uruchamiania samego kontenera.
            Zależności komponentu mogą być wyrażane wprost, w formie argumentów
            konstruktora lub metody fabrykującej i (lub) właściwości komponentu.
        n   Każda właściwość lub argument konstruktora w definicji komponentu ma albo postać
            wartości wymagającej ustawienia, albo odwołania do innego komponentu w ramach
            danej fabryki komponentów lub nadrzędnej fabryki komponentów.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   103

n   Kontener wykona tyle operacji sprawdzających poprawność definiowanych zależności,
    ile będzie mógł w czasie inicjalizacji definicji komponentu. Jeśli korzystasz
    z formatu konfiguracji XML, w przypadku niezgodności konfiguracji z typem XML
    DTD w pierwszej kolejności otrzymamy wyjątek wygenerowany przez parser
    XML. Nawet jeśli konfiguracja w formacie XML jest poprawna z punktu widzenia
    definicji typów DTD, w razie wykrycia logicznej niespójności przez Springa także
    otrzymamy odpowiedni wyjątek — przykładowo, dwie właściwości mogą się wzajemnie
    wykluczać, czego nie da się wykazać wyłącznie na podstawie analizy typów DTD.
n   Jeśli zależność komponentu nie może być zrealizowana w praktyce (jeśli zależność
    komponentu ma postać innego komponentu, który nie istnieje) lub jeśli argument
    konstruktora lub wartość właściwości nie może zostać prawidłowo ustawiona,
    otrzymamy komunikat o błędzie tylko wtedy, gdy dany kontener rzeczywiście
    będzie musiał uzyskać nowy egzemplarz tego komponentu i wstrzyknąć jego
    zależności. Jeśli okaże się, że egzemplarz komponentu nigdy nie będzie potrzebny,
    nie można wykluczyć, że ewentualne błędy w definicji zależności komponentu lub
    samego komponentu nigdy nie zostaną wykryte (przynajmniej do momentu jego
    użycia). Między innymi po to, aby umożliwić możliwie szybkie wykrywanie błędów,
    konteksty aplikacji (ale nie fabryki komponentów) domyślnie tworzą wstępne
    egzemplarze komponentów singletonowych. Faza wstępnego tworzenia egzemplarzy
    obejmuje iteracyjne przeszukiwanie wszystkich komponentów singletonowych
    (w ich domyślnych stanach), utworzenie egzemplarzy każdej ze wstrzykiwanych
    zależności i umieszczenie tych egzemplarzy w pamięci podręcznej. Warto pamiętać,
    że etap wstępnego tworzenia egzemplarzy komponentów można zmodyfikować
    albo za pomocą atrybutu refault-lazy-init elementu beans definiowanego na
    najwyższym poziomie pliku konfiguracji, albo na poziomie poszczególnych
    komponentów (elementów bean) za pośrednictwem atrybutu lazy-init.
n   Kiedy kontener potrzebuje nowego egzemplarza danego komponentu (zazwyczaj
    w wyniku wywołania metody getBean() lub odwołania ze strony innego komponentu,
    dla którego bieżący komponent jest zależnością), uzyskuje początkowy egzemplarz
    za pośrednictwem konfigurowanego konstruktora lub metody fabrykującej, po czym
    podejmuje próby wstrzykiwania zależności, opcjonalnych argumentów konstruktora
    lub metody fabrykującej oraz opcjonalnych wartości właściwości.
n   Argumenty konstruktora lub właściwości komponentu, które odwołują się
    do innego komponentu, w pierwszej kolejności wymuszają na kontenerze tworzenie
    lub uzyskiwanie dostępu do tamtego komponentu. W efekcie wskazywany
    komponent jest w istocie zależnością komponentu, który się do niego odwołuje.
    Operacje tworzenia lub uzyskiwania dostępu do komponentów składają się na
    logicznie spójny łańcuch, który można reprezentować w formie grafu zależności.
n   Każdy argument konstruktora lub wartość właściwości musi zapewniać możliwość
    konwersji z oryginalnego typu lub formatu na typ faktycznie oczekiwany przez
    argument konstruktora lub właściwość komponentu (oczywiście jeśli oba typy są
    różne). Spring oferuje mechanizmy konwersji argumentów przekazywanych w formacie
    łańcuchowym do wszystkich wbudowanych typów skalarnych, a więc int, long,
    boolean itd., oraz wszystkich typów opakowań, w tym Integer, Long, Boolean itd.
    Spring wykorzystuje też implementacje interfejsu PropertyPritor komponentów
    JavaBeans do konwersji wartości typu String na niemal dowolne typy danych.
    Kontener automatycznie rejestruje i wykorzystuje wiele różnych implementacji
104   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

            tego interfejsu. Przykładem takiej implementacji jest klasa ClassPritor, która
            konwertuje łańcuch reprezentujący nazwę klasy na egzemplarz klasy Class, który
            może być przekazany do właściwości oczekującej takiego egzemplarza; innym
            przykładem jest klasa ResourcePritor, która konwertuje łańcuch reprezentujący
            ścieżkę lokalizacji na obiekt klasy Resource Springa, który może być wykorzystywany
            do abstrakcyjnego uzyskiwania dostępu do zasobów. Wszystkie wbudowane
            edytory właściwości omówimy w następnym rozdziale. Istnieje też możliwość
            rejestrowania własnych implementacji interfejsu PropertyPritor obsługujących
            Twoje typy niestandardowe (patrz podpunkt „Tworzenie własnych edytorów
            właściwości” w dalszej części tego rozdziału).
        n   Także wariant konfiguracji oparty na formacie XML, który jest wykorzystywany
            przez większość implementacji fabryk komponentów i kontekstów aplikacji,
            wykorzystuje własne elementy i atrybuty umożliwiające definiowanie złożonych
            kolekcji, czyli list, zbiorów, map i właściwości. Okazuje się, że wartości tych
            kolekcji mogą być dowolnie zagnieżdżane.
        n   Zależności mogą też mieć charakter niejawny — w ekstremalnych przypadkach
            Spring może użyć refleksji do sprawdzenia argumentów pobieranych przez
            konstruktor komponentu (lub wartości właściwości tego komponentu) nawet wtedy,
            gdy nie zadeklarowano odpowiednich zależności. Kontener może na podstawie
            takiej procedury zbudować listę prawidłowych zależności danego komponentu.
            Następnie, korzystając z mechanizmu nazywanego wiązaniem automatycznym
            (ang. autowiring), kontener może te zależności wypełnić w oparciu o dopasowania
            według typów lub nazw. Na razie pominiemy temat automatycznego wiązania,
            ale z pewnością do niego wrócimy w dalszej części tego rozdziału.


Definiowanie zależności komponentów w szczegółach

      W tym podpunkcie szczegółowo omówimy techniki definiowania wartości właściwości
      komponentów i argumentów konstruktorów w formacie XML. Każdy element bean może
      zawierać zero, jeden lub wiele elementów constructor-arg określających argumenty kon-
      struktora lub metody wyszukującej. Elementy bean mogą też zawierać zero, jeden lub wiele
      elementów property określających właściwości JavaBean wymagających ustawienia. Jeśli
      sytuacja tego wymaga, można łączyć oba podejścia (argumenty konstruktora i właściwości
      JavaBean) — takie rozwiązanie zastosowano w poniższym przykładzie, gdzie argument
      konstruktora jest w istocie odwołaniem do innego komponentu, natomiast właściwość typu
      int jest zwykłą wartością:
         <beans<
           <bean id="weatherService" class="ch02.sample6.WeatherServiceImpl"<
         <constructor-arg index="0"<
               <ref local="weatherDao"/<
             </constructor-arg<
             <property name="maxRetryAttempts"<<value<2</value<</property<
         </bean<

           <bean id="weatherDao" class="ch02.sample6.StaticDataWeatherDaoImpl"<
           </bean<
         </beans<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   105

Analiza wspominanego już dokumentu XML DTD pokazuje, że w ramach elementów pro-
perty i constructor-arg można stosować wiele innych elementów. Każdy z tych elemen-
tów definiuje jakiś typ wartości deklarowanej właściwości lub argumentu konstruktora:
   (bean | ref | idref | list | set | map | props | value | null)

Element ref służy do takiego ustawiania wartości właściwości lub argumentu konstruktora,
aby odwoływała się do innego komponentu w ramach tej samej fabryki komponentów lub
w ramach jej fabryki nadrzędnej:
   <ref local="weatherDao"/<
   <ref bean="weatherDao"/<
   <ref parent="weatherDao"/<

Atrybuty local, bean i parent wzajemnie się wykluczają i muszą zawierać identyfikator in-
nego komponentu. W przypadku użycia atrybutu local analizator składni dokumentu XML
może zweryfikować istnienie wskazanego komponentu już na etapie analizy składniowej.
Ponieważ jednak całość bazuje na mechanizmie IDREF języka XML, komponent musi być
zdefiniowany w tym samym pliku XML co odwołanie do tego komponentu, a jego defini-
cja musi wykorzystywać atrybut ir określający identyfikator, do którego będziemy się od-
woływali (zamiast atrybutu name). W przypadku użycia atrybutu bean wskazany komponent
może się znajdować w tym samym lub innym fragmencie dokumentu XML wykorzysty-
wanym albo do budowy definicji fabryki komponentów, albo w definicji fabryki nadrzęd-
nej względem fabryki bieżącej. Za weryfikację istnienia konkretnych komponentów może
co prawda odpowiadać sam Spring (nie analizator składniowy XML), ale tylko wówczas,
gdy dana zależność rzeczywiście musi zostać zrealizowana (a więc nie w czasie ładowania
fabryki komponentów). Znacznie rzadziej stosowany atrybut parent określa, że komponent
docelowy musi pochodzić z fabryki nadrzędnej względem bieżącej fabryki komponentów.
Takie rozwiązanie może być przydatne w nieczęstych sytuacjach, w których występuje
konflikt nazw komponentów w bieżącej i nadrzędnej fabryce komponentów.

Element value służy do określania wartości prostych właściwości lub argumentów kon-
struktora. Jak już wspominano, niezbędnym krokiem jest konwersja wartości źródłowej
(która ma postać łańcucha) na odpowiedni typ docelowej właściwości lub argumentu kon-
struktora, czyli dowolny wbudowany typ skalarny, odpowiedni typ opakowania lub dowolny
inny typ, dla którego w kontenerze zarejestrowano implementację interfejsu PropertyPritor
zdolną do obsługi tego typu. Przeanalizujmy teraz konkretny przykład:
   <property name="classname"<
     <value<ch02.sample6.StaticDataWeatherDaoImpl</value<
   </property<

Powyższy fragment kodu ustawia właściwość typu String nazwaną classname i przypisuje
mu stałą wartość ch02.sample6.StaticDataWeatherDaoImpl; gdyby jednak właściwość
classname była typu java.lang.Class, fabryka komponentów musiałaby użyć wbudowanej
(i automatycznie rejestrowanej) implementacji interfejsu PropertyPritor (w tym przypadku kla-
sy ClassPritor) do konwersji tej wartości łańcuchowej na egzemplarz obiektu klasy Class.

Istnieje możliwość stosunkowo prostego rejestrowania własnych, niestandardowych im-
plementacji interfejsu PropertyPritor, które będą obsługiwały konwersję łańcuchów na
dowolne inne typy danych niezbędne do właściwej konfiguracji kontenera. Dobrym przykładem
106   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      sytuacji, w której takie rozwiązanie jest uzasadnione, jest przekazywanie łańcuchów repre-
      zentujących daty, które mają być następnie wykorzystywane do ustawiania właściwości typu
      Date. Ponieważ daty są szczególnie wrażliwe na uwarunkowania regionalne, użycie odpo-
      wiedniej implementacji interfejsu PropertyPritor, która będzie prawidłowo obsługiwała
      łańcuch źródłowy, jest najprostszym rozwiązaniem tego problemu. Sposób rejestrowania
      niestandardowych implementacji tego interfejsu zostanie przedstawiony w dalszej części
      tego rozdziału, przy okazji omawiania klasy CustomPritorConfigurer i postprocesora fa-
      bryki komponentów. Niewielu programistów zdaje sobie sprawę z tego, że odpowiednie
      mechanizmy interfejsu PropertyPritor automatycznie wykrywają i wykorzystują wszystkie
      implementacje tego interfejsu, które należą do tego samego pakietu co klasa przeznaczona
      do konwersji (jedynym warunkiem jest zgodność nazwy tej klasy z nazwą klasy implementują-
      cej wspomniany interfejs, która dodatkowo musi zawierać sufiks Pritor). Oznacza to, że
      w przypadku klasy MyType implementacja interfejsu PropertyPritor nazwana MyTypePritor
      i należąca do tego samego pakietu co klasa MyType zostanie automatycznie wykryta i użyta
      przez kod pomocniczy JavaBeans zdefiniowany w odpowiedniej bibliotece Javy (bez naj-
      mniejszego udziału Springa).

      Właściwości lub argumenty konstruktora, którym należy przypisać wartość null, wymagają
      specjalnego traktowania, ponieważ pusty element value jest interpretowany jak łańcuch
      pusty. Zamiast braku wartości należy więc użyć elementu null:
         <property name="optionalDescription"<<null/<</property<

      Element irref jest wygodnym sposobem wychwytywania błędów w odwołaniach do in-
      nych komponentów za pośrednictwem wartości łańcuchowych reprezentujących ich nazwy.
      Istnieje kilka komponentów pomocniczych samego Springa, które odwołują się do innych
      komponentów (i wykonują za ich pomocą pewne działania) właśnie w formie wartości
      swoich właściwości. Wartości tego rodzaju właściwości zwykle definiuje się w następujący
      sposób:
         <property name="beanName"<<value<weatherService</value<</property<

      Możliwie szybkie wykrywanie ewentualnych literówek w podobnych odwołaniach byłoby
      oczywiście korzystne — element irref w praktyce odpowiada właśnie za taką weryfikację.
      Użycie elementu property w postaci:
         <property name="beanName"<<idref local="weatherService"/<</property<

      umożliwia analizatorowi składniowemu XML udział we wczesnym procesie weryfikacji,
      ponieważ już na etapie analizy składniowej można bez trudu wykryć odwołania do kompo-
      nentów, które w rzeczywistości nie istnieją. Wartość wynikowa tej właściwości będzie do-
      kładnie taka sama jak w przypadku użycia standardowego znacznika value.

      Elementy list, set, map i props umożliwiają definiowanie i ustawianie złożonych właściwości
      lub argumentów konstruktorów (odpowiednio typów java.util.List, java.util.Set,
      java.util.Map i java.util.Properties). Przeanalizujmy teraz całkowicie nierzeczywisty
      przykład, w którym definiowany komponent JavaBean będzie zawierał po jednej właściwości
      każdego z wymienionych typów złożonych:
         <beans<
           <bean id="collectionsExample" class="ch02.sample .CollectionsBean"<
             <property name="theList"<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   107

         <list<
           <value<red</value<
           <value<red</value<
           <value<blue</value<
           <ref local="curDate"/<
           <list<
             <value<one</value<
             <value<two</value<
             <value<three</value<
           </list<
         </list<
       </property<
       <property name="theSet"<
   <set<
           <value<red</value<
           <value<red</value<
           <value<blue</value<
         </set<
   </property<
       <property name="theMap"<
   <map<
           <entry key="left"<
             <value<right</value<
           </entry<
           <entry key="up"<
             <value<down</value<
           </entry<
           <entry key="date"<
             <ref local="curDate"/<
           </entry<
         </map<
   </property<
       <property name="theProperties"<
   <props<
           <prop key="left"<right</prop<
           <prop key="up"<down</prop<
         </props<
   </property<
     </bean<

     <bean id="curDate" class="java.util.GregorianCalendar"/<
   </beans<

Kolekcje typu List, Map i Set mogą zawierać dowolne spośród wymienionych poniżej ele-
mentów:
    (bean | ref | idref | list | set | map | props | value | null)

Jak pokazuje przedstawiony przykład listy, typy kolekcji mogą być dowolnie zagnieżdżane.
Warto jednak pamiętać, że właściwości lub argumenty konstruktorów otrzymujące na wejściu
typy kolekcji muszą być deklarowane za pomocą typów java.util.List, java.util.Set lub
java.util.Map. Nie możesz stosować innych typów kolekcji (np. ArrayList), nawet jeśli są
obsługiwane w Springu. Tego rodzaju ograniczenia mogą stanowić poważny problem, jeśli
musimy zapewnić wartość dla właściwości istniejącej klasy, która oczekuje określonego
108   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      typu — w takim przypadku nie można konkretnego typu kolekcji zastąpić jego uniwersal-
      nym odpowiednikiem. Jednym z rozwiązań jest użycie udostępnianych przez Springa po-
      mocniczych komponentów fabrykujących (ang. factory beans): ListFactoryBean, SetFac-
      toryBean lub MapFactoryBean. Za ich pomocą można swobodnie określać docelowy typ
      kolekcji, w tym np. java.util.LinkerList. Więcej informacji na ten temat znajdziesz
      w dokumentacji JavaDoc Springa. Same komponenty fabrykujące omówimy w dalszej części
      rozdziału.

      Ostatnim elementem, który może występować w roli wartości właściwości lub wartości ar-
      gumentu konstruktora (lub wewnątrz któregoś z opisanych przed chwilą elementów kolekcji),
      jest element bean. Oznacza to, że definicja komponentów może być de facto zagnieżdżana
      w definicji innego komponentu (jako właściwość komponentu zewnętrznego). Przeanali-
      zujmy teraz przebudowany przykład wstrzykiwania przez metodę ustawiającą:
         <beans<
           <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
             <property name="weatherDao"<
               <bean class="ch02.sample2.StaticDataWeatherDaoImpl"/<
                 ...
               </bean<
             </property<
           </bean<
         </beans<

      Zagnieżdżone definicje komponentów są szczególnie przydatne w sytuacji, gdy komponent
      wewnętrzny nie znajduje żadnych zastosowań poza zakresem komponentu zewnętrznego.
      W porównaniu z poprzednią wersją tego przykładu obiekt DAO (który ustawiono jako za-
      leżność usługi pogodowej) został włączony do komponentu usługi pogodowej i przyjął po-
      stać jego komponentu wewnętrznego. Żaden inny komponent ani użytkownik zewnętrzny
      nie będzie przecież potrzebował tego obiektu DAO, zatem utrzymywanie go poza zakresem
      komponentu usługi (jako osobnej, zewnętrznej definicji) mijałoby się z celem. Użycie kompo-
      nentu wewnętrznego jest w tym przypadku rozwiązaniem bardziej zwięzłym i klarownym.
      Komponent wewnętrzny nie musi mieć zdefiniowanego identyfikatora, choć nie jest to za-
      bronione. Uwaga: Komponenty wewnętrzne zawsze są komponentami prototypowymi,
      a ewentualny atrybut singleton jest ignorowany. W tym przypadku nie ma to najmniejszego
      znaczenia — ponieważ istnieje tylko jeden egzemplarz komponentu zewnętrznego (który
      jest singletonem), utworzenie więcej niż jednego egzemplarza komponentu wewnętrznego
      jest wykluczone (niezależnie od tego, czy zadeklarujemy ten komponent jako singletonowy
      czy jako prototypowy). Gdyby jednak prototypowy komponent zewnętrzny potrzebował
      zależności singletonowej, nie powinniśmy tej zależności definiować w formie komponentu
      wewnętrznego, tylko jako odwołanie do singletonowego komponentu zewnętrznego.


Samodzielne (ręczne) deklarowanie zależności

      Kiedy właściwość komponentu lub argument konstruktora odwołuje się do innego komponentu,
      mamy do czynienia z deklaracją zależności od tego zewnętrznego komponentu. W niektó-
      rych sytuacjach konieczne jest wymuszenie inicjalizacji jednego komponentu przed innym,
      nawet jeśli ten drugi nie został zadeklarowany jako właściwość pierwszego. Wymuszanie
      określonej kolejności inicjalizacji jest uzasadnione także w innych przypadkach, np. kiedy
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   109

     dana klasa wykonuje jakieś statyczne operacje inicjalizujące w czasie wczytywania. Przy-
     kładowo, sterowniki baz danych zwykle są rejestrowane w egzemplarzu interfejsu Driver-
     Manager biblioteki JDBC. Do ręcznego określania zależności pomiędzy komponentami słu-
     ży atrybut repenrs-on, który wymusza utworzenie egzemplarza innego komponentu jeszcze
     przed uzyskaniem dostępu do komponentu zależnego. Poniższy przykład pokazuje, jak
     można wymuszać wczytywanie sterownika bazy danych:
        <bean id="load-jdbc-driver" class="oracle.jdbc.driver.OracleDriver"/<

        <bean id="weatherService" depends-on="load-jdbc-driver" class="..."<
          ...
        </bean<

     Warto pamiętać, że większość pul połączeń z bazą danych oraz klas pomocniczych Springa
     (np. DriverManagerDataSource) korzysta z tego mechanizmu wymuszania wczytywania,
     zatem powyższy fragment kodu jest tylko przykładem popularnego rozwiązania.


Automatyczne wiązanie zależności

     Do tej pory mieliśmy do czynienia z deklaracjami zależności komponentów wyrażanymi
     wprost (za pośrednictwem wartości właściwości i argumentów konstruktora). W pewnych
     okolicznościach Spring może użyć mechanizmu introspekcji klas komponentów w ramach
     bieżącej fabryki i przeprowadzić automatyczne wiązanie zależności. W takim przypadku
     właściwość komponentu lub argument kontenera nie muszą być deklarowane (np. w pliku
     XML), ponieważ Spring użyje refleksji do odnalezienia typu i nazwy odpowiedniej wła-
     ściwości, po czym dopasuje ją do innego komponentu w danej fabryce (według jego typu
     lub nazwy). Takie rozwiązanie może co prawda oszczędzić programiście mnóstwo pracy
     związanej z samodzielnym przygotowywaniem odpowiedniego kodu, jednak niewątpliwym
     kosztem tego podejścia jest mniejsza przejrzystość. Mechanizm automatycznego wiązania
     zależności można kontrolować zarówno na poziomie całego kontenera, jak i na poziomie
     definicji poszczególnych komponentów. Ponieważ nieostrożne korzystanie z tej techniki
     może prowadzić do nieprzewidywalnych rezultatów, mechanizm automatycznego wiązania
     jest domyślnie wyłączony. Automatyczne wiązanie na poziomie komponentów jest kon-
     trolowane za pośrednictwem atrybutu autowire, który może zawierać pięć wartości:
       n no  — mechanizm automatycznego wiązania zależności w ogóle nie będzie
          stosowany dla danego komponentu. Właściwości tego komponentu oraz argumenty
          konstruktora muszą być deklarowane wprost, a wszelkie odwołania do innych
          komponentów wymagają używania elementu ref. Okazuje się, że jest to domyślny
          sposób obsługi poszczególnych komponentów (przynajmniej jeśli domyślne
          ustawienia na poziomie fabryki komponentów nie zostały zmienione). Opisany tryb
          jest zalecany w większości sytuacji, szczególnie w przypadku większych wdrożeń,
          gdzie zależności deklarowane wprost stanowią swoistą dokumentację struktury
          oprogramowania i są dużo bardziej przejrzyste.
       n byName   — wymusza automatyczne wiązanie zależności według nazw właściwości.
          Nazwy właściwości są wykorzystywane podczas odnajdywania dopasowań
          do komponentów w bieżącej fabryce komponentów. Przykładowo, jeśli dana
          właściwość ma przypisaną nazwę weatherDao, wówczas kontener spróbuje ustawić
          tę właściwość jako odwołanie do innego komponentu nazwanego właśnie weatherDao.
110   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

           Jeśli taki pasujący komponent nie zostanie znaleziony, właściwość pozostanie
           nieustawiona. Taka reakcja na brak dopasowania pomiędzy nazwą właściwości
           a nazwą komponentu jest opcjonalna — jeśli chcesz traktować brak dopasowania
           jak błąd, możesz do definicji komponentu dodać atrybut repenrency-check="objects"
           (patrz dalsza część tego rozdziału).
        n byType   — automatyczne wiązanie zależności przez dopasowywanie typów. Podobne
           rozwiązanie zastosowano w kontenerze PicoContainer, innym popularnym produkcie
           obsługującym wstrzykiwanie zależności. W przypadku każdej właściwości — jeśli
           w bieżącej fabryce komponentów istnieje dokładnie jeden komponent tego samego
           typu co ta właściwość — wartość jest ustawiana właśnie jako ten komponent. Jeśli
           w fabryce istnieje więcej niż jeden komponent pasującego typu, efektem nieudanej
           próby dopasowania jest błąd krytyczny, który zwykle prowadzi do wygenerowania
           odpowiedniego wyjątku. Tak jak w przypadku automatycznego wiązania zależności
           według nazwy (wartość byName) w przypadku braku pasujących komponentów
           właściwość pozostaje nieustawiona. Jeśli brak dopasowania powinien być traktowany
           jak błąd, wystarczy do definicji komponentu dodać atrybut repenrency-check="objects"
           (patrz dalsza część tego rozdziału).
        n constructor   — automatyczne wiązanie zależności według typów argumentów
           konstruktora. Ten tryb bardzo przypomina wiązanie zależności z właściwościami,
           tyle że polega na szukaniu w fabryce dokładnie jednego pasującego (według typu)
           komponentu dla każdego z argumentów konstruktora. W przypadku wielu
           konstruktorów Spring w sposób zachłanny będzie próbował spełnić wymagania
           konstruktora z największą liczbą pasujących argumentów.
        n autoretect    — wybiera tryby byType i constructor w zależności od tego, który
           z nich lepiej pasuje do danej sytuacji. W przypadku wykrycia domyślnego,
           bezargumentowego konstruktora stosowany jest tryb byType; w przeciwnym
           razie zostanie zastosowany tryb constructor.

      Istnieje możliwość ustawienia innego domyślnego trybu automatycznego wiązania zależno-
      ści (innego niż no) dla wszystkich komponentów w danej fabryce — wystarczy użyć atry-
      butu refault-autowire w elemencie beans na najwyższym poziomie dokumentu XML.
      Warto też pamiętać o możliwości łączenia technik automatycznego wiązania z wiązaniem
      wprost, wówczas elementy definiujące zależności (property lub contructor-arg) mają wyż-
      szy priorytet od zależności wykrywanych automatycznie.

      Sprawdźmy teraz, jak można użyć mechanizmu automatycznego wiązania zależności dla
      naszej usługi pogodowej i jej obiektu DAO. Jak widać, możemy wyeliminować z definicji
      komponentu właściwość weatherDao i włączyć automatyczne wiązanie według nazw, a mimo
      to Spring nadal będzie w stanie odnaleźć wartość właściwości wyłącznie w oparciu o dopa-
      sowanie jego nazwy. W tym przypadku moglibyśmy użyć także trybu automatycznego
      wiązania zależności według typu, ponieważ do typu naszej właściwość (WeatherDao) pasuje
      tylko jeden komponent w kontenerze:
         <beans<
           <bean id="weatherService" autowire="byName"
                 class="ch02.sample2.WeatherServiceImpl"<
             <!-- brak deklaracji właściwości weatherDao --<
           </bean<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji        111

          <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"<
          </bean<
        </beans<

     Korzystanie z technik automatycznego wiązania zależności jest o tyle kuszące, że zwalnia
     programistę z obowiązku pisania znacznej ilości kodu konfiguracji fabryki komponentów,
     jednak warto zachować należytą ostrożność i stosować ten mechanizm z rozwagą.

        Usuwając zależności deklarowane wprost, rezygnujemy z jednej z form dokumentowa-
        nia tych zależności. Co więcej, ze stosowaniem trybów byType lub nawet byName wiąże
        się ryzyko nieprzewidywalnych zachowań w sytuacji, gdy uda się odnaleźć więcej niż
        jedno dopasowanie (pasujący komponent) lub w razie braku jakiegokolwiek dopaso-
        wania. Szczególnie w przypadku większych, bardziej skomplikowanych wdrożeń zaleca
        się unikanie automatycznego wiązania zależności szerokim łukiem lub przynajmniej
        bardzo rozważne korzystanie z tego mechanizmu, ponieważ w przeciwnym razie może
        się okazać, że ograniczając ilość kodu XML, dodatkowo skomplikowaliśmy całą aplikację.
        Większość współczesnych środowisk IDE oferuje (wbudowane lub dostępne w formie
        modułów rozszerzeń) edytory XML z obsługą DTD, które mogą oszczędzić mnóstwo
        czasu i wysiłku programisty podczas tworzenia konfiguracji komponentów, zatem roz-
        miar deklaracji zależności wyrażonych wprost nie stanowi wielkiego problemu. Tym, co
        może się doskonale sprawdzać w pewnych sytuacjach, jest stosowanie automatycznego
        wiązania zależności dla prostych, niskopoziomowych „wnętrzności” (np. DataSource)
        i jednocześnie deklarowanie zależności wprost w przypadku bardziej skomplikowanych
        aspektów. W ten sposób można wyeliminować nadmiar pracy bez konieczności po-
        święcania transparentności.


Dopasowywanie argumentów konstruktora

     Ogólna zasada mówi, że dla każdego deklarowanego elementu constructor-arg należy do-
     datkowo stosować opcjonalne atrybuty inrex i (lub) type. Chociaż oba wymienione atry-
     buty są opcjonalne, w przypadku pominięcia choćby jednego z nich zadeklarowana lista ar-
     gumentów konstruktora będzie dopasowywana do rzeczywistych argumentów według ich
     typów. Jeśli zadeklarowane argumenty są odwołaniami do innych typów (np. skomplikowanych
     komponentów lub takich typów złożonych jak java.lang.Map), kontener będzie mógł zna-
     leźć odpowiednie dopasowanie stosunkowo łatwo, szczególnie w sytuacji, gdy będzie ist-
     niał tylko jeden konstruktor. Jeśli jednak zadeklarujemy wiele argumentów tego samego
     typu lub użyjemy znacznika value, który będzie de facto wartością bez typu (zadeklarowaną
     w formie łańcucha), próby automatycznego dopasowywania mogą się zakończyć błędami
     lub innymi nieprzewidywalnymi zachowaniami.

     Przeanalizujmy przykład komponentu, który zawiera pojedynczy konstruktor pobierający
     numeryczną wartość kodu błędu oraz łańcuchową wartość komunikatu o błędzie. Jeśli spróbu-
     jemy użyć znacznika <value> do dostarczania wartości dla tych argumentów, będziemy mu-
     sieli przekazać kontenerowi jakąś dodatkową wskazówkę odnośnie realizacji tego zadania.
     Możemy posłużyć się albo atrybutem inrex do określenia właściwego indeksu argumentu
     (liczonego od zera), co ułatwi dopasowanie przekazywanej wartości do odpowiedniego ar-
     gumentu konstruktora:
        <beans<
          <bean id="errorBean" class="ch02.sampleX.ErrorBean"<
            <constructor-arg index="0"<<value<1000</value<</constructor-arg<
112   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

             <constructor-arg index="1"<<value<Nieoczekiwany błąd</value<</constructor-arg<
           </bean<
         </beans<

      albo dostarczyć kontenerowi taką ilość informacji, która pozwoli mu właściwie przeprowa-
      dzić proces dopasowania w oparciu o typy danych — wówczas powinniśmy użyć atrybutu
      type określającego typ danej wartości:
         <beans<
           <bean id="errorBean" class="ch02.sampleX.ErrorBean"<
             <constructor-arg type="int"<<value<1000</value<</constructor-arg<
             <constructor-arg type="java.lang.String"<
               <value<Nieoczekiwany błąd</value<
             </constructor-arg<
           </bean<
         </beans<



Sprawdzanie poprawności zależności komponentów

      Często się zdarza, że część właściwości JavaBean danego obiektu ma charakter opcjonalny.
      Możesz, ale nie musisz, ustawiać ich wartości w zależności od tego, czy odpowiednia war-
      tość jest potrzebna w konkretnym przypadku testowym; warto jednak pamiętać, że kontener
      nie dysponuje mechanizmami, które mogłyby Ci pomóc w wychwytywaniu błędów nie-
      ustawienia właściwości, które tego wymagają. Jeśli jednak masz do czynienia z kompo-
      nentem, którego wszystkie właściwości (lub przynajmniej wszystkie właściwości określo-
      nego typu) muszą być ustawione, dużym ułatwieniem może być oferowany przez kontener
      mechanizm weryfikacji poprawności zależności. Jeśli zdecydujemy się na użycie tego me-
      chanizmu, kontener będzie traktował ewentualny brak deklaracji wprost lub automatycznego
      wiązania zależności jako błąd. Kontener domyślnie nie podejmuje prób takiej weryfikacji
      (sprawdzania, czy wszystkie zależności są odpowiednio ustawione), można jednak zmienić
      to zachowanie w definicji komponentu za pomocą atrybutu repenrency-check, który może
      mieć następujące wartości:
        n none   — brak weryfikacji zależności. Jeśli dla którejś z właściwości nie określono
           wartości, w przypadku braku weryfikacji taka sytuacja nie będzie traktowana jak
           błąd. Jest to domyślny sposób obsługi poszczególnych komponentów (przynajmniej
           jeśli ustawienia domyślne nie zostaną zmienione na poziomie fabryki komponentów).
        n simple  — sprawdza, czy ustawiono typy proste i kolekcje — brak wartości w tych
           właściwościach będzie traktowany jak błąd. Pozostałe właściwości mogą, ale nie
           muszą być ustawione.
        n objects — sprawdza tylko właściwości innych typów niż typy proste i kolekcje
           — brak wartości w tych właściwościach będzie traktowany jak błąd. Pozostałe
           właściwości mogą, ale nie muszą być ustawione.
        n all — sprawdza, czy ustawiono wartości wszystkich właściwości, w tym we
           właściwościach typów prostych, kolekcjach i właściwościach typów złożonych.

      Istnieje możliwość zmiany domyślnego trybu sprawdzania zależności (ustawienie innego
      trybu niż none) dla wszystkich komponentów w ramach danej fabryki — wystarczy użyć
      atrybutu refault-repenrency-check w elemencie beans na najwyższym poziomie hierarchii.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji         113

    Pamiętaj też, że także interfejs wywołań zwrotnych InitializingBean (patrz następny punkt)
    może być wykorzystywany do ręcznego sprawdzania poprawności zależności.


Zarządzanie cyklem życia komponentu
    Komponent w fabryce może się charakteryzować zarówno bardzo prostym, jak i względnie
    skomplikowanym cyklem życia — wszystko zależy od tego, jaki jest zakres odpowiedzial-
    ności danego komponentu. Ponieważ mówimy o obiektach POJO, cykl życia komponentu
    nie musi obejmować działań wykraczających poza tworzenie i używanie danego obiektu.
    Istnieje jednak wiele sposobów zarządzania i obsługi bardziej złożonymi cyklami życia, z któ-
    rych większość koncentruje się wokół wywołań zwrotnych, gdzie sam komponent i ze-
    wnętrzne obiekty obserwatorów (nazywane postprocesorami komponentu) mogą obsłu-
    giwać wiele etapów procesów inicjalizacji i destrukcji. Przeanalizujmy teraz możliwe akcje
    kontenera (patrz poniższa tabela), które mogą mieć miejsce w cyklu życia komponentu za-
    rządzanego przez kontener.

      Akcja                      Opis
      Inicjalizacja              Egzemplarz nowego komponentu jest tworzony za pośrednictwem
      rozpoczynająca się         konstruktora lub metody fabrykującej (oba rozwiązania są
      w momencie utworzenia      równoważne). Proces tworzenia egzemplarza komponentu jest
      egzemplarza komponentu     inicjowany przez wywołanie metody getBean() fabryki komponentów
                                 lub przez żądanie ze strony innego komponentu, którego egzemplarz
                                 już istnieje i który zawiera zależność od bieżącego komponentu.
      Wstrzyknięcie zależności   Zależności są wstrzykiwane do nowo utworzonego egzemplarza
                                 komponentu (w ramach omówionej już procedury).
      Wywołanie metody           Jeśli komponent implementuje opcjonalny interfejs BeanNameAware,
      setBeanName()              wówczas metoda setBeanName() należąca właśnie do tego interfejsu
                                 jest wywoływana w celu nadania komponentowi jego głównego
                                 identyfikatora (zadeklarowanemu w definicji komponentu).
      Wywołanie metody           Jeśli komponent implementuje opcjonalny interfejs BeanFactoryAware,
      setBeanFactory()           referencję do fabryki, w ramach której ten komponent został
                                 wdrożony, można wprowadzić do komponentu, wywołując metodę
                                 setBeanFactory() tego interfejsu. Warto pamiętać, że ponieważ także
                                 konteksty aplikacji są fabrykami komponentów, wspomniana metoda
                                 będzie wywoływana również w przypadku komponentów
                                 wdrożonych w ramach kontekstu aplikacji (jednak wówczas
                                 komponent otrzyma odwołanie do fabryki wykorzystywanej
                                 wewnętrznie przez dany kontekst).
      Wywołanie metody           Jeśli dany komponent implementuje opcjonalny interfejs
      setResourceLoader()        ResourceLoaderAware i jest wdrażany w ramach kontekstu aplikacji,
                                 przedmiotem wywołania jest metoda setResourceLoader() tego
                                 interfejsu, z parametrem będącym kontekstem aplikacjim który
                                 implementuje interfejs ResourceLoader (to rozwiązanie omówimy
                                 już w następnym rozdziale).
114   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie


       Akcja                          Opis
       Wywołanie metody               Jeśli komponent implementuje opcjonalny interfejs
       setApplicationEvent-           ApplicationEventPublisherAware i jest wdrażany w ramach
       Publisher()                    kontekstu aplikacji, przedmiotem wywołania jest metoda
                                      setApplicationEventPublisher() tego interfejsu, a parametrem
                                      jest kontekst aplikacji, który implementuje interfejs
                                      ApplicationEventPublisher (także to rozwiązanie omówimy
                                      w następnym rozdziale).
       Wywołanie metody               Jeśli komponent implementuje opcjonalny interfejs
       setMessageSource()             MessageSourceAware i jest wdrażany w ramach kontekstu aplikacji,
                                      przedmiotem wywołania jest należąca do tego interfejsu metoda
                                      setResourceLoader(), a przekazywany jest kontekst aplikacji, który
                                      implementuje interfejs MessageSource (także to rozwiązanie
                                      omówimy w następnym rozdziale).
       Wywołanie metody               Jeśli dany komponent implementuje opcjonalny interfejs
       setApplicationContext()        ApplicationContextAware i jest wdrażany w ramach kontekstu
                                      aplikacji, zapewnienie komponentowi referencji do tego kontekstu
                                      wymaga wywołania metody setApplicationContext(), która jest
                                      częścią interfejsu ApplicationContextAware.
       Przekazanie                    Postprocesory komponentu (które omówimy w dalszej części
       postprocesorom                 tego rozdziału) są specjalnymi rozwiązaniami pomocniczymi
       komponentu wywołania           rejestrowanymi przez aplikację wraz z fabrykami komponentów.
       zwrotnego „przed               Postprocesory otrzymują wraz z wywołaniami zwrotnymi
       inicjalizacją”                 (poprzedzającymi właściwą inicjalizację) komponent, który mogą
                                      dowolnie przetwarzać.
       Wywołanie metody               Jeśli komponent implementuje interfejs InitializingBean,
       afterPropertiesSet()           następuje wywołanie definiowanej przez ten interfejs metody
                                      afterPropertiesSet(), która umożliwia komponentowi samodzielną
                                      inicjalizację.
       Wywołanie                      Jeśli definicja danego komponentu zawiera deklarację metody
       zadeklarowanej metody          inicjalizującej (w formie odpowiedniej wartości atrybutu
       inicjalizującej                init-method), właśnie ta metoda zostanie wywołana,
                                      aby umożliwić komponentowi samodzielną inicjalizację.
       Przekazanie postprocesorom     Każdy postprocesor komponentu otrzymuje wywołanie zwrotne
       komponentu wywołania           „po inicjalizacji” z dołączonym (w formie argumentu)
       zwrotnego „po inicjalizacji”   egzemplarzem komponentu, który — w razie potrzeby
       z argumentem zawierającym      — może dowolnie przetwarzać.
       egzemplarz komponentu
       Użycie komponentu              Na tym etapie egzemplarz komponentu jest przedmiotem właściwych
                                      operacji. Oznacza to, że komponent jest zwracany do kodu obiektu
                                      wywołującego metodę getBean() i wykorzystywany do ustawienia
                                      właściwości pozostałych komponentów (które wywołały bieżący
                                      komponent jako swoje zależności) itd. Ważna uwaga: Tylko cykl
                                      życia komponentów singletonowych jest od tego momentu śledzony
                                      przez kontener, natomiast właścicielami komponentów prototypowych
                                      są obiekty klienckie, które z nich korzystają. Kontener będzie więc
                                      uczestniczył w przyszłych zdarzeniach związanych tylko z cyklem
                                      życia komponentów singletonowych. Od tego momentu wszystkie
                                      komponenty prototypowe muszą być w całości zarządzane przez
                                      swoich klientów (dotyczy to także wywołania metody niszczącej).
Rozdział 2. n Fabryka komponentów i kontekst aplikacji          115


       Akcja                     Opis
       Rozpoczęcie procedury     W ramach procesu kończenia pracy fabryki komponentów lub
       destrukcji komponentu     kontekstu aplikacji (który obejmuje trzy opisane poniżej kroki)
                                 należy zniszczyć wszystkie składowane w pamięci podręcznej
                                 komponenty singletonowe. Należy pamiętać, że komponenty są
                                 niszczone w kolejności odpowiadającej występującymi między nimi
                                 zależnościami (najczęściej w kolejności odwrotnej do inicjalizacji).
       Przekazanie               Każdy postprocesor komponentu implementujący interfejs
       postprocesorom            DestructionAwareBeanPostProcessor otrzymuje wywołanie zwrotne,
       komponentu wywołania      które umożliwia mu przygotowanie komponentu singletonowego
       zwrotnego „zniszcz”       do zniszczenia.
       Wywołanie metody          Jeśli komponent singletonowy implementuje interfejs
       destroy()                 DisposableBean, wówczas jest wywoływana metoda destroy()
                                 tego interfejsu, która umożliwia komponentowi wykonanie
                                 wszystkich niezbędnych operacji zwalniania zasobów.
       Wywołanie                 Jeśli definicja komponentu singletonowego zawiera deklarację
       zadeklarowanej metody     metody niszczącej (w formie wartości atrybutu destroy-method),
       niszczącej                odpowiednia metoda jest wywoływana na samym końcu, a jej
                                 działania najczęściej sprowadzają się do zwalniania wszystkich
                                 zasobów, które tego wymagają.


Wywołania zwrotne inicjalizacji i destrukcji

      Wspomniane przed chwilą metody inicjalizujące i niszczące mogą być wykorzystywane do
      wykonywania wszystkich niezbędnych operacji w zakresie rezerwowania i zwalniania za-
      sobów dla danego komponentu. W przypadku istniejących klas, które zawierają już unika-
      towo nazwane metody inicjalizujące i niszczące, jedynym skutecznym rozwiązaniem jest
      użycie atrybutów init-methor i restroy-methor wymuszających na kontenerze wywoływa-
      nie tych metod we właściwych momentach — patrz poniższy przykład, gdzie konieczne
      jest wywołanie metody close() zastosowanej w implementacji DataSource (korzystającej
      z Jakarta Commons DBCP):
         <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
         method="close"<
           <property name="driverClassName"<
             <value<oracle.jdbc.driver.OracleDriver</value<
           </property<
           <property name="url"<
             <value<jdbc:oracle:thin:@db-server:1 21:devdb</value<
           </property<
           <property name="username"<<value<john</value<</property<
           <property name="password"<<value<password</value<</property<
         </bean<

      Ogólnie nawet podczas budowania nowego oprogramowania zaleca się stosowanie atrybutów
      init-methor i restroy-methor, które zasygnalizują kontenerowi konieczność wywołania
      odpowiednich metod inicjalizujących i niszczących — takie rozwiązanie jest pod wieloma
      względami lepsze od implementowania przez sam komponent interfejsów InitializingBean
116   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      i DisposableBean Springa z ich metodami afterPropertiesSet() i restroy(). To drugie po-
      dejście jest z jednej strony wygodne, ponieważ wspomniane interfejsy są automatycznie
      wykrywane przez Springa, co oznacza, że także wymienione metody są wywoływane przez
      ten framework bez udziału programisty, z drugiej strony niepotrzebnie wiąże Twój kod z kon-
      tenerem Springa. Jeśli kod aplikacji jest związany ze Springiem także z innych powodów
      i w innych punktach, ta dodatkowa zależność nie stanowi większego problemu, a stosowanie
      interfejsów InitializingBean i DisposableBean jednocześnie jest niemałym ułatwieniem
      dla programisty. Okazuje się, że także kod samego Springa, który jest wdrażany w formie
      komponentów, często wykorzystuje oba interfejsy.

      Jak już wspomniano, po zakończeniu fazy inicjalizacji niesingletonowe, prototypowe kom-
      ponenty nie są składowane ani zarządzane w kontenerze. Oznacza to, że w przypadku tych
      komponentów Spring nie ma żadnych możliwości ani w zakresie wywoływania metod
      niszczących, ani w kwestii jakichkolwiek innych działań związanych z cyklem życia kom-
      ponentów (właściwych tylko dla zarządzanych przez kontener komponentów singletono-
      wych). Każda taka metoda musi być wywoływana z poziomu kodu użytkownika. Co więcej,
      postprocesory nie mogą przetwarzać komponentów znajdujących się w fazie niszczenia.


Wywołania zwrotne interfejsów BeanFactoryAware i ApplicationContextAware

      Komponent, który z jakiegoś względu musi mieć dostęp do danych i mechanizmów swojej
      fabryki komponentów lub kontekstu aplikacji, może implementować odpowiednio interfejs
      BeanFactoryAware i ApplicationContextAware. Z przedstawionej przed chwilą tabeli pre-
      zentującej kolejność akcji podejmowanych przez kontener i związanych z cyklem życia
      komponentu wynika, że referencje do kontenerów są przekazywane do komponentów za
      pośrednictwem metod setBeanFactory() i setApplicationContext() (definiowanych przez
      wspomniane interfejsy). Ogólnie, większość kodu aplikacji w ogóle nie powinna do prawi-
      dłowego funkcjonowania potrzebować informacji o swoich kontenerach, jednak w kodzie
      ściśle związanym ze Springiem takie rozwiązanie może się okazać bardzo przydatne (tego
      rodzaju wywołania zwrotne są stosowane między innymi w klasach samego Springa). Jed-
      nym z przypadków, w których kod aplikacji może wymagać referencji do fabryki kompo-
      nentu, jest komponent singletonowy współpracujący z komponentami prototypowymi. Po-
      nieważ zależności komponentu singletonowego są do niego wstrzykiwane tylko raz, tak
      stosowany mechanizm wstrzykiwania wyklucza możliwość uzyskiwania nowych egzem-
      plarzy komponentów prototypowych w czasie wykonywania właściwych zadań (a więc po
      fazie inicjalizacji). Oznacza to, że jednym ze skutecznych rozwiązań tego problemu jest
      bezpośredni dostęp do fabryki tego komponentu. Wydaje się jednak, że w większości przy-
      padków jeszcze lepszym wyjściem jest użycie wspominanej już techniki wstrzykiwania
      metody wyszukującej, która dodatkowo zapewnia izolację klasy od mechanizmów Springa
      i nazwy komponentu prototypowego.


Abstrakcja dostępu do usług i zasobów
      Istnieje co prawda dużo więcej zaawansowanych funkcji oferowanych przez fabryki kom-
      ponentów i konteksty aplikacji, o których do tej pory nawet nie wspominaliśmy, jednak
      udało nam się przeanalizować niemal wszystkie niskopoziomowe elementy składowe nie-
      zbędne do skutecznego programowania i wdrażania rozwiązań IoC. Przekonaliśmy się, jak
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   117

obiekty aplikacji mogą realizować swoje zadania wyłącznie we współpracy z pozostałymi
obiektami udostępnianymi przez kontener (za pośrednictwem odpowiednich interfejsów i abs-
trakcyjnych nadklas) bez konieczności dostosowywania swoich zachowań do implementacji
lub kodu źródłowego tych obiektów. Wiedza przekazana do tej pory powinna Ci w zupeł-
ności wystarczyć do realizacji typowych scenariuszy korzystania z technik wstrzykiwania
zależności.

Tym, co może jeszcze budzić pewne wątpliwości, jest rzeczywisty sposób uzyskiwania po-
zostałych obiektów współpracujących (o których wiesz, że mogą być konfigurowane i udo-
stępniane na rozmaite sposoby). Aby zrozumieć, jak Spring chroni nas przed potencjalnymi
zawiłościami i jak radzi sobie z transparentnym zarządzaniem tego rodzaju usługami, prze-
analizujmy kilka ciekawych przykładów.

Wyobraź sobie nieco zmieniony obiekt DAO naszej usługi pogodowej, który zamiast wy-
korzystywać dane statyczne używa JDBC do uzyskiwania historycznych informacji pogo-
dowych z bazy danych. Wstępna implementacja takiego rozwiązania może wykorzystywać
oryginalny interfejs DriverManager JDBC 1.0 i za jego pośrednictwem uzyskiwać niezbędne
połączenie — patrz metoda finr():
   public WeatherData find(Date date) {
     // Zwróć uwagę, że w konstruktorze lub metodzie inicjalizującej użyliśmy
     // wywołania Class.forName(driverClassName) do zarejestrowania
     // sterownika JDBC. Wraz z nazwą użytkownika i hasła wspomniany
     // sterownik został skonfigurowany jako właściwości tego komponentu.
     try {
         Connection con = DriverManager.getConnection(url, username, password);
         // od tego miejsca możemy używać tego połączenia
        ...

Kiedy wdrożymy taki obiekt DAO w postaci komponentu wewnątrz kontenera Springa, bę-
dziemy mogli skorzystać z ułatwień (przede wszystkim mechanizmów wstrzykiwania przez
metody ustawiające i wstrzykiwania przez konstruktor) w zakresie dostarczania wszystkich
wartości wymaganych przez sterownik JDBC do nawiązania połączenia, czyli adresu URL,
nazwy użytkownika i hasła. Nie mamy jednak zamiaru tworzyć puli połączeń (niezależnie
od tego, czy będziemy korzystać ze środowiska J2EE czy z mniej zaawansowanego konte-
nera); nasze połączenie w założeniu nie ma podlegać żadnym procedurom zarządzania
transakcjami (znanym z kontenera J2EE i stosowanym tylko w przypadku połączeń zarzą-
dzanych przez kontener).

Dość oczywistym rozwiązaniem w kwestii uzyskiwania niezbędnych połączeń jest użycie
implementacji interfejsu DataSource wprowadzonych w JDBC 2.0. Kiedy nasz obiekt DAO
będzie już zawierał egzemplarz takiej implementacji, wystarczy że zażąda odpowiedniego
połączenia (faktyczna realizacja tego żądania następuje gdzie indziej). Teoretyczna dostęp-
ność egzemplarza DataSource nie stanowi problemu, ponieważ wiemy, że istnieją takie nie-
zależne, autonomiczne implementacje puli połączeń jak Apache Jakarta Commons DBCP,
które można z powodzeniem stosować w środowiskach J2SE oraz J2EE i które są udostęp-
niane za pośrednictwem interfejsu DataSource; wiemy także, że podobne mechanizmy za-
rządzania pulą połączeń na poziomie kontenera (będące częścią szerszych rozwiązań w za-
kresie zarządzania transakcjami) są dostępne w większości środowisk kontenerowych J2EE,
gdzie także mają postać implementacji interfejsu DataSource.
118   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      Warto jednak pamiętać, że angażowanie interfejsu DataSource w proces uzyskiwania połą-
      czeń z bazą danych wprowadza dodatkową złożoność, ponieważ tworzenie i uzyskiwanie
      samego egzemplarza implementacji tego interfejsu może się odbywać na wiele różnych
      sposobów. Przykładowo, implementacja DataSource dla Commons DBCP ma postać pro-
      stego komponentu JavaBean wypełnianego kilkoma właściwościami konfiguracyjnymi,
      natomiast w większości środowisk kontenerów J2EE egzemplarz interfejsu DataSource
      uzyskuje się za pośrednictwem interfejsu JNDI i wykorzystuje w ramach sekwencji kodu
      podobnej do tej, którą przedstawiono poniżej:
         try {
           InitialContext context = new InitialContext();
           DataSource ds = (DataSource)context.lookup("java:comp/env/jdbc/datasource");
           // od tego miejsca możemy używać egzemplarza DataSource
           ...
         }
         catch (NamingException e) {
           // obsługuje wyjątek nazewnictwa w przypadku braku żądanego zasobu
         }

      Pozostałe implementacje DataSource mogą wymagać stosowania zupełnie innej strategii
      tworzenia i dostępu.

      Być może nasz obiekt DAO mógłby sam wybierać właściwy sposób tworzenia lub uzyski-
      wania poszczególnych typów implementacji interfejsu DataSource — być może wybór od-
      powiedniej implementacji powinien być jednym z elementów konfiguracji. Ponieważ wie-
      my już co nieco o Springu i technice odwracania kontroli (IoC), doskonale zdajemy sobie
      sprawę, że nie byłoby to najlepsze rozwiązanie, ponieważ niepotrzebnie wiązałoby nasz
      obiekt DAO z implementacją DataSource i — tym samym — utrudniłoby konfigurację i bardzo
      skomplikowało proces testowania. Dość oczywistym rozwiązaniem jest uczynienie z im-
      plementacji interfejsu DataSource właściwości naszego DAO, którą będzie można wstrzy-
      kiwać do tego obiektu z poziomu kontenera Springa. Takie podejście doskonale zdaje eg-
      zamin w przypadku implementacji DataSource w wersji Commons DBCP, gdzie tworzony
      egzemplarz tego interfejsu jest w prosty sposób wstrzykiwany do obiektu DAO:
         public class JdbcWeatherDaoImpl implements WeatherDao {

             DataSource dataSource;

             public void setDataSource(DataSource dataSource) {
               this.dataSource = dataSource;
             }

             public WeatherData find(Date date) {
               try {
                 Connection con = dataSource.getConnection();
                 // od tego miejsca możemy używać tego połączenia
               ...
             }
             ...
         }

         <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
         method="close"<
           <property name="driverClassName"<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   119

            <value<oracle.jdbc.driver.OracleDriver</value<
          </property<
          <property name="url"<
            <value<jdbc:oracle:thin:@db-server:1 21:devdb</value<
          </property<
          <property name="username"<<value<john</value<</property<
          <property name="password"<<value<password</value<</property<
        </bean<

        <bean id="weatherDao" class="ch02.sampleX.JdbcWeatherDaoImpl"<
          <property name="dataSource"<
            <ref bean="dataSource"/<
          </property<
        </bean<

     Warto się jeszcze zastanowić nad sposobem wymiany wykorzystywanej implementacji in-
     terfejsu DataSource (w tym przypadku DBCP) na inną (np. uzyskiwaną ze środowiska JNDI).
     Wygląda na to, że rozwiązanie tego problemu nie będzie łatwe, ponieważ poza ustawie-
     niem w naszym obiekcie DAO właściwości typu DataSource musimy otrzymać odpowied-
     nią wartość JNDI — na razie wiemy tylko, jak przygotowywać konfigurację kontenera, aby
     definiowała właściwości JavaBean zawierające konkretne wartości lub odwołania do innych
     komponentów. Okazuje się, że realizacja tego zadania wcale nie wymaga wielkich umiejętno-
     ści; wystarczy użyć klasy pomocniczej nazwanej JnriObjectFactoryBean:
        <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"<
          <property name="jndiName"<
            <value<java:comp/env/jdbc/datasource</value<
          </property<
        </bean<

        <bean id="weatherDao" class="ch02.sampleX.JdbcWeatherDaoImpl"<
          <property name="dataSource"<
            <ref bean="dataSource"/<
          </property<
        </bean<


Analiza komponentów fabrykujących

     JnriObjectFactoryBean jest przykładem komponentu fabrykującego. Komponenty fabry-
     kujące Springa bazują na bardzo prostej koncepcji — najprościej mówiąc, komponent fa-
     brykujący to taki, który na żądanie generuje inny obiekt. Wszystkie komponenty fabrykują-
     ce Springa implementują interfejs org.springframework.beans.factory.FactoryBean.
     „Magia” tego rozwiązania tkwi we wprowadzeniu kolejnego poziomu pośredniczenia. Sam
     komponent fabrykujący jest tak naprawdę zwykłym komponentem JavaBean. Wdrażając
     komponent fabrykujący (tak jak każdy inny komponent JavaBean), musimy określić wła-
     ściwości i argumenty konstruktora niezbędne do prawidłowego funkcjonowania tego kom-
     ponentu. Okazuje się jednak, że kiedy inny komponent w ramach tego samego kontenera
     odwołuje się do tego komponentu fabrykującego (za pośrednictwem elementu <ref>) lub
     kiedy programista ręcznie zażądał tego komponentu (za pośrednictwem wywołania metody
     getBean()), kontener nie zwraca samego komponentu fabrykującego — wykrywa, że ma do
     czynienia z komponentem fabrykującym (za pomocą odpowiedniego interfejsu) i zamiast
     egzemplarza tego komponentu zwraca utworzony przez niego obiekt. Oznacza to, że z punktu
120   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      widzenia zależności każdy komponent fabrykujący w istocie może być traktowany jak
      obiekt, który jest przez niego zwracany. Przykładowo, w przypadku komponentu JnriObject-
      FactoryBean tym obiektem jest obiekt odnaleziony w środowisku JNDI (w naszym przy-
      kładzie egzemplarz implementacji interfejsu DataSource).

      Interfejs FactoryBean jest bardzo prosty:
         public interface FactoryBean {

             Object getObject() throws Exception;

             Class getObjectType();

             boolean isSingleton();

         }

      Wywoływana przez kontener metoda getObject() zwraca obiekt wynikowy. Metoda
      isSingleton() informuje, czy w kolejnych wywołaniach metody getObject() będzie zwra-
      cany ten sam egzemplarz obiektu czy wiele różnych egzemplarzy. I wreszcie metoda get-
      ObjectType() pozwala określić typ zwróconego obiektu (lub wartość null, jeśli typ tego
      obiektu nie jest znany). Komponenty fabrykujące są co prawda normalnie konfigurowane
      i wdrażane w kontenerach, jednak są także komponentami JavaBeans, zatem — w razie
      konieczności — można z nich korzystać także programowo.

         Warto się zastanowić, jak to możliwe, że komponent fabrykujący jest uzyskiwany za
         pośrednictwem wywołania metody getBean(), skoro odpowiedzią na żądanie pobrania
         komponentu fabrykującego jest jakiś inny obiekt wynikowy. Kluczem jest specjalny
         mechanizm, który sygnalizuje kontenerowi, że chodzi o sam komponent fabrykujący,
         a nie generowany przez niego obiekt wynikowy — wystarczy poprzedzić identyfikator
         komponentu znakiem &:
             FactoryBean facBean = (FactoryBean)appContext.getBean("&dataSource");


      Tworzenie własnych, niestandardowych komponentów fabrykujących jest bardzo proste.
      Warto jednak pamiętać, że Spring zawiera wiele przydatnych implementacji tego typu kompo-
      nentów, które oferują abstrakcje dostępu do większości tych popularnych zasobów i usług,
      w przypadku których stosowanie komponentów fabrykujących przynosi określone korzyści.
      Poniżej przedstawiono dość okrojoną listę tych implementacji:
        n JnriObjectFactoryBean — zwraca obiekt będący wynikiem operacji wyszukiwania JNDI.

        n ProxyFactoryBean      — opakowuje istniejący obiekt wewnątrz odpowiedniego
             pośrednika, który jest następnie zwracany w odpowiedzi na żądanie. Rzeczywiste
             funkcjonowanie tego pośrednika zależy od konfiguracji zdefiniowanej przez
             użytkownika — może obejmować przechwytywanie i modyfikowanie zachowania
             obiektu, przeprowadzanie testów bezpieczeństwa itd. Techniki stosowania tego
             komponentu fabrykującego omówimy bardziej szczegółowo w rozdziale poświęconym
             frameworkowi programowania aspektowego (AOP) oferowanemu w ramach Springa.
        n TransactionProxyFactoryBean      — specjalizacja klasy ProxyFactoryBean,
             która opakowuje obiekt transakcyjnym pośrednikiem.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji    121

       n RmiProxyFactoryBean      — tworzy pośrednik obsługujący przezroczysty dostęp do
           zdalnego obiektu za pośrednictwem technologii zdalnego wywoływania metod
           (RMI). Podobne obiekty pośredniczące, tyle że dla dostępu do zdalnych obiektów
           przez protokoły HTTP, JAX-RPC, Hessian i Burlap są tworzone odpowiednio
           przez klasy HttpInvokerProxyFactoryBean, JaxRpcPortProxyFactoryBean,
           HessianProxyFactoryBean i BurlapProxyFactoryBean. We wszystkich przypadkach
           klient nie musi dysponować żadną wiedzą o pośredniku — wystarczy przystosowanie
           do współpracy z interfejsem biznesowym.
       n LocalSessionFactoryBean    — konfiguruje i zwraca obiekt SessionFactory
           frameworka Hibernate. Istnieją podobne klasy także dla innych mechanizmów
           zarządzania zasobami, w tym JDO i iBatis.
       n LocalStatelessSessionProxyFactoryBean i SimpleRemoteStatelessSessionProxyFactoryBean
           — tworzą obiekt pośrednika wykorzystywany do uzyskiwania dostępu odpowiednio
           do lokalnych lub zdalnych bezstanowych komponentów sesyjnych EJB. Sam klient
           wykorzystuje tylko interfejs biznesowy (bez konieczności obsługi dostępu do JNDI
           lub interfejsów EJB).
       n MethorInvokingFactoryBean    — zwraca wynik wywołania metody innego
           komponentu, natomiast klasa FielrRetrievingFactoryBean zwraca wartość pola
           należącego do innego komponentu.
       n   Wiele komponentów fabrykujących związanych z technologią JMS zwraca jej zasoby.


Podsumowanie i efekt końcowy

     W poprzednich punktach przekonaliśmy się, że konfigurowanie obiektów z wykorzystaniem
     technik IoC jest bardzo proste. Zanim jednak przystąpimy do wzajemnego wiązania obiek-
     tów, musimy te obiekty najpierw utworzyć lub uzyskać. W przypadku kilku potencjalnych
     obiektów współpracujących (nawet jeśli są ostatecznie udostępniane i konfigurowane za
     pośrednictwem standardowego interfejsu) sam fakt tworzenia i uzyskiwania rozmaitych
     obiektów za pomocą skomplikowanych i niestandardowych mechanizmów może stanowić
     przeszkodę w efektywnym przetwarzaniu obiektów. Okazuje się, że można tę przeszkodę
     wyeliminować za pomocą komponentów fabrykujących. Po początkowych fazach tworze-
     nia i wiązania obiektów produkty generowane przez komponenty fabrykujące (osadzane
     w pośrednikach i innych podobnych obiektach opakowań) mogą pełnić funkcję adaptera —
     mogą pomagać w budowie abstrakcji ponad rzeczywistymi zasobami i usługami oraz za-
     pewniać dostęp do zupełnie niepodobnych usług za pośrednictwem podobnych interfejsów.

     Jak wiemy, źródła danych można dowolnie wymieniać bez konieczności umieszczania
     w kodzie klienta (w analizowanym przykładzie tę funkcję pełnił obiekt weatherDao) jakich-
     kolwiek zapisów uzależniających od stosowanej technologii — przykładowo, zastąpiliśmy
     oryginalną implementację interfejsu DataSource opartą na Commons DBCP (mającą postać
     lokalnego komponentu) implementacją interfejsu DataSource właściwą dla technologii JNDI.
     Taka zamiana jest możliwa nie tylko dzięki mechanizmowi IoC, ale także dzięki wyprowa-
     dzeniu operacji dostępu do zasobów poza kod aplikacji i — tym samym — przygotowaniu
     właściwej struktury dla technik IoC.
122   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie


         Mamy nadzieję, że dostrzegasz dwie najważniejsze zalety omawianego mechanizmu —
         z jednej możliwość łatwego abstrahowania od usług i zasobów, z drugiej strony możli-
         wość wymiany jednego rozwiązania na inne bez konieczności wykorzystywania sposo-
         bów wdrożenia i technologii, których stosowanie podczas budowy wielu aplikacji naj-
         zwyczajniej w świecie mija się z celem. Cechą wyróżniającą Springa jest niezależność
         kodu klienta zarówno od faktycznego wdrożenia, jak i od użytego sposobu implementacji.

      Skoro możemy w sposób transparentny, przezroczysty (przynajmniej z perspektywy klien-
      ta) uzyskiwać dostęp do zdalnych usług za pośrednictwem takich technologii jak RMI,
      RPC przez HTTP czy EJB, niby dlaczego nie mielibyśmy wdrażać całkowicie niezależnych
      rozwiązań, i po co w ogóle wiązać kod klienta z jedną implementacją ponad inną tam, gdzie
      nie jest to konieczne? W rozwiązaniach platformy J2EE można zaobserwować tendencję do
      udostępniania takich zasobów jak implementacje DataSource, zasoby JMS czy interfejsy
      JavaMail za pośrednictwem JNDI. Nawet jeśli ten sposób udostępniania zasobów znajduje
      jakieś uzasadnienie, bezpośredni dostęp klienta do środowiska JNDI jest całkowicie sprzeczny
      z założeniami efektywnego wytwarzania oprogramowania. Budowa dodatkowej warstwy
      abstrakcji np. za pomocą klasy JnriObjectFactoryBean oznacza, że w przyszłości będzie
      można zmienić środowisko na inne niż JNDI bez konieczności modyfikowania kodu
      klienta (odpowiednio dostosowując wyłącznie konfigurację samej fabryki komponentów).
      Nawet jeśli nie planujesz zmiany środowiska wdrożeniowego ani technologii implementa-
      cji już po wdrożeniu oprogramowania w docelowym środowisku, możesz być pewien, że
      taka abstrakcja znacznie ułatwi testy jednostkowe i testy integracyjne, ponieważ umożliwi
      weryfikację różnych konfiguracji wdrożeń i scenariuszy testowych. Tym zagadnieniom po-
      święcimy więcej miejsca w następnym rozdziale. Warto też podkreślić, że stosując komponent
      JnriObjectFactoryBean, eliminujemy potrzebę pisania zaawansowanego kodu w obiekcie
      DAO (konkretnie wyszukiwania obiektu w środowisku JNDI), który nie miałby nic wspól-
      nego z właściwą funkcjonalnością biznesową tego obiektu. Ten przykład dobrze pokazuje,
      jak stosowanie techniki wstrzykiwania zależności ogranicza złożoność i nieład w kodzie
      źródłowym aplikacji.


Wielokrotne wykorzystywanie tych samych definicji komponentów
      W porównaniu z ilością kodu Javy niezbędnego do pełnej konfiguracji i deklaracji wszyst-
      kich zależności definicje komponentów wyrażane w języku XML wydają się dość zwięzłe.
      Nie można jednak wykluczyć, że będziemy zmuszeni do opracowania wielu definicji kom-
      ponentów, które w zdecydowanej większości będą do siebie bardzo podobne.

      Przeanalizuj następującą definicję komponentu WeatherService:
         <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
           <property name="weatherDao"<
             <ref local="weatherDao"/<
           </property<
         </bean<

      W typowym scenariuszu budowy aplikacji, gdzie nieodłącznym elementem zaplecza jest
      relacyjna baza danych, musielibyśmy korzystać z podobnych obiektów usług w sposób bar-
      dziej „transakcyjny”. Szczegółowe omówienie tego zagadnienia znajdziesz w rozdziale 6.
Rozdział 2. n Fabryka komponentów i kontekst aplikacji    123

Jednym z najprostszych sposobów osiągnięcia tego celu jest deklaratywne opakowanie ta-
kiego obiektu, aby nabrał cech obiektu transakcyjnego (np. za pomocą komponentu fabry-
kującego Springa nazwanego TransactionProxyFactoryBean):
   <bean id="weatherServiceTarget" class="ch02.sample2.WeatherServiceImpl"<
     <property name="weatherDao"<
       <ref local="weatherDao"/<
     </property<
   </bean<

   <!-- pośrednik transakcyjny -->
   <bean id="weatherService"
     class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"<

     <property name="target"<<ref local="weatherServiceTarget"/<</property<

     <property name="transactionManager"<<ref local="transactionManager"/<</property<
     <property name="transactionAttributes"<
       <props<
         <prop key="*"<PROPAGATION_REQUIRED</prop<
       </props<
     </property<
   </bean<

Jeśli na tym etapie nie do końca rozumiesz, jak należy szczegółowo konfigurować kompo-
nent TransactionProxyFactoryBean lub jak ten komponent faktycznie działa, nie masz po-
wodów do zmartwień. Istotne jest to, że wspomniany komponent jest implementacją inter-
fejsu FactoryBean, która dla otrzymanego na wejściu komponentu docelowego generuje
obiekt transakcyjnego pośrednika (z jednej strony implementujący te same interfejsy co
komponent docelowy, z drugiej strony obsługujący dodatkową semantykę przetwarzania
transakcyjnego). Ponieważ chcemy, aby kod kliencki wykorzystywał obiekt opakowania,
nazwa oryginalnego (nieopakowanego i nietransakcyjnego) komponentu jest zmieniana (rozsze-
rzana o sufiks Target), a jego pierwotną nazwę otrzymuje wygenerowany komponent trans-
akcyjny (w tym przypadku będą to odpowiednio weatherServiceTarget i weatherService).
Oznacza to, że istniejący kod kliencki, który korzysta z usługi pogodowej, w ogóle „nie wie”,
że od momentu zmiany konfiguracji korzysta z usługi transakcyjnej.

Opisany mechanizm deklaratywnego opakowywania obiektów jest co prawda bardzo wy-
godny (szczególnie w zestawieniu z rozwiązaniem alternatywnym, czyli działaniami pro-
gramowymi na poziomie kodu), jednak w przypadku większych aplikacji z dziesiątkami
lub setkami interfejsów usług, które wymagają stosowania bardzo podobnych technika
opakowywania, wielokrotne definiowanie niemal identycznego, szablonowego kodu XML
wydaje się stratą czasu. Okazuje się, że wprost idealnym rozwiązaniem tego problemu jest
oferowana przez kontener obsługa definicji zarówno macierzystych, jak i potomnych kompo-
nentów. Korzystając z dobrodziejstw tego mechanizmu, możemy w prosty sposób zdefi-
niować abstrakcyjny, macierzysty (szablonowy) pośrednik transakcyjny:
   <bean id="txProxyTemplate" abstract="true"
     class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"<
   <property name="transactionManager"<
       <ref local="transactionManager"/<
     </property<
     <property name="transactionAttributes"<
124   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

             <props<
               <prop key="*"<PROPAGATION_REQUIRED</prop<
             </props<
           </property<
         </bean<

      Następnym krokiem jest utworzenie właściwych pośredników za pomocą odpowiednich
      definicji potomnych, które będą obejmowały tylko właściwości odróżniające tych pośred-
      ników od ich wspólnego, macierzystego szablonu (w tym przypadku komponentu txProxy-
      Template):
         <bean id="weatherServiceTarget" class="ch02.sample2.WeatherServiceImpl"<
           <property name="weatherDao"<
             <ref local="weatherDao"/<
           </property<
         </bean<

         <bean id="weatherService" parent="txProxyTemplate"<
         <property name="target"<<ref local="weatherServiceTarget"/<</property<
         </bean<

      Istnieje możliwość skorzystania z jeszcze bardziej klarownej i zwięzłej formy dziedzicze-
      nia. Ponieważ żaden z klientów nigdy nie będzie potrzebował dostępu do nieopakowanego
      komponentu naszej usługi pogodowej, można go zdefiniować jako komponent wewnętrzny
      względem pośrednika, który go opakowuje:
         <bean id="weatherService" parent="txProxyTemplate"<
         <property name="target"<
             <bean class="ch02.sample2.WeatherServiceImpl"<
               <property name="weatherDao"<
                 <ref local="weatherDao"/<
               </property<
             </bean<
           </property<
         </bean<

      Jeśli jakaś inna usługa będzie wymagała podobnego opakowania, programista będzie mógł
      użyć definicji komponentu, która w bardzo podobny sposób odziedziczy właściwości ma-
      cierzystego szablonu. W poniższym przykładzie odziedziczona właściwość transaction-
      Attributes jest dodatkowo przykrywana — w ten sposób wprowadzono nowe (właściwe
      dla danego pośrednika) ustawienia propagowania transakcji:
         <bean id="anotherWeatherService" parent="txProxyTemplate"<
         <property name="target"<
             <bean class="ch02.sampleX.AnotherWeatherServiceImpl"/<
           </property<
           <property name="transactionAttributes"<
             <props<
               <prop key="save*"<PROPAGATION_REQUIRED</prop<
               <prop key="*"<PROPAGATION_REQUIRED,readOnly</prop<
             </props<
           </property<
         </bean<
Rozdział 2. n Fabryka komponentów i kontekst aplikacji         125


        Jeszcze prostszym rozwiązaniem jest skorzystanie z mechanizmu automatycznego po-
        średniczenia (ang. autoproxying) programowania aspektowego, który automatycznie
        wykrywa wszelkie cechy wspólne porad AOP w definicjach różnych komponentów. Więcej
        informacji na ten temat znajdziesz w rozdziale 4.


Cechy charakterystyczne definicji komponentów potomnych

     Definicja komponentu potomnego dziedziczy wartości właściwości i argumenty konstruk-
     tora zadeklarowane w definicji komponentu macierzystego (przodka, rodzica). Okazuje się,
     że komponent potomny dziedziczy po definicji komponentu macierzystego także wiele
     opcjonalnych atrybutów (oczywiście pod warunkiem, że zadeklarowano je w definicji ma-
     cierzystej). Zapewne zauważyłeś w poprzednim przykładzie, że do wskazywania identyfi-
     katora komponentu macierzystego w definicji komponentu potomnego służy atrybut parent.

     Definicja komponentu potomnego może wskazywać konkretną klasę lub pozostawiać od-
     powiedni atrybut nieustawiony i — tym samym — odziedziczyć jego wartość po definicji
     komponentu macierzystego. Warto pamiętać, że jeśli definicja potomka określa własną klasę
     (inną niż definicja komponentu macierzystego), nowa klasa musi prawidłowo obsługiwać
     wszystkie argumenty konstruktora i (lub) właściwości zadeklarowane w definicji rodzica,
     ponieważ zostaną one odziedziczone i użyte.

     Potomek dziedziczy co prawda po swoim komponencie macierzystym wartości argumen-
     tów konstruktora, wartości właściwości i metody, jednak — w razie potrzeby — może do-
     dawać także nowe, własne wartości. Z drugiej strony, dziedziczone przez komponent po-
     tomny wartości atrybutów init-methor, restroy-methor lub factory-methor są w całości
     przykrywane przez odpowiednie wartości tego potomka.

     Niektóre ustawienia konfiguracyjne komponentu macierzystego nigdy nie są dziedziczone
     i zawsze są określane w oparciu o definicję bieżącego (potomnego) komponentu. Należą do nich
     wartości atrybutów repenrs-on, autowire, repenrency-check, singleton oraz lazy-init.

     Egzemplarze komponentów oznaczonych jako abstrakcyjne (za pomocą atrybutu abstract
     — patrz powyższy przykład) jako takie nie mogą być tworzone. Jeśli nie przewidujesz sy-
     tuacji, w której będzie konieczne stworzenie egzemplarza komponentu macierzystego, zawsze
     powinieneś go oznaczać jako komponent abstrakcyjny. Należy to traktować jak dobrą
     praktykę, ponieważ egzemplarze nieabstrakcyjnego komponentu macierzystego mogą być
     tworzone przez kontener nawet wtedy, gdy tego nie zażądamy ani nie użyjemy żadnej za-
     leżności wskazującej na ten komponent. Konteksty aplikacji (ale nie fabryki komponentów)
     domyślnie podejmują próby utworzenia wstępnego egzemplarza dla każdego nieabstrak-
     cyjnego komponentu singletonowego.

        Warto pamiętać, że nawet jeśli nie użyjemy atrybutu abstract wprost, definicje kom-
        ponentów mogą być niejawnie interpretowane jako abstrakcyjne ze względu na brak
        klasy lub metody fabrykującej, czyli de facto brak informacji niezbędnych do utworzenia
        ich egzemplarzy. Każda próba utworzenia egzemplarza abstrakcyjnego komponentu
        spowoduje błąd (niezależnie od tego, czy podjęto ją na żądanie programisty czy w spo-
        sób niejawny przez któryś z mechanizmów kontenera).
126   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie


Stosowanie postprocesorów
do obsługi niestandardowych komponentów i kontenerów
      Postprocesory komponentów są w istocie specjalnymi obiektami nasłuchującymi (ang.
      listeners), które można rejestrować (wprost lub niejawnie) w kontenerze i które otrzymują
      ze strony kontenera wywołania zwrotne dla każdego komponentu, dla którego ten kontener
      tworzy egzemplarz. Postprocesory fabryk komponentów są bardzo podobne do postproce-
      sorów komponentów z tą różnicą, że otrzymują wywołania zwrotne w momencie utworzenia
      egzemplarza samego kontenera. W sytuacji, gdy konieczne jest regularne dostosowywanie
      do zmieniających się warunków konfiguracji komponentu, grupy komponentów lub całego
      kontenera, można to zadanie bardzo ułatwić, tworząc własny postprocesor lub korzystając
      z jednego z wielu istniejących postprocesorów dołączanych do Springa.


Postprocesory komponentów

      Postprocesory komponentów implementują interfejs BeanPostProcessor:
         public interface BeanPostProcessor {
           Object postProcessBeforeInitialization(Object bean, String beanName)
               throws BeansException;
           Object postProcessAfterInitialization(Object bean, String beanName)
               throws BeansException;
         }

      Interfejs BeanPostProcessor jest rozszerzany przez interfejs DestructionAwareBeanPostProcessor:
         public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
           void postProcessBeforeDestruction(Object bean, String beanName)
               throws BeansException;
         }

      W przedstawionej w tym rozdziale tabeli zdarzeń składających się na cykl życia kompo-
      nentów dość precyzyjnie wskazano punkty, w których następują poszczególne wywołania
      zwrotne. Spróbujmy teraz stworzyć postprocesor komponentów, który będzie wykorzysty-
      wał wywołanie zwrotne metody postProcessAfterInitialization() do wypisywania na
      konsoli nazwy każdego komponentu, dla którego w danym kontenerze utworzono egzem-
      plarz (wywołanie będzie następowało bezpośrednio po zdarzeniu utworzenia egzemplarza):
         public class BeanInitializationLogger implements BeanPostProcessor {

             public Object postProcessBeforeInitialization(Object bean, String beanName)
                 throws BeansException {
               return bean;
             }

             public Object postProcessAfterInitialization(Object bean, String beanName)
                 throws BeansException {

                 System.out.println("Zainicjalizowano komponent '" + beanName + "'");
                 return bean;
             }
         }
Rozdział 2. n Fabryka komponentów i kontekst aplikacji               127

   Przedstawiony przykład nie jest zbyt skomplikowany — rzeczywiste postprocesory
   najczęściej przetwarzają i zmieniają egzemplarze komponentów, a informacje o reje-
   strowanych zdarzeniach są zapisywane w pliku dziennika, a nie na konsoli.

   W kontekście aplikacji postprocesory komponentów są rozpoznawane i wykorzystywa-
   ne przez kontener automatycznie, a ich wdrażanie przebiega dokładnie tak jak w przy-
   padku wszystkich innych komponentów:
     <beans<
       <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"<
         <property name="weatherDao"<
           <ref local="weatherDao"/<
         </property<
       </bean<
       <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"/<
       <bean id="beanInitLogger" class="ch02.sample8.BeanInitializationLogger"/>
     </beans<


Kiedy skonfigurowany w ten sposób kontekst aplikacji zostanie załadowany, nasz postpro-
cesor otrzyma dwa wywołania zwrotne i w odpowiedzi wygeneruje (wyświetli na konsoli)
następujące dane wyjściowe:
   Zainicjalizowano komponent 'weatherDao'
   Zainicjalizowano komponent 'weatherService'

Stosowanie postprocesorów komponentów dla nawet stosunkowo prostych fabryk kompo-
nentów jest nieco bardziej skomplikowane niż w przypadku kontekstów aplikacji, ponie-
waż wymaga ręcznego rejestrowania (co jest zgodne z duchem fabryk komponentów, który
przewiduje bardziej programowe podejście), zamiast samego zadeklarowania postprocesora
w formie komponentu w pliku konfiguracyjnym XML:
   XmlBeanFactory factory =
              new XmlBeanFactory(new ClassPathResource("ch02/sample8/beans.xml"));
   BeanInitializationLogger logger = new BeanInitializationLogger();
   factory.addBeanPostProcessor(logger);
   // Ponieważ nasze komponenty są singletonami, będą podlegały fazie wstępnego tworzenia egzemplarzy
   // (także wówczas postprocesor otrzyma odpowiednie wywołania zwrotne).
   factory.preInstantiateSingletons();

Jak widać, do utworzenia wstępnych egzemplarzy komponentów singletonowych w danej
fabryce użyto metody BeanFactory.preInstantiateSingletons(), ponieważ domyślnie tyl-
ko konteksty aplikacji tworzą wstępne egzemplarze singletonów. Tak czy inaczej postpro-
cesor jest wywoływany w momencie, w którym egzemplarz danego komponentu faktycznie
jest tworzony, niezależnie od tego, czy w ramach procesu przygotowywania wstępnych eg-
zemplarzy czy w odpowiedzi na konkretne żądanie (jeśli wspomniany proces zostanie po-
minięty).
128   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie


Postprocesory fabryk komponentów

      Postprocesory fabryk komponentów implementują interfejs BeanFactoryPostProcessor:
         public interface BeanFactoryPostProcessor {
           void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                   throws BeansException;
         }

      Poniżej przedstawiono przykład postprocesora fabryki komponentów, którego działanie
      sprowadza się do pobrania i wyświetlenia listy nazw wszystkich komponentów w danej fabryce:
         public class AllBeansLister implements BeanFactoryPostProcessor {

             public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
                 throws BeansException {

                 System.out.println("Fabryka zawiera następujące komponenty:");
                 String[] beanNames = factory.getBeanDefinitionNames();
                 for (int i = 0; i < beanNames.length; ++i)
                   System.out.println(beanNames[i]);
             }
         }

      Samo używanie postprocesorów fabryki komponentów nie różni się zbytnio od stosowania
      postprocesorów komponentów. Ich wdrażanie w kontekście aplikacji przebiega tak samo
      jak w przypadku wszystkich innych komponentów — postprocesory zostaną automatycznie
      wykryte i użyte przez kontekst aplikacji. Z drugiej strony, w przypadku zwykłej fabryki
      komponentów postprocesor musi być uruchomiony ręcznie:
         XmlBeanFactory factory = new XmlBeanFactory(
                 new ClassPathResource("ch02/sample8/beans.xml"));
         AllBeansLister lister = new AllBeansLister();
         lister.postProcessBeanFactory(factory);

      Przeanalizujmy teraz kilka najbardziej przydatnych postprocesorów komponentów i fabryk
      komponentów oferowanych w ramach Springa.


Postprocesor PropertyPlaceholderConfigurer

      Podczas wdrażania aplikacji bazujących na Springu często się okazuje, że większość ele-
      mentów konfiguracji kontenera nie będzie modyfikowana w czasie wdrażania. Zmuszanie
      kogokolwiek do przeszukiwania skomplikowanych plików konfiguracyjnych tylko po to,
      by zmienić kilka wartości, które tego rzeczywiście wymagają, bywa bardzo niewygodne.
      Takie rozwiązanie stwarza też niebezpieczeństwo popełnienia przypadkowego błędu w pliku
      konfiguracyjnym, np. przez nieumyślne zmodyfikowanie niewłaściwej wartości.

      PropertyPlaceholrerConfigurer jest postprocesorem fabryki komponentów, który — użyty
      w definicji fabryki komponentów lub kontekstu aplikacji — umożliwia określanie pewnych
      wartości za pośrednictwem specjalnych łańcuchów zastępczych, łańcuchów-wypełniaczy (ang.
      placeholder strings), które są następnie zastępowane przez rzeczywiste wartości pochodzące
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   129

     z pliku zewnętrznego (w formacie Java Properties). Co więcej, mechanizm konfiguracyjny
     domyślnie będzie weryfikował te łańcuchy pod kątem zgodności z właściwościami syste-
     mowymi Javy, jeśli nie znajdzie odpowiednich dopasowań w pliku zewnętrznym. Tryb takiej
     dodatkowej weryfikacji można włączać i wyłączać za pomocą właściwości systemProperties-
     More konfiguratora (która oferuje też możliwość wymuszania pierwszeństwa dopasowań do
     właściwości System Properties względem zapisów zewnętrznego pliku właściwości).

     Dobrym przykładem wartości, które warto wyprowadzić poza skomplikowane pliki konfi-
     guracyjne, są łańcuchy konfiguracyjne właściwe dla puli połączeń z bazą danych. Poniżej
     przedstawiono ponownie fragment kodu definiującego implementację DataSource korzy-
     stającą z Commons DBCP — tym razem użyto łańcuchów zastępczych zamiast rzeczywi-
     stych wartości:
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-
        method="close"<
        <property name="driverClassName"<<value<${db.driverClassName}</value<</property<
          <property name="url"<<value<${db.url}</value<</property<
          <property name="username"<<value<${db.username}</value<</property<
          <property name="password"<<value<${db.password}</value<</property<
        </bean<

     Rzeczywiste wartości będą pochodziły z zewnętrznego pliku właściwości nazwanego
     jdbc.properties:
        db.driverClassName=oracle.jdbc.driver.OracleDriver
        db.url=jdbc:oracle:thin:@db-server:1 21:devdb
        db.username=john
        db.password=password

     Aby użyć egzemplarza klasy PropertyPlaceholrerConfigurer i za jej pomocą wydobyć
     właściwe wartości z pliku właściwości do kontekstu aplikacji, wystarczy wdrożyć ten eg-
     zemplarz tak jak każdy inny komponent:
        <bean id="placeholderConfig"
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"<
          <property name="location"<<value<jdbc.properties</value<</property<
        </bean<

     Aby użyć teraz zadeklarowanego przed chwilą komponentu konfiguratora dla prostej fa-
     bryki komponentów, należy go uruchomić ręcznie:
        XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocation(new FileSystemResource("db.properties"));
        ppc.postProcessBeanFactory(factory);



Postprocesor PropertyOverrideConfigurer

     PropertyOverrireConfigurer, który także jest postprocesorem fabryk komponentów, co
     prawda pod wieloma względami przypomina postprocesor PropertyPlaceholrerConfigurer,
     jednak o ile w przypadku tego drugiego wartości musiały pochodzić z zewnętrznego pliku
130   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

      właściwości, o tyle PropertyOverrireConfigurer umożliwia przykrywanie tymi wartościami
      wartości właściwości komponentów w fabryce komponentów lub kontekście aplikacji.

      Każdy wiersz pliku właściwości musi mieć następujący format:
         beanName.property=value

      gdzie beanName reprezentuje identyfikator komponentu, property jest jedną z właściwości
      tego komponentu, a value reprezentuje docelową wartość tej właściwości. Plik właściwości
      może zawierać np. następujące zapisy:
         dataSource.driverClassName=oracle.jdbc.driver.OracleDriver
         dataSource.url=jdbc:oracle:thin:@db-server:1 21:devdb
         dataSource.username=john
         dataSource.password=password

      W ten sposób można przykryć cztery właściwości komponentu rataSource. Wszelkie wła-
      ściwości (wszystkich komponentów) w danym kontenerze, które nie zostały przykryte przez
      nowe wartości przedstawionego pliku właściwości, utrzymają swoje dotychczasowe warto-
      ści — albo te zdefiniowane w konfiguracji kontenera, albo wartości domyślne komponentu
      (w przypadku braku odpowiednich zapisów w pliku konfiguracyjnym). Ponieważ sama
      analiza konfiguracji kontenera nie pozwala określić, czy dana wartość zostanie przykryta
      (dla pewności konieczne jest jeszcze przestudiowanie pliku właściwości), tego typu funk-
      cjonalność należy wykorzystywać tylko w uzasadnionych przypadkach.


Postprocesor CustomEditorConfigurer

      CustomPritorConfigurer jest postprocesorem fabryki komponentów, za pomocą którego
      możemy rejestrować własne edytory właściwości (rozszerzenia klasy PropertyPritorSupport)
      komponentów JavaBeans obsługujące konwersję wartości łańcuchowych na docelowe
      wartości właściwości lub argumentów konstruktora (w konkretnym, często złożonym for-
      macie obiektowym).

      Tworzenie własnych edytorów właściwości

      Częstym powodem stosowania własnych, niestandardowych edytorów właściwości jest ko-
      nieczność ustawiania właściwości typu java.util.Date, których wartości źródłowe zapisa-
      no w postaci łańcuchów. Ponieważ formaty dat bardzo często są uzależnione od ustawień
      regionalnych, użycie egzemplarza PropertyPritor eksportującego określony format łańcu-
      cha źródłowego jest najprostszym sposobem radzenia sobie z problemem niezgodności
      formatów. Ponieważ utrudnienia związane z formatami dat dotyczą większości użytkowni-
      ków, zamiast prezentować jakiś abstrakcyjny przykład, posłużymy się klasą CustomDate-
      Pritor (implementacją PropertyPritor dla typu Date) — przeanalizujemy nie tylko kod tej
      klasy, ale także techniki jej rejestrowania i wykorzystywania w praktyce. Własne edytory
      właściwości powinny być implementowane i rejestrowane w bardzo podobny sposób:
         public class CustomDateEditor extends PropertyEditorSupport {

           private final DateFormat dateFormat;
           private final boolean allowEmpty;
Rozdział 2. n Fabryka komponentów i kontekst aplikacji    131

       public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) {
         this.dateFormat = dateFormat;
         this.allowEmpty = allowEmpty;
       }

       public void setAsText(String text) throws IllegalArgumentException {
         if (this.allowEmpty && !StringUtils.hasText(text)) {
           // traktuje pusty łańcuch jak wartość null
           setValue(null);
         }
         else {
           try {
               setValue(this.dateFormat.parse(text));
           }
           catch (ParseException ex) {
               throw new IllegalArgumentException("Konwersja daty nie powiodła się: " +
                         ex.getMessage());
           }
         }
       }

       public String getAsText() {
         return (getValue() == null ? "" : this.dateFormat.format((Date)getValue()));
       }
   }

Pełna dokumentacja tej klasy w formacie JavaDoc jest dołączana do wersji instalacyjnej
Springa. Implementację własnego edytora właściwości najlepiej rozpocząć od rozszerzenia
pomocniczej klasy bazowej java.beans.PropertyPritorSupport będącej częścią standar-
dowej biblioteki klas Javy. Wspomniana klasa implementuje większość potrzebnych me-
chanizmów edycji właściwości (także poza standardowymi metodami setAsText() i getAs-
Text(), których implementacje zawsze musimy przykrywać). Warto pamiętać, że chociaż
egzemplarze PropertyPritor mają swój stan i w normalnych warunkach nie gwarantują
bezpieczeństwa przetwarzania wielowątkowego, sam Spring zapewnia odpowiednie me-
chanizmy synchronizacji całej sekwencji wywołań metod niezbędnych do prawidłowego
przeprowadzania konwersji.

CustomDatePritor może korzystać z dowolnej (przekazywanej za pośrednictwem argumentu
jej konstruktora) implementacji interfejsu java.text.DateFormat do przeprowadzania wła-
ściwej implementacji. Wdrażając klasę CustomDatePritor, możesz użyć implementacji
java.text.SimpleDateFormat. Nasz edytor właściwości można też skonfigurować w taki spo-
sób, aby łańcuchy puste albo interpretował jak wartości null, albo traktował jak błąd nie-
prawidłowego argumentu.

Rejestrowanie i używanie własnych, niestandardowych edytorów właściwości

Przyjrzyjmy się teraz definicji kontekstu aplikacji, w której użyto postprocesora CustomP-
ritorConfigurer do zarejestrowania edytora CustomDatePritor (implementacji uniwersal-
nego edytora PropertyPritor), którego zadaniem jest konwersja łańcuchów znakowych na
obiekty klasy java.util.Date. Definicja konfiguracji zawiera konkretny format łańcuchów
reprezentujących daty:
132   Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

         <bean id="customEditorConfigurer"
               class="org.springframework.beans.factory.config.CustomEditorConfigurer"<
           <property name="customEditors"<
             <map<

                <!-- rejestruje edytor właściwości dla typu java.util.Date -->
                <entry key="java.util.Date"<
                  <bean class="org.springframework.beans.propertyeditors.CustomDateEditor"<
                     <constructor-arg index="0"<
                        <bean class="java.text.SimpleDateFormat"<
                          <constructor-arg<<value<M/d/yy</value<</constructor-arg<
                        </bean<
                     </constructor-arg<
                     <constructor-arg index="1"<<value<true</value<</constructor-arg<
                  </bean<
                </entry<

             </map<
           </property<
         </bean<

         <!-- sprawdza funkcjonowanie edytora dat, ustawiając dwie właściwości typu Date w postaci łańcuchów -->
         <bean id="testBean" class="ch02.sample9.StartEndDatesBean"<
           <property name="startDate"<<value<10/09/1968</value<</property<
           <property name="endDate"<<value<10/26/2004</value<</property<
         </bean<

      Postprocesor CustomPritorConfigurer może rejestrować jeden lub wiele niestandardowych
      edytorów właściwości (choć w prezentowanym przykładzie ograniczymy się do jednego,
      CustomDatePritor). Twoje edytory (zaimplementowane z myślą o innych typach) mogą nie
      wymagać żadnych specjalnych zabiegów konfiguracyjnych. Z powyższego kodu konfigu-
      racji wynika, że klasa CustomDatePritor, której konstruktor pobiera na wejściu dwa argu-
      menty, otrzyma obiekt klasy SimpleDateFormat reprezentujący łańcuch formatu daty oraz
      wartość logiczną (typu boolean) określającą, czy łańcuchy puste powinny być traktowane
      jak wartości null.

      Przedstawiony przykład ilustruje też wygodny sposób definiowania komponentu testowego
      (w tym przypadku nazwanego testBean), który zawiera dwie właściwości typu Date usta-
      wiane za pośrednictwem wartości łańcuchowych — w ten sposób sprawdzamy, czy nasz
      edytor właściwości działa prawidłowo.


Postprocesor BeanNameAutoProxyCreator

      BeanNameAutoProxyCreator jest postprocesorem komponentów. Co prawda techniki korzy-
      stania z tego postprocesora omówimy bardziej szczegółowo w rozdziale poświęconym pro-
      gramowaniu aspektowemu, jednak już teraz dobrze jest wiedzieć o jego istnieniu. Najkrócej
      mówiąc, dla danej na wejściu listy nazw komponentów postprocesor BeanNameAutoProxy-
      Creator może opakować te spośród otrzymanych komponentów danej fabryki, których na-
      zwy pasują do nazw znajdujących się na tej liście (proces opakowywania następuje w cza-
      sie tworzenia egzemplarzy komponentów, a zadaniem obiektów pośredniczących jest albo
      przechwytywanie operacji dostępu do oryginalnych komponentów, albo modyfikowanie
      ich zachowań).
Rozdział 2. n Fabryka komponentów i kontekst aplikacji   133


Postprocesor DefaultAdvisorAutoProxyCreator

     Ten postprocesor komponentów przypomina opisany przed chwilą postprocesor BeanName-
     AutoProxyCreator, jednak oprócz samych nazw komponentów przeznaczonych do opako-
     wania odnajduje też informacje na temat sposobu tego opakowania (tzw. porady). Także w tym
     przypadku warto się zapoznać z treścią rozdziału poświęconego technikom programowania
     aspektowego (AOP).




     Po przeczytaniu tego rozdziału powinieneś znacznie lepiej rozumieć, co tak naprawdę
     oznaczają takie pojęcia jak odwracanie kontroli i wstrzykiwanie zależności oraz jak obie
     koncepcje zostały urzeczywistnione w postaci fabryk komponentów i kontekstów aplikacji
     Springa. Przeanalizowaliśmy i użyliśmy większości podstawowych elementów funkcjonal-
     ności kontenera Springa. Ponieważ właśnie kontener IoC jest podstawą pozostałych me-
     chanizmów Springa, dobre rozumienie sposobu jego funkcjonowania i technik konfiguracji
     w połączeniu ze świadomością potencjału tego kontenera jest kluczem do efektywnego ko-
     rzystania ze Springa.

     Dowiedziałeś się między innymi:
       n   Jak dzięki funkcjom kontenera można korzystać z zalet jednego, logicznie spójnego
           i przewidywalnego mechanizmu dostępu, konfigurowania i wiązania obiektów
           (zamiast używać programowych lub tworzonych naprędce mechanizmów łączenia
           klas, które tylko utrudniają testowanie). Ogólnie, kontener pozwala całkowicie
           wyeliminować konieczność korzystania z niestandardowych, przystosowanych
           do konkretnych klas fabryk komponentów oraz singletonów.
       n   Jak kontener wspiera programistę w stosowaniu pożądanej praktyki oddzielania
           interfejsu od implementacji w kodzie aplikacji.
       n   Że postprocesory oferują możliwość elastycznego dostosowywania zachowania
           komponentów i kontenera (za pomocą odpowiednich plików zewnętrznych).
       n   Jakie są podstawowe reguły odwracania kontroli i jak w połączeniu z fabryką
           komponentów tworzą doskonałe narzędzie do budowy abstrakcji ponad operacjami
           wymagającymi dostępu do usług i zasobów.
       n   Że technika IoC wraz z odpowiednim kontenerem może stanowić solidną podstawę
           dla budowy aplikacji w oparciu o framework Spring bez konieczności tworzenia
           ścisłych związków pomiędzy konstruowanym oprogramowaniem a samym kontenerem.

     W następnym rozdziale skupimy się na bardziej zaawansowanych mechanizmach kontekstu
     aplikacji i przedstawimy kilka zaawansowanych scenariuszy użycia kontenera.

More Related Content

PDF
Spring. Zapiski programisty
PDF
J2EE. Stosowanie wzorców projektowych
PDF
J2EE. Podstawy programowania aplikacji korporacyjnych
PDF
Visual Basic .NET. Encyklopedia
PDF
Ajax w akcji
PDF
Visual Basic 2005. Programowanie
PDF
ASP.NET 2.0. Projektowanie aplikacji internetowych
PDF
J2EE. Wzorce projektowe. Wydanie 2
Spring. Zapiski programisty
J2EE. Stosowanie wzorców projektowych
J2EE. Podstawy programowania aplikacji korporacyjnych
Visual Basic .NET. Encyklopedia
Ajax w akcji
Visual Basic 2005. Programowanie
ASP.NET 2.0. Projektowanie aplikacji internetowych
J2EE. Wzorce projektowe. Wydanie 2

Similar to Spring Framework. Profesjonalne tworzenie oprogramowania w Javie (20)

PDF
Java. Tworzenie aplikacji sieciowych za pomocą Springa, Hibernate i Eclipse
PDF
J2EE. Vademecum profesjonalisty. Wydanie II
PDF
Java 2. Techniki zaawansowane. Wydanie II
PDF
JavaServer Faces. Wydanie II
PDF
J2EE. Wzorce projektowe
PDF
Java 2. Techniki zaawansowane
PDF
Java. Techniki zaawansowane. Wydanie VIII
PDF
Java. Aplikacje bazodanowe. Najlepsze rozwiązania
PDF
Java. Usługi WWW. Vademecum profesjonalisty
PDF
Eclipse Web Tools Platform. Tworzenie aplikacji WWW w języku Java
PDF
Praktyczny kurs Java
PDF
Java Data Objects
PDF
Struktury danych i techniki obiektowe na przykładzie Javy 5.0
PDF
JBuilder. Vademecum profesjonalisty
PDF
Integracja JSF + Facelets + Spring + JPA + Tomahawk
PDF
Hibernate w akcji
PPT
User Experience – wpływ internetu na aplikacje enterprise - Netcamp #14
PDF
Java. Współbieżność dla praktyków
PDF
Core Java Servlets i JavaServer Pages. Tom II. Wydanie II
PDF
JBuilder i bazy danych
Java. Tworzenie aplikacji sieciowych za pomocą Springa, Hibernate i Eclipse
J2EE. Vademecum profesjonalisty. Wydanie II
Java 2. Techniki zaawansowane. Wydanie II
JavaServer Faces. Wydanie II
J2EE. Wzorce projektowe
Java 2. Techniki zaawansowane
Java. Techniki zaawansowane. Wydanie VIII
Java. Aplikacje bazodanowe. Najlepsze rozwiązania
Java. Usługi WWW. Vademecum profesjonalisty
Eclipse Web Tools Platform. Tworzenie aplikacji WWW w języku Java
Praktyczny kurs Java
Java Data Objects
Struktury danych i techniki obiektowe na przykładzie Javy 5.0
JBuilder. Vademecum profesjonalisty
Integracja JSF + Facelets + Spring + JPA + Tomahawk
Hibernate w akcji
User Experience – wpływ internetu na aplikacje enterprise - Netcamp #14
Java. Współbieżność dla praktyków
Core Java Servlets i JavaServer Pages. Tom II. Wydanie II
JBuilder i bazy danych
Ad

More from Wydawnictwo Helion (20)

PDF
Tworzenie filmów w Windows XP. Projekty
PDF
Blog, więcej niż internetowy pamiętnik
PDF
Access w biurze i nie tylko
PDF
Pozycjonowanie i optymalizacja stron WWW. Ćwiczenia praktyczne
PDF
E-wizerunek. Internet jako narzędzie kreowania image&#39;u w biznesie
PDF
Microsoft Visual C++ 2008. Tworzenie aplikacji dla Windows
PDF
Co potrafi Twój iPhone? Podręcznik użytkownika. Wydanie II
PDF
Makrofotografia. Magia szczegółu
PDF
Windows PowerShell. Podstawy
PDF
Java. Efektywne programowanie. Wydanie II
PDF
JavaScript. Pierwsze starcie
PDF
Ajax, JavaScript i PHP. Intensywny trening
PDF
PowerPoint 2007 PL. Seria praktyk
PDF
Excel 2007 PL. Seria praktyk
PDF
Access 2007 PL. Seria praktyk
PDF
Word 2007 PL. Seria praktyk
PDF
Serwisy społecznościowe. Budowa, administracja i moderacja
PDF
AutoCAD 2008 i 2008 PL
PDF
Bazy danych. Pierwsze starcie
PDF
Inventor. Pierwsze kroki
Tworzenie filmów w Windows XP. Projekty
Blog, więcej niż internetowy pamiętnik
Access w biurze i nie tylko
Pozycjonowanie i optymalizacja stron WWW. Ćwiczenia praktyczne
E-wizerunek. Internet jako narzędzie kreowania image&#39;u w biznesie
Microsoft Visual C++ 2008. Tworzenie aplikacji dla Windows
Co potrafi Twój iPhone? Podręcznik użytkownika. Wydanie II
Makrofotografia. Magia szczegółu
Windows PowerShell. Podstawy
Java. Efektywne programowanie. Wydanie II
JavaScript. Pierwsze starcie
Ajax, JavaScript i PHP. Intensywny trening
PowerPoint 2007 PL. Seria praktyk
Excel 2007 PL. Seria praktyk
Access 2007 PL. Seria praktyk
Word 2007 PL. Seria praktyk
Serwisy społecznościowe. Budowa, administracja i moderacja
AutoCAD 2008 i 2008 PL
Bazy danych. Pierwsze starcie
Inventor. Pierwsze kroki
Ad

Spring Framework. Profesjonalne tworzenie oprogramowania w Javie

  • 1. IDZ DO PRZYK£ADOWY ROZDZIA£ SPIS TREŒCI Spring Framework. Profesjonalne tworzenie KATALOG KSI¥¯EK oprogramowania w Javie KATALOG ONLINE Autorzy: Rod Johnson, Juergen Hoeller, Alef Arendsen, Thomas Risberg, Colin Sampaleanu T³umaczenie: Piotr Rajca, Miko³aj Szczepaniak ZAMÓW DRUKOWANY KATALOG ISBN: 83-246-0208-9 Tytu³ orygina³u: Professional Java Development TWÓJ KOSZYK with the Spring Framework Format: B5, stron: 824 DODAJ DO KOSZYKA Spring to szkielet wytwarzania aplikacji (framework), dziêki któremu proces budowania oprogramowania w jêzyku Java dla platformy J2EE staje siê znacznie prostszy CENNIK I INFORMACJE i efektywniejszy. Spring oferuje us³ugi, które mo¿na z powodzeniem u¿ywaæ w wielu œrodowiskach — od apletów po autonomiczne aplikacje klienckie, od aplikacji internetowych pracuj¹cych w prostych kontenerach serwletów po z³o¿one systemy ZAMÓW INFORMACJE korporacyjne pracuj¹ce pod kontrol¹ rozbudowanych serwerów aplikacji J2EE. O NOWOŒCIACH Spring pozwala na korzystanie z mo¿liwoœci programowania aspektowego, znacznie sprawniejsz¹ obs³ugê relacyjnych baz danych, b³yskawiczne budowanie graficznych ZAMÓW CENNIK interfejsów u¿ytkownika oraz integracjê z innymi szkieletami takimi, jak Struts czy JSF. Ksi¹¿ka „Spring Framework. Profesjonalne tworzenie oprogramowania w Javie” odkryje CZYTELNIA przed Tob¹ wszystkie tajniki stosowania Springa w procesie wytwarzania systemów informatycznych dla platformy J2EE. Dowiesz siê, jak efektywnie korzystaæ FRAGMENTY KSI¥¯EK ONLINE z najwa¿niejszych sk³adników szkieletu Spring i poznasz ich rolê w budowaniu aplikacji. Nauczysz siê nie tylko tego, co mo¿na zrealizowaæ za pomoc¹ poszczególnych us³ug, ale tak¿e tego, w jaki sposób zrobiæ to najlepiej. W kolejnych æwiczeniach przeanalizujesz proces tworzenie kompletnej aplikacji w oparciu o Spring. W ksi¹¿ce poruszono m.in. tematy: • Struktura szkieletu Spring • Tworzenie komponentów i definiowanie zale¿noœci pomiêdzy nimi • Testowanie aplikacji i testy jednostkowe • Programowanie aspektowe w Spring • Po³¹czenia z relacyjnymi bazami danych za pomoc¹ JDBC • Zarz¹dzanie transakcjami Wydawnictwo Helion • Korzystanie z mechanizmu Hibernate ul. Chopina 6 • Zabezpieczanie aplikacji 44-100 Gliwice • Stosowanie szkieletu Web MVC tel. (32)230-98-63 Przekonaj siê, jak Spring mo¿e zmieniæ Twoj¹ pracê nad tworzeniem aplikacji J2EE e-mail: helion@helion.pl
  • 2. Wstęp .........................................................................................................................................................19 Rozdział 1. Wprowadzenie do Springa .................................................................................................... 27 Dlaczego Spring? ........................................................................................................ 27 Problemy związane z tradycyjnym podejściem do programowania dla platformy J2EE ... 27 Lekkie frameworki .................................................................................................. 31 Podstawowe składniki Springa ................................................................................ 32 Zalety Springa ............................................................................................................ 34 Kontekst Springa ........................................................................................................ 37 Technologie ........................................................................................................... 37 Techniki ................................................................................................................ 51 Związki z pozostałymi frameworkami ....................................................................... 52 Budowa architektury aplikacji opartej na frameworku Spring ........................................... 56 Szerszy kontekst ................................................................................................... 57 Trwałość i integracja .............................................................................................. 58 Obiekty usług biznesowych ..................................................................................... 62 Prezentacja ........................................................................................................... 63 Przyszłość .................................................................................................................. 65 Harmonogram wydawania kolejnych wersji ............................................................... 66 Ewolucja Javy i platformy J2EE ................................................................................ 66 Współczesny kontekst technologiczny ...................................................................... 69 Standardy i otwarty dostęp do kodu źródłowego ....................................................... 69 Projekt Spring i społeczność programistów .................................................................... 70 Historia ................................................................................................................. 71 Krótkie omówienie modułów Springa ....................................................................... 73 Obsługiwane środowiska ........................................................................................ 78 Podsumowanie ........................................................................................................... 78 Rozdział 2. Fabryka komponentów i kontekst aplikacji ......................................................................... 81 Odwracanie kontroli i wstrzykiwanie zależności .............................................................. 82 Różne formy wstrzykiwania zależności ..................................................................... 85 Wybór pomiędzy wstrzykiwaniem przez metody ustawiające a wstrzykiwaniem przez konstruktory ..................................................................... 89
  • 3. 4 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Kontener .................................................................................................................... 91 Fabryka komponentów ........................................................................................... 91 Kontekst aplikacji .................................................................................................. 93 Uruchamianie kontenera ........................................................................................ 95 Korzystanie komponentów uzyskiwanych z fabryki ..................................................... 97 Konfiguracja komponentów w formacie XML ............................................................. 98 Podstawowa definicja komponentu .......................................................................... 99 Definiowanie zależności komponentów .................................................................. 102 Zarządzanie cyklem życia komponentu ................................................................... 113 Abstrakcja dostępu do usług i zasobów ................................................................. 116 Wielokrotne wykorzystywanie tych samych definicji komponentów ............................ 122 Stosowanie postprocesorów do obsługi niestandardowych komponentów i kontenerów .. 126 Podsumowanie ......................................................................................................... 133 Rozdział 3. Zaawansowane mechanizmy kontenera ...........................................................................135 Abstrakcje dla niskopoziomowych zasobów ................................................................. 136 Kontekst aplikacji jako implementacja interfejsu ResourceLoader ............................ 136 Kontekst aplikacji jako źródło komunikatów ........................................................... 139 Zdarzenia aplikacji .................................................................................................... 142 Zarządzanie kontenerem ............................................................................................ 145 Ścieżki lokalizacji zasobów przekazywane do konstruktorów implementacji interfejsu ApplicationContext .............................................................................. 145 Deklaratywne korzystanie z kontekstów aplikacji .................................................... 147 Podział definicji kontenera na wiele plików ............................................................. 149 Strategie obsługi modułów ................................................................................... 151 Singletony obsługujące dostęp do kontenera ......................................................... 154 Pomocnicze komponenty fabrykujące .......................................................................... 155 Komponent PropertyPathFactoryBean .................................................................... 155 Komponent FieldRetrievingFactoryBean ................................................................. 156 Komponent MethodInvokingFactoryBean ................................................................ 157 Edytory właściwości oferowane w ramach Springa ........................................................ 158 Strategie testowania ................................................................................................. 159 Testy jednostkowe ............................................................................................... 160 Testy wykorzystujące kontener Springa .................................................................. 163 Rozwiązania alternatywne względem konfiguracji w formacie XML ................................. 166 Definicje konfiguracji w plikach właściwości ........................................................... 166 Programowe definicje komponentów ...................................................................... 168 Pozostałe formaty ................................................................................................ 168 Materiały dodatkowe ................................................................................................. 169 Podsumowanie ......................................................................................................... 169 Rozdział 4. Spring i AOP ...........................................................................................................................171 Cele ......................................................................................................................... 171 Założenia ................................................................................................................. 173 Przykład ................................................................................................................... 173 Framework programowania aspektowego Springa ........................................................ 177 Łańcuch przechwytywania ..................................................................................... 178 Zalety i wady ....................................................................................................... 178 Rada .................................................................................................................. 180 Przecięcia ........................................................................................................... 186
  • 4. Spis treści 5 Doradcy .............................................................................................................. 193 Integracja z kontenerem IoC Springa ..................................................................... 195 Analizowanie i zmiana stanu pośrednika w czasie wykonywania programu ................ 212 Programowe tworzenie pośredników ...................................................................... 213 Korzystanie z zaawansowanych funkcji Spring AOP ....................................................... 214 Źródła obiektów docelowych ................................................................................. 214 Wczesne kończenie łańcucha przechwytywania ....................................................... 221 Stosowanie wprowadzeń ...................................................................................... 221 Udostępnianie bieżącego pośrednika .................................................................... 224 Udostępnianie bieżącego egzemplarza interfejsu MethodInvocation ......................... 225 Zrozumienie typów pośredników ............................................................................ 226 Diagnozowanie i testowanie .................................................................................. 228 Rozmaitości ........................................................................................................ 231 Integracja z innymi frameworkami programowania aspektowego ................................... 234 Cele ................................................................................................................... 235 Integracja z narzędziem AspectJ ............................................................................ 235 AspectWerkz ....................................................................................................... 242 Materiały dodatkowe ................................................................................................. 243 Podsumowanie ......................................................................................................... 243 Rozdział 5. Obiekty DAO i framework Spring JDBC .............................................................................245 Wzorzec obiektów dostępu do danych ......................................................................... 246 Wprowadzenie do Spring JDBC ................................................................................... 248 Motywacja — problemy bezpośredniego korzystania z interfejsu JDBC ...................... 248 W czym może pomóc Spring? ................................................................................ 251 Prosty przykład .................................................................................................... 251 Budowa warstwy dostępu do danych dla przykładowej aplikacji ..................................... 253 Model danych stosowany w przykładowej aplikacji .................................................. 253 Źródło danych ...................................................................................................... 254 Tłumaczenie wyjątków .......................................................................................... 256 Operacje klasy JdbcTemplate ..................................................................................... 259 Stosowanie metod zwrotnych ................................................................................ 259 Metody pomocnicze klasy JdbcTemplate ................................................................ 261 Wykonywanie prostych zapytań za pośrednictwem klasy JdbcTemplate ..................... 262 Wykonywanie prostych aktualizacji za pośrednictwem klasy JdbcTemplate ................ 263 Zaawansowane zastosowania klasy JdbcTemplate ................................................. 264 Obsługa interfejsu RowSet ................................................................................... 266 Klasy obsługujące operacje na relacyjnych systemach zarządzania bazami danych ......... 267 Klasy SqlQuery i MappingSqlQuery ........................................................................ 268 Operacje wstawiania i aktualizacji realizowane za pomocą klasy SqlUpdate .............. 271 Aktualizowanie zbioru wynikowego za pomocą klasy UpdateSqlQuery ....................... 272 Generowanie kluczy głównych ............................................................................... 273 Uzyskiwanie kluczy wygenerowanych przez bazę danych .......................................... 274 Wywoływanie procedur składowanych .................................................................... 275 Zagadnienia zaawansowane ....................................................................................... 278 Uruchamianie Spring JDBC na serwerze aplikacji .................................................... 278 Stosowanie niestandardowych mechanizmów tłumaczenia wyjątków ........................ 280 Odczytywanie i zapisywanie danych obiektów LOB .................................................. 284 Aktualizacje wsadowe .......................................................................................... 289 Zaawansowane techniki korzystania z procedur składowanych ................................. 290
  • 5. 6 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Zagadnienia dodatkowe ............................................................................................. 295 Wydajność .......................................................................................................... 295 Kiedy należy używać biblioteki JDBC, a kiedy narzędzi odwzorowań obiektowo-relacyjnych? ........................................... 296 Wersje biblioteki JDBC i platformy J2EE ................................................................. 296 Podsumowanie ......................................................................................................... 297 Rozdział 6. Zarządzanie transakcjami i źródłami danych .................................................................299 Pojęcia podstawowe .................................................................................................. 299 Czym jest transakcja? .......................................................................................... 300 Właściwości ACID ................................................................................................ 300 Sterowanie współbieżnością ................................................................................. 303 Transakcje i platforma J2EE ....................................................................................... 303 Transakcje lokalne ............................................................................................... 304 Transakcje globalne (rozproszone) ........................................................................ 304 Propagowanie transakcji ....................................................................................... 305 Wyznaczanie granic pomiędzy transakcjami ............................................................ 305 Przykład obsługi transakcji w Springu .......................................................................... 306 Wprowadzenie do oferowanej przez Spring warstwy abstrakcji ponad transakcjami ......... 308 Przegląd możliwych opcji sterowania transakcjami .................................................. 310 Definicja transakcji .............................................................................................. 312 Status transakcji ................................................................................................. 314 Strategie wyznaczania granic transakcji ................................................................. 314 Strategie zarządzania transakcjami ....................................................................... 328 Deklaracje źródeł danych ........................................................................................... 338 Źródła lokalne bez puli ......................................................................................... 338 Źródła lokalne z pulą ............................................................................................ 339 JNDI ................................................................................................................... 340 Wybór pomiędzy lokalnym źródłem danych a źródłem danych JNDI ........................... 341 Podsumowanie ......................................................................................................... 341 Rozdział 7. Odwzorowania obiektowo-relacyjne ................................................................................343 Pojęcia podstawowe .................................................................................................. 344 Podstawy odwzorowań obiektowo-relacyjnych ......................................................... 345 Obiektowe języki zapytań ...................................................................................... 346 Przezroczyste utrwalanie ....................................................................................... 347 Kiedy należy korzystać z narzędzi odwzorowań obiektowo-relacyjnych? ..................... 348 Obsługa odwzorowań obiektowo-relacyjnych w Springu ................................................. 349 Obiekty dostępu do danych (DAO) ......................................................................... 349 Zarządzanie transakcjami ..................................................................................... 350 iBATIS SQL Maps ...................................................................................................... 351 Plik odwzorowania ............................................................................................... 352 Implementacja interfejsu DAO ............................................................................... 354 Konfiguracja w kontekście Springa ........................................................................ 356 Zarządzanie transakcjami ..................................................................................... 357 Podsumowanie analizy narzędzia iBATIS ................................................................ 359 Hibernate ................................................................................................................. 360 Plik odwzorowań .................................................................................................. 362 Implementacja interfejsu DAO ............................................................................... 363 Konfiguracja w kontekście Springa ........................................................................ 366 Zarządzanie transakcjami ..................................................................................... 370
  • 6. Spis treści 7 Otwarta sesja w widoku ........................................................................................ 377 Obsługa obiektów BLOB i CLOB ............................................................................ 381 Hibernate: podsumowanie .................................................................................... 383 JDO ......................................................................................................................... 385 Cykl życia trwałego obiektu ................................................................................... 386 Implementacje interfejsów DAO ............................................................................ 387 Konfiguracja kontekstu aplikacji Springa ................................................................ 389 Zarządzanie transakcjami ..................................................................................... 391 Cykl życia egzemplarza PersistenceManager .......................................................... 392 Otwarty egzemplarz PersistenceManager w widoku ................................................. 393 Dialekt JDO ......................................................................................................... 396 Podsumowanie analizy technologii JDO .................................................................. 397 Pozostałe narzędzia odwzorowań obiektowo-relacyjnych ............................................... 399 Apache OJB ......................................................................................................... 399 TopLink ............................................................................................................... 401 Cayenne ............................................................................................................. 403 Specyfikacja JSR-220 ........................................................................................... 403 Podsumowanie ......................................................................................................... 404 Rozdział 8. Lekki framework zdalnego dostępu ..................................................................................407 Pojęcia podstawowe i zakres tematyczny rozdziału ....................................................... 408 Jednolity styl konfiguracji ........................................................................................... 410 Hessian i Burlap ....................................................................................................... 412 Uzyskiwanie dostępu do usługi ............................................................................. 414 Eksportowanie usługi ........................................................................................... 416 Obiekt wywołujący HTTP ............................................................................................. 417 Uzyskiwanie dostępu do usługi ............................................................................. 419 Eksportowanie usługi ........................................................................................... 420 Opcje konfiguracyjne ............................................................................................ 421 RMI ......................................................................................................................... 422 Uzyskiwanie dostępu do usługi ............................................................................. 424 Strategie wyszukiwania pieńków ........................................................................... 426 Eksportowanie usługi ........................................................................................... 428 Opcje konfiguracyjne ............................................................................................ 429 RMI-IIOP .............................................................................................................. 429 Usługi sieciowe przez JAX-RPC ................................................................................... 430 Uzyskiwanie dostępu do usługi ............................................................................. 432 Eksportowanie usługi ........................................................................................... 435 Niestandardowe odwzorowania typów .................................................................... 437 Podsumowanie ......................................................................................................... 439 Rozdział 9. Usługi wspomagające .........................................................................................................443 JMS ......................................................................................................................... 443 Wprowadzenie ..................................................................................................... 444 Cele obsługi JMS w Springu .................................................................................. 445 Dostęp do JMS za pomocą szablonu ..................................................................... 446 Obsługa wyjątków ................................................................................................ 449 Zarządzanie obiektem ConnectionFactory .............................................................. 449 Konwertery komunikatów ...................................................................................... 450 Zarządzanie miejscami docelowymi ....................................................................... 451
  • 7. 8 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Zarządzanie transakcjami ..................................................................................... 453 Klasa JmsGatewaySupport ................................................................................... 453 W przyszłości ....................................................................................................... 455 Planowanie w Springu ............................................................................................... 455 Czasomierze a Quartz .......................................................................................... 456 Czasomierze ....................................................................................................... 457 Framework Quartz ................................................................................................ 459 Wysyłanie poczty elektronicznej w Springu ................................................................... 466 Od czego zacząć? ................................................................................................ 466 Wielokrotne stosowanie tej samej sesji pocztowej .................................................. 467 Wysyłanie wiadomości przy użyciu COS .................................................................. 468 Menedżer poczty elektronicznej ogólnego zastosowania .......................................... 468 Zastosowanie języków skryptowych ............................................................................ 473 Spójny model ...................................................................................................... 474 Inne języki skryptowe ........................................................................................... 479 Podsumowanie ......................................................................................................... 479 Rozdział 10. System bezpieczeństwa Acegi dla Springa ......................................................................481 Sposoby zabezpieczania aplikacji korporacyjnych ......................................................... 481 Typowe wymagania .............................................................................................. 481 System bezpieczeństwa Acegi w zarysie ................................................................ 483 Usługa uwierzytelniania i autoryzacji Javy ............................................................... 488 Specyfikacja serwletów ........................................................................................ 491 Podstawy systemu bezpieczeństwa Acegi .................................................................... 493 Uwierzytelnianie ................................................................................................... 493 Przechowywanie obiektów Authentication ............................................................... 498 Autoryzacja .......................................................................................................... 500 Bezpieczeństwo obiektów dziedziny ....................................................................... 503 Przykład ................................................................................................................... 505 Wprowadzenie do przykładowej aplikacji ................................................................ 505 Implementacja pozbawiona mechanizmów bezpieczeństwa ..................................... 507 Rozwiązanie wykorzystujące zabezpieczenia ........................................................... 509 Uwierzytelnianie ................................................................................................... 509 Autoryzacja .......................................................................................................... 510 Podsumowanie ......................................................................................................... 514 Rozdział 11. Spring i EJB ..........................................................................................................................517 Określanie, czy stosowanie komponentów EJB jest potrzebne ....................................... 518 Dostęp do komponentów EJB ..................................................................................... 519 Typowe rozwiązanie .............................................................................................. 520 Rozwiązanie z wykorzystaniem Springa .................................................................. 521 Tworzenie komponentów EJB w Springu ...................................................................... 529 Bezstanowe komponenty sesyjne .......................................................................... 529 Stanowe komponenty sesyjne ............................................................................... 532 Komponenty sterowane komunikatami .................................................................. 534 Kilka słów o XDoclet ............................................................................................ 535 Dostęp na zasadzie singletonu — rozwiązanie dobre czy złe? ....................................... 536 ContextSingletonBeanFactoryLocator i SingletonBeanFactoryLocator ........................ 537 Wspólny kontekst jako „rodzic” kontekstu aplikacji sieciowej .................................. 540 Stosowanie wspólnego kontekstu w komponentach EJB ......................................... 543
  • 8. Spis treści 9 Zagadnienia związane z testowaniem ......................................................................... 543 Implementacja funkcjonalności biznesowej w zwyczajnych obiektach Javy ................. 544 Zastosowanie imitacji kontenera EJB ..................................................................... 544 Testy integracyjne w środowisku serwera aplikacji .................................................. 545 Podsumowanie ......................................................................................................... 546 Rozdział 12. Framework Web MVC .......................................................................................................547 Prosty przykład ......................................................................................................... 548 Ogólna architektura ................................................................................................... 550 Pojęcia związane z Web MVC ................................................................................ 550 Ogólne działanie Web MVC wykorzystujące serwlet dyspozytora oraz kontrolery ......... 551 Wymagania dobrego sieciowego frameworku MVC .................................................. 552 Elementy Spring Web MVC ................................................................................... 554 Komponenty infrastruktury ......................................................................................... 556 DispatcherServlet ................................................................................................ 557 WebApplicationContext ......................................................................................... 560 Przepływ sterowania związanego z przetwarzaniem żądań ............................................. 563 Typowy układ aplikacji Spring MVC ............................................................................. 566 Odwzorowania HandlerMapping .................................................................................. 568 BeanNameUrlHandlerMapping .............................................................................. 568 SimpleUrlHandlerMapping .................................................................................... 569 CommonsPathMapUrlHandlerMapping ................................................................... 571 Więcej niż jedno odwzorowanie HandlerMapping ..................................................... 572 HandlerExecutionChain oraz obiekty przechwytujące .................................................... 573 WebContentInterceptor ........................................................................................ 575 UserRoleAuthorizationInterceptor .......................................................................... 576 Inne obiekty przechwytujące udostępniane przez Spring MVC .................................. 577 Obiekty obsługi oraz ich adaptery ............................................................................... 577 ModelAndView oraz ViewResolver ............................................................................... 577 UrlBasedViewResolver ......................................................................................... 578 BeanNameViewResolver oraz XmlViewResolver ...................................................... 579 ResourceBundleViewResolver ............................................................................... 579 Tworzenie łańcucha obiektów ViewResolver ........................................................... 580 Zmiana i wybór ustawień lokalnych ............................................................................. 582 Obiekty HandlerExceptionResolver .............................................................................. 584 Kontrolery ................................................................................................................ 587 WebContentGenerator .......................................................................................... 587 AbstractController ................................................................................................ 587 UrlFilenameViewController .................................................................................... 588 ParametrizableViewController ................................................................................ 589 MulitActionController ............................................................................................ 590 Wiązanie danych ....................................................................................................... 591 Przydatne możliwości używane podczas wiązania danych ........................................ 592 Praktyczne przykłady zastosowania kontrolerów ........................................................... 593 Przeglądanie listy przedstawień przy użyciu kontrolera AbstractController ................. 594 Rezerwacja miejsc ............................................................................................... 596 Kreatory ................................................................................................................... 606 Podstawowa konfiguracja ..................................................................................... 606 Metody szablonowe ............................................................................................. 607 Przepływ sterowania kreatora ................................................................................ 608 Zmiany stron, numeracja oraz inne akcje ............................................................... 609
  • 9. 10 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Rozszerzanie infrastruktury obiektów obsługi ............................................................... 610 Przesyłanie plików na serwer ..................................................................................... 611 Konfiguracja resolvera .......................................................................................... 611 Tworzenie formularza do przesyłania plików na serwer ............................................ 612 Obsługa przesłanych danych ................................................................................. 612 Testowanie kontrolerów ............................................................................................. 614 Testowanie bez kontekstu aplikacji ....................................................................... 614 Bardziej szczegółowe testowanie przy użyciu obiektów pozornych ............................. 615 Testowanie przy wykorzystaniu pełnego kontekstu aplikacji ..................................... 616 Inne sposoby testowania aplikacji ......................................................................... 618 Podsumowanie ......................................................................................................... 618 Rozdział 13. Technologie widoków .........................................................................................................621 Przykład ................................................................................................................... 622 Ogólna konfiguracja ............................................................................................. 623 JavaServer Pages ................................................................................................ 623 FreeMarker ......................................................................................................... 624 Generacja dokumentów PDF przy użyciu biblioteki iText ........................................... 624 Czynniki mające wpływ na wybór technologii generacji widoków ..................................... 625 Obiekty widoków i modeli ........................................................................................... 626 Możliwości klasy AbstractView ................................................................................... 628 Zgłaszanie nowych żądań przy użyciu widoków RedirectView .................................... 629 Użycie prefiksu widoku do generacji przekazań i przekierowań ................................. 631 JavaServer Pages ...................................................................................................... 631 Konfiguracja aplikacji korzystającej z JSP ............................................................... 632 Tworzenie formularzy przy użyciu znaczników niestandardowych ............................... 633 Wykorzystanie plików znaczników do tworzenia elementów nadających się do wielokrotnego zastosowania .......................................................................... 640 Velocity oraz FreeMarker ............................................................................................ 642 Konfiguracja resolvera widoków ............................................................................ 642 Stosowanie makr ułatwiających tworzenie formularzy .............................................. 645 Tiles ........................................................................................................................ 648 Widoki bazujące na dokumentach XML i XSLT ............................................................. 652 Widoki generujące arkusze Excela lub inne dokumenty ................................................. 654 Generacja arkusza kalkulacyjnego na podstawie listy przedstawień .......................... 654 Wykorzystanie szablonów jako podstawy do generacji arkuszy kalkulacyjnych ........... 656 Inne widoki generujące dokumenty ........................................................................ 656 Zastosowanie obiektów przechwytujących HandlerInterceptor w celu rozróżniania wybranego typu odpowiedzi ................................................................................ 657 Implementacja widoków niestandardowych ................................................................. 659 Interfejs View i klasa AbstractView ........................................................................ 659 Implementacja widoku generującego dane XML na podstawie obiektu danych .......... 660 Czynniki, jakie należy uwzględniać podczas tworzenia widoków niestandardowych ..... 662 Podsumowanie ......................................................................................................... 662 Rozdział 14. Integracja z innymi frameworkami sieciowymi .............................................................665 Czynniki wpływające na wybór używanego frameworka MVC .......................................... 666 Porównanie tradycyjnych frameworków Web MVC .................................................... 666 Integracja z frameworkiem Spring. Pojęcia podstawowe ............................................... 682 Integracja z frameworkiem WebWork .......................................................................... 684 Przygotowanie obiektu ObjectFactory ..................................................................... 684
  • 10. Spis treści 11 Integracja z frameworkiem Struts ............................................................................... 685 Stosowanie obiektów ActionSupport ...................................................................... 686 Stosowanie klas DelegatingRequestProcessor oraz DelegatingActionProxy ............... 687 Stosowanie bazowej akcji dysponującej możliwością automatycznego wiązania ......... 691 Integracja z frameworkiem Tapestry ............................................................................ 692 Pobieranie komponentów na potrzeby Tapestry ...................................................... 692 Klasa strony ........................................................................................................ 693 Definicja strony ................................................................................................... 693 Szablon strony ..................................................................................................... 695 Ostatnie informacje o integracji z frameworkiem Tapestry ....................................... 696 Integracja z JavaServer Faces .................................................................................... 697 Podsumowanie ......................................................................................................... 698 Rozdział 15. Aplikacja przykładowa ......................................................................................................701 Wybór technologii serwera ......................................................................................... 702 Warstwy aplikacji ...................................................................................................... 702 Warstwa trwałości ..................................................................................................... 704 Model danych ...................................................................................................... 704 Model obiektów dziedziny ..................................................................................... 705 Odwzorowania obiektowo-relacyjne ........................................................................ 707 Implementacja DAO ............................................................................................. 712 Konfiguracja dostępu do danych ........................................................................... 714 Warstwa usług biznesowych ....................................................................................... 715 Usługi ................................................................................................................. 715 Kontekst aplikacji ................................................................................................ 716 Warstwa sieciowa ..................................................................................................... 718 Przepływ sterowania w aplikacji ............................................................................. 718 Konfiguracja aplikacji przy użyciu pliku web.xml ...................................................... 720 Kontrolery sieciowe .............................................................................................. 721 Technologia widoków ........................................................................................... 723 Porównanie z implementacją przedstawioną w książce J2EE Design and Development ... 725 Prostsza technologia ............................................................................................ 725 Zmiany związane z bazą danych ............................................................................ 725 Konfiguracja serwera ................................................................................................. 726 MySQL ................................................................................................................ 726 Tomcat ............................................................................................................... 727 Kompilacja aplikacji i jej wdrożenie ............................................................................. 727 Utworzenie tabel bazy danych i zapisanie w nich informacji ..................................... 727 Kompilacja aplikacji i wdrożenie jej na serwerze Tomcat ......................................... 728 Podsumowanie ......................................................................................................... 728 Rozdział 16. Wnioski ...............................................................................................................................729 Problemy, jakie rozwiązuje Spring ............................................................................... 729 Rozwiązania przyjęte w Springu .................................................................................. 730 Porady związane ze stosowaniem Springa ................................................................... 733 Wybór technologii ................................................................................................ 733 Warstwy aplikacji ................................................................................................. 736 Struktura aplikacji ................................................................................................ 744 Testowanie aplikacji ............................................................................................. 749 Projekty związane z frameworkiem Spring .................................................................... 752 System bezpieczeństwa Acegi Security .................................................................. 752 Inne projekty ....................................................................................................... 753
  • 11. 12 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Spring poza środowiskiem J2EE ................................................................................. 754 Poszukiwanie informacji dodatkowych ......................................................................... 755 Książki i artykuły .................................................................................................. 755 Zasoby dostępne na stronach WWW ..................................................................... 756 Przykładowe aplikacje .......................................................................................... 757 Przyszłość ................................................................................................................ 758 A Wymagania dla przykładowej aplikacji .............................................................................................761 Przegląd ................................................................................................................... 761 Grupy użytkowników .................................................................................................. 762 Publiczni użytkownicy internetowi .......................................................................... 762 Kasjerzy .............................................................................................................. 763 Administratorzy .................................................................................................... 763 Założenia ................................................................................................................. 764 Ograniczenia zakresu aplikacji .................................................................................... 765 Terminarz prac .......................................................................................................... 765 Interfejs użytkowników internetowych .......................................................................... 766 Podstawowy schemat działania ............................................................................. 766 Obsługa błędów ................................................................................................... 767 Ekrany aplikacji ................................................................................................... 767 Wymagania niefunkcjonalne ....................................................................................... 780 Środowisko sprzętowe i programowe .......................................................................... 782 Skorowidz ..............................................................................................................................................785
  • 12. Sercem Springa jest funkcjonalność lekkiego kontenera IoC (ang. Inversion of Control). Do skonfigurowania i właściwego powiązania obiektów aplikacji z obiektami frameworka (oraz zarządzania ich cyklami życia) można użyć jednego lub wielu egzemplarzy tego kontenera. Podstawowe cechy kontenera IoC dają nam pewność, że zdecydowana większość tych obiektów nie będzie zawierała zależności wiążących je z samym kontenerem, zatem relacje pomiędzy obiektami można wyrażać za pomocą tak naturalnych rozwiązań (języka Java) jak interfejsy czy abstrakcyjne klasy bazowe, co niemal w stu procentach uniezależnia nas od sposobu implementacji tych obiektów oraz lokalizacji ich zależności. Okazuje się, że kontener IoC jest podstawą dla bardzo wielu funkcji i mechanizmów, które będziemy analizowali w tym i kolejnych rozdziałach. Z tego rozdziału dowiesz się, jak konfigurować i korzystać z fabryk komponentów Springa oraz kontekstów aplikacji, czyli dwóch podstawowych składników decydujących o kształcie i funkcjonalności kontenera IoC tego frameworka. Poznasz interfejsy BeanFactory i Appli- cationContext wraz ze wszystkimi ważnymi interfejsami i klasami pokrewnymi, które są wykorzystywane zawsze wtedy, gdy należy programowo utworzyć lub uzyskać dostęp do kontenera IoC. W niniejszym rozdziale skupimy się na tych odmianach interfejsów Bean- Factory i ApplicationContext, które można deklaratywnie konfigurować za pośrednictwem odpowiednich dokumentów XML. Wspomniane interfejsy stanowią podstawową funkcjo- nalność w zakresie konfigurowania i korzystania z kontenera IoC i są stosowane przez użytkowników Springa w zdecydowanej większości przypadków. Warto jednak pamiętać, że Spring oddziela konfigurację kontenera od mechanizmów jego użytkowania. Podczas lektury kolejnego rozdziału przekonasz się, że dostęp do wszystkich możliwości kontenera można uzyskiwać zarówno za pośrednictwem konfiguracji programowej, jak i alternatywnych for- matów deklaratywnych.
  • 13. 82 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie W rozdziale zajmiemy się następującymi zagadnieniami: n odwracaniem kontroli (IoC) i wstrzykiwaniem zależności, n podstawowymi technikami konfigurowania obiektów i definiowania relacji łączących obiekty w ramach kontenera Springa, n sposobem interpretowania zależności i różnicami pomiędzy jawnym a automatycznym wprowadzaniem zależności, n obsługą cyklu życia obiektów w kontenerze Springa, n abstrakcją technik dostępu do usług i zasobów, n postprocesorami fabryki komponentów i samych komponentów, które odpowiadają za dostosowywanie zachowania kontenera i komponentów, n technikami programowego korzystania z interfejsów BeanFactory i ApplicationContext. W rozdziale 1. opisano koncepcję odwracania kontroli (IoC) i wyjaśniono, dlaczego jest ona tak ważna dla procesu wytwarzania oprogramowania. W pierwszej kolejności krótko przypomnimy, co to pojęcie rzeczywiście oznacza, by zaraz potem przystąpić do analizy kilku przykładów jego praktycznego stosowania dla kontenera Springa. Kod oprogramowania rozbija się zwykle na logiczne, współpracujące ze sobą komponenty lub usługi. W Javie takie komponenty mają przeważnie postać egzemplarzy klas, czyli obiektów. Każdy obiekt realizuje swoje zadania z wykorzystaniem lub we współpracy z pozostałymi obiektami. Przypuśćmy, że mamy dany obiekt A; można powiedzieć, że po- zostałe obiekty, z których korzysta obiekt A, są jego zależnościami (ang. dependencies). Odwracanie kontroli (IoC) jest w wielu sytuacjach pożądanym wzorcem architekturalnym, który przewiduje, że obiekty są ze sobą wiązane przez byt zewnętrzny (w tym przypadku kontener), który — tym samym — odpowiada za obsługę ich zależności (eliminując ko- nieczność bezpośredniego tworzenia egzemplarzy jednych obiektów w drugich). Przyjrzyjmy się teraz kilku przykładom. Większość przykładów prezentowanych w tym rozdziale (nawet tych, które nie są pre- zentowane w formie kompletnych klas czy interfejsów) jest dostępna także w postaci gotowej do kompilacji, z którą możesz swobodnie eksperymentować (patrz ftp://ftp. åhelion.pl/przyklady/sprifr.zip). Przypuśćmy, że dysponujemy usługą obsługującą historyczne dane pogodowe, którą zako- dowano w tradycyjny sposób (bez korzystania z kontenera IoC): public class WeatherService { WeatherDao weatherDao = new StaticDataWeatherDaoImpl();
  • 14. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 83 public Double getHistoricalHigh(Date date) { WeatherData wd = weatherDao.find(date); if (wd != null) return new Double(wd.getHigh()); return null; } } public interface WeatherDao { WeatherData find(Date date); WeatherData save(Date date); WeatherData update(Date date); } public class StaticDataWeatherDaoImpl implements WeatherDao { public WeatherData find(Date date) { WeatherData wd = new WeatherData(); wd.setDate((Date) date.clone()); ... return wd; } public WeatherData save(Date date) { ... } public WeatherData update(Date date) { ... } } Zgodnie z dobrymi praktykami napisano też przypadek testowy, który weryfikuje prawi- dłowe funkcjonowanie tego kodu. Jeśli użyjemy popularnego narzędzia JUnit, kod takiego testu może mieć następującą postać: public class WeatherServiceTest extends TestCase { public void testSample1() throws Exception { WeatherService ws = new WeatherService(); Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime()); // … w tym miejscu powinieneś umieścić dodatkowy kod sprawdzający zwracaną wartość… } } Nasza usługa pogodowa wykorzystuje dane pogodowe w formie obiektu DAO (obiektu do- stępu do danych; ang. Data Access Object), który jest niezbędny do uzyskania danych histo- rycznych. Do obsługi obiektu DAO program wykorzystuje co prawda interfejs WeatherDao, jednak w przedstawionym przykładzie usługa pogodowa bezpośrednio tworzy egzemplarz konkretnego, znanego typu DAO, który implementuje ten interfejs: StaticDataWeather- åDaoImpl. Dodatkowo nasza aplikacja testowa, WeatherServiceTest, bezpośrednio wyko- rzystuje konkretną klasę WeatherService (eliminuje możliwość specjalizacji). Prezentowany przykład zakodowano bez korzystania z technik IoC. O ile usługa pogodowa współpracuje z odpowiednim obiektem DAO za pośrednictwem interfejsu, konkretny egzemplarz obiektu
  • 15. 84 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie DAO (określonego typu) jest przez tę usługę tworzony bezpośrednio i właśnie usługa po- godowa steruje jego cyklem życia; oznacza to, że wspomniana usługa jest obarczona zależ- nościami zarówno od interfejsu DAO, jak i konkretnej klasy implementującej ten interfejs. Co więcej, przykładowa aplikacja testowa reprezentuje klienta tej usługi, który bezpośred- nio tworzy egzemplarz konkretnego typu usługi pogodowej, zamiast korzystać z pośred- nictwa właściwego interfejsu. W rzeczywistej aplikacji bardzo prawdopodobne byłoby wy- stępowanie innych niejawnych zależności (np. od konkretnego frameworku utrwalania danych), a prezentowane do tej pory podejście wymagałoby kodowania tych zależności na stałe w kodzie źródłowym programu. Spójrzmy teraz na prosty przykład tego, jak kontener Springa może obsługiwać mechanizm odwracania kontroli. W pierwszej kolejności przebudujemy naszą usługę pogodową, aby prezentowała właściwy podział na interfejs i implementację oraz umożliwiała definiowanie (konfigurowanie) dla tej implementacji konkretnego egzemplarza obiektu DAO w formie właściwości komponentu JavaBean: public interface WeatherService { Double getHistoricalHigh(Date date); } public class WeatherServiceImpl implements WeatherService { private WeatherDao weatherDao; public void setWeatherDao(WeatherDao weatherDao) { this.weatherDao = weatherDao; } public Double getHistoricalHigh(Date date) { WeatherData wd = weatherDao.find(date); if (wd != null) return new Double(wd.getHigh()); return null; } } // ta sama klasa co w poprzednim przykładzie public class StaticDataWeatherDaoImpl implements WeatherDao { ... } Do zarządzania egzemplarzem usługi pogodowej użyjemy kontekstu aplikacji Springa, a kon- kretnie klasy ClassPathXmlApplicationContext, i upewnimy się, że kontekst aplikacji otrzymał obiekt DAO naszej usługi, z którym będzie mógł współpracować. W pierwszej kolejności musimy zdefiniować plik konfiguracyjny w formacie XML, applicationContext.xml: <?xml version="1.0" encoding="UTF-8"?< <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"< <beans< <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/<
  • 16. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 85 </property< </bean< <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"< </bean< </beans< Obiekt weatherService konfigurujemy za pomocą elementu bean, w którym określamy, że jego właściwość, weatherDao, powinna być ustawiona na egzemplarz komponentu weatherDao (który także definiujemy jako element bean). Pozostaje nam już tylko zmodyfikowanie klasy testowej, aby tworzyła odpowiedni kontener i uzyskiwała dostęp do znajdującej się w tym kontenerze usługi pogodowej: public class WeatherServiceTest extends TestCase { public void testSample2() throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext( "ch02/sample2/applicationContext.xml"); WeatherService ws = (WeatherService)ctx.getBean("weatherService"); Double high = ws.getHistoricalHigh(new GregorianCalendar(2004, 0, 1).getTime()); // … w tym miejscu powinieneś umieścić dodatkowy kod sprawdzający zwracaną wartość… } } Wszystkie analizowane klasy zostały już zakodowane i wdrożone zgodnie z zaleceniami IoC. Usługa pogodowa nie ma i nie potrzebuje żadnej wiedzy o szczegółach implementacji wykorzystywanego obiektu DAO, ponieważ zależność pomiędzy tymi składnikami aplikacji bazuje wyłącznie na interfejsie WeatherDao. Co więcej, podział oryginalnej klasy Weather- åService na interfejs WeatherService i implementującą go klasę WeatherServiceImpl chro- ni klientów usługi pogodowej przed takimi szczegółami implementacyjnymi jak sposób loka- lizowania przez tę usługę odpowiedniego obiektu DAO (w tym konkretnym przypadku wy- korzystano metodę ustawiającą znaną z komponentów JavaBeans). Takie rozwiązanie powoduje, że teraz implementacja usługi pogodowej może być zmieniana w sposób przezroczysty. Okazuje się, że na tym etapie możemy wymieniać implementacje interfejsu WeatherDao i (lub) interfejsu WeatherService, i że tego rodzaju zmiany będą wymagały wyłącznie od- powiedniego dostosowania zapisów pliku konfiguracyjnego (bez najmniejszego wpływu na funkcjonowanie klientów zmienianych komponentów). Przedstawiony przykład dobrze ilu- struje zastosowanie interfejsów w sytuacji, gdy kod jednej z warstw wykorzystuje kod innej warstwy (wspominano o tym w rozdziale 1.). Różne formy wstrzykiwania zależności Termin wstrzykiwanie zależności (ang. Dependency Injection) opisuje proces dostarczania komponentowi niezbędnych zależności z wykorzystaniem techniki IoC — można więc po- wiedzieć, że zależności są w istocie wstrzykiwane do komponentów. Wersja wstrzykiwania zależności, którą mieliśmy okazję analizować w poprzednim przykładzie, jest nazywana wstrzykiwaniem przez metody ustawiające (ang. Setter Injection), ponieważ do wprowa- dzania (wstrzykiwania) zależności do właściwych obiektów wykorzystuje się znane z kom- ponentów JavaBeans metody ustawiające. Więcej informacji na temat komponentów Java- Beans oraz specyfikację tej technologii znajdziesz na stronie internetowej http://java.sun. åcom/products/javabeans.
  • 17. 86 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Przeanalizujmy teraz nieco inny wariant wstrzykiwania zależności — wstrzykiwanie przez konstruktor (ang. Constructor Injection), gdzie zależności są dostarczane do obiektu za pośrednictwem jego własnego konstruktora: public interface WeatherService { Double getHistoricalHigh(Date date); } public class WeatherServiceImpl implements WeatherService { private final WeatherDao weatherDao; public WeatherServiceImpl(WeatherDao weatherDao) { this.weatherDao = weatherDao; } public Double getHistoricalHigh(Date date) { WeatherData wd = weatherDao.find(date); if (wd != null) return new Double(wd.getHigh()); return null; } } // niezmieniony interfejs WeatherDao public interface WeatherDao { ... } // niezmieniona klasa StaticDataWeatherDaoImpl public class StaticDataWeatherDaoImpl implements WeatherDao { ... } Klasa WeatherServiceImpl zawiera teraz konstruktor, który pobiera w formie argumentu egzemplarz interfejsu WeatherDao (zamiast — jak w poprzednim przykładzie — odpowied- niej metody ustawiającej, która także pobierała na wejściu egzemplarz tego interfejsu). Oczywiście także konfiguracja kontekstu aplikacji wymaga odpowiedniej modyfikacji. Okazuje się jednak, że zmiany w żaden sposób nie dotyczą klasy testowej, która nie wymaga żadnych dodatkowych czynności: <beans< <bean id="weatherService" class="ch02.sample3.WeatherServiceImpl"< <constructor-arg< <ref local="weatherDao"/< </constructor-arg< </bean< <bean id="weatherDao" class="ch02.sample3.StaticDataWeatherDaoImpl"< </bean< </beans< // niezmieniona klasa WeatherServiceTest public class WeatherServiceTest extends TestCase { ... }
  • 18. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 87 Wstrzykiwanie metod jest ostatnią formą wstrzykiwania zależności, którą się zajmiemy (jest stosowana zdecydowanie rzadziej od dwóch poprzednich technik). Wstrzykiwanie za- leżności w tej formie przenosi na kontener odpowiedzialność za implementowanie metod w czasie wykonywania programu. Przykładowo, obiekt może definiować chronioną metodę abstrakcyjną, a kontener może tę metodę implementować w czasie wykonywania programu w taki sposób, aby zwracała obiekt zlokalizowany podczas przeszukiwania środowiska wy- konywania aplikacji. Celem wstrzykiwania metod jest oczywiście wyeliminowanie zależ- ności od interfejsów API kontenera i tym samym ograniczanie niepożądanych związków. Jednym z podstawowych i jednocześnie najlepszych zastosowań techniki wstrzykiwania metod jest obsługa przypadków, w których bezstanowy obiekt, singleton, musi współpra- cować z niesingletonem, z obiektem stanowym lub takim, który nie zapewnia bezpieczeń- stwa przetwarzania wielowątkowego. Przypomnij sobie naszą usługę pogodową, w której potrzebujemy tylko jednego egzemplarza, ponieważ nasza implementacja jest bezstanowa i jako taka może być wdrażana za pomocą domyślnego kontenera Springa, który traktuje ją jak singleton (tworzy tylko jeden egzemplarz, który można przechowywać w pamięci pod- ręcznej i wykorzystywać wielokrotnie). Warto się jednak zastanowić, co by było, gdyby usługa pogodowa musiała korzystać z klasy StatefulWeatherDao, implementacji interfejsu WeatherDao niezapewniającej bezpieczeństwa wątków. W każdym wywołaniu metody WeatherService.getHistoricalHigh() usługa pogodowa musiałaby wykorzystywać świeży egzemplarz obiektu DAO (lub przynajmniej musiałaby się upewniać, że w danej chwili ża- den inny egzemplarz samej usługi nie wykorzystuje tego samego obiektu DAO). Jak się za chwilę przekonasz, można w prosty sposób zasygnalizować kontenerowi konieczność traktowania DAO jak obiektu, który nie jest singletonem — wówczas każde żądanie doty- czące tego obiektu spowoduje zwrócenie nowego egzemplarza. Problem w tym, że poje- dynczy egzemplarz usługi pogodowej jest przedmiotem wstrzykiwania zależności tylko raz. Jednym z rozwiązań jest oczywiście umieszczenie w kodzie tej usługi odwołań do interfejsu API kontenera Springa i — w razie potrzeby — żądanie nowego obiektu DAO. Takie podejście ma jednak jedną zasadniczą wadę: wiąże naszą usługę ze Springiem, co jest sprzeczne z dążeniem do jak największej izolacji obu struktur. Zamiast tego możemy zastosować ofe- rowany przez Springa mechanizm wstrzykiwania metody wyszukującej (ang. Lookup Method Injection), gdzie usługa pogodowa będzie miała dostęp do odpowiedniego obiektu DAO za pośrednictwem metody JavaBean getWeatherDao(), która — w zależności od po- trzeb — może być albo konkretną implementacją albo metodą abstrakcyjną. Następnie, już w definicji fabryki, nakazujemy kontenerowi przykrycie tej metody i zapewnienie imple- mentacji zwracającej nowy egzemplarz DAO w formie innego komponentu: public abstract class WeatherServiceImpl implements WeatherService { protected abstract WeatherDao getWeatherDao(); public Double getHistoricalHigh(Date date) { WeatherData wd = getWeatherDao().find(date); if (wd != null) return new Double(wd.getHigh()); return null; } } <beans< <bean id="weatherService" class="ch02.sample4.WeatherServiceImpl"<
  • 19. 88 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie <lookup-method name="getWeatherDao" bean="weatherDao"/< </bean< <bean id="weatherDao" singleton="false" class="ch02.sample4.StatefulDataWeatherDaoImpl"< </bean< </beans< Zauważ, że nakazaliśmy kontenerowi, aby nie traktował obiektu DAO jak singleton, zatem każde wywołanie metody getWeatherDao() może zwrócić nowy egzemplarz tego obiektu. Gdybyśmy tego nie zrobili, metoda za każdym razem zwracałaby ten sam egzemplarz sin- gletonu (składowany w pamięci podręcznej). Takie rozwiązanie jest co prawda poprawne, jednak jest mało prawdopodobne, byś kiedykolwiek chciał uzyskać właśnie taki efekt, po- nieważ podstawową zaletą techniki wstrzykiwania metod wyszukujących jest możliwość wstrzykiwania prototypów (niesingletonów), jak choćby w analizowanym przykładzie. W tym przypadku klasa WeatherServiceImpl i metoda getWeatherDao() są abstrakcyjne, możemy jednak przykryć dowolną metodę zwracającą każdego komponentu JavaBean (w praktyce metody kandydujące nie muszą pobierać żadnych argumentów, a ich nazwy nie muszą nawet być zgodne z konwencją nazewnictwa JavaBeans, choć ze względu na klarowność kodu takie rozwiązanie jest zalecane). Warto pamiętać, że także pozostały kod aplikacji musi korzystać z naszej metody zwracającej (a nie z odpowiedniego pola) do uzyskiwania dostępu do obiektu DAO. Wywoływanie metod zamiast bezpośredniego odwoływania się do pól obiektów jest jeszcze jedną dobrą praktyką przetwarzania właściwości komponentów JavaBeans. Stosując techniki wstrzykiwania metod, warto odpowiedzieć sobie na pytanie, jak można testować kod (np. wykonywać testy jednostkowe) aplikacji bez kontenera, który wstrzykuje daną metodę. Podobne wątpliwości są szczególnie istotne w przypadku aplikacji podob- nych do analizowanej usługi pogodowej, gdzie klasa implementacji jest abstrakcyjna. Sto- sunkowo prostą i jednocześnie możliwą do zrealizowania strategią przeprowadzania testów jednostkowych w podobnych przypadkach jest stworzenie dla wspomnianej klasy abstrak- cyjnej podklasy zawierającej testową implementację metody, która w normalnych warun- kach jest wstrzykiwana, przykładowo: ... WeatherService ws = new WeatherServiceImpl() { protected WeatherDao getWeatherDao() { // zwraca obiekt DAO dla danego testu ... } }; Stosowanie przez użytkowników Springa tak zaawansowanych mechanizmów jak wstrzy- kiwanie metod zawsze powinno być poprzedzone dogłębną analizą — należy zdecydować, które rozwiązanie w danej sytuacji jest najbardziej uzasadnione i które nie będzie stanowiło niepotrzebnego utrudnienia dla programistów. Niezależnie od wyników tej analizy naszym zdaniem przedstawione powyżej podejście jest pod wieloma względami lepsze od umiesz- czania w kodzie aplikacji zależności od interfejsów API kontenera.
  • 20. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 89 Wybór pomiędzy wstrzykiwaniem przez metody ustawiające a wstrzykiwaniem przez konstruktory Podczas korzystania z istniejących klas może się zdarzyć, że nie będziesz nawet miał wy- boru pomiędzy użyciem techniki wstrzykiwania przez metody ustawiające a zastosowaniem wstrzykiwania przez konstruktor. Jeśli dana klasa zawiera wyłącznie wieloargumentowy konstruktor i żadnych właściwości komponentu JavaBean lub jednoargumentowy kon- struktor i proste właściwości JavaBean, wybór metody wstrzykiwania nie zależy od Ciebie — dokonano go już wcześniej. Co więcej, niektóre spośród istniejących klas mogą wymu- szać stosowanie kombinacji obu form wstrzykiwania zależności, gdzie oprócz wieloargu- mentowego konstruktora będziesz zobligowany do ustawienia kilku opcjonalnych właści- wości JavaBean. Jeśli masz wybór odnośnie wersji wstrzykiwania zależności, której chcesz użyć lub dla której chcesz zaprojektować architekturę swojej aplikacji, przed podjęciem ostatecznej decyzji powinieneś wziąć pod uwagę kilka czynników: n Stosowanie właściwości JavaBean generalnie ułatwia obsługę domyślnych lub opcjonalnych wartości w sytuacji, gdy nie wszystkie wartości faktycznie są wymagane. W przypadku wstrzykiwania przez konstruktor takie podejście musiałoby prowadzić do stworzenia wielu wariantów konstruktora, gdzie jeden konstruktor wywoływałby w swoim ciele inny. Wiele wersji konstruktora i długie listy argumentów mogą być zbyt rozwlekłe i utrudniać konserwację kodu. n W przeciwieństwie do konstruktorów właściwości JavaBean (jeśli nie są prywatne) są automatycznie dziedziczone przez podklasy. Ograniczenie związane z dziedziczeniem konstruktorów często zmusza programistów do tworzenia w kodzie podklas szablonowych konstruktorów, których jedynym zadaniem jest wywoływanie odpowiednich konstruktorów nadklasy. Warto jednak pamiętać, że większość środowisk IDE oferuje obecnie rozmaite mechanizmy uzupełniania kodu, które czynią proces tworzenia konstruktorów lub właściwości JavaBean niezwykle łatwym. n Właściwości JavaBean są zdecydowanie łatwiejsze w interpretacji (nawet bez stosowania dodatkowych komentarzy dokumentujących) na poziomie kodu źródłowego niż argumenty konstruktora. Właściwości JavaBean wymagają też mniejszego wysiłku podczas dodawania komentarzy JavaDoc, ponieważ (w przeciwieństwie do konstruktorów) nie powtarzają się w kodzie. n W czasie działania, właściwości JavaBean mogą być dopasowywane według nazw, które są widoczne z poziomu mechanizmu refleksji Javy. Z drugiej strony, w skompilowanym pliku klasy użyte w kodzie źródłowym nazwy argumentów konstruktora nie są zachowywane, zatem automatyczne dopasowywanie według nazw jest niemożliwe. n Właściwości JavaBean oferują możliwość zwracania (i ustawiania) bieżącego stanu, jeśli tylko zdefiniowano odpowiednią metodę zwracającą (i ustawiającą). Takie rozwiązanie jest w wielu sytuacjach niezwykle przydatne, np. kiedy należy zapisać bieżący stan w innymi miejscu.
  • 21. 90 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie n Interfejs PropertyPritor dla właściwości JavaBean umożliwia wykonywanie automatycznej konwersji typów tam, gdzie jest to konieczne. Właśnie to wygodne rozwiązanie jest wykorzystywane i obsługiwane przez Springa. n Właściwości JavaBean mogą być zmienne, ponieważ odpowiednie metody ustawiające można wywoływać wiele razy. Oznacza to, że jeśli wymaga tego realizowany przypadek testowy, można w prosty sposób modyfikować istniejące zależności. Inaczej jest w przypadku zależności przekazywanych za pośrednictwem konstruktorów, które — jeśli nie są dodatkowo udostępniane w formie właściwości — pozostają niezmienne. Z drugiej strony, jeśli jednym z wymagań jest bezwzględna niezmienność zależności, właśnie technika wstrzykiwania przez konstruktor umożliwia zadeklarowanie pola ustawianego w ciele konstruktora ze słowem kluczowym final (metoda ustawiająca w najlepszym razie może odpowiedzieć wyjątkiem na próbę drugiego lub kolejnego wywołania; nie ma też możliwości zadeklarowania pola ustawianego w takiej metodzie ze słowem kluczowym final, które zabezpieczyłoby to pole przed ewentualnymi modyfikacjami przez pozostały kod danej klasy). n Argumenty konstruktora dają programiście pewność, że poprawny obiekt zostanie skonstruowany, ponieważ zadeklarowanie wszystkich wymaganych wartości wymusi ich przekazanie, a sama klasa w procesie inicjalizacji może je wykorzystywać w wymaganej kolejności w procesie inicjalizacji. W przypadku właściwości JavaBean nie można wykluczyć sytuacji, w której część właściwości nie została ustawiona przed użyciem danego obiektu, który — tym samym — znajdował się w niewłaściwym stanie. Co więcej, jeśli użyjesz właściwości JavaBean, nie będziesz mógł w żaden sposób wymusić kolejności wywołań metod ustawiających, co może wymagać dodatkowych zabiegów inicjalizujących już po ustawieniu właściwości (za pomocą metody init()). Taka dodatkowa metoda może też sprawdzać, czy wszystkie właściwości zostały ustawione. Spring oferuje mechanizmy automatycznego wywoływania dowolnej zadeklarowanej metody inicjalizującej bezpośrednio po ustawieniu wszystkich właściwości; alternatywnym rozwiązaniem jest zaimplementowanie interfejsu InitializingBean, który deklaruje automatycznie wywoływaną metodę afterPropertiesSet(). n Stosowanie kilku konstruktorów może być bardziej zwięzłym rozwiązaniem niż korzystanie z wielu właściwości JavaBean wraz z odpowiednimi metodami ustawiającymi i zwracającymi. Większość środowisk IDE oferuje jednak funkcje uzupełniania kodu, które bardzo ułatwiają i skracają proces tworzenia zarówno konstruktorów, jak i właściwości JavaBean. Ogólnie, zespół Springa w większości praktycznych zastosowań preferuje stosowanie techniki wstrzykiwania przez metody ustawiające zamiast wstrzykiwania przez konstruktory, choć tego rodzaju decyzje powinny być dobrze przemyślane i w żadnym razie nie należy upra- wiać doktrynerstwa. Wymienione powyżej aspekty obejmują większość czynników, które należy brać pod uwagę w każdej z sytuacji. Przyjmuje się, że wstrzykiwanie przez kon- struktory lepiej się sprawdza w przypadku prostszych scenariuszy inicjalizacji, gdzie kon- struktor otrzymuje na wejściu zaledwie kilka argumentów, które dodatkowo powinny re- prezentować złożone, a więc łatwiejsze do dopasowania, i unikatowe typy. Wraz ze wzrostem złożoności całej konfiguracji rośnie przewaga techniki wstrzykiwania przez metody ustawiające (zarówno w zakresie możliwości konserwacji, jak i nakładów pracy samego programisty). Warto pamiętać, że także inne popularne kontenery IoC obsługują zarówno
  • 22. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 91 wstrzykiwanie przez konstruktory, jak i wstrzykiwanie przez metody ustawiające, zatem obawa przed nadmiernym związaniem aplikacji z mechanizmami Springa nie powinna być uwzględniana podczas dokonywania tego wyboru. Niezależnie od wybranej formy wstrzykiwania zależności jest oczywiste, że należy ten me- chanizm stosować w taki sposób, aby odpowiednie konstrukcje były wprowadzane wyłącz- nie do klasy implementacji oraz właściwej konfiguracji tej klasy. Pozostały kod aplikacji powinien współpracować z pozostałymi modułami (komponentami) za pośrednictwem in- terfejsów i w żaden sposób nie powinien być uzależniony od problemów konfiguracyjnych. Podstawowy kontener IoC Springa jest często nazywany fabryką komponentów (ang. bean factory). Każda fabryka komponentów umożliwia logicznie spójne konfigurowanie i wią- zanie wielu obiektów za pomocą odpowiednich mechanizmów wstrzykiwania zależności. Fabryka komponentów oferuje też pewne funkcje w zakresie zarządzania obiektami, a w szcze- gólności ich cyklem życia. Podejście bazujące na technice odwracania kontroli umożliwia daleko idące oddzielanie kodu poszczególnych składników aplikacji. Co więcej, poza ko- rzystaniem z mechanizmu refleksji podczas uzyskiwania dostępu do obiektów odwracanie kontroli uniezależnia kod aplikacji od samej fabryki komponentów i — tym samym — eli- minuje konieczność dostosowywania kodu do współpracy z funkcjami Springa. Kod aplikacji potrzebny do konfigurowania obiektów (i do uzyskiwania obiektów z wykorzystaniem ta- kich wzorców jak singletony czy fabryki obiektów specjalnych) można albo w całości wy- eliminować, albo przynajmniej w znacznym stopniu zredukować. W typowej aplikacji bazującej na Springu tylko bardzo niewielka ilość kodu scalającego będzie świadoma do współpracy z kontenerem Springa lub korzystania z interfejsów tego kontenera. Nawet tę skromną ilość kodu w wielu przypadkach udaje się wyelimi- nować, korzystając z istniejącego kodu frameworka do załadowania fabryki komponen- tów w sposób deklaratywny. Fabryka komponentów Istnieją różne implementacje fabryki komponentów, z których każda oferuje nieco inną funkcjonalność (w praktyce różnice mają związek z działaniem mechanizmów konfiguracji, a najczęściej stosowaną reprezentacją jest format XML). Wszystkie fabryki komponentów implementują interfejs org.springframework.beans.factory.BeanFactory — programowe zarządzanie egzemplarzami wymaga dostępności właśnie za pośrednictwem tego interfejsu. Dodatkową funkcjonalność udostępniają ewentualne podinterfejsy interfejsu BeanFactory. Poniższa lista zawiera część tej hierarchii interfejsów: n BeanFactory — podstawowy interfejs wykorzystywany do uzyskiwania dostępu do wszystkich fabryk komponentów. Przykładowo wywołanie metody getBean(String name) umożliwia uzyskanie komponentu z kontenera na podstawie nazwy. Inny wariant tej metody, getBean(String name, Class requirerType), dodatkowo daje
  • 23. 92 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie programiście możliwość określenia wymaganej klasy zwracanego komponentu i generuje wyjątek, jeśli taki komponent nie istnieje. Inne metody umożliwiają nam odpytywanie fabryki komponentów w zakresie istnienia poszczególnych komponentów (według nazw), odnajdywanie typów komponentów (także według nazw) oraz sprawdzanie, czy w danej fabryce istnieją jakieś aliasy danego komponentu (czy dany komponent występuje w fabryce pod wieloma nazwami). I wreszcie można określić, czy dany komponent skonfigurowano jako singleton (czy pierwszy utworzony egzemplarz komponentu będzie wykorzystywany wielokrotnie dla wszystkich kolejnych żądań czy fabryka za każdym razem będzie tworzyła nowy egzemplarz). n HierarchicalBeanFactory — większość fabryk komponentów może być tworzona jako część większej hierarchii — wówczas żądanie od fabryki dostępu do komponentu, który w tej konkretnej fabryce nie istnieje, spowoduje przekazanie żądania do jego fabryki nadrzędnej (przodka), która może z kolei zażądać odpowiedniego komponentu od swojej fabryki nadrzędnej itd. Z perspektywy aplikacji klienta cała hierarchia powyżej bieżącej fabryki (włącznie z tą fabryką) może być traktowana jak jedna, scalona fabryka. Jedną z zalet takiej hierarchicznej struktury jest możliwość jej dopasowania do rzeczywistych warstw architekturalnych lub modułów aplikacji. O ile uzyskiwanie komponentu z fabryki będącej częścią hierarchii odbywa się w sposób całkowicie przezroczysty, interfejs HierarchicalBeanFactory jest niezbędny, jeśli chcemy mieć możliwość dostępu do komponentów składowanych w jej fabryce nadrzędnej. n ListableBeanFactory — metody tego podinterfejsu interfejsu BeanFactory umożliwiają generowanie rozmaitych list komponentów dostępnych w bieżącej fabryce, włącznie z listą nazw komponentów, nazwami wszystkich komponentów określonego typu oraz liczbą komponentów w danej fabryce. Kilka metod tego interfejsu umożliwia dodatkowo tworzenie egzemplarza Map zawierającego wszystkie komponenty określonego typu. Warto pamiętać, że o ile metody interfejsu BeanFactory automatycznie są udostępniane na wszystkich poziomach hierarchii fabryk komponentów, metody interfejsu ListableBeanFactory są stosowane tylko dla jednej, bieżącej fabryki. Klasa pomocnicza BeanFactoryUtils oferuje niemal identyczne metody jak interfejs ListableBeanFactory, tyle że w przeciwieństwie do metod tego interfejsu uwzględniają w generowanych wynikach całą hierarchię fabryk komponentów. W wielu przypadkach stosowanie metod klasy BeanFactoryUtils jest lepszym rozwiązaniem niż korzystanie z interfejsu ListableBeanFactory. n AutowireCapableBeanFactory — interfejs AutowireCapableBeanFactory umożliwia (za pośrednictwem metod autowireBeanProperties() oraz applyBeanPropertyValues()) konfigurowanie istniejącego, zewnętrznego obiektu i dostarczanie zależności z poziomu fabryki komponentów. Oznacza to, że wspomniane metody pozwalają pominąć normalny krok tworzenia obiektu będący częścią uzyskiwania komponentu za pośrednictwem metody BeanFactory.getBean(). Podczas pracy z zewnętrznym kodem, który wymaga tworzenia egzemplarzy obiektów jako takich, tworzenie komponentów za pomocą odpowiednich mechanizmów Springa nie zawsze jest możliwe, co nie oznacza, że należy rezygnować z oferowanych przez ten framework cennych rozwiązań w zakresie wstrzykiwania zależności. Inna metoda, autowire(), umożliwia określanie na potrzeby fabryki komponentów nazwy klasy, wykorzystanie tej fabryki do utworzenia egzemplarza tej klasy, użycie refleksji do wykrycia wszystkich zależności tej klasy i wstrzyknięcia tych zależności do danego
  • 24. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 93 komponentu (w efekcie powinieneś otrzymać w pełni skonfigurowany obiekt). Warto pamiętać, że fabryka nie będzie zawierała i zarządzała obiektami konfigurowanymi za pomocą tych metod (inaczej niż w przypadku tworzonych przez siebie egzemplarzy singletonów), choć możemy sami dodać do tej fabryki nasze egzemplarze jako singletony. n ConfigurableBeanFactory — ten interfejs wprowadza do podstawowej fabryki komponentów dodatkowe opcje konfiguracyjne, które mogą być stosowane w fazie inicjalizacji. Ogólnie Spring próbuje stosować tzw. wyjątki nieweryfikowalne (ang. non-checked excep- tions), czyli podklasy klasy RuntimePxception dla nieweryfikowalnych błędów. Interfejsy fabryki komponentów, w tym BeanFactory i jego podinterfejsy, nie są pod tym względem wyjątkiem. W większości przypadków błędy konfiguracji są nieweryfikowalne, zatem wszystkie wyjątki zwracane przez opisywane interfejsy API są podklasami klasy niewery- fikowalnych wyjątków BeansPxception. Decyzja o tym, gdzie i kiedy należy przechwytywać i obsługiwać te wyjątki, należy do programisty — jeśli istnieje odpowiednia strategia obsługi sytuacji wyjątkowej, można nawet podejmować próby odtwarzania stanu sprzed wyjątku. Kontekst aplikacji Spring obsługuje też mechanizm znacznie bardziej zaawansowany od fabryki komponentów — tzw. kontekst aplikacji (ang. application context). Warto podkreślić, że kontekst aplikacji jest fabryką komponentów, a interfejs org.sp- åringframework.context.ApplicationContext jest podinterfejsem interfejsu BeanFactory. Kompletną hierarchię interfejsów przedstawiono na rysunku 2.1. Ogólnie wszystkie zadania, które można realizować za pomocą fabryki komponentów, można też wykonywać z wykorzystaniem kontekstu aplikacji. Po co więc wprowadzono takie rozróżnienie? Przede wszystkim po to, aby podkreślić zwiększoną funkcjonalność i nieco inny sposób używania kontekstu aplikacji: n Ogólny styl pracy z frameworkiem — pewne operacje na komponentach w kontenerze lub na samym kontenerze, które w fabryce komponentów muszą być obsługiwane programowo, w kontekście aplikacji mogą być realizowane deklaratywnie. Chodzi między innymi o rozpoznawanie i stosowanie specjalnych postprocesorów dla komponentów oraz postprocesorów dla fabryki komponentów. Co więcej, istnieje wiele mechanizmów Springa, które ułatwiają automatyczne wczytywanie kontekstów aplikacji — przykładowo, w warstwie MVC aplikacji internetowych większość fabryk komponentów będzie tworzona przez kod użytkownika, natomiast konteksty aplikacji będą wykorzystywane najczęściej za pomocą technik deklaratywnych i tworzone przez kod Springa. W obu przypadkach znaczna część kodu użytkownika będzie oczywiście zarządzana przez kontener, zatem nie będzie dysponowała żadną wiedzą o samym kontenerze. n Obsługa interfejsu MessageSource — kontekst aplikacji implementuje interfejs MessageSource, którego zadaniem jest uzyskiwanie zlokalizowanych komunikatów i którego implementacja może być w prosty sposób włączana do aplikacji.
  • 25. 94 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Rysunek 2.1. n Obsługa zdarzeń aplikacji i frameworka — kontekst aplikacji oferuje możliwość wywoływania zdarzeń frameworka lub aplikacji, które są następnie przechwytywane i obsługiwane przez zarejestrowane obiekty nasłuchujące. n Obsługa interfejsu ResourceLoader — interfejs ResourceLoarer Springa jest elastyczną i uniwersalną abstrakcją obsługującą niskopoziomowe zasoby. Sam kontekst aplikacji jest implementacją tego interfejsu i — tym samym — zapewnia aplikacji dostęp do egzemplarzy Resource właściwych dla danego wdrożenia. Być może zastanawiasz się, kiedy lepszym rozwiązaniem jest tworzenie i korzystanie z fabryki komponentów, a kiedy lepiej użyć kontekstu aplikacji. Niemal we wszystkich przypadkach warto rozważyć użycie kontekstu aplikacji, który bez żadnych dodatkowych kosztów poszerza zakres funkcjonalności. Prawdopodobnie jedynym wyjątkiem od tej reguły są aplikacje bazujące na apletach, gdzie każdy zajmowany (lub przesyłany) bajt pamięci może mieć ogromne znaczenie — fabryka komponentów pozwala na pewne oszczędności w tym zakresie, ponieważ umożliwia korzystanie z pakietu bibliotek Springa, które zawierają wyłącznie funkcjonalność fabryki (bez rozbudowanej funkcjo- nalności kontekstu aplikacji). W tym i innych rozdziałach poświęconych funkcjonalno- ści fabryki komponentów możesz bezpiecznie zakładać, że wszystkie analizowane elementy funkcjonalności są wspólne zarówno dla fabryki komponentów, jak i dla kon- tekstu aplikacji.
  • 26. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 95 W tym i w większości pozostałych rozdziałów tej książki analiza konfiguracji i funkcjonal- ności kontenera będzie ilustrowana przykładami w wersji deklaratywnej, opartej na forma- cie XML (w tym XmlBeanFactory lub ClassPathXmlApplicationContext) fabryki kompo- nentów i kontekstu aplikacji. Warto zdać sobie sprawę z tego, że funkcjonalność kontenera w żadnym razie nie jest tożsama z formatem jego konfiguracji. Konfiguracja oparta na XML jest co prawda wykorzystywana przez zdecydowaną większość użytkowników Springa, jednak istnieje też kompletny interfejs API dla mechanizmów konfiguracji i dostępu do kontenerów, w tym obsługa innych formatów konfiguracji (które mogą być konstruowane i obsługiwane w bardzo podobny sposób jak odpowiednie dokumenty XML). Przykładowo, istnieje klasa PropertiesBeanDefinitionRearer, która odpowiada za załadowanie do fabryki komponentów definicji zapisanych w plikach właściwości Javy. Uruchamianie kontenera Poprzednie przykłady pokazały, jak można programowo uruchamiać kontener z poziomu kodu użytkownika. W tym punkcie przeanalizujemy kilka ciekawych rozwiązań w tym zakresie. Egzemplarz (implementację) interfejsu ApplicationContext można wczytać, wskazując ścieżkę do odpowiedniej klasy (pliku): ApplicationContext appContext = new ClassPathXmlApplicationContext("ch03/sample2/applicationContext.xml"); // Uwaga: ApplicationContext jest egzemplarzem BeanFactory, to oczywiste! BeanFactory factory = (BeanFactory) appContext; Można też wskazać konkretną lokalizację w systemie plików: ApplicationContext appContext = new FileSystemXmlApplicationContext("/some/file/path/applicationContext.xml"); Istnieje też możliwość łączenia dwóch lub większej liczby fragmentów prezentowanych już dokumentów XML. Takie rozwiązanie umożliwia nam lokalizowanie definicji komponen- tów w ramach logicznych modułów, do których należą, i jednocześnie tworzenie jednego kontekstu na bazie połączonych definicji. Przykład prezentowany w następnym rozdziale pokaże, że opisane podejście może być bardzo użyteczne także podczas testów. Poniżej przedstawiono jeden z wcześniejszych przykładów konfiguracji, tyle że tym razem złożony z dwóch fragmentów kodu XML: applicationContext-dao.xml: <?xml version="1.0" encoding="UTF-8"?< <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"< <beans< <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"< </bean< </beans<
  • 27. 96 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie applicationContext-services.xml: <?xml version="1.0" encoding="UTF-8"?< <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"< <beans< <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref bean="weatherDao"/< </property< </bean< </beans< Uważni Czytelnicy zapewne zauważyli, że w porównaniu z wcześniejszymi przykłada- mi nieznacznie zmieniono sposób odwoływania się do komponentu weatherDao, który wykorzystujemy jako właściwość komponentu usługi pogodowej; odwołania do kom- ponentów szczegółowo omówimy w dalszej części rozdziału. Aby wczytać i połączyć oba fragmenty (teoretycznie może ich być więcej), musimy je tylko wymienić w tablicy nazw (łańcuchów) przekazywanej do konstruktora klasy ClassPathXml- åApplicationContext: ApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] {"applicationContext-services.xml", "applicationContext-dao.xml"}); Abstrakcja Resource Springa (którą szczegółowo omówimy nieco później) umożliwia sto- sowanie prefiksu classpath*: do wyszczególniania tych wszystkich zasobów pasujących do konkretnej nazwy, które są widoczne z perspektywy classloadera i jego classloaderów nadrzędnych. Przykładowo, gdyby nasza aplikacja została rozproszona pomiędzy wiele pli- ków JAR, wszystkich należących do ścieżki do klas, z których każdy zawierałby własny fragment kontekstu aplikacji nazwany applicationContext.xml, moglibyśmy w prosty spo- sób określić, że chcemy utworzyć kontekst złożony ze wszystkich istniejących fragmentów: ApplicationContext appContext = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml"}); Okazuje się, że tworzenie i wczytywanie fabryki komponentów skonfigurowanych za po- mocą odpowiednich zapisów języka XML jest bardzo łatwe. Najprostszym rozwiązaniem jest użycie abstrakcji Resource Springa, która umożliwia uzyskiwanie dostępu do zasobów według ścieżki klas: ClassPathResource res = new ClassPathResource("org/springframework/prospering/beans.xml"}); XmlBeanFactory factory = new XmlBeanFactory(res); lub: FilesystemResource res = new FilesystemResource("/some/file/path/beans.xml"); XmlBeanFactory factory = new XmlBeanFactory(res);
  • 28. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 97 Równie dobrze moglibyśmy użyć egzemplarza InputStream: InputStream is = new FileInputStream("/some/file/path/beans.xml"); XmlBeanFactory factory = new XmlBeanFactory(is); Aby wyczerpać ten temat, musimy wspomnieć o możliwości prostego oddzielenia operacji tworzenia fabryki komponentów od procesu przetwarzania definicji komponentów. Nie bę- dziemy się zajmować tym zagadnieniem w szczegółach, warto jednak pamiętać, że takie wyodrębnienie zachowania fabryki komponentów od mechanizmów przetwarzania definicji komponentów upraszcza stosowanie innych formatów konfiguracji: ClassPathResource res = new ClassPathResource("beans.xml"}); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res); Stosując klasę GenericApplicationContext dla kontekstu aplikacji, możemy w podobny sposób oddzielić kod tworzący ten kontekst od kodu przetwarzającego definicje kompo- nentów. Wiele aplikacji Springa w ogóle nie tworzy kontenera programowo, ponieważ bazuje na odpowiednim kodzie tego frameworka (który wykonuje odpowiednie działania w ich imieniu). Przykładowo, możemy deklaratywnie skonfigurować mechanizm ContextLoarer Springa w taki sposób, aby automatycznie wczytywał kontekst aplikacji w momencie uruchamiania aplikacji internetowej. Odpowiednie techniki konfiguracji omówimy w na- stępnym rozdziale. Korzystanie komponentów uzyskiwanych z fabryki Kiedy już fabryka komponentów lub kontekst aplikacji zostaną wczytane, uzyskanie dostępu do komponentów sprowadza się do wywołania metody getBean() interfejsu BeanFactory: WeatherService ws = (WeatherService)ctx.getBean("weatherService"); lub jednej z metod któregoś z bardziej zaawansowanych interfejsów: Map allWeatherServices = ctx.getBeansOfType(WeatherService.class); Żądanie od kontenera dostępu do komponentu wywołuje proces jego tworzenia i inicjaliza- cji, który obejmuje omówioną już fazę wstrzykiwania zależności. Krok wstrzykiwania za- leżności może z kolei zainicjować proces tworzenia pozostałych komponentów (zależności pierwszego komponentu) itd., aż do utworzenia kompletnego grafu wzajemnie powiąza- nych egzemplarzy obiektów. W związku z opisaną procedurą nasuwa się dość oczywiste pytanie: co należałoby zrobić z samą fabryką komponentów lub kontekstem aplikacji, aby pozostały kod, który tego wy- maga, mógł uzyskać do nich dostęp? Obecnie skupiamy się na analizie sposobu konfiguro- wania i funkcjonowania kontenera, zatem przedstawienie i wyjaśnienie odpowiedzi na to pytanie odłóżmy na dalszą część tego rozdziału.
  • 29. 98 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Pamiętaj, że z wyjątkiem bardzo niewielkiej ilości kodu scalającego zdecydowana więk- szość kodu aplikacji pisanego i łączonego zgodnie z założeniami IoC nie musi zawierać żadnych operacji związanych z uzyskiwaniem dostępu do fabryki, ponieważ za zarzą- dzanie zależnościami pomiędzy obiektami i samymi obiektami odpowiada kontener. Najprostszą strategią zapewniania odpowiednich proporcji pomiędzy kodem scalają- cym (niezbędnym do inicjowania pewnych działań) a właściwym kodem aplikacji jest umieszczenie fabryki komponentów w znanym miejscu, które będzie odpowiadało nie tylko oczekiwanym zastosowaniom, ale też tym elementom kodu, które będą potrze- bowały dostępu do tej fabryki. Sam Spring oferuje mechanizm deklaratywnego wczyty- wania kontekstu dla aplikacji internetowych i składowania tego kontekstu w obiekcie ServletContext. Co więcej, Spring zawiera klasy pomocnicze przypominające single- tony, które mogą być z powodzeniem stosowane do składowania i wydobywania z fa- bryki komponentów (jeśli takie rozwiązanie z jakiegoś powodu wydaje Ci się lepsze lub jeśli nie istnieje strategia składowania fabryki komponentów w ramach konkretnej aplikacji). Konfiguracja komponentów w formacie XML Mieliśmy już okazję przejrzeć przykładowe pliki z definicjami fabryk komponentów w formacie XML, jednak do tej pory nie poddaliśmy zawartych tam zapisów szczegółowej analizie. W największym uproszczeniu definicja fabryki komponentów składa się z elementu beans (na najwyższym poziomie) oraz jednego lub wielu elementów bean: <?xml version="1.0" encoding="UTF-8"?< <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"< <beans< <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"< </bean< </beans< Prawidłowe elementy i atrybuty pliku definicji szczegółowo opisano w pliku XML DTD (ang. Document Type Definition) nazwanym spring-beans.dtd. Wspomniany plik DTD w połączeniu z podręcznikiem użytkownika Springa powinien być traktowany jak ostateczne źródło informacji o technikach konfiguracji aplikacji. Ogólnie, opcjonalne atrybuty ele- mentu beans (na najwyższym poziomie elementów XML definicji komponentów) mają wpływ na zachowanie całego pliku konfiguracji i zawierają domyślne wartości dla rozma- itych aspektów wszystkich definiowanych komponentów, natomiast większość opcjonalnych atrybutów i podelementów potomnych elementów bean opisuje konfigurację i cykl życia poszczególnych komponentów. Plik spring-beans.dtd jest co prawda dołączany do Springa, jednak z jego zawartością można się zapoznać także w internecie (patrz www.springframe- åwork.org/dtd/spring-beans.dtd).
  • 30. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 99 Podstawowa definicja komponentu Definicja pojedynczego komponentu zawiera wszystkie informacje, których kontener po- trzebuje do jego utworzenia, w tym trochę szczegółowych danych o cyklu życia kompo- nentu oraz jego zależnościach. Przyjrzyjmy się dwóm pierwszym składnikom elementu bean. Identyfikator W definicji komponentów na najwyższym poziomie hierarchii niemal we wszystkich przy- padkach definiujemy jeden lub wiele identyfikatorów (lub nazw) komponentów, aby pozo- stałe komponenty mogły się do nich odwoływać podczas programowego korzystania z da- nego kontenera. Do definiowania identyfikatorów (lub głównych identyfikatorów) komponentów służy atrybut ir. Zaletą tego atrybutu jest zgodność z typem XML IDREF — kiedy pozostałe komponenty będą się odwoływały do komponentu za pośrednictwem tak zdefiniowanego identyfikatora, sam parser dokumentu XML może pomóc w sprawdzeniu, czy takie odwołanie jest poprawne (czy w tym samym pliku zdefiniowano odpowiedni identyfikator), zatem atrybut ir ułatwia wczesną weryfikację prawidłowości konfiguracji. Warto jednak pamiętać, że typ XML IDREF wprowadza pewne ograniczenia w kwestii ak- ceptowanych znaków — identyfikatory muszą się rozpoczynać od litery, a na kolejnych pozycjach mogą zawierać dowolne znaki alfanumeryczne i znaki podkreślenia (ale nie mo- gą zawierać spacji). W większości przypadków opisane ograniczenia nie stanowią co prawda żadnego problemu, jeśli jednak z jakiegoś powodu chcesz je ominąć, możesz zdefi- niować identyfikator w atrybucie name. Przykładowo, takie rozwiązanie jest uzasadnione, jeśli identyfikator komponentu jest poza kontrolą użytkownika i reprezentuje ścieżkę URL. Co więcej, atrybut name dopuszcza możliwość stosowania listy identyfikatorów oddzielo- nych przecinkami. Kiedy definicja komponentu definiuje więcej niż jeden identyfikator, np. w formie kombinacji atrybutu ir i (lub) atrybutu name, wszystkie dodatkowe identyfikatory (poza pierwszym) należy traktować jak aliasy. Odwołania do wszystkich identyfikatorów są równie poprawne. Przeanalizujmy kilka przykładów: <beans< <bean id="bean1" class="ch02.sample .TestBean"/< <bean name="bean2" class="ch02.sample .TestBean"/< <bean name="/myservlet/myaction" class="ch02.sample .TestBean"/< <bean id="component1-dataSource" name="component2-dataSource,component3-dataSource" class="ch02.sample .TestBean"/< </beans< Ponieważ trzeci definiowany komponent wymaga identyfikatora rozpoczynającego się od znaku ukośnika (/), użycie atrybutu ir jest niemożliwe — musimy użyć atrybutu name. Za- uważ, że czwarty komponent zawiera aż trzy identyfikatory, wszystkie równie poprawne. Być może zastanawiasz się, po co w ogóle miałbyś definiować więcej niż jeden identyfikator dla komponentu. Dobrą praktyką jest takie dzielenie konfiguracji według komponentów lub
  • 31. 100 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie modułów, aby dla każdego modułu istniał fragment pliku XML zawierający komponenty powiązane z tym modułem (wraz z ich zależnościami). Nazwy tych zależności mogą (jak w przedstawionym przykładzie) być definiowane z prefiksem właściwym dla danego kom- ponentu (w tym przypadku rataSource). Kiedy fabryka komponentów lub kontekst aplika- cji jest ostatecznie budowany z wielu przygotowanych wcześniej fragmentów (lub kiedy tworzona jest hierarchia kontekstów — patrz dalsza część tego rozdziału), każdy z tych komponentów będzie się odwoływał do tego samego, fizycznego bytu. Takie rozwiązanie należy traktować jak techniczną próbę rozwiązania problemu izolacji komponentów. Mechanizm tworzenia komponentów Może też zaistnieć konieczność wskazania kontenerowi sposobu, w jaki ten powinien two- rzyć lub uzyskiwać egzemplarze komponentu, kiedy będzie tego potrzebował. Najczęściej stosowanym mechanizmem jest tworzenie komponentu za pośrednictwem jego konstruktora. Do określania nazwy klasy komponentu służy atrybut class. Za każdym razem, gdy kontener potrzebuje nowego egzemplarza komponentu, wewnętrznie wykonuje odpowiednik znanego z języka Java operatora new. Wszystkie prezentowane do tej pory przykłady wykorzysty- wały właśnie ten mechanizm. Innym mechanizmem tworzenia komponentów jest sygnalizowanie kontenerowi konieczno- ści użycia statycznej metody fabrykującej (ang. factory method), której jedynym zadaniem jest zwracanie nowego egzemplarza komponentu. Istniejący kod, nad którym nie masz kontroli, będzie Cię czasami zmuszał do korzystania z takiej statycznej metody fabrykującej. Nazwę klasy zawierającej tę metodę można określić za pośrednictwem atrybutu class, natomiast nazwę samej metody farykującej należy zdefiniować w atrybucie factory-methor: ... <bean id="testBeanObtainedViaStaticFactory" class="ch02.sample4.StaticFactory" factory-method="getTestBeanInstance"/< ... public class StaticFactory { public static TestBean getTestBeanInstance() { return new TestBean(); } } Tak zdefiniowana statyczna metoda fabrykująca może zwracać egzemplarze obiektów do- wolnego typu; klasa zwracanego egzemplarza nie musi być tożsama z klasą zawierającą samą metodę fabrykującą. Trzecim mechanizmem tworzenia nowych egzemplarzy komponentu jest wywoływanie niestatycznych metod fabrykujących innych egzemplarzy komponentów w ramach tego samego kontenera: ... <bean id="nonStaticFactory" class="ch02.sample4.NonStaticFactory"/< <bean id="testBeanObtainedViaNonStaticFactory" factory-bean="nonStaticFactory" factory-method="getTestBeanInstance"/< ...
  • 32. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 101 public class NonStaticFactory { public TestBean getTestBeanInstance() { return new TestBean(); } } Kiedy będzie potrzebny nowy egzemplarz komponentu testBeanObtainerViaNonStaticFactory, kontener w pierwszej kolejności utworzy egzemplarz fabryki nonStaticFactory i wywoła udostępnianą przez tę fabrykę metodę getTestBeanInstance(). Zauważ, że w tym przypadku w ogóle nie określiliśmy wartości atrybutu class. Kiedy już uda się uzyskać nowy egzemplarz obiektu, kontener traktuje go dokładnie tak samo jak wszystkie pozostałe egzemplarze, niezależnie od tego, czy został utworzony za pośrednictwem konstruktora, statycznej metody fabrykującej czy metody fabrykującej in- nego egzemplarza. Oznacza to, że każdy z tych egzemplarzy może być przedmiotem wstrzykiwania zależności przez metody ustawiające i podlega normalnemu cyklowi życia (w tym odpowiednim wywołaniom zwrotnym). Komponenty singletonowe kontra komponenty niesingletonowe (prototypowe) Ważnym aspektem cyklu życia komponentu jest to, czy kontener traktuje go jak singleton czy jak zwykłą klasę z wieloma egzemplarzami. Domyślne komponenty singletonowe są tworzone przez kontener tylko raz. Kontener następnie przechowuje i wykorzystuje ten sam egzemplarz komponentu za każdym razem, gdy następuje odwołanie do danego kompo- nentu. Takie rozwiązanie może oznaczać znaczne oszczędności zasobów (w szczególności pamięci, ale także obciążenia procesora) w porównaniu z tworzeniem nowego egzemplarza komponentu w odpowiedzi na każde kolejne żądanie. Komponenty singletonowe są więc najlepszym rozwiązaniem zawsze wtedy, gdy tylko implementacja właściwych klas do- puszcza taką możliwość; czyli wtedy, gdy komponent jest bezstanowy lub gdy jego stan jest ustawiany tylko raz, w czasie inicjalizacji, i — tym samym — zapewnia bezpieczeń- stwo wątków (może być wykorzystywany jednocześnie przez więcej niż jeden wątek). Komponenty singletonowe są rozwiązaniem domyślnym, ponieważ zdecydowana więk- szość praktycznych usług, kontrolerów i zasobów konfigurowanych w ramach kontenera i tak jest implementowana w formie klas gwarantujących bezpieczną pracę w środowisku wielowątkowym, zatem nie modyfikują swojego stanu po wykonaniu fazy inicjalizacji. Niesingletonowe, prototypowe komponenty, jak sama nazwa wskazuje, powinny być defi- niowane z atrybutem singleton równym false. Warto pamiętać, że cykl życia komponentu prototypowego często różni się od cyklu życia komponentu singletonowego. Kiedy konte- ner otrzymuje żądanie dostarczenia komponentu prototypowego, następuje oczywiście ini- cjalizacja i przekazanie odpowiedniego egzemplarza, ale na tym rola kontenera się kończy (od tego momentu kontener nie przechowuje zwróconego egzemplarza). O ile więc istnieje możliwość wymuszenia na kontenerze Springa wykonania pewnych operacji kończących cykl życia komponentów singletonowych (patrz dalsza część tego rozdziału), w przypadku komponentów prototypowych te same operacje będzie trzeba zrealizować w kodzie użyt- kownika, ponieważ kontener nie będzie już miał żadnej wiedzy o zwróconych egzemplarzach tych komponentów:
  • 33. 102 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie <bean id="singleton1" class="ch02.sample4.TestBean"/< <bean id="singleton2" singleton="true" class="ch02.sample .TestBean"/< <bean id="prototype1" singleton="false" class="ch02.sample .TestBean"/< Definiowanie zależności komponentów Spełnianie zależności komponentów (czy to w formie innych komponentów, czy tylko pro- stych wartości potrzebnych do działania danego komponentu) jest prawdziwym jądrem, kluczowym elementem funkcjonalności kontenera, warto więc dobrze zrozumieć rzeczywiste działanie tego procesu. Miałeś już okazję zapoznać się z przykładami podstawowych typów wstrzykiwania zależności, wstrzykiwaniem przez konstruktory i wstrzykiwaniem przez metody ustawiające, wiesz też, że Spring obsługuje obie formy tej techniki. Przekonałeś się również, jak Spring może wykorzystać odpowiednią metodę fabrykującą — zamiast używać konstruktora do uzyskiwania początkowego egzemplarza obiektu. Właśnie z uwagi na konieczność dostarczenia zależności do komponentu stosowanie metody fabrykującej do uzyskiwania egzemplarza komponentu można de facto uznać za odpowiednik tworzenia egzemplarza za pośrednictwem konstruktora. W przypadku użycia konstruktora to kontener odpowiada za zapewnienie wartości (opcjonalnych) argumentów konstruktora (które repre- zentują zależności). Podobnie, w przypadku metody fabrykującej kontener dostarcza warto- ści (opcjonalnych) argumentów do metody fabrykującej (także reprezentujące zależności). Niezależnie od tego, czy początkowy egzemplarz obiektu jest tworzony przez konstruktor czy przez metodę fabrykującą, od tego momentu wszystkie egzemplarze są traktowane jednakowo. Techniki wstrzykiwania przez konstruktory i wstrzykiwania przez metody ustawiające nie wykluczają się wzajemnie. Nawet jeśli Spring uzyskuje początkowy egzemplarz kompo- nentu za pośrednictwem konstruktora lub metody fabrykującej i dostarcza wartości argu- mentów do konstruktora lub metody fabrykującej (wstrzykuje zależności), nadal może sto- sować mechanizm wstrzykiwania przez metody ustawiające do wprowadzania kolejnych zależności. Takie rozwiązanie może być szczególnie przydatne np. wtedy, gdy będziemy musieli użyć i zainicjalizować istniejącą klasę, która z jednej strony zawiera konstruktor pobierający jeden lub wiele argumentów i generujący komponent w znanym (prawidłowym) stanie początkowym, ale z drugiej strony bazuje na metodach ustawiających JavaBeans dla części opcjonalnych właściwości. Gdyby nie obsługa obu form wstrzykiwania zależności, nie byłbyś w stanie prawidłowo inicjalizować tego rodzaju obiektów w sytuacji, gdyby któraś z nich wymagała ustawienia opcjonalnych właściwości. Przeanalizujmy teraz sposób, w jaki kontener inicjalizuje i realizuje zależności komponentów: n Kontener w pierwszej kolejności inicjalizuje definicję komponentu bez tworzenia jego egzemplarza — zwykle następuje to w czasie uruchamiania samego kontenera. Zależności komponentu mogą być wyrażane wprost, w formie argumentów konstruktora lub metody fabrykującej i (lub) właściwości komponentu. n Każda właściwość lub argument konstruktora w definicji komponentu ma albo postać wartości wymagającej ustawienia, albo odwołania do innego komponentu w ramach danej fabryki komponentów lub nadrzędnej fabryki komponentów.
  • 34. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 103 n Kontener wykona tyle operacji sprawdzających poprawność definiowanych zależności, ile będzie mógł w czasie inicjalizacji definicji komponentu. Jeśli korzystasz z formatu konfiguracji XML, w przypadku niezgodności konfiguracji z typem XML DTD w pierwszej kolejności otrzymamy wyjątek wygenerowany przez parser XML. Nawet jeśli konfiguracja w formacie XML jest poprawna z punktu widzenia definicji typów DTD, w razie wykrycia logicznej niespójności przez Springa także otrzymamy odpowiedni wyjątek — przykładowo, dwie właściwości mogą się wzajemnie wykluczać, czego nie da się wykazać wyłącznie na podstawie analizy typów DTD. n Jeśli zależność komponentu nie może być zrealizowana w praktyce (jeśli zależność komponentu ma postać innego komponentu, który nie istnieje) lub jeśli argument konstruktora lub wartość właściwości nie może zostać prawidłowo ustawiona, otrzymamy komunikat o błędzie tylko wtedy, gdy dany kontener rzeczywiście będzie musiał uzyskać nowy egzemplarz tego komponentu i wstrzyknąć jego zależności. Jeśli okaże się, że egzemplarz komponentu nigdy nie będzie potrzebny, nie można wykluczyć, że ewentualne błędy w definicji zależności komponentu lub samego komponentu nigdy nie zostaną wykryte (przynajmniej do momentu jego użycia). Między innymi po to, aby umożliwić możliwie szybkie wykrywanie błędów, konteksty aplikacji (ale nie fabryki komponentów) domyślnie tworzą wstępne egzemplarze komponentów singletonowych. Faza wstępnego tworzenia egzemplarzy obejmuje iteracyjne przeszukiwanie wszystkich komponentów singletonowych (w ich domyślnych stanach), utworzenie egzemplarzy każdej ze wstrzykiwanych zależności i umieszczenie tych egzemplarzy w pamięci podręcznej. Warto pamiętać, że etap wstępnego tworzenia egzemplarzy komponentów można zmodyfikować albo za pomocą atrybutu refault-lazy-init elementu beans definiowanego na najwyższym poziomie pliku konfiguracji, albo na poziomie poszczególnych komponentów (elementów bean) za pośrednictwem atrybutu lazy-init. n Kiedy kontener potrzebuje nowego egzemplarza danego komponentu (zazwyczaj w wyniku wywołania metody getBean() lub odwołania ze strony innego komponentu, dla którego bieżący komponent jest zależnością), uzyskuje początkowy egzemplarz za pośrednictwem konfigurowanego konstruktora lub metody fabrykującej, po czym podejmuje próby wstrzykiwania zależności, opcjonalnych argumentów konstruktora lub metody fabrykującej oraz opcjonalnych wartości właściwości. n Argumenty konstruktora lub właściwości komponentu, które odwołują się do innego komponentu, w pierwszej kolejności wymuszają na kontenerze tworzenie lub uzyskiwanie dostępu do tamtego komponentu. W efekcie wskazywany komponent jest w istocie zależnością komponentu, który się do niego odwołuje. Operacje tworzenia lub uzyskiwania dostępu do komponentów składają się na logicznie spójny łańcuch, który można reprezentować w formie grafu zależności. n Każdy argument konstruktora lub wartość właściwości musi zapewniać możliwość konwersji z oryginalnego typu lub formatu na typ faktycznie oczekiwany przez argument konstruktora lub właściwość komponentu (oczywiście jeśli oba typy są różne). Spring oferuje mechanizmy konwersji argumentów przekazywanych w formacie łańcuchowym do wszystkich wbudowanych typów skalarnych, a więc int, long, boolean itd., oraz wszystkich typów opakowań, w tym Integer, Long, Boolean itd. Spring wykorzystuje też implementacje interfejsu PropertyPritor komponentów JavaBeans do konwersji wartości typu String na niemal dowolne typy danych. Kontener automatycznie rejestruje i wykorzystuje wiele różnych implementacji
  • 35. 104 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie tego interfejsu. Przykładem takiej implementacji jest klasa ClassPritor, która konwertuje łańcuch reprezentujący nazwę klasy na egzemplarz klasy Class, który może być przekazany do właściwości oczekującej takiego egzemplarza; innym przykładem jest klasa ResourcePritor, która konwertuje łańcuch reprezentujący ścieżkę lokalizacji na obiekt klasy Resource Springa, który może być wykorzystywany do abstrakcyjnego uzyskiwania dostępu do zasobów. Wszystkie wbudowane edytory właściwości omówimy w następnym rozdziale. Istnieje też możliwość rejestrowania własnych implementacji interfejsu PropertyPritor obsługujących Twoje typy niestandardowe (patrz podpunkt „Tworzenie własnych edytorów właściwości” w dalszej części tego rozdziału). n Także wariant konfiguracji oparty na formacie XML, który jest wykorzystywany przez większość implementacji fabryk komponentów i kontekstów aplikacji, wykorzystuje własne elementy i atrybuty umożliwiające definiowanie złożonych kolekcji, czyli list, zbiorów, map i właściwości. Okazuje się, że wartości tych kolekcji mogą być dowolnie zagnieżdżane. n Zależności mogą też mieć charakter niejawny — w ekstremalnych przypadkach Spring może użyć refleksji do sprawdzenia argumentów pobieranych przez konstruktor komponentu (lub wartości właściwości tego komponentu) nawet wtedy, gdy nie zadeklarowano odpowiednich zależności. Kontener może na podstawie takiej procedury zbudować listę prawidłowych zależności danego komponentu. Następnie, korzystając z mechanizmu nazywanego wiązaniem automatycznym (ang. autowiring), kontener może te zależności wypełnić w oparciu o dopasowania według typów lub nazw. Na razie pominiemy temat automatycznego wiązania, ale z pewnością do niego wrócimy w dalszej części tego rozdziału. Definiowanie zależności komponentów w szczegółach W tym podpunkcie szczegółowo omówimy techniki definiowania wartości właściwości komponentów i argumentów konstruktorów w formacie XML. Każdy element bean może zawierać zero, jeden lub wiele elementów constructor-arg określających argumenty kon- struktora lub metody wyszukującej. Elementy bean mogą też zawierać zero, jeden lub wiele elementów property określających właściwości JavaBean wymagających ustawienia. Jeśli sytuacja tego wymaga, można łączyć oba podejścia (argumenty konstruktora i właściwości JavaBean) — takie rozwiązanie zastosowano w poniższym przykładzie, gdzie argument konstruktora jest w istocie odwołaniem do innego komponentu, natomiast właściwość typu int jest zwykłą wartością: <beans< <bean id="weatherService" class="ch02.sample6.WeatherServiceImpl"< <constructor-arg index="0"< <ref local="weatherDao"/< </constructor-arg< <property name="maxRetryAttempts"<<value<2</value<</property< </bean< <bean id="weatherDao" class="ch02.sample6.StaticDataWeatherDaoImpl"< </bean< </beans<
  • 36. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 105 Analiza wspominanego już dokumentu XML DTD pokazuje, że w ramach elementów pro- perty i constructor-arg można stosować wiele innych elementów. Każdy z tych elemen- tów definiuje jakiś typ wartości deklarowanej właściwości lub argumentu konstruktora: (bean | ref | idref | list | set | map | props | value | null) Element ref służy do takiego ustawiania wartości właściwości lub argumentu konstruktora, aby odwoływała się do innego komponentu w ramach tej samej fabryki komponentów lub w ramach jej fabryki nadrzędnej: <ref local="weatherDao"/< <ref bean="weatherDao"/< <ref parent="weatherDao"/< Atrybuty local, bean i parent wzajemnie się wykluczają i muszą zawierać identyfikator in- nego komponentu. W przypadku użycia atrybutu local analizator składni dokumentu XML może zweryfikować istnienie wskazanego komponentu już na etapie analizy składniowej. Ponieważ jednak całość bazuje na mechanizmie IDREF języka XML, komponent musi być zdefiniowany w tym samym pliku XML co odwołanie do tego komponentu, a jego defini- cja musi wykorzystywać atrybut ir określający identyfikator, do którego będziemy się od- woływali (zamiast atrybutu name). W przypadku użycia atrybutu bean wskazany komponent może się znajdować w tym samym lub innym fragmencie dokumentu XML wykorzysty- wanym albo do budowy definicji fabryki komponentów, albo w definicji fabryki nadrzęd- nej względem fabryki bieżącej. Za weryfikację istnienia konkretnych komponentów może co prawda odpowiadać sam Spring (nie analizator składniowy XML), ale tylko wówczas, gdy dana zależność rzeczywiście musi zostać zrealizowana (a więc nie w czasie ładowania fabryki komponentów). Znacznie rzadziej stosowany atrybut parent określa, że komponent docelowy musi pochodzić z fabryki nadrzędnej względem bieżącej fabryki komponentów. Takie rozwiązanie może być przydatne w nieczęstych sytuacjach, w których występuje konflikt nazw komponentów w bieżącej i nadrzędnej fabryce komponentów. Element value służy do określania wartości prostych właściwości lub argumentów kon- struktora. Jak już wspominano, niezbędnym krokiem jest konwersja wartości źródłowej (która ma postać łańcucha) na odpowiedni typ docelowej właściwości lub argumentu kon- struktora, czyli dowolny wbudowany typ skalarny, odpowiedni typ opakowania lub dowolny inny typ, dla którego w kontenerze zarejestrowano implementację interfejsu PropertyPritor zdolną do obsługi tego typu. Przeanalizujmy teraz konkretny przykład: <property name="classname"< <value<ch02.sample6.StaticDataWeatherDaoImpl</value< </property< Powyższy fragment kodu ustawia właściwość typu String nazwaną classname i przypisuje mu stałą wartość ch02.sample6.StaticDataWeatherDaoImpl; gdyby jednak właściwość classname była typu java.lang.Class, fabryka komponentów musiałaby użyć wbudowanej (i automatycznie rejestrowanej) implementacji interfejsu PropertyPritor (w tym przypadku kla- sy ClassPritor) do konwersji tej wartości łańcuchowej na egzemplarz obiektu klasy Class. Istnieje możliwość stosunkowo prostego rejestrowania własnych, niestandardowych im- plementacji interfejsu PropertyPritor, które będą obsługiwały konwersję łańcuchów na dowolne inne typy danych niezbędne do właściwej konfiguracji kontenera. Dobrym przykładem
  • 37. 106 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie sytuacji, w której takie rozwiązanie jest uzasadnione, jest przekazywanie łańcuchów repre- zentujących daty, które mają być następnie wykorzystywane do ustawiania właściwości typu Date. Ponieważ daty są szczególnie wrażliwe na uwarunkowania regionalne, użycie odpo- wiedniej implementacji interfejsu PropertyPritor, która będzie prawidłowo obsługiwała łańcuch źródłowy, jest najprostszym rozwiązaniem tego problemu. Sposób rejestrowania niestandardowych implementacji tego interfejsu zostanie przedstawiony w dalszej części tego rozdziału, przy okazji omawiania klasy CustomPritorConfigurer i postprocesora fa- bryki komponentów. Niewielu programistów zdaje sobie sprawę z tego, że odpowiednie mechanizmy interfejsu PropertyPritor automatycznie wykrywają i wykorzystują wszystkie implementacje tego interfejsu, które należą do tego samego pakietu co klasa przeznaczona do konwersji (jedynym warunkiem jest zgodność nazwy tej klasy z nazwą klasy implementują- cej wspomniany interfejs, która dodatkowo musi zawierać sufiks Pritor). Oznacza to, że w przypadku klasy MyType implementacja interfejsu PropertyPritor nazwana MyTypePritor i należąca do tego samego pakietu co klasa MyType zostanie automatycznie wykryta i użyta przez kod pomocniczy JavaBeans zdefiniowany w odpowiedniej bibliotece Javy (bez naj- mniejszego udziału Springa). Właściwości lub argumenty konstruktora, którym należy przypisać wartość null, wymagają specjalnego traktowania, ponieważ pusty element value jest interpretowany jak łańcuch pusty. Zamiast braku wartości należy więc użyć elementu null: <property name="optionalDescription"<<null/<</property< Element irref jest wygodnym sposobem wychwytywania błędów w odwołaniach do in- nych komponentów za pośrednictwem wartości łańcuchowych reprezentujących ich nazwy. Istnieje kilka komponentów pomocniczych samego Springa, które odwołują się do innych komponentów (i wykonują za ich pomocą pewne działania) właśnie w formie wartości swoich właściwości. Wartości tego rodzaju właściwości zwykle definiuje się w następujący sposób: <property name="beanName"<<value<weatherService</value<</property< Możliwie szybkie wykrywanie ewentualnych literówek w podobnych odwołaniach byłoby oczywiście korzystne — element irref w praktyce odpowiada właśnie za taką weryfikację. Użycie elementu property w postaci: <property name="beanName"<<idref local="weatherService"/<</property< umożliwia analizatorowi składniowemu XML udział we wczesnym procesie weryfikacji, ponieważ już na etapie analizy składniowej można bez trudu wykryć odwołania do kompo- nentów, które w rzeczywistości nie istnieją. Wartość wynikowa tej właściwości będzie do- kładnie taka sama jak w przypadku użycia standardowego znacznika value. Elementy list, set, map i props umożliwiają definiowanie i ustawianie złożonych właściwości lub argumentów konstruktorów (odpowiednio typów java.util.List, java.util.Set, java.util.Map i java.util.Properties). Przeanalizujmy teraz całkowicie nierzeczywisty przykład, w którym definiowany komponent JavaBean będzie zawierał po jednej właściwości każdego z wymienionych typów złożonych: <beans< <bean id="collectionsExample" class="ch02.sample .CollectionsBean"< <property name="theList"<
  • 38. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 107 <list< <value<red</value< <value<red</value< <value<blue</value< <ref local="curDate"/< <list< <value<one</value< <value<two</value< <value<three</value< </list< </list< </property< <property name="theSet"< <set< <value<red</value< <value<red</value< <value<blue</value< </set< </property< <property name="theMap"< <map< <entry key="left"< <value<right</value< </entry< <entry key="up"< <value<down</value< </entry< <entry key="date"< <ref local="curDate"/< </entry< </map< </property< <property name="theProperties"< <props< <prop key="left"<right</prop< <prop key="up"<down</prop< </props< </property< </bean< <bean id="curDate" class="java.util.GregorianCalendar"/< </beans< Kolekcje typu List, Map i Set mogą zawierać dowolne spośród wymienionych poniżej ele- mentów: (bean | ref | idref | list | set | map | props | value | null) Jak pokazuje przedstawiony przykład listy, typy kolekcji mogą być dowolnie zagnieżdżane. Warto jednak pamiętać, że właściwości lub argumenty konstruktorów otrzymujące na wejściu typy kolekcji muszą być deklarowane za pomocą typów java.util.List, java.util.Set lub java.util.Map. Nie możesz stosować innych typów kolekcji (np. ArrayList), nawet jeśli są obsługiwane w Springu. Tego rodzaju ograniczenia mogą stanowić poważny problem, jeśli musimy zapewnić wartość dla właściwości istniejącej klasy, która oczekuje określonego
  • 39. 108 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie typu — w takim przypadku nie można konkretnego typu kolekcji zastąpić jego uniwersal- nym odpowiednikiem. Jednym z rozwiązań jest użycie udostępnianych przez Springa po- mocniczych komponentów fabrykujących (ang. factory beans): ListFactoryBean, SetFac- toryBean lub MapFactoryBean. Za ich pomocą można swobodnie określać docelowy typ kolekcji, w tym np. java.util.LinkerList. Więcej informacji na ten temat znajdziesz w dokumentacji JavaDoc Springa. Same komponenty fabrykujące omówimy w dalszej części rozdziału. Ostatnim elementem, który może występować w roli wartości właściwości lub wartości ar- gumentu konstruktora (lub wewnątrz któregoś z opisanych przed chwilą elementów kolekcji), jest element bean. Oznacza to, że definicja komponentów może być de facto zagnieżdżana w definicji innego komponentu (jako właściwość komponentu zewnętrznego). Przeanali- zujmy teraz przebudowany przykład wstrzykiwania przez metodę ustawiającą: <beans< <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <bean class="ch02.sample2.StaticDataWeatherDaoImpl"/< ... </bean< </property< </bean< </beans< Zagnieżdżone definicje komponentów są szczególnie przydatne w sytuacji, gdy komponent wewnętrzny nie znajduje żadnych zastosowań poza zakresem komponentu zewnętrznego. W porównaniu z poprzednią wersją tego przykładu obiekt DAO (który ustawiono jako za- leżność usługi pogodowej) został włączony do komponentu usługi pogodowej i przyjął po- stać jego komponentu wewnętrznego. Żaden inny komponent ani użytkownik zewnętrzny nie będzie przecież potrzebował tego obiektu DAO, zatem utrzymywanie go poza zakresem komponentu usługi (jako osobnej, zewnętrznej definicji) mijałoby się z celem. Użycie kompo- nentu wewnętrznego jest w tym przypadku rozwiązaniem bardziej zwięzłym i klarownym. Komponent wewnętrzny nie musi mieć zdefiniowanego identyfikatora, choć nie jest to za- bronione. Uwaga: Komponenty wewnętrzne zawsze są komponentami prototypowymi, a ewentualny atrybut singleton jest ignorowany. W tym przypadku nie ma to najmniejszego znaczenia — ponieważ istnieje tylko jeden egzemplarz komponentu zewnętrznego (który jest singletonem), utworzenie więcej niż jednego egzemplarza komponentu wewnętrznego jest wykluczone (niezależnie od tego, czy zadeklarujemy ten komponent jako singletonowy czy jako prototypowy). Gdyby jednak prototypowy komponent zewnętrzny potrzebował zależności singletonowej, nie powinniśmy tej zależności definiować w formie komponentu wewnętrznego, tylko jako odwołanie do singletonowego komponentu zewnętrznego. Samodzielne (ręczne) deklarowanie zależności Kiedy właściwość komponentu lub argument konstruktora odwołuje się do innego komponentu, mamy do czynienia z deklaracją zależności od tego zewnętrznego komponentu. W niektó- rych sytuacjach konieczne jest wymuszenie inicjalizacji jednego komponentu przed innym, nawet jeśli ten drugi nie został zadeklarowany jako właściwość pierwszego. Wymuszanie określonej kolejności inicjalizacji jest uzasadnione także w innych przypadkach, np. kiedy
  • 40. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 109 dana klasa wykonuje jakieś statyczne operacje inicjalizujące w czasie wczytywania. Przy- kładowo, sterowniki baz danych zwykle są rejestrowane w egzemplarzu interfejsu Driver- Manager biblioteki JDBC. Do ręcznego określania zależności pomiędzy komponentami słu- ży atrybut repenrs-on, który wymusza utworzenie egzemplarza innego komponentu jeszcze przed uzyskaniem dostępu do komponentu zależnego. Poniższy przykład pokazuje, jak można wymuszać wczytywanie sterownika bazy danych: <bean id="load-jdbc-driver" class="oracle.jdbc.driver.OracleDriver"/< <bean id="weatherService" depends-on="load-jdbc-driver" class="..."< ... </bean< Warto pamiętać, że większość pul połączeń z bazą danych oraz klas pomocniczych Springa (np. DriverManagerDataSource) korzysta z tego mechanizmu wymuszania wczytywania, zatem powyższy fragment kodu jest tylko przykładem popularnego rozwiązania. Automatyczne wiązanie zależności Do tej pory mieliśmy do czynienia z deklaracjami zależności komponentów wyrażanymi wprost (za pośrednictwem wartości właściwości i argumentów konstruktora). W pewnych okolicznościach Spring może użyć mechanizmu introspekcji klas komponentów w ramach bieżącej fabryki i przeprowadzić automatyczne wiązanie zależności. W takim przypadku właściwość komponentu lub argument kontenera nie muszą być deklarowane (np. w pliku XML), ponieważ Spring użyje refleksji do odnalezienia typu i nazwy odpowiedniej wła- ściwości, po czym dopasuje ją do innego komponentu w danej fabryce (według jego typu lub nazwy). Takie rozwiązanie może co prawda oszczędzić programiście mnóstwo pracy związanej z samodzielnym przygotowywaniem odpowiedniego kodu, jednak niewątpliwym kosztem tego podejścia jest mniejsza przejrzystość. Mechanizm automatycznego wiązania zależności można kontrolować zarówno na poziomie całego kontenera, jak i na poziomie definicji poszczególnych komponentów. Ponieważ nieostrożne korzystanie z tej techniki może prowadzić do nieprzewidywalnych rezultatów, mechanizm automatycznego wiązania jest domyślnie wyłączony. Automatyczne wiązanie na poziomie komponentów jest kon- trolowane za pośrednictwem atrybutu autowire, który może zawierać pięć wartości: n no — mechanizm automatycznego wiązania zależności w ogóle nie będzie stosowany dla danego komponentu. Właściwości tego komponentu oraz argumenty konstruktora muszą być deklarowane wprost, a wszelkie odwołania do innych komponentów wymagają używania elementu ref. Okazuje się, że jest to domyślny sposób obsługi poszczególnych komponentów (przynajmniej jeśli domyślne ustawienia na poziomie fabryki komponentów nie zostały zmienione). Opisany tryb jest zalecany w większości sytuacji, szczególnie w przypadku większych wdrożeń, gdzie zależności deklarowane wprost stanowią swoistą dokumentację struktury oprogramowania i są dużo bardziej przejrzyste. n byName — wymusza automatyczne wiązanie zależności według nazw właściwości. Nazwy właściwości są wykorzystywane podczas odnajdywania dopasowań do komponentów w bieżącej fabryce komponentów. Przykładowo, jeśli dana właściwość ma przypisaną nazwę weatherDao, wówczas kontener spróbuje ustawić tę właściwość jako odwołanie do innego komponentu nazwanego właśnie weatherDao.
  • 41. 110 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Jeśli taki pasujący komponent nie zostanie znaleziony, właściwość pozostanie nieustawiona. Taka reakcja na brak dopasowania pomiędzy nazwą właściwości a nazwą komponentu jest opcjonalna — jeśli chcesz traktować brak dopasowania jak błąd, możesz do definicji komponentu dodać atrybut repenrency-check="objects" (patrz dalsza część tego rozdziału). n byType — automatyczne wiązanie zależności przez dopasowywanie typów. Podobne rozwiązanie zastosowano w kontenerze PicoContainer, innym popularnym produkcie obsługującym wstrzykiwanie zależności. W przypadku każdej właściwości — jeśli w bieżącej fabryce komponentów istnieje dokładnie jeden komponent tego samego typu co ta właściwość — wartość jest ustawiana właśnie jako ten komponent. Jeśli w fabryce istnieje więcej niż jeden komponent pasującego typu, efektem nieudanej próby dopasowania jest błąd krytyczny, który zwykle prowadzi do wygenerowania odpowiedniego wyjątku. Tak jak w przypadku automatycznego wiązania zależności według nazwy (wartość byName) w przypadku braku pasujących komponentów właściwość pozostaje nieustawiona. Jeśli brak dopasowania powinien być traktowany jak błąd, wystarczy do definicji komponentu dodać atrybut repenrency-check="objects" (patrz dalsza część tego rozdziału). n constructor — automatyczne wiązanie zależności według typów argumentów konstruktora. Ten tryb bardzo przypomina wiązanie zależności z właściwościami, tyle że polega na szukaniu w fabryce dokładnie jednego pasującego (według typu) komponentu dla każdego z argumentów konstruktora. W przypadku wielu konstruktorów Spring w sposób zachłanny będzie próbował spełnić wymagania konstruktora z największą liczbą pasujących argumentów. n autoretect — wybiera tryby byType i constructor w zależności od tego, który z nich lepiej pasuje do danej sytuacji. W przypadku wykrycia domyślnego, bezargumentowego konstruktora stosowany jest tryb byType; w przeciwnym razie zostanie zastosowany tryb constructor. Istnieje możliwość ustawienia innego domyślnego trybu automatycznego wiązania zależno- ści (innego niż no) dla wszystkich komponentów w danej fabryce — wystarczy użyć atry- butu refault-autowire w elemencie beans na najwyższym poziomie dokumentu XML. Warto też pamiętać o możliwości łączenia technik automatycznego wiązania z wiązaniem wprost, wówczas elementy definiujące zależności (property lub contructor-arg) mają wyż- szy priorytet od zależności wykrywanych automatycznie. Sprawdźmy teraz, jak można użyć mechanizmu automatycznego wiązania zależności dla naszej usługi pogodowej i jej obiektu DAO. Jak widać, możemy wyeliminować z definicji komponentu właściwość weatherDao i włączyć automatyczne wiązanie według nazw, a mimo to Spring nadal będzie w stanie odnaleźć wartość właściwości wyłącznie w oparciu o dopa- sowanie jego nazwy. W tym przypadku moglibyśmy użyć także trybu automatycznego wiązania zależności według typu, ponieważ do typu naszej właściwość (WeatherDao) pasuje tylko jeden komponent w kontenerze: <beans< <bean id="weatherService" autowire="byName" class="ch02.sample2.WeatherServiceImpl"< <!-- brak deklaracji właściwości weatherDao --< </bean<
  • 42. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 111 <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"< </bean< </beans< Korzystanie z technik automatycznego wiązania zależności jest o tyle kuszące, że zwalnia programistę z obowiązku pisania znacznej ilości kodu konfiguracji fabryki komponentów, jednak warto zachować należytą ostrożność i stosować ten mechanizm z rozwagą. Usuwając zależności deklarowane wprost, rezygnujemy z jednej z form dokumentowa- nia tych zależności. Co więcej, ze stosowaniem trybów byType lub nawet byName wiąże się ryzyko nieprzewidywalnych zachowań w sytuacji, gdy uda się odnaleźć więcej niż jedno dopasowanie (pasujący komponent) lub w razie braku jakiegokolwiek dopaso- wania. Szczególnie w przypadku większych, bardziej skomplikowanych wdrożeń zaleca się unikanie automatycznego wiązania zależności szerokim łukiem lub przynajmniej bardzo rozważne korzystanie z tego mechanizmu, ponieważ w przeciwnym razie może się okazać, że ograniczając ilość kodu XML, dodatkowo skomplikowaliśmy całą aplikację. Większość współczesnych środowisk IDE oferuje (wbudowane lub dostępne w formie modułów rozszerzeń) edytory XML z obsługą DTD, które mogą oszczędzić mnóstwo czasu i wysiłku programisty podczas tworzenia konfiguracji komponentów, zatem roz- miar deklaracji zależności wyrażonych wprost nie stanowi wielkiego problemu. Tym, co może się doskonale sprawdzać w pewnych sytuacjach, jest stosowanie automatycznego wiązania zależności dla prostych, niskopoziomowych „wnętrzności” (np. DataSource) i jednocześnie deklarowanie zależności wprost w przypadku bardziej skomplikowanych aspektów. W ten sposób można wyeliminować nadmiar pracy bez konieczności po- święcania transparentności. Dopasowywanie argumentów konstruktora Ogólna zasada mówi, że dla każdego deklarowanego elementu constructor-arg należy do- datkowo stosować opcjonalne atrybuty inrex i (lub) type. Chociaż oba wymienione atry- buty są opcjonalne, w przypadku pominięcia choćby jednego z nich zadeklarowana lista ar- gumentów konstruktora będzie dopasowywana do rzeczywistych argumentów według ich typów. Jeśli zadeklarowane argumenty są odwołaniami do innych typów (np. skomplikowanych komponentów lub takich typów złożonych jak java.lang.Map), kontener będzie mógł zna- leźć odpowiednie dopasowanie stosunkowo łatwo, szczególnie w sytuacji, gdy będzie ist- niał tylko jeden konstruktor. Jeśli jednak zadeklarujemy wiele argumentów tego samego typu lub użyjemy znacznika value, który będzie de facto wartością bez typu (zadeklarowaną w formie łańcucha), próby automatycznego dopasowywania mogą się zakończyć błędami lub innymi nieprzewidywalnymi zachowaniami. Przeanalizujmy przykład komponentu, który zawiera pojedynczy konstruktor pobierający numeryczną wartość kodu błędu oraz łańcuchową wartość komunikatu o błędzie. Jeśli spróbu- jemy użyć znacznika <value> do dostarczania wartości dla tych argumentów, będziemy mu- sieli przekazać kontenerowi jakąś dodatkową wskazówkę odnośnie realizacji tego zadania. Możemy posłużyć się albo atrybutem inrex do określenia właściwego indeksu argumentu (liczonego od zera), co ułatwi dopasowanie przekazywanej wartości do odpowiedniego ar- gumentu konstruktora: <beans< <bean id="errorBean" class="ch02.sampleX.ErrorBean"< <constructor-arg index="0"<<value<1000</value<</constructor-arg<
  • 43. 112 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie <constructor-arg index="1"<<value<Nieoczekiwany błąd</value<</constructor-arg< </bean< </beans< albo dostarczyć kontenerowi taką ilość informacji, która pozwoli mu właściwie przeprowa- dzić proces dopasowania w oparciu o typy danych — wówczas powinniśmy użyć atrybutu type określającego typ danej wartości: <beans< <bean id="errorBean" class="ch02.sampleX.ErrorBean"< <constructor-arg type="int"<<value<1000</value<</constructor-arg< <constructor-arg type="java.lang.String"< <value<Nieoczekiwany błąd</value< </constructor-arg< </bean< </beans< Sprawdzanie poprawności zależności komponentów Często się zdarza, że część właściwości JavaBean danego obiektu ma charakter opcjonalny. Możesz, ale nie musisz, ustawiać ich wartości w zależności od tego, czy odpowiednia war- tość jest potrzebna w konkretnym przypadku testowym; warto jednak pamiętać, że kontener nie dysponuje mechanizmami, które mogłyby Ci pomóc w wychwytywaniu błędów nie- ustawienia właściwości, które tego wymagają. Jeśli jednak masz do czynienia z kompo- nentem, którego wszystkie właściwości (lub przynajmniej wszystkie właściwości określo- nego typu) muszą być ustawione, dużym ułatwieniem może być oferowany przez kontener mechanizm weryfikacji poprawności zależności. Jeśli zdecydujemy się na użycie tego me- chanizmu, kontener będzie traktował ewentualny brak deklaracji wprost lub automatycznego wiązania zależności jako błąd. Kontener domyślnie nie podejmuje prób takiej weryfikacji (sprawdzania, czy wszystkie zależności są odpowiednio ustawione), można jednak zmienić to zachowanie w definicji komponentu za pomocą atrybutu repenrency-check, który może mieć następujące wartości: n none — brak weryfikacji zależności. Jeśli dla którejś z właściwości nie określono wartości, w przypadku braku weryfikacji taka sytuacja nie będzie traktowana jak błąd. Jest to domyślny sposób obsługi poszczególnych komponentów (przynajmniej jeśli ustawienia domyślne nie zostaną zmienione na poziomie fabryki komponentów). n simple — sprawdza, czy ustawiono typy proste i kolekcje — brak wartości w tych właściwościach będzie traktowany jak błąd. Pozostałe właściwości mogą, ale nie muszą być ustawione. n objects — sprawdza tylko właściwości innych typów niż typy proste i kolekcje — brak wartości w tych właściwościach będzie traktowany jak błąd. Pozostałe właściwości mogą, ale nie muszą być ustawione. n all — sprawdza, czy ustawiono wartości wszystkich właściwości, w tym we właściwościach typów prostych, kolekcjach i właściwościach typów złożonych. Istnieje możliwość zmiany domyślnego trybu sprawdzania zależności (ustawienie innego trybu niż none) dla wszystkich komponentów w ramach danej fabryki — wystarczy użyć atrybutu refault-repenrency-check w elemencie beans na najwyższym poziomie hierarchii.
  • 44. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 113 Pamiętaj też, że także interfejs wywołań zwrotnych InitializingBean (patrz następny punkt) może być wykorzystywany do ręcznego sprawdzania poprawności zależności. Zarządzanie cyklem życia komponentu Komponent w fabryce może się charakteryzować zarówno bardzo prostym, jak i względnie skomplikowanym cyklem życia — wszystko zależy od tego, jaki jest zakres odpowiedzial- ności danego komponentu. Ponieważ mówimy o obiektach POJO, cykl życia komponentu nie musi obejmować działań wykraczających poza tworzenie i używanie danego obiektu. Istnieje jednak wiele sposobów zarządzania i obsługi bardziej złożonymi cyklami życia, z któ- rych większość koncentruje się wokół wywołań zwrotnych, gdzie sam komponent i ze- wnętrzne obiekty obserwatorów (nazywane postprocesorami komponentu) mogą obsłu- giwać wiele etapów procesów inicjalizacji i destrukcji. Przeanalizujmy teraz możliwe akcje kontenera (patrz poniższa tabela), które mogą mieć miejsce w cyklu życia komponentu za- rządzanego przez kontener. Akcja Opis Inicjalizacja Egzemplarz nowego komponentu jest tworzony za pośrednictwem rozpoczynająca się konstruktora lub metody fabrykującej (oba rozwiązania są w momencie utworzenia równoważne). Proces tworzenia egzemplarza komponentu jest egzemplarza komponentu inicjowany przez wywołanie metody getBean() fabryki komponentów lub przez żądanie ze strony innego komponentu, którego egzemplarz już istnieje i który zawiera zależność od bieżącego komponentu. Wstrzyknięcie zależności Zależności są wstrzykiwane do nowo utworzonego egzemplarza komponentu (w ramach omówionej już procedury). Wywołanie metody Jeśli komponent implementuje opcjonalny interfejs BeanNameAware, setBeanName() wówczas metoda setBeanName() należąca właśnie do tego interfejsu jest wywoływana w celu nadania komponentowi jego głównego identyfikatora (zadeklarowanemu w definicji komponentu). Wywołanie metody Jeśli komponent implementuje opcjonalny interfejs BeanFactoryAware, setBeanFactory() referencję do fabryki, w ramach której ten komponent został wdrożony, można wprowadzić do komponentu, wywołując metodę setBeanFactory() tego interfejsu. Warto pamiętać, że ponieważ także konteksty aplikacji są fabrykami komponentów, wspomniana metoda będzie wywoływana również w przypadku komponentów wdrożonych w ramach kontekstu aplikacji (jednak wówczas komponent otrzyma odwołanie do fabryki wykorzystywanej wewnętrznie przez dany kontekst). Wywołanie metody Jeśli dany komponent implementuje opcjonalny interfejs setResourceLoader() ResourceLoaderAware i jest wdrażany w ramach kontekstu aplikacji, przedmiotem wywołania jest metoda setResourceLoader() tego interfejsu, z parametrem będącym kontekstem aplikacjim który implementuje interfejs ResourceLoader (to rozwiązanie omówimy już w następnym rozdziale).
  • 45. 114 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Akcja Opis Wywołanie metody Jeśli komponent implementuje opcjonalny interfejs setApplicationEvent- ApplicationEventPublisherAware i jest wdrażany w ramach Publisher() kontekstu aplikacji, przedmiotem wywołania jest metoda setApplicationEventPublisher() tego interfejsu, a parametrem jest kontekst aplikacji, który implementuje interfejs ApplicationEventPublisher (także to rozwiązanie omówimy w następnym rozdziale). Wywołanie metody Jeśli komponent implementuje opcjonalny interfejs setMessageSource() MessageSourceAware i jest wdrażany w ramach kontekstu aplikacji, przedmiotem wywołania jest należąca do tego interfejsu metoda setResourceLoader(), a przekazywany jest kontekst aplikacji, który implementuje interfejs MessageSource (także to rozwiązanie omówimy w następnym rozdziale). Wywołanie metody Jeśli dany komponent implementuje opcjonalny interfejs setApplicationContext() ApplicationContextAware i jest wdrażany w ramach kontekstu aplikacji, zapewnienie komponentowi referencji do tego kontekstu wymaga wywołania metody setApplicationContext(), która jest częścią interfejsu ApplicationContextAware. Przekazanie Postprocesory komponentu (które omówimy w dalszej części postprocesorom tego rozdziału) są specjalnymi rozwiązaniami pomocniczymi komponentu wywołania rejestrowanymi przez aplikację wraz z fabrykami komponentów. zwrotnego „przed Postprocesory otrzymują wraz z wywołaniami zwrotnymi inicjalizacją” (poprzedzającymi właściwą inicjalizację) komponent, który mogą dowolnie przetwarzać. Wywołanie metody Jeśli komponent implementuje interfejs InitializingBean, afterPropertiesSet() następuje wywołanie definiowanej przez ten interfejs metody afterPropertiesSet(), która umożliwia komponentowi samodzielną inicjalizację. Wywołanie Jeśli definicja danego komponentu zawiera deklarację metody zadeklarowanej metody inicjalizującej (w formie odpowiedniej wartości atrybutu inicjalizującej init-method), właśnie ta metoda zostanie wywołana, aby umożliwić komponentowi samodzielną inicjalizację. Przekazanie postprocesorom Każdy postprocesor komponentu otrzymuje wywołanie zwrotne komponentu wywołania „po inicjalizacji” z dołączonym (w formie argumentu) zwrotnego „po inicjalizacji” egzemplarzem komponentu, który — w razie potrzeby z argumentem zawierającym — może dowolnie przetwarzać. egzemplarz komponentu Użycie komponentu Na tym etapie egzemplarz komponentu jest przedmiotem właściwych operacji. Oznacza to, że komponent jest zwracany do kodu obiektu wywołującego metodę getBean() i wykorzystywany do ustawienia właściwości pozostałych komponentów (które wywołały bieżący komponent jako swoje zależności) itd. Ważna uwaga: Tylko cykl życia komponentów singletonowych jest od tego momentu śledzony przez kontener, natomiast właścicielami komponentów prototypowych są obiekty klienckie, które z nich korzystają. Kontener będzie więc uczestniczył w przyszłych zdarzeniach związanych tylko z cyklem życia komponentów singletonowych. Od tego momentu wszystkie komponenty prototypowe muszą być w całości zarządzane przez swoich klientów (dotyczy to także wywołania metody niszczącej).
  • 46. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 115 Akcja Opis Rozpoczęcie procedury W ramach procesu kończenia pracy fabryki komponentów lub destrukcji komponentu kontekstu aplikacji (który obejmuje trzy opisane poniżej kroki) należy zniszczyć wszystkie składowane w pamięci podręcznej komponenty singletonowe. Należy pamiętać, że komponenty są niszczone w kolejności odpowiadającej występującymi między nimi zależnościami (najczęściej w kolejności odwrotnej do inicjalizacji). Przekazanie Każdy postprocesor komponentu implementujący interfejs postprocesorom DestructionAwareBeanPostProcessor otrzymuje wywołanie zwrotne, komponentu wywołania które umożliwia mu przygotowanie komponentu singletonowego zwrotnego „zniszcz” do zniszczenia. Wywołanie metody Jeśli komponent singletonowy implementuje interfejs destroy() DisposableBean, wówczas jest wywoływana metoda destroy() tego interfejsu, która umożliwia komponentowi wykonanie wszystkich niezbędnych operacji zwalniania zasobów. Wywołanie Jeśli definicja komponentu singletonowego zawiera deklarację zadeklarowanej metody metody niszczącej (w formie wartości atrybutu destroy-method), niszczącej odpowiednia metoda jest wywoływana na samym końcu, a jej działania najczęściej sprowadzają się do zwalniania wszystkich zasobów, które tego wymagają. Wywołania zwrotne inicjalizacji i destrukcji Wspomniane przed chwilą metody inicjalizujące i niszczące mogą być wykorzystywane do wykonywania wszystkich niezbędnych operacji w zakresie rezerwowania i zwalniania za- sobów dla danego komponentu. W przypadku istniejących klas, które zawierają już unika- towo nazwane metody inicjalizujące i niszczące, jedynym skutecznym rozwiązaniem jest użycie atrybutów init-methor i restroy-methor wymuszających na kontenerze wywoływa- nie tych metod we właściwych momentach — patrz poniższy przykład, gdzie konieczne jest wywołanie metody close() zastosowanej w implementacji DataSource (korzystającej z Jakarta Commons DBCP): <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy- method="close"< <property name="driverClassName"< <value<oracle.jdbc.driver.OracleDriver</value< </property< <property name="url"< <value<jdbc:oracle:thin:@db-server:1 21:devdb</value< </property< <property name="username"<<value<john</value<</property< <property name="password"<<value<password</value<</property< </bean< Ogólnie nawet podczas budowania nowego oprogramowania zaleca się stosowanie atrybutów init-methor i restroy-methor, które zasygnalizują kontenerowi konieczność wywołania odpowiednich metod inicjalizujących i niszczących — takie rozwiązanie jest pod wieloma względami lepsze od implementowania przez sam komponent interfejsów InitializingBean
  • 47. 116 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie i DisposableBean Springa z ich metodami afterPropertiesSet() i restroy(). To drugie po- dejście jest z jednej strony wygodne, ponieważ wspomniane interfejsy są automatycznie wykrywane przez Springa, co oznacza, że także wymienione metody są wywoływane przez ten framework bez udziału programisty, z drugiej strony niepotrzebnie wiąże Twój kod z kon- tenerem Springa. Jeśli kod aplikacji jest związany ze Springiem także z innych powodów i w innych punktach, ta dodatkowa zależność nie stanowi większego problemu, a stosowanie interfejsów InitializingBean i DisposableBean jednocześnie jest niemałym ułatwieniem dla programisty. Okazuje się, że także kod samego Springa, który jest wdrażany w formie komponentów, często wykorzystuje oba interfejsy. Jak już wspomniano, po zakończeniu fazy inicjalizacji niesingletonowe, prototypowe kom- ponenty nie są składowane ani zarządzane w kontenerze. Oznacza to, że w przypadku tych komponentów Spring nie ma żadnych możliwości ani w zakresie wywoływania metod niszczących, ani w kwestii jakichkolwiek innych działań związanych z cyklem życia kom- ponentów (właściwych tylko dla zarządzanych przez kontener komponentów singletono- wych). Każda taka metoda musi być wywoływana z poziomu kodu użytkownika. Co więcej, postprocesory nie mogą przetwarzać komponentów znajdujących się w fazie niszczenia. Wywołania zwrotne interfejsów BeanFactoryAware i ApplicationContextAware Komponent, który z jakiegoś względu musi mieć dostęp do danych i mechanizmów swojej fabryki komponentów lub kontekstu aplikacji, może implementować odpowiednio interfejs BeanFactoryAware i ApplicationContextAware. Z przedstawionej przed chwilą tabeli pre- zentującej kolejność akcji podejmowanych przez kontener i związanych z cyklem życia komponentu wynika, że referencje do kontenerów są przekazywane do komponentów za pośrednictwem metod setBeanFactory() i setApplicationContext() (definiowanych przez wspomniane interfejsy). Ogólnie, większość kodu aplikacji w ogóle nie powinna do prawi- dłowego funkcjonowania potrzebować informacji o swoich kontenerach, jednak w kodzie ściśle związanym ze Springiem takie rozwiązanie może się okazać bardzo przydatne (tego rodzaju wywołania zwrotne są stosowane między innymi w klasach samego Springa). Jed- nym z przypadków, w których kod aplikacji może wymagać referencji do fabryki kompo- nentu, jest komponent singletonowy współpracujący z komponentami prototypowymi. Po- nieważ zależności komponentu singletonowego są do niego wstrzykiwane tylko raz, tak stosowany mechanizm wstrzykiwania wyklucza możliwość uzyskiwania nowych egzem- plarzy komponentów prototypowych w czasie wykonywania właściwych zadań (a więc po fazie inicjalizacji). Oznacza to, że jednym ze skutecznych rozwiązań tego problemu jest bezpośredni dostęp do fabryki tego komponentu. Wydaje się jednak, że w większości przy- padków jeszcze lepszym wyjściem jest użycie wspominanej już techniki wstrzykiwania metody wyszukującej, która dodatkowo zapewnia izolację klasy od mechanizmów Springa i nazwy komponentu prototypowego. Abstrakcja dostępu do usług i zasobów Istnieje co prawda dużo więcej zaawansowanych funkcji oferowanych przez fabryki kom- ponentów i konteksty aplikacji, o których do tej pory nawet nie wspominaliśmy, jednak udało nam się przeanalizować niemal wszystkie niskopoziomowe elementy składowe nie- zbędne do skutecznego programowania i wdrażania rozwiązań IoC. Przekonaliśmy się, jak
  • 48. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 117 obiekty aplikacji mogą realizować swoje zadania wyłącznie we współpracy z pozostałymi obiektami udostępnianymi przez kontener (za pośrednictwem odpowiednich interfejsów i abs- trakcyjnych nadklas) bez konieczności dostosowywania swoich zachowań do implementacji lub kodu źródłowego tych obiektów. Wiedza przekazana do tej pory powinna Ci w zupeł- ności wystarczyć do realizacji typowych scenariuszy korzystania z technik wstrzykiwania zależności. Tym, co może jeszcze budzić pewne wątpliwości, jest rzeczywisty sposób uzyskiwania po- zostałych obiektów współpracujących (o których wiesz, że mogą być konfigurowane i udo- stępniane na rozmaite sposoby). Aby zrozumieć, jak Spring chroni nas przed potencjalnymi zawiłościami i jak radzi sobie z transparentnym zarządzaniem tego rodzaju usługami, prze- analizujmy kilka ciekawych przykładów. Wyobraź sobie nieco zmieniony obiekt DAO naszej usługi pogodowej, który zamiast wy- korzystywać dane statyczne używa JDBC do uzyskiwania historycznych informacji pogo- dowych z bazy danych. Wstępna implementacja takiego rozwiązania może wykorzystywać oryginalny interfejs DriverManager JDBC 1.0 i za jego pośrednictwem uzyskiwać niezbędne połączenie — patrz metoda finr(): public WeatherData find(Date date) { // Zwróć uwagę, że w konstruktorze lub metodzie inicjalizującej użyliśmy // wywołania Class.forName(driverClassName) do zarejestrowania // sterownika JDBC. Wraz z nazwą użytkownika i hasła wspomniany // sterownik został skonfigurowany jako właściwości tego komponentu. try { Connection con = DriverManager.getConnection(url, username, password); // od tego miejsca możemy używać tego połączenia ... Kiedy wdrożymy taki obiekt DAO w postaci komponentu wewnątrz kontenera Springa, bę- dziemy mogli skorzystać z ułatwień (przede wszystkim mechanizmów wstrzykiwania przez metody ustawiające i wstrzykiwania przez konstruktor) w zakresie dostarczania wszystkich wartości wymaganych przez sterownik JDBC do nawiązania połączenia, czyli adresu URL, nazwy użytkownika i hasła. Nie mamy jednak zamiaru tworzyć puli połączeń (niezależnie od tego, czy będziemy korzystać ze środowiska J2EE czy z mniej zaawansowanego konte- nera); nasze połączenie w założeniu nie ma podlegać żadnym procedurom zarządzania transakcjami (znanym z kontenera J2EE i stosowanym tylko w przypadku połączeń zarzą- dzanych przez kontener). Dość oczywistym rozwiązaniem w kwestii uzyskiwania niezbędnych połączeń jest użycie implementacji interfejsu DataSource wprowadzonych w JDBC 2.0. Kiedy nasz obiekt DAO będzie już zawierał egzemplarz takiej implementacji, wystarczy że zażąda odpowiedniego połączenia (faktyczna realizacja tego żądania następuje gdzie indziej). Teoretyczna dostęp- ność egzemplarza DataSource nie stanowi problemu, ponieważ wiemy, że istnieją takie nie- zależne, autonomiczne implementacje puli połączeń jak Apache Jakarta Commons DBCP, które można z powodzeniem stosować w środowiskach J2SE oraz J2EE i które są udostęp- niane za pośrednictwem interfejsu DataSource; wiemy także, że podobne mechanizmy za- rządzania pulą połączeń na poziomie kontenera (będące częścią szerszych rozwiązań w za- kresie zarządzania transakcjami) są dostępne w większości środowisk kontenerowych J2EE, gdzie także mają postać implementacji interfejsu DataSource.
  • 49. 118 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Warto jednak pamiętać, że angażowanie interfejsu DataSource w proces uzyskiwania połą- czeń z bazą danych wprowadza dodatkową złożoność, ponieważ tworzenie i uzyskiwanie samego egzemplarza implementacji tego interfejsu może się odbywać na wiele różnych sposobów. Przykładowo, implementacja DataSource dla Commons DBCP ma postać pro- stego komponentu JavaBean wypełnianego kilkoma właściwościami konfiguracyjnymi, natomiast w większości środowisk kontenerów J2EE egzemplarz interfejsu DataSource uzyskuje się za pośrednictwem interfejsu JNDI i wykorzystuje w ramach sekwencji kodu podobnej do tej, którą przedstawiono poniżej: try { InitialContext context = new InitialContext(); DataSource ds = (DataSource)context.lookup("java:comp/env/jdbc/datasource"); // od tego miejsca możemy używać egzemplarza DataSource ... } catch (NamingException e) { // obsługuje wyjątek nazewnictwa w przypadku braku żądanego zasobu } Pozostałe implementacje DataSource mogą wymagać stosowania zupełnie innej strategii tworzenia i dostępu. Być może nasz obiekt DAO mógłby sam wybierać właściwy sposób tworzenia lub uzyski- wania poszczególnych typów implementacji interfejsu DataSource — być może wybór od- powiedniej implementacji powinien być jednym z elementów konfiguracji. Ponieważ wie- my już co nieco o Springu i technice odwracania kontroli (IoC), doskonale zdajemy sobie sprawę, że nie byłoby to najlepsze rozwiązanie, ponieważ niepotrzebnie wiązałoby nasz obiekt DAO z implementacją DataSource i — tym samym — utrudniłoby konfigurację i bardzo skomplikowało proces testowania. Dość oczywistym rozwiązaniem jest uczynienie z im- plementacji interfejsu DataSource właściwości naszego DAO, którą będzie można wstrzy- kiwać do tego obiektu z poziomu kontenera Springa. Takie podejście doskonale zdaje eg- zamin w przypadku implementacji DataSource w wersji Commons DBCP, gdzie tworzony egzemplarz tego interfejsu jest w prosty sposób wstrzykiwany do obiektu DAO: public class JdbcWeatherDaoImpl implements WeatherDao { DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public WeatherData find(Date date) { try { Connection con = dataSource.getConnection(); // od tego miejsca możemy używać tego połączenia ... } ... } <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy- method="close"< <property name="driverClassName"<
  • 50. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 119 <value<oracle.jdbc.driver.OracleDriver</value< </property< <property name="url"< <value<jdbc:oracle:thin:@db-server:1 21:devdb</value< </property< <property name="username"<<value<john</value<</property< <property name="password"<<value<password</value<</property< </bean< <bean id="weatherDao" class="ch02.sampleX.JdbcWeatherDaoImpl"< <property name="dataSource"< <ref bean="dataSource"/< </property< </bean< Warto się jeszcze zastanowić nad sposobem wymiany wykorzystywanej implementacji in- terfejsu DataSource (w tym przypadku DBCP) na inną (np. uzyskiwaną ze środowiska JNDI). Wygląda na to, że rozwiązanie tego problemu nie będzie łatwe, ponieważ poza ustawie- niem w naszym obiekcie DAO właściwości typu DataSource musimy otrzymać odpowied- nią wartość JNDI — na razie wiemy tylko, jak przygotowywać konfigurację kontenera, aby definiowała właściwości JavaBean zawierające konkretne wartości lub odwołania do innych komponentów. Okazuje się, że realizacja tego zadania wcale nie wymaga wielkich umiejętno- ści; wystarczy użyć klasy pomocniczej nazwanej JnriObjectFactoryBean: <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"< <property name="jndiName"< <value<java:comp/env/jdbc/datasource</value< </property< </bean< <bean id="weatherDao" class="ch02.sampleX.JdbcWeatherDaoImpl"< <property name="dataSource"< <ref bean="dataSource"/< </property< </bean< Analiza komponentów fabrykujących JnriObjectFactoryBean jest przykładem komponentu fabrykującego. Komponenty fabry- kujące Springa bazują na bardzo prostej koncepcji — najprościej mówiąc, komponent fa- brykujący to taki, który na żądanie generuje inny obiekt. Wszystkie komponenty fabrykują- ce Springa implementują interfejs org.springframework.beans.factory.FactoryBean. „Magia” tego rozwiązania tkwi we wprowadzeniu kolejnego poziomu pośredniczenia. Sam komponent fabrykujący jest tak naprawdę zwykłym komponentem JavaBean. Wdrażając komponent fabrykujący (tak jak każdy inny komponent JavaBean), musimy określić wła- ściwości i argumenty konstruktora niezbędne do prawidłowego funkcjonowania tego kom- ponentu. Okazuje się jednak, że kiedy inny komponent w ramach tego samego kontenera odwołuje się do tego komponentu fabrykującego (za pośrednictwem elementu <ref>) lub kiedy programista ręcznie zażądał tego komponentu (za pośrednictwem wywołania metody getBean()), kontener nie zwraca samego komponentu fabrykującego — wykrywa, że ma do czynienia z komponentem fabrykującym (za pomocą odpowiedniego interfejsu) i zamiast egzemplarza tego komponentu zwraca utworzony przez niego obiekt. Oznacza to, że z punktu
  • 51. 120 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie widzenia zależności każdy komponent fabrykujący w istocie może być traktowany jak obiekt, który jest przez niego zwracany. Przykładowo, w przypadku komponentu JnriObject- FactoryBean tym obiektem jest obiekt odnaleziony w środowisku JNDI (w naszym przy- kładzie egzemplarz implementacji interfejsu DataSource). Interfejs FactoryBean jest bardzo prosty: public interface FactoryBean { Object getObject() throws Exception; Class getObjectType(); boolean isSingleton(); } Wywoływana przez kontener metoda getObject() zwraca obiekt wynikowy. Metoda isSingleton() informuje, czy w kolejnych wywołaniach metody getObject() będzie zwra- cany ten sam egzemplarz obiektu czy wiele różnych egzemplarzy. I wreszcie metoda get- ObjectType() pozwala określić typ zwróconego obiektu (lub wartość null, jeśli typ tego obiektu nie jest znany). Komponenty fabrykujące są co prawda normalnie konfigurowane i wdrażane w kontenerach, jednak są także komponentami JavaBeans, zatem — w razie konieczności — można z nich korzystać także programowo. Warto się zastanowić, jak to możliwe, że komponent fabrykujący jest uzyskiwany za pośrednictwem wywołania metody getBean(), skoro odpowiedzią na żądanie pobrania komponentu fabrykującego jest jakiś inny obiekt wynikowy. Kluczem jest specjalny mechanizm, który sygnalizuje kontenerowi, że chodzi o sam komponent fabrykujący, a nie generowany przez niego obiekt wynikowy — wystarczy poprzedzić identyfikator komponentu znakiem &: FactoryBean facBean = (FactoryBean)appContext.getBean("&dataSource"); Tworzenie własnych, niestandardowych komponentów fabrykujących jest bardzo proste. Warto jednak pamiętać, że Spring zawiera wiele przydatnych implementacji tego typu kompo- nentów, które oferują abstrakcje dostępu do większości tych popularnych zasobów i usług, w przypadku których stosowanie komponentów fabrykujących przynosi określone korzyści. Poniżej przedstawiono dość okrojoną listę tych implementacji: n JnriObjectFactoryBean — zwraca obiekt będący wynikiem operacji wyszukiwania JNDI. n ProxyFactoryBean — opakowuje istniejący obiekt wewnątrz odpowiedniego pośrednika, który jest następnie zwracany w odpowiedzi na żądanie. Rzeczywiste funkcjonowanie tego pośrednika zależy od konfiguracji zdefiniowanej przez użytkownika — może obejmować przechwytywanie i modyfikowanie zachowania obiektu, przeprowadzanie testów bezpieczeństwa itd. Techniki stosowania tego komponentu fabrykującego omówimy bardziej szczegółowo w rozdziale poświęconym frameworkowi programowania aspektowego (AOP) oferowanemu w ramach Springa. n TransactionProxyFactoryBean — specjalizacja klasy ProxyFactoryBean, która opakowuje obiekt transakcyjnym pośrednikiem.
  • 52. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 121 n RmiProxyFactoryBean — tworzy pośrednik obsługujący przezroczysty dostęp do zdalnego obiektu za pośrednictwem technologii zdalnego wywoływania metod (RMI). Podobne obiekty pośredniczące, tyle że dla dostępu do zdalnych obiektów przez protokoły HTTP, JAX-RPC, Hessian i Burlap są tworzone odpowiednio przez klasy HttpInvokerProxyFactoryBean, JaxRpcPortProxyFactoryBean, HessianProxyFactoryBean i BurlapProxyFactoryBean. We wszystkich przypadkach klient nie musi dysponować żadną wiedzą o pośredniku — wystarczy przystosowanie do współpracy z interfejsem biznesowym. n LocalSessionFactoryBean — konfiguruje i zwraca obiekt SessionFactory frameworka Hibernate. Istnieją podobne klasy także dla innych mechanizmów zarządzania zasobami, w tym JDO i iBatis. n LocalStatelessSessionProxyFactoryBean i SimpleRemoteStatelessSessionProxyFactoryBean — tworzą obiekt pośrednika wykorzystywany do uzyskiwania dostępu odpowiednio do lokalnych lub zdalnych bezstanowych komponentów sesyjnych EJB. Sam klient wykorzystuje tylko interfejs biznesowy (bez konieczności obsługi dostępu do JNDI lub interfejsów EJB). n MethorInvokingFactoryBean — zwraca wynik wywołania metody innego komponentu, natomiast klasa FielrRetrievingFactoryBean zwraca wartość pola należącego do innego komponentu. n Wiele komponentów fabrykujących związanych z technologią JMS zwraca jej zasoby. Podsumowanie i efekt końcowy W poprzednich punktach przekonaliśmy się, że konfigurowanie obiektów z wykorzystaniem technik IoC jest bardzo proste. Zanim jednak przystąpimy do wzajemnego wiązania obiek- tów, musimy te obiekty najpierw utworzyć lub uzyskać. W przypadku kilku potencjalnych obiektów współpracujących (nawet jeśli są ostatecznie udostępniane i konfigurowane za pośrednictwem standardowego interfejsu) sam fakt tworzenia i uzyskiwania rozmaitych obiektów za pomocą skomplikowanych i niestandardowych mechanizmów może stanowić przeszkodę w efektywnym przetwarzaniu obiektów. Okazuje się, że można tę przeszkodę wyeliminować za pomocą komponentów fabrykujących. Po początkowych fazach tworze- nia i wiązania obiektów produkty generowane przez komponenty fabrykujące (osadzane w pośrednikach i innych podobnych obiektach opakowań) mogą pełnić funkcję adaptera — mogą pomagać w budowie abstrakcji ponad rzeczywistymi zasobami i usługami oraz za- pewniać dostęp do zupełnie niepodobnych usług za pośrednictwem podobnych interfejsów. Jak wiemy, źródła danych można dowolnie wymieniać bez konieczności umieszczania w kodzie klienta (w analizowanym przykładzie tę funkcję pełnił obiekt weatherDao) jakich- kolwiek zapisów uzależniających od stosowanej technologii — przykładowo, zastąpiliśmy oryginalną implementację interfejsu DataSource opartą na Commons DBCP (mającą postać lokalnego komponentu) implementacją interfejsu DataSource właściwą dla technologii JNDI. Taka zamiana jest możliwa nie tylko dzięki mechanizmowi IoC, ale także dzięki wyprowa- dzeniu operacji dostępu do zasobów poza kod aplikacji i — tym samym — przygotowaniu właściwej struktury dla technik IoC.
  • 53. 122 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Mamy nadzieję, że dostrzegasz dwie najważniejsze zalety omawianego mechanizmu — z jednej możliwość łatwego abstrahowania od usług i zasobów, z drugiej strony możli- wość wymiany jednego rozwiązania na inne bez konieczności wykorzystywania sposo- bów wdrożenia i technologii, których stosowanie podczas budowy wielu aplikacji naj- zwyczajniej w świecie mija się z celem. Cechą wyróżniającą Springa jest niezależność kodu klienta zarówno od faktycznego wdrożenia, jak i od użytego sposobu implementacji. Skoro możemy w sposób transparentny, przezroczysty (przynajmniej z perspektywy klien- ta) uzyskiwać dostęp do zdalnych usług za pośrednictwem takich technologii jak RMI, RPC przez HTTP czy EJB, niby dlaczego nie mielibyśmy wdrażać całkowicie niezależnych rozwiązań, i po co w ogóle wiązać kod klienta z jedną implementacją ponad inną tam, gdzie nie jest to konieczne? W rozwiązaniach platformy J2EE można zaobserwować tendencję do udostępniania takich zasobów jak implementacje DataSource, zasoby JMS czy interfejsy JavaMail za pośrednictwem JNDI. Nawet jeśli ten sposób udostępniania zasobów znajduje jakieś uzasadnienie, bezpośredni dostęp klienta do środowiska JNDI jest całkowicie sprzeczny z założeniami efektywnego wytwarzania oprogramowania. Budowa dodatkowej warstwy abstrakcji np. za pomocą klasy JnriObjectFactoryBean oznacza, że w przyszłości będzie można zmienić środowisko na inne niż JNDI bez konieczności modyfikowania kodu klienta (odpowiednio dostosowując wyłącznie konfigurację samej fabryki komponentów). Nawet jeśli nie planujesz zmiany środowiska wdrożeniowego ani technologii implementa- cji już po wdrożeniu oprogramowania w docelowym środowisku, możesz być pewien, że taka abstrakcja znacznie ułatwi testy jednostkowe i testy integracyjne, ponieważ umożliwi weryfikację różnych konfiguracji wdrożeń i scenariuszy testowych. Tym zagadnieniom po- święcimy więcej miejsca w następnym rozdziale. Warto też podkreślić, że stosując komponent JnriObjectFactoryBean, eliminujemy potrzebę pisania zaawansowanego kodu w obiekcie DAO (konkretnie wyszukiwania obiektu w środowisku JNDI), który nie miałby nic wspól- nego z właściwą funkcjonalnością biznesową tego obiektu. Ten przykład dobrze pokazuje, jak stosowanie techniki wstrzykiwania zależności ogranicza złożoność i nieład w kodzie źródłowym aplikacji. Wielokrotne wykorzystywanie tych samych definicji komponentów W porównaniu z ilością kodu Javy niezbędnego do pełnej konfiguracji i deklaracji wszyst- kich zależności definicje komponentów wyrażane w języku XML wydają się dość zwięzłe. Nie można jednak wykluczyć, że będziemy zmuszeni do opracowania wielu definicji kom- ponentów, które w zdecydowanej większości będą do siebie bardzo podobne. Przeanalizuj następującą definicję komponentu WeatherService: <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< W typowym scenariuszu budowy aplikacji, gdzie nieodłącznym elementem zaplecza jest relacyjna baza danych, musielibyśmy korzystać z podobnych obiektów usług w sposób bar- dziej „transakcyjny”. Szczegółowe omówienie tego zagadnienia znajdziesz w rozdziale 6.
  • 54. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 123 Jednym z najprostszych sposobów osiągnięcia tego celu jest deklaratywne opakowanie ta- kiego obiektu, aby nabrał cech obiektu transakcyjnego (np. za pomocą komponentu fabry- kującego Springa nazwanego TransactionProxyFactoryBean): <bean id="weatherServiceTarget" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< <!-- pośrednik transakcyjny --> <bean id="weatherService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"< <property name="target"<<ref local="weatherServiceTarget"/<</property< <property name="transactionManager"<<ref local="transactionManager"/<</property< <property name="transactionAttributes"< <props< <prop key="*"<PROPAGATION_REQUIRED</prop< </props< </property< </bean< Jeśli na tym etapie nie do końca rozumiesz, jak należy szczegółowo konfigurować kompo- nent TransactionProxyFactoryBean lub jak ten komponent faktycznie działa, nie masz po- wodów do zmartwień. Istotne jest to, że wspomniany komponent jest implementacją inter- fejsu FactoryBean, która dla otrzymanego na wejściu komponentu docelowego generuje obiekt transakcyjnego pośrednika (z jednej strony implementujący te same interfejsy co komponent docelowy, z drugiej strony obsługujący dodatkową semantykę przetwarzania transakcyjnego). Ponieważ chcemy, aby kod kliencki wykorzystywał obiekt opakowania, nazwa oryginalnego (nieopakowanego i nietransakcyjnego) komponentu jest zmieniana (rozsze- rzana o sufiks Target), a jego pierwotną nazwę otrzymuje wygenerowany komponent trans- akcyjny (w tym przypadku będą to odpowiednio weatherServiceTarget i weatherService). Oznacza to, że istniejący kod kliencki, który korzysta z usługi pogodowej, w ogóle „nie wie”, że od momentu zmiany konfiguracji korzysta z usługi transakcyjnej. Opisany mechanizm deklaratywnego opakowywania obiektów jest co prawda bardzo wy- godny (szczególnie w zestawieniu z rozwiązaniem alternatywnym, czyli działaniami pro- gramowymi na poziomie kodu), jednak w przypadku większych aplikacji z dziesiątkami lub setkami interfejsów usług, które wymagają stosowania bardzo podobnych technika opakowywania, wielokrotne definiowanie niemal identycznego, szablonowego kodu XML wydaje się stratą czasu. Okazuje się, że wprost idealnym rozwiązaniem tego problemu jest oferowana przez kontener obsługa definicji zarówno macierzystych, jak i potomnych kompo- nentów. Korzystając z dobrodziejstw tego mechanizmu, możemy w prosty sposób zdefi- niować abstrakcyjny, macierzysty (szablonowy) pośrednik transakcyjny: <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"< <property name="transactionManager"< <ref local="transactionManager"/< </property< <property name="transactionAttributes"<
  • 55. 124 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie <props< <prop key="*"<PROPAGATION_REQUIRED</prop< </props< </property< </bean< Następnym krokiem jest utworzenie właściwych pośredników za pomocą odpowiednich definicji potomnych, które będą obejmowały tylko właściwości odróżniające tych pośred- ników od ich wspólnego, macierzystego szablonu (w tym przypadku komponentu txProxy- Template): <bean id="weatherServiceTarget" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< <bean id="weatherService" parent="txProxyTemplate"< <property name="target"<<ref local="weatherServiceTarget"/<</property< </bean< Istnieje możliwość skorzystania z jeszcze bardziej klarownej i zwięzłej formy dziedzicze- nia. Ponieważ żaden z klientów nigdy nie będzie potrzebował dostępu do nieopakowanego komponentu naszej usługi pogodowej, można go zdefiniować jako komponent wewnętrzny względem pośrednika, który go opakowuje: <bean id="weatherService" parent="txProxyTemplate"< <property name="target"< <bean class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< </property< </bean< Jeśli jakaś inna usługa będzie wymagała podobnego opakowania, programista będzie mógł użyć definicji komponentu, która w bardzo podobny sposób odziedziczy właściwości ma- cierzystego szablonu. W poniższym przykładzie odziedziczona właściwość transaction- Attributes jest dodatkowo przykrywana — w ten sposób wprowadzono nowe (właściwe dla danego pośrednika) ustawienia propagowania transakcji: <bean id="anotherWeatherService" parent="txProxyTemplate"< <property name="target"< <bean class="ch02.sampleX.AnotherWeatherServiceImpl"/< </property< <property name="transactionAttributes"< <props< <prop key="save*"<PROPAGATION_REQUIRED</prop< <prop key="*"<PROPAGATION_REQUIRED,readOnly</prop< </props< </property< </bean<
  • 56. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 125 Jeszcze prostszym rozwiązaniem jest skorzystanie z mechanizmu automatycznego po- średniczenia (ang. autoproxying) programowania aspektowego, który automatycznie wykrywa wszelkie cechy wspólne porad AOP w definicjach różnych komponentów. Więcej informacji na ten temat znajdziesz w rozdziale 4. Cechy charakterystyczne definicji komponentów potomnych Definicja komponentu potomnego dziedziczy wartości właściwości i argumenty konstruk- tora zadeklarowane w definicji komponentu macierzystego (przodka, rodzica). Okazuje się, że komponent potomny dziedziczy po definicji komponentu macierzystego także wiele opcjonalnych atrybutów (oczywiście pod warunkiem, że zadeklarowano je w definicji ma- cierzystej). Zapewne zauważyłeś w poprzednim przykładzie, że do wskazywania identyfi- katora komponentu macierzystego w definicji komponentu potomnego służy atrybut parent. Definicja komponentu potomnego może wskazywać konkretną klasę lub pozostawiać od- powiedni atrybut nieustawiony i — tym samym — odziedziczyć jego wartość po definicji komponentu macierzystego. Warto pamiętać, że jeśli definicja potomka określa własną klasę (inną niż definicja komponentu macierzystego), nowa klasa musi prawidłowo obsługiwać wszystkie argumenty konstruktora i (lub) właściwości zadeklarowane w definicji rodzica, ponieważ zostaną one odziedziczone i użyte. Potomek dziedziczy co prawda po swoim komponencie macierzystym wartości argumen- tów konstruktora, wartości właściwości i metody, jednak — w razie potrzeby — może do- dawać także nowe, własne wartości. Z drugiej strony, dziedziczone przez komponent po- tomny wartości atrybutów init-methor, restroy-methor lub factory-methor są w całości przykrywane przez odpowiednie wartości tego potomka. Niektóre ustawienia konfiguracyjne komponentu macierzystego nigdy nie są dziedziczone i zawsze są określane w oparciu o definicję bieżącego (potomnego) komponentu. Należą do nich wartości atrybutów repenrs-on, autowire, repenrency-check, singleton oraz lazy-init. Egzemplarze komponentów oznaczonych jako abstrakcyjne (za pomocą atrybutu abstract — patrz powyższy przykład) jako takie nie mogą być tworzone. Jeśli nie przewidujesz sy- tuacji, w której będzie konieczne stworzenie egzemplarza komponentu macierzystego, zawsze powinieneś go oznaczać jako komponent abstrakcyjny. Należy to traktować jak dobrą praktykę, ponieważ egzemplarze nieabstrakcyjnego komponentu macierzystego mogą być tworzone przez kontener nawet wtedy, gdy tego nie zażądamy ani nie użyjemy żadnej za- leżności wskazującej na ten komponent. Konteksty aplikacji (ale nie fabryki komponentów) domyślnie podejmują próby utworzenia wstępnego egzemplarza dla każdego nieabstrak- cyjnego komponentu singletonowego. Warto pamiętać, że nawet jeśli nie użyjemy atrybutu abstract wprost, definicje kom- ponentów mogą być niejawnie interpretowane jako abstrakcyjne ze względu na brak klasy lub metody fabrykującej, czyli de facto brak informacji niezbędnych do utworzenia ich egzemplarzy. Każda próba utworzenia egzemplarza abstrakcyjnego komponentu spowoduje błąd (niezależnie od tego, czy podjęto ją na żądanie programisty czy w spo- sób niejawny przez któryś z mechanizmów kontenera).
  • 57. 126 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Stosowanie postprocesorów do obsługi niestandardowych komponentów i kontenerów Postprocesory komponentów są w istocie specjalnymi obiektami nasłuchującymi (ang. listeners), które można rejestrować (wprost lub niejawnie) w kontenerze i które otrzymują ze strony kontenera wywołania zwrotne dla każdego komponentu, dla którego ten kontener tworzy egzemplarz. Postprocesory fabryk komponentów są bardzo podobne do postproce- sorów komponentów z tą różnicą, że otrzymują wywołania zwrotne w momencie utworzenia egzemplarza samego kontenera. W sytuacji, gdy konieczne jest regularne dostosowywanie do zmieniających się warunków konfiguracji komponentu, grupy komponentów lub całego kontenera, można to zadanie bardzo ułatwić, tworząc własny postprocesor lub korzystając z jednego z wielu istniejących postprocesorów dołączanych do Springa. Postprocesory komponentów Postprocesory komponentów implementują interfejs BeanPostProcessor: public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; } Interfejs BeanPostProcessor jest rozszerzany przez interfejs DestructionAwareBeanPostProcessor: public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor { void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; } W przedstawionej w tym rozdziale tabeli zdarzeń składających się na cykl życia kompo- nentów dość precyzyjnie wskazano punkty, w których następują poszczególne wywołania zwrotne. Spróbujmy teraz stworzyć postprocesor komponentów, który będzie wykorzysty- wał wywołanie zwrotne metody postProcessAfterInitialization() do wypisywania na konsoli nazwy każdego komponentu, dla którego w danym kontenerze utworzono egzem- plarz (wywołanie będzie następowało bezpośrednio po zdarzeniu utworzenia egzemplarza): public class BeanInitializationLogger implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Zainicjalizowano komponent '" + beanName + "'"); return bean; } }
  • 58. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 127 Przedstawiony przykład nie jest zbyt skomplikowany — rzeczywiste postprocesory najczęściej przetwarzają i zmieniają egzemplarze komponentów, a informacje o reje- strowanych zdarzeniach są zapisywane w pliku dziennika, a nie na konsoli. W kontekście aplikacji postprocesory komponentów są rozpoznawane i wykorzystywa- ne przez kontener automatycznie, a ich wdrażanie przebiega dokładnie tak jak w przy- padku wszystkich innych komponentów: <beans< <bean id="weatherService" class="ch02.sample2.WeatherServiceImpl"< <property name="weatherDao"< <ref local="weatherDao"/< </property< </bean< <bean id="weatherDao" class="ch02.sample2.StaticDataWeatherDaoImpl"/< <bean id="beanInitLogger" class="ch02.sample8.BeanInitializationLogger"/> </beans< Kiedy skonfigurowany w ten sposób kontekst aplikacji zostanie załadowany, nasz postpro- cesor otrzyma dwa wywołania zwrotne i w odpowiedzi wygeneruje (wyświetli na konsoli) następujące dane wyjściowe: Zainicjalizowano komponent 'weatherDao' Zainicjalizowano komponent 'weatherService' Stosowanie postprocesorów komponentów dla nawet stosunkowo prostych fabryk kompo- nentów jest nieco bardziej skomplikowane niż w przypadku kontekstów aplikacji, ponie- waż wymaga ręcznego rejestrowania (co jest zgodne z duchem fabryk komponentów, który przewiduje bardziej programowe podejście), zamiast samego zadeklarowania postprocesora w formie komponentu w pliku konfiguracyjnym XML: XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("ch02/sample8/beans.xml")); BeanInitializationLogger logger = new BeanInitializationLogger(); factory.addBeanPostProcessor(logger); // Ponieważ nasze komponenty są singletonami, będą podlegały fazie wstępnego tworzenia egzemplarzy // (także wówczas postprocesor otrzyma odpowiednie wywołania zwrotne). factory.preInstantiateSingletons(); Jak widać, do utworzenia wstępnych egzemplarzy komponentów singletonowych w danej fabryce użyto metody BeanFactory.preInstantiateSingletons(), ponieważ domyślnie tyl- ko konteksty aplikacji tworzą wstępne egzemplarze singletonów. Tak czy inaczej postpro- cesor jest wywoływany w momencie, w którym egzemplarz danego komponentu faktycznie jest tworzony, niezależnie od tego, czy w ramach procesu przygotowywania wstępnych eg- zemplarzy czy w odpowiedzi na konkretne żądanie (jeśli wspomniany proces zostanie po- minięty).
  • 59. 128 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie Postprocesory fabryk komponentów Postprocesory fabryk komponentów implementują interfejs BeanFactoryPostProcessor: public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; } Poniżej przedstawiono przykład postprocesora fabryki komponentów, którego działanie sprowadza się do pobrania i wyświetlenia listy nazw wszystkich komponentów w danej fabryce: public class AllBeansLister implements BeanFactoryPostProcessor { public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { System.out.println("Fabryka zawiera następujące komponenty:"); String[] beanNames = factory.getBeanDefinitionNames(); for (int i = 0; i < beanNames.length; ++i) System.out.println(beanNames[i]); } } Samo używanie postprocesorów fabryki komponentów nie różni się zbytnio od stosowania postprocesorów komponentów. Ich wdrażanie w kontekście aplikacji przebiega tak samo jak w przypadku wszystkich innych komponentów — postprocesory zostaną automatycznie wykryte i użyte przez kontekst aplikacji. Z drugiej strony, w przypadku zwykłej fabryki komponentów postprocesor musi być uruchomiony ręcznie: XmlBeanFactory factory = new XmlBeanFactory( new ClassPathResource("ch02/sample8/beans.xml")); AllBeansLister lister = new AllBeansLister(); lister.postProcessBeanFactory(factory); Przeanalizujmy teraz kilka najbardziej przydatnych postprocesorów komponentów i fabryk komponentów oferowanych w ramach Springa. Postprocesor PropertyPlaceholderConfigurer Podczas wdrażania aplikacji bazujących na Springu często się okazuje, że większość ele- mentów konfiguracji kontenera nie będzie modyfikowana w czasie wdrażania. Zmuszanie kogokolwiek do przeszukiwania skomplikowanych plików konfiguracyjnych tylko po to, by zmienić kilka wartości, które tego rzeczywiście wymagają, bywa bardzo niewygodne. Takie rozwiązanie stwarza też niebezpieczeństwo popełnienia przypadkowego błędu w pliku konfiguracyjnym, np. przez nieumyślne zmodyfikowanie niewłaściwej wartości. PropertyPlaceholrerConfigurer jest postprocesorem fabryki komponentów, który — użyty w definicji fabryki komponentów lub kontekstu aplikacji — umożliwia określanie pewnych wartości za pośrednictwem specjalnych łańcuchów zastępczych, łańcuchów-wypełniaczy (ang. placeholder strings), które są następnie zastępowane przez rzeczywiste wartości pochodzące
  • 60. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 129 z pliku zewnętrznego (w formacie Java Properties). Co więcej, mechanizm konfiguracyjny domyślnie będzie weryfikował te łańcuchy pod kątem zgodności z właściwościami syste- mowymi Javy, jeśli nie znajdzie odpowiednich dopasowań w pliku zewnętrznym. Tryb takiej dodatkowej weryfikacji można włączać i wyłączać za pomocą właściwości systemProperties- More konfiguratora (która oferuje też możliwość wymuszania pierwszeństwa dopasowań do właściwości System Properties względem zapisów zewnętrznego pliku właściwości). Dobrym przykładem wartości, które warto wyprowadzić poza skomplikowane pliki konfi- guracyjne, są łańcuchy konfiguracyjne właściwe dla puli połączeń z bazą danych. Poniżej przedstawiono ponownie fragment kodu definiującego implementację DataSource korzy- stającą z Commons DBCP — tym razem użyto łańcuchów zastępczych zamiast rzeczywi- stych wartości: <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy- method="close"< <property name="driverClassName"<<value<${db.driverClassName}</value<</property< <property name="url"<<value<${db.url}</value<</property< <property name="username"<<value<${db.username}</value<</property< <property name="password"<<value<${db.password}</value<</property< </bean< Rzeczywiste wartości będą pochodziły z zewnętrznego pliku właściwości nazwanego jdbc.properties: db.driverClassName=oracle.jdbc.driver.OracleDriver db.url=jdbc:oracle:thin:@db-server:1 21:devdb db.username=john db.password=password Aby użyć egzemplarza klasy PropertyPlaceholrerConfigurer i za jej pomocą wydobyć właściwe wartości z pliku właściwości do kontekstu aplikacji, wystarczy wdrożyć ten eg- zemplarz tak jak każdy inny komponent: <bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"< <property name="location"<<value<jdbc.properties</value<</property< </bean< Aby użyć teraz zadeklarowanego przed chwilą komponentu konfiguratora dla prostej fa- bryki komponentów, należy go uruchomić ręcznie: XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); ppc.setLocation(new FileSystemResource("db.properties")); ppc.postProcessBeanFactory(factory); Postprocesor PropertyOverrideConfigurer PropertyOverrireConfigurer, który także jest postprocesorem fabryk komponentów, co prawda pod wieloma względami przypomina postprocesor PropertyPlaceholrerConfigurer, jednak o ile w przypadku tego drugiego wartości musiały pochodzić z zewnętrznego pliku
  • 61. 130 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie właściwości, o tyle PropertyOverrireConfigurer umożliwia przykrywanie tymi wartościami wartości właściwości komponentów w fabryce komponentów lub kontekście aplikacji. Każdy wiersz pliku właściwości musi mieć następujący format: beanName.property=value gdzie beanName reprezentuje identyfikator komponentu, property jest jedną z właściwości tego komponentu, a value reprezentuje docelową wartość tej właściwości. Plik właściwości może zawierać np. następujące zapisy: dataSource.driverClassName=oracle.jdbc.driver.OracleDriver dataSource.url=jdbc:oracle:thin:@db-server:1 21:devdb dataSource.username=john dataSource.password=password W ten sposób można przykryć cztery właściwości komponentu rataSource. Wszelkie wła- ściwości (wszystkich komponentów) w danym kontenerze, które nie zostały przykryte przez nowe wartości przedstawionego pliku właściwości, utrzymają swoje dotychczasowe warto- ści — albo te zdefiniowane w konfiguracji kontenera, albo wartości domyślne komponentu (w przypadku braku odpowiednich zapisów w pliku konfiguracyjnym). Ponieważ sama analiza konfiguracji kontenera nie pozwala określić, czy dana wartość zostanie przykryta (dla pewności konieczne jest jeszcze przestudiowanie pliku właściwości), tego typu funk- cjonalność należy wykorzystywać tylko w uzasadnionych przypadkach. Postprocesor CustomEditorConfigurer CustomPritorConfigurer jest postprocesorem fabryki komponentów, za pomocą którego możemy rejestrować własne edytory właściwości (rozszerzenia klasy PropertyPritorSupport) komponentów JavaBeans obsługujące konwersję wartości łańcuchowych na docelowe wartości właściwości lub argumentów konstruktora (w konkretnym, często złożonym for- macie obiektowym). Tworzenie własnych edytorów właściwości Częstym powodem stosowania własnych, niestandardowych edytorów właściwości jest ko- nieczność ustawiania właściwości typu java.util.Date, których wartości źródłowe zapisa- no w postaci łańcuchów. Ponieważ formaty dat bardzo często są uzależnione od ustawień regionalnych, użycie egzemplarza PropertyPritor eksportującego określony format łańcu- cha źródłowego jest najprostszym sposobem radzenia sobie z problemem niezgodności formatów. Ponieważ utrudnienia związane z formatami dat dotyczą większości użytkowni- ków, zamiast prezentować jakiś abstrakcyjny przykład, posłużymy się klasą CustomDate- Pritor (implementacją PropertyPritor dla typu Date) — przeanalizujemy nie tylko kod tej klasy, ale także techniki jej rejestrowania i wykorzystywania w praktyce. Własne edytory właściwości powinny być implementowane i rejestrowane w bardzo podobny sposób: public class CustomDateEditor extends PropertyEditorSupport { private final DateFormat dateFormat; private final boolean allowEmpty;
  • 62. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 131 public CustomDateEditor(DateFormat dateFormat, boolean allowEmpty) { this.dateFormat = dateFormat; this.allowEmpty = allowEmpty; } public void setAsText(String text) throws IllegalArgumentException { if (this.allowEmpty && !StringUtils.hasText(text)) { // traktuje pusty łańcuch jak wartość null setValue(null); } else { try { setValue(this.dateFormat.parse(text)); } catch (ParseException ex) { throw new IllegalArgumentException("Konwersja daty nie powiodła się: " + ex.getMessage()); } } } public String getAsText() { return (getValue() == null ? "" : this.dateFormat.format((Date)getValue())); } } Pełna dokumentacja tej klasy w formacie JavaDoc jest dołączana do wersji instalacyjnej Springa. Implementację własnego edytora właściwości najlepiej rozpocząć od rozszerzenia pomocniczej klasy bazowej java.beans.PropertyPritorSupport będącej częścią standar- dowej biblioteki klas Javy. Wspomniana klasa implementuje większość potrzebnych me- chanizmów edycji właściwości (także poza standardowymi metodami setAsText() i getAs- Text(), których implementacje zawsze musimy przykrywać). Warto pamiętać, że chociaż egzemplarze PropertyPritor mają swój stan i w normalnych warunkach nie gwarantują bezpieczeństwa przetwarzania wielowątkowego, sam Spring zapewnia odpowiednie me- chanizmy synchronizacji całej sekwencji wywołań metod niezbędnych do prawidłowego przeprowadzania konwersji. CustomDatePritor może korzystać z dowolnej (przekazywanej za pośrednictwem argumentu jej konstruktora) implementacji interfejsu java.text.DateFormat do przeprowadzania wła- ściwej implementacji. Wdrażając klasę CustomDatePritor, możesz użyć implementacji java.text.SimpleDateFormat. Nasz edytor właściwości można też skonfigurować w taki spo- sób, aby łańcuchy puste albo interpretował jak wartości null, albo traktował jak błąd nie- prawidłowego argumentu. Rejestrowanie i używanie własnych, niestandardowych edytorów właściwości Przyjrzyjmy się teraz definicji kontekstu aplikacji, w której użyto postprocesora CustomP- ritorConfigurer do zarejestrowania edytora CustomDatePritor (implementacji uniwersal- nego edytora PropertyPritor), którego zadaniem jest konwersja łańcuchów znakowych na obiekty klasy java.util.Date. Definicja konfiguracji zawiera konkretny format łańcuchów reprezentujących daty:
  • 63. 132 Spring Framework. Profesjonalne tworzenie oprogramowania w Javie <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"< <property name="customEditors"< <map< <!-- rejestruje edytor właściwości dla typu java.util.Date --> <entry key="java.util.Date"< <bean class="org.springframework.beans.propertyeditors.CustomDateEditor"< <constructor-arg index="0"< <bean class="java.text.SimpleDateFormat"< <constructor-arg<<value<M/d/yy</value<</constructor-arg< </bean< </constructor-arg< <constructor-arg index="1"<<value<true</value<</constructor-arg< </bean< </entry< </map< </property< </bean< <!-- sprawdza funkcjonowanie edytora dat, ustawiając dwie właściwości typu Date w postaci łańcuchów --> <bean id="testBean" class="ch02.sample9.StartEndDatesBean"< <property name="startDate"<<value<10/09/1968</value<</property< <property name="endDate"<<value<10/26/2004</value<</property< </bean< Postprocesor CustomPritorConfigurer może rejestrować jeden lub wiele niestandardowych edytorów właściwości (choć w prezentowanym przykładzie ograniczymy się do jednego, CustomDatePritor). Twoje edytory (zaimplementowane z myślą o innych typach) mogą nie wymagać żadnych specjalnych zabiegów konfiguracyjnych. Z powyższego kodu konfigu- racji wynika, że klasa CustomDatePritor, której konstruktor pobiera na wejściu dwa argu- menty, otrzyma obiekt klasy SimpleDateFormat reprezentujący łańcuch formatu daty oraz wartość logiczną (typu boolean) określającą, czy łańcuchy puste powinny być traktowane jak wartości null. Przedstawiony przykład ilustruje też wygodny sposób definiowania komponentu testowego (w tym przypadku nazwanego testBean), który zawiera dwie właściwości typu Date usta- wiane za pośrednictwem wartości łańcuchowych — w ten sposób sprawdzamy, czy nasz edytor właściwości działa prawidłowo. Postprocesor BeanNameAutoProxyCreator BeanNameAutoProxyCreator jest postprocesorem komponentów. Co prawda techniki korzy- stania z tego postprocesora omówimy bardziej szczegółowo w rozdziale poświęconym pro- gramowaniu aspektowemu, jednak już teraz dobrze jest wiedzieć o jego istnieniu. Najkrócej mówiąc, dla danej na wejściu listy nazw komponentów postprocesor BeanNameAutoProxy- Creator może opakować te spośród otrzymanych komponentów danej fabryki, których na- zwy pasują do nazw znajdujących się na tej liście (proces opakowywania następuje w cza- sie tworzenia egzemplarzy komponentów, a zadaniem obiektów pośredniczących jest albo przechwytywanie operacji dostępu do oryginalnych komponentów, albo modyfikowanie ich zachowań).
  • 64. Rozdział 2. n Fabryka komponentów i kontekst aplikacji 133 Postprocesor DefaultAdvisorAutoProxyCreator Ten postprocesor komponentów przypomina opisany przed chwilą postprocesor BeanName- AutoProxyCreator, jednak oprócz samych nazw komponentów przeznaczonych do opako- wania odnajduje też informacje na temat sposobu tego opakowania (tzw. porady). Także w tym przypadku warto się zapoznać z treścią rozdziału poświęconego technikom programowania aspektowego (AOP). Po przeczytaniu tego rozdziału powinieneś znacznie lepiej rozumieć, co tak naprawdę oznaczają takie pojęcia jak odwracanie kontroli i wstrzykiwanie zależności oraz jak obie koncepcje zostały urzeczywistnione w postaci fabryk komponentów i kontekstów aplikacji Springa. Przeanalizowaliśmy i użyliśmy większości podstawowych elementów funkcjonal- ności kontenera Springa. Ponieważ właśnie kontener IoC jest podstawą pozostałych me- chanizmów Springa, dobre rozumienie sposobu jego funkcjonowania i technik konfiguracji w połączeniu ze świadomością potencjału tego kontenera jest kluczem do efektywnego ko- rzystania ze Springa. Dowiedziałeś się między innymi: n Jak dzięki funkcjom kontenera można korzystać z zalet jednego, logicznie spójnego i przewidywalnego mechanizmu dostępu, konfigurowania i wiązania obiektów (zamiast używać programowych lub tworzonych naprędce mechanizmów łączenia klas, które tylko utrudniają testowanie). Ogólnie, kontener pozwala całkowicie wyeliminować konieczność korzystania z niestandardowych, przystosowanych do konkretnych klas fabryk komponentów oraz singletonów. n Jak kontener wspiera programistę w stosowaniu pożądanej praktyki oddzielania interfejsu od implementacji w kodzie aplikacji. n Że postprocesory oferują możliwość elastycznego dostosowywania zachowania komponentów i kontenera (za pomocą odpowiednich plików zewnętrznych). n Jakie są podstawowe reguły odwracania kontroli i jak w połączeniu z fabryką komponentów tworzą doskonałe narzędzie do budowy abstrakcji ponad operacjami wymagającymi dostępu do usług i zasobów. n Że technika IoC wraz z odpowiednim kontenerem może stanowić solidną podstawę dla budowy aplikacji w oparciu o framework Spring bez konieczności tworzenia ścisłych związków pomiędzy konstruowanym oprogramowaniem a samym kontenerem. W następnym rozdziale skupimy się na bardziej zaawansowanych mechanizmach kontekstu aplikacji i przedstawimy kilka zaawansowanych scenariuszy użycia kontenera.