wtorek, 3 czerwca 2008

Spring MVC + Freemarker

Witam. Dzisiejszy post dedykuję Panu Kazimierzowi z którym przepiłem 22100.

Specjalnie dla Pana Kazimierza zmierzymy się dziś ze Spring MVC.
Generalnie szkielety MVC dzielą się na 2 rodzaje. W jednym z każdą akcją (wysłaniem formularza) związana jest klasa Java, w drugim z każdą stroną związana jest klasa. Spring MVC należy do tych pierwszych. Do tych drugich np. JSF czy Apache Wicket.
Ma to jednak zasadniczą zaletę, strona nie jest związana ze stojącym za nią ziarnem (Java Bean) i


do jednej strony dane można wrzucać na wiele sposobów.

Niestety nie znalazłem prostego archetypu dla aplikacji webowej ze springiem, więc posłużymy się archetypem maven-archetype-webapp. Żeby było ciekawiej, tym razem nie z linii poleceń.
Zainstalowałem bowiem wtyczkę M2Eclipse do mojego Eclipsa 3.3.

1. Tworzenie aplikacji webowej

Teraz wybieram
File/New.../Project
i zaznaczam
Maven project

Następnie wybieram
Next
i zaznaczam odpowiedni archetyp, czyli maven-archetype-webapp.



Podaję Group Id na pl.matt i ArtifactId na aquarium (to będzie nazwa projektu). Wersję ustawiam na 1.0.

Klikam
Finish
i projekt gotowy.

Żeby stworzyć archiwum WAR i skompilować wszystkie klasy (których jeszcze nie ma), klikam prawym przyciskiem myszy (ppm) na projekcie, wybieram
Run As... / Maven install


Projekt zbudowany. Teraz wystarczy skopiować lub podlinkować wara z katalogu target do katalogu webapps Tomcata.
Uruchamiam Tomcata, wpisuję adres:
http://localhost:8080/aquarium/
i widzę piękne witaj świecie.

Aplikacja działa. Czas podłączyć Springa.

2. Konfiguracja SpringMVC
Aplikacja, która powstała jest bardzo prosta. Nie ma nawet żadnego serwletu. Dorzucę do niej springa. Ponieważ korzystam z mavena2 odbędzie się to przez modyfikację pliku pom.xml



<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>aquarium</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>aquarium Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.9</version>
</dependency>
</dependencies>
<build>
<finalName>aquarium</finalName>
<directory>/tmp/target</directory>
<outputDirectory>/tmp/target/classes</outputDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>




Dodałem w sekcji dependencies zależności do springa, springa-webmvx, servlet API i freemarkera.
SpringMVC w warstwie prezentacji może korzystać z wielu technologii, m. in. JSP, szablonów Velocity i Freemarkera. Wybrałem tą ostatnią, bo jeszcze z niej nie korzystałem - może będzie ciekawiej.
Poczyniłem jeszcze pewne zmiany w sekcji build. Tagi directory i outputDirectory pozwalają wskazać katalog, gdzie maven będzie budował aplikację. Ustawiam je na katalog tmp, nie lubię jak mi się coś buduje w przestrzeni roboczej (workspace) środowiska Eclipse. Wtyczka (plugin) maven-compiler-plugin pozwala ustawić poziom kompilacji na Javę w wersji 1.6 (domyślnie jest 1.4).


Jeszcze trochę konfiguracji. Springa można podłączyć do aplikacji webowej na 2 sposoby. Za pomocą serwletu, przez który będą przechodziły żądania HTTP lub za pomocą filtra. Wybieram to pierwsze podejście.
Edytuję plik web.xml:


<?xml version="1.0" encoding="UTF-8"?>

<web-app 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">

<servlet>
<servlet-name>springapp</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>springapp</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>

</web-app>


Dodałem servlet springapp, przez który przejdą wszystkie żądania http do zasobów o rozszerzeniu *.htm. Nimi zajmie się spring. Konfiguracja springa znajdzie się w pliku springapp-servlet.xml (ponieważ nasz serwlet nazwałem springapp). Plik ten trzeba dodać do katalogu WEB-INF



<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<!-- the application context definition for the springapp DispatcherServlet -->

<bean name="/info.htm" class="pl.matt.aquarium.InfoController" />

<!-- freemarker config -->
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath"
value="/freemarker/" />
</bean>

<!--

View resolvers can also be configured with ResourceBundles or XML files. If you need
different view resolving based on Locale, you have to use the resource bundle resolver.

-->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true" />
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />

<!-- if you want to use the Spring FreeMarker macros, set this property to true -->
<property name="exposeSpringMacroHelpers" value="true" />

</bean>

</beans>



Co się tutaj dzieje? W pliku springapp-servlet.xml mamy 2 rzeczy.

<bean name="/info.htm" class="pl.matt.aquarium.InfoController" />
mówi Springowi, że rządania zasobu info.htm będzie realizowała klasa pl.matt.aquarium.InfoController.

Reszta to konfiguracja FreeMarkera. Raczej wiadomo o co chodzi.
Teraz czas stworzyć klasę zarządcy (controller we wzorcu MVC).



package pl.matt.aquarium;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
* @author mateusz
*
*/
public class InfoController implements Controller {

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

int fishCount = (int) (Math.random() * 100);
return new ModelAndView("info", "fishCount", fishCount);
}

}




Każdy zarządca ww SpringMVC implementuje
org.springframework.web.servlet.mvc.Controller. Mój, InfoController, sprawdza (losuje) ile mam rybek w akwarium. Wylosowaną wartość przekazuje do warstwy prezentacji, o nazwie "info".
Stworzę więc i ją - plik info.ftl w katalogu webapp/freemarker

<html>
<body>
<h2>Masz ${fishCount} rybek</h2>
</body>
</html>

Proste, prawda? Żadnych taglibów, ani innych bajerów. Praktycznie czysty HTML. Za fishCount zostanie oczywiście podstawiona wartość wylosowana w klasie InfoController.

Czas to wszystko uruchomić. Ponownie daję maven install. Jeżeli nie podlinkowałeś wara, musisz go ponownie skopiować do katalogu webapps Tomcata.
i wchodzę na adres:
http://localhost:8080/aquarium/info.htm

na koniec konkurs. Ile macie rybek?
Ja mam 25.

4 komentarze:

Mariusz Lipiński pisze...

W JSF "z każdą stroną związana jest klasa" ??? Chyba jednak nie. Możesz mieć np. jedną klasę BBean'a i powiedzmy 10 stron, albo na odwrót.

MZ pisze...

no racja - dzięki za zwrócenie uwagi. Jedna strona i 10 BB też może być na dobrą sprawę. Muszę pomyśleć, jak napisać to co miałem na myśli...

PIG pisze...

Fajny artykuł, przydał się. Ja rybek miałem 23, ale napisałem swoją wersję tylko wzorując się na Twoich przykładach, bez randoma i ta liczba przyszła mi na myśl. Ciekawe że tak blisko:]

Pozdrawiam abstynenta JETI;)
Paweł Ignasiak

kadoel pisze...

Przepiłem całą wypłatę. Ale jak Pan Kazimierz niezwykle to pozytywny człowiek. Jeśli chodzi o art jak zrobić to w NetBeans ?