Úvod Jste v roli Spring konzultanta a zákazník vás požádal o refaktoring rozpracované bankovní aplikace. Od refaktoringu si slibuje lepší udržovatelnost a testovatelnost. Aplikace je tvořena 3 samostatnými projekty: • SpringDemo-Banking – jádro bankovní aplikace • SpringDemo-Commons – společná rozhraní • SpringDemo-Server – služby poskytované vzdáleným systémem 3. strany Aplikace zatím nemá hotové žádné uživatelské rozhraní. To nám však nebrání ve vývoji, místo stylu práce „kóduju-nasadím-proklikám“ píšeme automatizované testy. Těmi ověřujeme že aplikace dělá to co má a zároveň můžeme odhalit možné regrese během refaktoringu. Jediná stávající funcionalita spočívá v přidání nového zákazníka do databáze a vylistování všech stávajících zákazníku. Design aplikace Srdcem je rozhraní BankService poskytující business služby getAllCustomers() a createNewCustomer(). Toto rozhraní má jedinou implementaci BankServiceImpl. Samotná BankServiceImpl neumí přistupovat k databází, ale tuto činnost deleguje na třídu PureJdbcCustomerDaoImpl. Ověření správné funčnosti dosavadní aplikace Rozbalte všechny 3 projekty a otevřete ve vývojovém prostředí NetBeans. Klikněte pravým tlačítkem na projekt SpringDemo-Commons, zvolte Custom → Goals, do dialogu vyplňte políčko Goals jako install a potvďte. Nyní otevřete třídu BankServiceTest, klikněte na ni pravým tlačítkem a zvolte Test File. Tester by se měl rozsvítit zeleně. Pokud ne, kontaktujte lektora. Úkol č. 1 – Uvolňujeme vazby BankServiceImpl je závislá na třídě PureJdbcCustomerDaoImpl. Našim prvním úkolem je tuto vazbu rozbít. Nápověda pro implementaci: Springová beana se v XML deskriptoru definuje takto: tím se vytvoří instance xx.yy.Foo jako beana pojmenovaná myBean. Pokud mám třídu xx.yy.Bar, která má metodu setFoo(Foo foo), tak beanu typu Bar zhotovím následujícím zápisem v XML: Spring vytvoří instanci třidy xx.yy.Bar a po vytvoření do ní přes setter injektne beanu pojmenovanou myFooBean. Postup implementace: Rozbití vazby se skládá z těchto kroků: 1. Místo aby si BankServiceImpl sama vytvářela instanci PureJdbcCustomerDaoImpl při každém voláni business metody, tak v souboru applicationContext.xml definujeme beanu typu PureJdbcCustomerDaoImpl s ID customerDao. 2. Do BankServiceImpl připišeme setter pro PureJdbcCustomerDaoImpl a upravíme applicationContext.xml tak, aby při vytvoření beany BankService zavolal tento setter a vložil skrze něj beanu customerDao. 3. Z PureJdbcCustomerDaoImpl extrahujeme rozhraní CustomerDao, které bude obsahovat metody findAll() a save(). Signatury metod ponechte stejné jako jsou PureJdbcCustomerDaoImpl. 4. Upravte PureJdbcCustomerDaoImpl tak, že implementuje rozhraní CustomerDao. Za název třídy stačí připsat implements CustomerDao a provést příslušný import. 5. Upravte BankServiceImpl tak, že všechny výskyty třídy PureJdbcCustomerDaoImpl nahradíte rozhraním CustomerDao. 6. Nyní je možno z BankServiceImpl zrušit setter na dataSource a odstranit příslušnou property z applicationContext.xml 7. Spusťte testy z BankServiceTest. Pokud jste postupovali správně, tak by měly procházet. Pokud ne, kontaktujte lektora. Pokud jste úspěšně došli až sem, tak gratuluji. Právě jste odstranili závislost třídy BookServiceImpl na konkretní implementaci přístupu k databázi. Nyní nám nic nebrání použít jinou implementaci a to pouze změnou konfiguračního souboru. Úkol č. 2 – Použití JdbcTemplate Protože BookServiceImpl nyní nezávísí na PureJdbcCustomerDaoImpl, tak si můžeme dovolit napsat alternativní implementaci rozhraní CustomerDao. Využijeme k tomu springovskou třídu JdbcTemplate, která nám výrazně usnadní práci s databází. Nápověda při implementaci: – Dokumentace JdbcTemplate na http://static.springsource.org/spring/docs/3.0.x/javadoc-api/ – JdbcTemplate potřebuje setnout beanu typu dataSource. - Kostra implementace metody findAll() : List customerList = jdbcTemplate.query(SQL_DOTAZ_STEJNY_JAKO_V_PUVODNI_IMPLEMENTACI, new BeanPropertyRowMapper(Customer.class)); - Kostra metody save: KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement stmt = connection.prepareStatement(SQL_PRO_INSERT, Statement.RETURN_GENERATED_KEYS); stmt.setString(1, customer.getFirstname()); stmt.setString(2, customer.getLastname()); return stmt; } }, keyHolder); customer.setId(keyHolder.getKey().longValue()); Postup implementace: 1. Vytvořte novou třídu JdbcTemplateCustomerDaoImpl, která implementuje rozhraní CustomerDao. 2. Tato třída bude mít private field typu JdbcTemplate a k němu přislušný setter. 3. Implementujte obě metody rozhraní podle výše uvedené nápovědy 4. V applicationContext.xml vytvořte beanu typu JdbcTemplateCustomerDaoImpl a pojmenujte ji springJdbcCustomerDao 5. Dále upravte applicationContext.xml tak, aby beaně bookService byla setnuta beana springJdbcCustomerDao místo dosavadní customerDao. 6. Můžete z applicationContext.xml zrušit definici beany customerDao. 7. Spusťte testy, opět by měli procházet. Nyní srovnejte třidy JdbcTemplateCustomerDaoImpl a PureJdbcCustomerDaoImpl. Funkčně jsou totožné, ale Springová varianta je výrazně úspornější a méně náchylná k chybám. Úkol č. 3 – AoP Prohlédněte si třídu BankServiceLoggingAspect. Jedná se o aspekt, který loguje všechna volání metody createNewCustomer() z BankService a zapisuje je na standardní výstup. Výhodou tohoto řešení je, že logovací kód je oddělen od samotné business logiky. Vytvořte v tomto aspektu novou metodu, která bude logovat volání getAllCustomers() z BankService. Spusťe test a sledujte standardní výstup. Úkol č. 4 – Separation of Concerns BankServiceLoggingAspect nyní loguje volání obou business metod. Ale umí logovat jen na standardní výstup. Od zákazníka přišel požadavek na flexibilnější logovací mechanismus. Upravte tedy BankServiceLoggingAspect tak, aby dostaval setterem instanci třídy implementující rozhraní Notifier a místo zápisu zprávy na standardní výstup volal metodu notify tohoto rozhraní. Následně upravte applicationContext.xml, tak aby do BankServiceLoggingAspect setnul instanci SysOutNotifier, která toto rozhraní implementuje. Následně spusťte testy a sledujte standardní výstup, zda dochází k logování. Úkol č. 5 – Remoting Od zákazníka přišel další požadavek – volání business metod je nyní třeba logovat do vzdáleného systému auditora. Systémy auditor podporují vzdálené volání přes rozhraní Hessian. Upravte tedy aplikaci tak, aby místo posílání logu na standardní výstup volala službu Notify na vzdáleném serveru. Jako vzdálený systém nám bude sloužit projekt SpringDemo-server. V souboru remotingservlet.xml vytvořte beanu typu SysOutNotifier, pojmenujte ji notifier a následně pomocí tohoto kusu XML vystavte pro vzdálený přístup: Z příkazové řadky z adresáře SpringDemo-server spusťte mvn jetty:run a ověřte že poslední řádka končí [INFO] Started Jetty Server V projektu SpringDemo-banking upravte applicationContext.xml, tak aby Spring do BankServiceLoggingAspect setoval proxy pro vzdálené volání služby přes protokol Hessian. Vytvoření proxy pro vzdálený přístup ke službě: Spusťte testy a ověřte že do konzole s jetty probíhá logování business akcí. Úkol č. 6 – Odstranění konfiguračních parametrů z XML Zákazník nyní požaduje flexibilnější konfiguraci. Není akceptovatelné, aby URL pro vzdálenou službu bylo zadrátované natvrdo v XML. Použijte PropertyPlaceholderConfigurer a vytáhněte toto URL do properties souboru. Viz http://www.mkyong.com/spring/spring- propertyplaceholderconfigurer-example/