Próba połączenia JSF i Spring - pierwszy kontakt (czyli historia pierwszego uderzenia głową w ścianę)...

Pierwsze problemy z deploy-em

A więc przyszedł czas żeby wprowadzić w życie dawno już powzięty zamiar nauczenia się JSF. Świadomy trudności z jakimi stawia się pierwsze kroki, zaposiłkowany w kilka tutoriali webowych przystąpiłem do realizacji małego projekciku będącego próbką wykorzystania JSF w połączenie ze Springiem - springjsfsample.
Na samym początku zaciągnąłem referencyjną Sun-owską wersję JSF w wersji 1.2 (http://java.sun.com/javaee/javaserverfaces/download.html) i spróbowałem odpalić jeden z dołączonych do nich sampli - jsf-guessNumber. Archiwum wrzuciłem do katalogu deploy i przepełniony radosną niecierpliwością oczekiwałem na reakcje serwera (JBoss 4.0.5). Zachował się przewidywalnie - splunął mi w twarz olbrzymim StackTrace(m):
...
11:35:09,489 ERROR [[/jsf-guessNumber]] StandardWrapper.Throwable
java.lang.IllegalStateException: No Factories configured for this Application. This happens if the faces-initialization does not work at all - make sure that you properly include all configuration settings necessary for a basic faces application and that all the necessary libs are included. Also check the logging output of your web application and your container for any exceptions!
If you did that and find nothing, the mistake might be due to the fact that you use some special web-containers which do not support registering context-listeners via TLD files and a context listener is not setup in your web.xml.
A typical config looks like this;
<listener>

<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
...

Sytuacja nie była za ciekawa. Pierwsze zderzenie z JSF, próba zadeployowania (he ale po polskiemu ! ) aplikacji o złożoności godnej HelloWorld i taki ZONK! Wszedłem w etap intentsywnego googlowania. Pierwszy ślad natrafiłem na stronie JBoss(http://wiki.jboss.org/wiki/Wiki.jsp?page=JBossWithIntegratedMyFaces). Okazuje się że JBoss przychodzi z własną implementacją JSF - MyFaces. Producent przestrzega, że deploy aplikacji, która sama w sobie zawiera biblioteki JSF może zakończyć się porażką. Na szczeście na samym dole znajduje się zdanie, które daje nadzieję na przyszłość:

"Using the JSF Reference Implementation
To use the JSF Reference Implementation instead of the bundled MyFaces implementation, simply delete the jbossweb-tomcat55.sar/jsf-lib directory. Then, package the RI in your WEB-INF/lib directory as usual.
Note: You may also need to delete temp directories such as server/default/tmp and server/default/work.
"

No i wszystko jasne... zrobiłem tak jak mówili.. ale niestety aplikacja z sampla ciągle nie chciała ruszyć...

Postanowiłem zarzucić pomysł deployu standardowych przykładów. Wróciłem do modyfikacji własnej aplikacji -springjsfsample- mając wielkie nadzieje że niedługo powie mi Hello :).

Na początek postanowiłem poczytać trochę w jaki sposób zintegrować JSF-a ze Springiem. Jeden z pierwszych linków, na które trafiłem zaprowadził mnie do projektu jsf-spring - wydawała mi się ona rozwiązaniem którego szukałem. Dostarcza ona bibliotek które mają posłużyć do integracji frameworków. Na stronie projektu znalazłem też ciekawy quickstart, którym postanowiłem się zaposiłkować. W miarę jego realizacji przyszło mi do głowy pewna wątpliwość, a mianowicie czytałem że spring integruje się z jsfem w sposób intuicyjny więc po co wprowadzać dodatkowe biblioteki pośredniczące o których w ogóle nic nie wiem? Pomyślałem że jak uda mi się cokolwiek uruchomić to poszukam innego sposobu połączenia... ale póki co przerobiłem manual, zrobiłem deploy i otrzymałem:

...
11:28:54,870 ERROR [STDERR] 2007-05-12 11:28:54 com.sun.faces.config.ConfigureListener registerELResolverAndListenerWithJsp
SEVERE: Error Instantiating ExpressionFactory
java.lang.ClassNotFoundException: com.sun.el.ExpressionFactoryImpl
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1355)
...

Niby mówi wszystko ale nie do końca bo w standardowej dystrybucji JSF 1.2 pobranej ze strony Sun-a nie ma Jar-a, w którym znalazłaby się ta klasa. Coś tu jest nie tak...

