The Spring Framework Alicja Truszkowska
Czym jest Spring Framework? framework do tworzenia aplikacji J2EE początek: październik 2003 open-source obecna wersja: 1.24 Rod Johnson - “Expert One-on-One J2EE Design and Development”
Dlaczego powstał Spring Framework? Problemy z EJB (2.0!): utrudnione testowanie obiekty nie mogą żyć poza kontenerem narzucone API zależność od serwera aplikacji narzucone nieobiektowe rozwiązania koncentracja na EJB zamiast na obiektach biznesowych konieczność wołania przez JNDI
Założenia twórców Spring Framework aplikacje oparte na POJO koncentracja na obiektach biznesowych brak zależności kodu od API nieskomplikowane API możliwość rozbudowy do własnych potrzeb łatwość testowania niezależność od serwera aplikacji
Architektura Podział na warstwy/moduły kompleksowość integracja niezależność “Nie wynajdujemy ponownie koła.” “Promujemy dobre wzorce”
Architektura
Inversion of Control zależności pomiędzy warstwami zasada: “Don’t call me, I’ll call you.” obsługa zależności leży po stronie kontenera
Jak to wygląda w EJB 2.0 wyszukiwanie przez JNDI interfejsów (interfejs zdalny lub lokalny) try { InitialContext cx; cx = new InitialContext(); Object ref = cx.lookup("ejb/ExampleSession"); sessionBean = (ExampleSessionHome)PortableRemoteObject.narrow(ref, ExampleSessionHome.class); } catch (NamingException e) { System.out.println("cannot get ExampleSession"); }
A gdyby tak... public ClientClass { private ProducerClass producer; public void useProducer(){ producer.getProduct(); }
A gdyby tak... public ClientClass { private ProducerClass producer; public void useProducer(){ producer.getProduct(); } public void setProducer(ProducerClass p){ this.producer = p; public ProducerClass getProducer(){ return producer;
A gdyby tak... to kontener jest odpowiedzialny za dostarczenie obiektu koncentracja na logice, a nie na API brak wywołań JNDI brak NamingException łatwe tworzenie testów jednostkowych
W Springu jest to możliwe! Podstawowym modułem Spring Framework jest IoC Container Bean Factory umożliwia odwoływanie się do obiektów po nazwie lub typie, obsługuje zależności pomiędzy klasami singletony/prototypy setter injection/constructor injection
Przykład źródło danych – exampleDataSource DAO – exampleDao obiekt logiki, korzystający z DAO - exampleBussinessObject Ustalanie zależności w ApplicationContext.xml
Przykład - xml ... <beans> <bean id=”exampleDataSource” class=”org.apache.commons.dbcp.BasicdataSource” destroy-method=”close”> <property name=”driverClassName” value=”com.mysql.jdbc.Driver” /> <property name=”url” value=”jdbc:mysql://localhost”3306/mydb” /> <property name=”username” value=”someone”/> </bean> ...
Przykład - xml <bean id=”exampleDao” class=”example.ExampleDao”> <property name=”dataSource” ref=”exampleDataSource”/> </bean> ...
Przykład - xml <bean id=”exampleBussinessObject” class=”examples.ExampleBussinessObject”> <property name=”myDao” ref bean=”exampleDao”/> <property name=”exampleParam>1</property> </bean> </beans>
Przykład - xml public class ExampleBussinessObject { private ExampleDao dao; private int exampleParam; public void setDao(ExampleDao dao){ this.dao = dao; } public void setExampleParam(int param){ this.exampleParam = param; public void myBussinessMethod() { //do stuff using dao
IoC - podsumowanie konfiguracja xml – możliwości zmian bez konieczności ingerencji w kod większa przejrzystość kodu pozostawienie dostarczenia obiektu kontenerowi łatwe testy jednostkowe silna kontrola typów – eliminacja błędów typu ClassCast jawne zależności w xmlu – bez studiowania kodu większość klas nie wymaga korzystania z API – niezależność od kontenera/serwera
Enterprise Java Beans EJB 3.0 działa na zasadzie IoC w EJB 2.x konieczność wywołań JNDI interfejsów EJB, wywoływanie metod przez RMI kontener EJB zależność kodu od API zależność od serwera aplikacji (deskryptory rozmieszczenia)
Co zyskujemy w Springu? brak wołania przez JNDI (IoC) brak zależności kodu od API (koniec z metodami typu setContext()) niezależność od serwera aplikacji (deskryptory rozmieszczenia, testowanie poza serwerem) łatwe testy jednostkowe (Junit zamiast Cactusa) POJO zamiast EJB – light-weight container
Dostęp do bazy danych Dostęp do bazy danych w J2EE EntityBean (EJB) JDBC O/R Mapping (Hibernate, JDO)
JDBC JDBC – dobry dostęp do danych, ale ciężkie API zarządzanie połączeniami – np. łatwe gubienie sesji SQLException dla wszystkich typów błędów
JDBC a Spring hierarchia wyjątków – naturalne rozróżnianie, co się stało
JDBC a Spring framework zajmuje się przekładaniem wyjątków JdbcTemplate templ = new JdbcTemplate(dataSrc); List names = templ.query(“SELECT name FROM user”, new RowMapper(){ public Object mapRow(Result rs, int rowNum) throws SQLException; return rs.getString(1); } });
JDBC a Spring proste zapytanie int youngUserCount = template.queryForInt( "SELECT COUNT(0) FROM USER WHERE USER.AGE < ?", new Object[] { new Integer(25) });
O/R Mapping zasada: “nie wynajdujemy koła” wsparcie dla Hibernate i JDO Zarządzanie sesjami Zarządzanie zasobami Zarządzanie transakcjami Opakowanie wyjątków
Dostęp do bazy danych podsumowanie mały narzut uproszczone API zarządzanie połączeniami (sesjami) koniec z wyciekami połączeń mniej kodu brak potrzeby używania finally hierarchia wyjątków zamiast komunikatów JDBC naturalna implementacja DAO brak zależności od technologii
Kilka słów o AOP... popatrzmy na aplikację pod kątem powtarzających się zadań podział aplikacji na moduły, połączone tam, gdzie się krzyżują Definicje: aspekt punkt złączenia punkt przecięcia przykład: logowanie
Programowanie aspektowe główny cel: nadanie POJO możliwości, które stwarza J2EE działa na serwerach aplikacji lub po prostu kontenerach serwletów wsparcie dla przechwytywania metod nie przechwytuje odwołań do pól Realizacja: dynamiczne proxy, o ile jest interfejs do klasy generacja kodu za pomocą CGLIB
Integracja z AspectJ Można włączać aspekty AspectJ do aplikacji Od wersji 1.1 możliwość dodawania aspektów poprzez IoC, jak zwykłych klas Javy
AOP – przykłady użycia w Spring zarządzanie transakcjami – nadajemy POJO możliwości EJB ! scentralizowana obsługa wyjątków kontrola dostępu - Acegi Security for Spring ( framework do kontroli dostępu oparty na AOP, zintegrowany ze Springiem) wysyłanie powiadomień e-mail do administratora monitorowanie i logowanie
MVC MVC = Model + View + Controller np. Struts Widok Model Dispatcher aktualizuje Model wybiera Dispatcher Kontroler używa wybiera Widok np. Struts
Spring MVC Framework “We don’t believe in reinventing wheels, except if they are not perfectly round.” Rod Johnson
Dlaczego nie Struts? większa elastyczność interfejsy zamiast nadklas zestaw klas abstrakcyjnych trudności w testowaniu wyraźniejszy podział na model, kontroler i widok interceptory (brak w Struts 1.1) niezależność od widoku (JSP,Velocity,XLST) walidacja może należeć do logiki warstwa widoku jest cieńsza
Najprotszy kontroler public class GoogleSearchController implements Controller { private IGoogleSearchPort google; private String googleKey; public void setGoogle(IGoogleSearchPort google) { this.google = google; } public void setGoogleKey(String googleKey) { this.googleKey = googleKey; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String query = request.getParameter("query"); GoogleSearchResult result = google.doGoogleSearch(this.googleKey, query, start, maxResults, filter, restrict,safeSearch, lr, ie, oe); return new ModelAndView("googleResults", "result", result);
Najprostszy kontroler Parametry są przekazane przez IoC – izolacja kontrolera Brak wołania obiektów logiki Można zmieniać obiekty, z których korzysta interfejs (na przykład do testowania) Zmiana logiki nie wymaga zmian w kodzie interfejsu
Przykład...
web.xml <web-app> ... <servlet> <servlet-name>example</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <url-pattern>*.htm</url-pattern> </servlet-mapping> </web-app>
example-servlet.xml [servletName]-servlet.xml Specjalne obiekty: handler mapping(s) controller(s) view resolver locale resolver theme resolver multipart resolver handlerexception resolver
example-servlet.xml <bean id="formController" class="example.MyFormController"> <property name="commandName" value=”formBeanId”/> <property name="commandClass" value=”example.FormBean”/> <property name="validator"> <ref local="MyValidator"/> </property> <property name="formView" value=”form”/> <property name="successView" value=”form”/> </bean> <bean id="urlMapping" class="org.springframework.web.servlet.handler .SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/form.htm">formController</prop> </props>
“Logika” public class Fruit { private static Map instanceMap = new HashMap(); public static final Fruit APPLE = new Fruit(0, "Apple"); public static final Fruit ORANGE = new Fruit(1, "Orange"); private long id; private String name; private Fruit(Long id, String name) { this.id = id; this.name = name; instanceMap.put(new Long(id), this); } ......
Klasa beana formularza public class FormBean { private String name; private Fruit fruit; public void setFruit(Fruit fruit) ... public Fruit getFruit() ... public void setName(String name) ... public String getName() ... }
Kontroler formularza public class MyFormController extends SimpleFormController { protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder)throws Exception { binder.registerCustomEditor(Fruit.class, new FruitSupport()); } protected Map referenceData(HttpServletRequest request) throws Exception { Map retval = new HashMap(); retval.put("fruits", Fruit.getAll()); return retval; protected ModelAndView onSubmit( HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { FormBean form = (FormBean)command; return showForm(request, response, errors);
PropertyEditor private class FruitSupport extends PropertyEditorSupport { public String getAsText() { Object value = getValue(); return value == null ? "":((Fruit)value).getId().toString(); } public void setAsText(String string) throws IllegalArgumentException { try { Long id = Long.valueOf(string); Fruit f = Fruit.getById(id); if (f == null) { throw new IllegalArgumentException("Invalid id: " + string); setValue(f); } catch (NumberFormatException ex) {
jsp <form method="post"> <spring:bind path="formBeanId.name"> Name: <input name="name" value="<c:out value="${status.value}"/>"><br> </spring:bind> <spring:bind path="formBeanId.fruit"> Favorite Fruit: <select name="fruit"> <c:forEach var="fruitType" items="${fruits}"> <option value="<c:out value="${fruitType.id}"/>" <c:if test="${fruitType.id == status.value}"> selected="selected" </c:if> > <c:out value="${fruitType.name}"/> </option> </c:forEach> </select> <input type="submit" name="Submit" value="Submit"> </form>
Testowanie możliwość testowania poza serwerem aplikacji jednostkowość (można przez IoC przekazać atrapę) brak skomplikowanych zależności, takich jak wywołania JNDI
Testowanie wsparcie ze strony Spring możliwość definiowania zależności do testów jednostkowych w pliku xml wielokrotne wykorzystywanie cache’owanie stanu kontenera pomiędzy testami (oszczędzanie czasu na długotrwałe inicjalizacje serwisów - np. JDBC connection pool lub Hibernate SessionFactory możliwość wywoływania rollback po każdym teście – nie musimy się martwić o stan bazy
Podsumowanie Spring rozwiązuje wiele typowych problemów J2EE. Spring propaguje dobre wzorce projektowe. Spring koncentruje się na obiektach biznesowych. Główne załozenia: IoC i POJO Środowisko: kompletne, podzielone na samodzielne moduły Obejmuje wszystkie warstwy Wspiera AOP Posiada unikalną warstwę dostępu do danych Posiada własny, rozszerzalny i elastyczny MVC
Literatura www.springframework.org Rod Johnson: Expert One-on-One J2EE Design and Development Rod Johnson, Juergen Hoeller: J2EE without EJB Bruce Tate, Justin Gehtland A Developer's Notebook : Introduction to Spring Rod Johnson, Juergen Hoeller, Alef Arendsen, Thomas Risberg, Colin Sampaleanu Professional Java Development with the Spring Framework