NetBeans + Maven: Integracja Spring Framework 3 z Struts2
W dzisiejszym poście zaprezentuję jak rozpocząć pracę z Spring Framework 3 oraz Struts2, wykorzystując do tego środowisko NetBeans (u mnie 6.9.1) oraz Maven 2. Jak zwykle zaprezentuję całą ścieżkę, łącznie z problemami jakie napotkałem
Aby utworzyć projekt należy wybrać przejść kolejne kroki kreatora:
New Project -> Maven -> Maven Project
Z listy “Maven Archetypes” wybieramy “Archetypes from Local Respository” i zaznaczamy “Maven Webapp Archetype (1.0)“. W kolejnym oknie wypełniamy odpowiednie pola i po kliknięciu zostanie wygenerowany projekt. Pierwszy krok to dodanie zależności do pliku pom.xml (dostępny w gałęzi “Project Files“).
Do wykonania i uruchomienia projektu potrzebne są podstawowe paczki z org.springframework, paczka struts2-core, oraz plugin pozwalający na współpracę między Spring a Struts.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-asm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>${org.apache.struts.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>${org.apache.struts.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>${org.apache.struts.version}</version>
</dependency>
</dependencies>
Jak widać zamiast wersji bibliotek użyłem ${org.springframework.version} oraz ${org.apache.struts.version}, oczywiście celowo – żeby w jednym miejscu zdeklarować numer wersji:
<properties>
<org.springframework.version>3.0.4.RELEASE</org.springframework.version>
<org.apache.struts.version>2.2.1</org.apache.struts.version>
</properties>
Po zapisaniu pliku można w menu kontekstowego gałezi “Libraries” wybrać “Download Missing Dependencies“.
Kolejny krok to utworzenie przykładowego kontrolera i konfiguracja Springa oraz Struts.
W tym celu w gałęzi “Source packages” tworzymy dowolną paczkę i dodajemy prostą klasę, niech to będzie klasa z akcją “Hello world”.
package eu.ryznar;
public class Hello {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String execute() {
return "SUCCESS";
}
}
W “Web Pages” tworzymy plik success.jsp:
<html>
<body>
<h2>Success</h2>
${message}
</body>
</html>
Teraz czas na konfigurację. W web.xml listener dla Springa oraz filter dla Struts2
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
Przeglądając różne tutoriale możecie trafić na przykład gdzie zamiast org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter została użyta klasa org.apache.struts2.dispatcher.FilterDispatcher. To zależy od wersji Struts2. Od wersji 2.1.3 FilterDispatcher jest oznaczona jako przestarzała.
Następnie w katalogu WEB-INF tworzymy plik applicationContext.xml w którym skonfigurujemy ziarna, w tym przypadku klasę “Hello”.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloClass" class="eu.ryznar.Hello" >
<property name="message" value="Hello World!" />
</bean>
</beans>
Następnie również w katalogu WEB-INF tworzymy plik struts.xml w którym definiujemy która klasa wykonuje akcję i zwraca odpowiedni widok.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="default" extends="struts-default">
<action name="hello" class="helloClass">
<result name="SUCCESS">/success.jsp</result>
</action>
</package>
</struts>
Atrybut “class” nie wskazuje bezpośrenio na klasę eu.ryznar.Hello a na ziarno zdefiniowane w pliku konfiguracyjnym Springa.
Przed uruchomieniem należy ustawić serwer. We właściwościach projektu, w gałęzi “Run” wybieramy serwer Tomcat.
To wszystko, czas uruchomić aplikację. Jednak czeka nas niespodzianka
w konsoli dostaniemy taki oto wyjątek:
2010-10-11 21:01:56 org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
2010-10-11 21:01:57 org.apache.catalina.core.StandardContext filterStart
SEVERE: Exception starting filter struts2
java.lang.reflect.InvocationTargetException - Class: com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector
File: ContainerImpl.java
Method: inject
Line: 295 - com/opensymphony/xwork2/inject/ContainerImpl.java:295:-1
at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:428)
at org.apache.struts2.dispatcher.ng.InitOperations.initDispatcher(InitOperations.java:69)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.init(StrutsPrepareAndExecuteFilter.java:51)
at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:295)
at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:422)
at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:115)
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:3838)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4488)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:546)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:637)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:521)
at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1359)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:297)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:836)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:761)
at org.apache.catalina.manager.ManagerServlet.check(ManagerServlet.java:1500)
at org.apache.catalina.manager.ManagerServlet.deploy(ManagerServlet.java:849)
at org.apache.catalina.manager.ManagerServlet.doGet(ManagerServlet.java:351)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:199)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:558)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.inject(ContainerImpl.java:295)
at com.opensymphony.xwork2.inject.ContainerImpl$ConstructorInjector.construct(ContainerImpl.java:431)
at com.opensymphony.xwork2.inject.ContainerBuilder$5.create(ContainerBuilder.java:207)
at com.opensymphony.xwork2.inject.Scope$2$1.create(Scope.java:51)
at com.opensymphony.xwork2.inject.ContainerBuilder$3.create(ContainerBuilder.java:93)
at com.opensymphony.xwork2.inject.ContainerBuilder$7.call(ContainerBuilder.java:487)
at com.opensymphony.xwork2.inject.ContainerBuilder$7.call(ContainerBuilder.java:484)
at com.opensymphony.xwork2.inject.ContainerImpl.callInContext(ContainerImpl.java:574)
at com.opensymphony.xwork2.inject.ContainerBuilder.create(ContainerBuilder.java:484)
at com.opensymphony.xwork2.config.impl.DefaultConfiguration.createBootstrapContainer(DefaultConfiguration.java:252)
at com.opensymphony.xwork2.config.impl.DefaultConfiguration.reloadContainer(DefaultConfiguration.java:193)
at com.opensymphony.xwork2.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:66)
at org.apache.struts2.dispatcher.Dispatcher.init_PreloadConfiguration(Dispatcher.java:371)
at org.apache.struts2.dispatcher.Dispatcher.init(Dispatcher.java:415)
... 41 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.opensymphony.xwork2.inject.ContainerImpl$MethodInjector.inject(ContainerImpl.java:293)
... 54 more
Caused by: java.lang.ExceptionInInitializerError
at com.opensymphony.xwork2.ognl.OgnlValueStackFactory.setContainer(OgnlValueStackFactory.java:85)
... 59 more
Caused by: java.lang.IllegalArgumentException: Javassist library is missing in classpath! Please add missed dependency!
at ognl.OgnlRuntime.(OgnlRuntime.java:165)
... 60 more
Caused by: java.lang.ClassNotFoundException: javassist.ClassPool
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1516)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1361)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:169)
at ognl.OgnlRuntime.(OgnlRuntime.java:162)
... 60 more
2010-10-11 21:01:57 org.apache.catalina.core.ApplicationContext log
INFO: Closing Spring root WebApplicationContext
Co się okazuje w wersji 2.2.1 wykluczono z zależności Javassist, dlatego trzeba dodać zależność ręcznie w pom.xml
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.9.0.GA</version>
</dependency>
Próbujemy jescze raz uruchomić projekt. W przeglądarce zostaje otworzony plik index.jsp, jednak my chcemy zobaczyć wynik akcji hello, dlatego w pasku adresu zamiast index.jsp wpisujemy hello.action. Niestety strona się nie wyświetla a w konsoli widzimy kolejny wyjątek.
There is no Action mapped for namespace / and action name helloWorld. - [unknown location]
at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:189)
at org.apache.struts2.impl.StrutsActionProxy.prepare(StrutsActionProxy.java:61)
at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:39)
at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:58)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:475)
at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:393)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
Tym razem okazuje się że plik struts.xml powinien znajdować się w /WEB-INF/classes/ a nie tak jak teraz bezpośrednio w /WEB-INF/
Po tej poprawce projekt uruchomi się poprawnie. Mam nadzieję że wpis pomoże jakiemuś początkującemu programiście
Jeśli ktoś miałby jakieś uwagi to chętnie je poznam
Za ten hint z przesunieciem STRUTS.XML do podkatalogu masz u mnie pifko
Wprawdzie post ma już rok, ale co tam skomentuje.
Natrafiłem na Twoj post bo właśnie przerabiam temat integracji SPRING3 i STRUTS2. Tak się składa, że ja również używam mavena do zarządzania swoim projektem. Używam SPRING w wersji:3.0.6.RELEASE, STRUTS: 2.2.3.1 w tej samej plugina integrującego. Przeglądając Twoje rozwiązanie, zauważyłem że umknął Ci jeden bardzo ważny fakt, otóż projekt: struts2-spring-plugin w wersji: 2.2.1 zależy od: spring-beans w wersji: ${struts2.springPlatformVersion}; spring-core w wersji: ${struts2.springPlatformVersion}; spring-context w wersji: ${struts2.springPlatformVersion}; spring-web w wersji: ${struts2.springPlatformVersion}; spring-test w wersji: ${struts2.springPlatformVersion}
Properta ta zdefiniowana jest w projekcie parent: struts2-parent i ma wartość: 2.5.6, zatem projekt, który stworzyłeś de facto używa dwóch wersji SPRINGA: 3.0.4.RELEASE i 2.5.6.
Żeby ożywać jednej wersji musisz w definicji zależności struts2-spring-plugin wykluczyć projekty springowe.
@Adrian dzięki za uwagę