Po solidnej porcji googlania, na jednym z forów trafiłem na opis problemu podobnego do mojego (http://forum.java.sun.com/thread.jspa?threadID=780619&messageID=4463090). Po kolejnych przybliżeniach uczestnicy forum doszli do rozwiązania:

"you can use jsf 1.1 - it works with jsp 2.0 (tomcat 5)
yep jsf 1.2 needs jsp 2.1 to work
jsf 1.2 should work on tomcat 5 too using faclets"

Wiedziony nadzieją zrobiłem tak napisał twórca postu, wróciłem do JSF 1.1, podmieniłem wszystkie Jary w WEB-INF/lib i EUREKA!!!! udało się - dostałem wyjątek którego nazwy już w tej chwili nie pamiętam wyrzucony przez którąś z klas z biblioteki pośredniczącej spring-jsf - a więc poszedłem krok dalej. Zagadka problemów z JSF-em na serwerze JBoss wydała się być rozwiązana.

Realizacja aplikacji:

Ale niestety moja aplikacja springjsfsample ciągle nie działała, więc postanowiłem napisać ją po swojemu,ale po kolei.
Po pierwsze należało zrobić gruntowne porządki w WEB-INF/lib i wyczyścić ślady wszystkich poprzednich eksperymentów. Zostawiłem to co wydawało mi się konieczne, a więc:

  • Biblioteki pochodzące z dystrybucji Springa (spring.jar,cglib-nodep-2.1_3.jar)
  • Biblioteki pochodzące z dystrybucji JSF 1.1 (commons-beanutils.jar,commons-collections.jar, commons-digester.jar,commons-logging.jar, html_basic.tld, jsf-api.jar, jsf-impl.jar, jsf-spring.jar, jsf_core.tld)
  • Biblioteki JSTL (jstl-1.2.jar) - są one wymagane w przypadku używania Sun-owskiej wersji JSF-a
Aplikacja ma być najprostsza jaka tylko może być więc będzie składała się z czterech podstawowych elementów:

  1. IHello.java - interfejs który będzie pełnił rolę interfejsu biznesowego
  2. HelloBean.java - bean będący implementacją intefejsu biznesoweg IHello
  3. UiBean.java - JSF-owy Managed Bean
  4. index.jsp - strony jsp

Opis koncepcji:
Mój pomysł był taki, by wykorzystać Springa jako kontener IoC, do zarządzania wszystkimi zależnościami, a JSF miałby pełnić rolę frameworku MVC. Punktem styku pomiędzy obiektami biznesowymi zarządzanymi przez Springa a frameworkiem JSF miałby być obiekt UiBean. To do niego powinien zostać wstrzyknięty interfejs IHello za którego pośrednictwem ten JSF Managed Bean miałby dostęp do warstwy biznesowej.

Pliki aplikacji:

IHello.java

package mw;
import java.util.Date;

public interface IHello {
Date getNow();
String sayHello();
}

HelloBean.java

package mw;
import java.util.Date;
public class HelloBean implements IHello {
public Date getNow() {
return new Date();
}

public String sayHello() {
// TODO Auto-generated method stub
return "I'm your Spring Managed Bean - I'm saying hello to you!";
}
}

UiBean.java

package mw;
import org.springframework.beans.factory.InitializingBean;
public class UiBean implements InitializingBean {
private IHello helloService = null;

public void afterPropertiesSet() throws Exception {}

public String getHello() {
return helloService.sayHello();
}

public IHello getHelloService() {
return helloService;
}

public void setHelloService(IHello helloService) {
this.helloService = helloService;
}
}


Pliki konfiguracyjne:

Na początek WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>springjsfsample</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*</url-pattern>
</servlet-mapping>

<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>

WEB-INF/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="uiBean" class="mw.UiBean">
<property name="helloService"><ref bean="helloBean"/></property>
</bean>
<bean id="helloBean" class="mw.HelloBean"/>
</beans>

WEB-INF/faces-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
<application>
<variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>

<managed-bean>
<managed-bean-name>uiBean</managed-bean-name>
<managed-bean-class>mw.UiBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>helloService</property-name>
<value>#{helloBean}</value>
</managed-property>
</managed-bean>
</faces-config>

Opis integracji Spring i JSF:

(zastosowany przeze mnie sposób integracji podpatrzyłem w bardzo ciekawym artykule "Spring into JavaServer Faces" autorstwa Michaela Klaene)

Istotą integracji jest niewielki wpis w pliku faces-config.xml :

<application>
<variable-resolver>org.springframework.web.jsf.DelegatingVariableResolver</variable-resolver>
</application>

Powoduje to rozszerzenie możliwości standardowego obiektu VariableResolver, którego zadaniem jest bindowanie obiektów znajdujących się w odpowiednim application scope(session, request).
Element DelegatingVariableResolver jest wywiedziony bezpośrednio z obiektu javax.faces.el.VariableResolver i powoduje, że do zakresu poszukiwań obiektów do bindowania włączony zostanie Spring root WebApplicationContext. Należy zwrócić uwagę na fakt, że nazwa, podana jako klucz wyszukiwania musi być zgodna z identyfikatorem beana zdefiniowanym na poziomie kontekstu springowego, a więc: aby nastąpiło prawidłowe zaincjalizowanie opisane następująco:

<managed-property>
<property-name>helloService</property-name>
<value>#{helloBean}</value>
</managed-property>

W kontekście springa musi znaleźć się definicja beana helloBean, która może wyglądać np. tak (oczywiście pomijam fakt, że nasz managed bean musi mieć właściwość o nazwie helloService i metody dostępowe do niej bo to wydaje się oczywiste):

</bean>
<bean id="helloBean" class="mw.HelloBean"/>
</beans>



Plik WEB-INF/index.jsp

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view>
<html>
<head>
<title>Spring quickstart</title>
</head>
<body>
<h:outputText value="#{uiBean.hello}"/>
</body>
</html>
</f:view>

W pliku tym następuje sięgnięcie do property hello w Managed Bean - UiBean, a w konsekwencji za jego pośrednictwem sięgnięcie do interfejsu biznesowego IHello zarządzanego przez Spring.

Uruchomienie aplikacji

Po kompilacji i pomyślnym deploy-u czas na osteczną organoleptyczną ocenę poprawności:

Po wpisaniu adresu: http://localhost:3333/springjsfsample/index.jsf

otrzymałem efekt:


A więc powtarzając za Mistrzem Boratem :
Great Success :)))))

9 komentarzy:

Mariusz Lipiński pisze...

A czemu chcesz używać Springa do IoC a nie JSF?

mario pisze...

Używam Springa ponieważ warstwa biznesowa całej aplikacji będzie dosyć rozbudowana. Wstrzyknięcia nie będą ograniczały się tylko do do inicjalizacji Managed Beans. JSF będzie odpowiedzialny tylko za prezentację, grom pracy dla kontenera IoC będzie skoncentrowane w niższych warstwach - już poza zasięgiem JSF.

madness pisze...

fajne howto... dzieki :) ale, ale - daloby rade zrobic cos podobnego z richfaces?
http://www.infoq.com/news/2007/09/richfaces

Omen pisze...

Bardzo mi pomogłeś :) Świetnie ze dzielisz sie swoja wiedzą :) Dzięki

msq pisze...

JSF-Spring nie dziala niestety ze springiem w wersji 2.5.X ;(

Jacek pisze...

Obecnie używam RichFaces ze względu na łatwą i efektywną integrację ze Spring Framework i ajaxowe przetwarzanie. Szkielety Ajax RichFaces, Spring Framework i Hibernate Validator dają się idealnie integrować. W moim artykule opisałem sposób wykorzystania anotacji szkieletu Hibernate Validator w ramach stron jspx wykorzystujacych odpowiedni komponent RichFaces. Dodałem także aplikacje przykładową, może okaże się pomocna.

Anonimowy pisze...

Zabrakło wyjaśnienia po co łączy się Springa i JFS ? Czy osobno te technologie są niewystarczające ?

Anonimowy pisze...

[url=http://louboutinshop.co.uk]christian louboutin shoe sale[/url] Buy Zune for its ease of use and WiFi capabilities. [url=http://dkgoose.com]canada goose[/url] Viwenqnkf [url=http://canadagoosesweden.com]canada goose jacka[/url]
fktklu 587330 [url=http://www.canadagoosestorontofactory.ca]canadien goose[/url] 803340 [url=http://www.officialcanadagooseparkas.ca]canada goose in toronto[/url]

Anonimowy pisze...

XohSjg http://sinsakuchanel.com/ JmlTsz [url=http://sinsakuchanel.com/]シャネル バッグ コピー[/url] QudMmz http://ninnkicoach.com/ RoaNhx [url=http://ninnkicoach.com/]ジャパンコーチ公式ファクトリー[/url] AhaFrf http://diorautoretto.com/ NwgTgl [url=http://diorautoretto.com/]ディオール バッグ[/url] KneWqv http://nihongucci.com/ GxkRau [url=http://nihongucci.com/]グッチ アウトレット 大阪[/url] ZivZks http://longchampnihon.com/ RmyPxz [url=http://longchampnihon.com/]ロンシャン 店舗 大阪[/url] NkePao http://sinsakuvuitton.com/ LmyQmb [url=http://sinsakuvuitton.com/]ルイヴィトン 財布 ダミエ[/url] KwkNcq http://gekiyasuprada.com/ FsyHal [url=http://gekiyasuprada.com/]プラダ ポーチ リボン[/url] OxsQpc http://uggsinsaku.com/ HhhVwa [url=http://uggsinsaku.com/]楽天 UGG メンズ[/url]