Clojure w aplikacji webowej Java EE 6 z Eclipse Helios, Apache Maven i Apache Tomcat
Z Jacek Laskowski - Wiki Projektanta Java EE
W końcu dane mi było rozpracować temat użycia Clojure w ramach aplikacji webowej Java EE 6 (JEE). Jak się okazało, problemem było niezbyt uważne (!) przyjrzenie się możliwościom Clojure jako języka (funkcyjnego), który "z pudełka" można uruchomić na wirtualnej maszynie Javy (JVM), a więc integracja z innymi aplikacjami javowymi jest po prostu "wliczona w kontrakt". I tutaj najwyraźniej zabrakło mi cierpliwości, aby wydedukować rozwiązanie - skoro produktem Clojure jest bajtkod Javy, to Clojure jest po prostu innym językiem do pisania aplikacji javowych. Możnaby założyć proces tworzenia oprogramowania, które składamy z klocków javowych (reprezentowanych przez bajtkod), ale wyprodukowanie ich zależy już od naszych upodobań - może to być Java, Groovy, JRuby, JavaScript, Scala, czy właśnie Clojure. Są to w końcu języki programowania, a ich produkt - bajtkod - jest dokładnie tym, co potrafimy uruchomić na JVM. Tu tkwiło zrozumienie problemu, a rozwiązanie przyszło po tym same.
I tak było wczoraj, kiedy zajrzałem do folderu grupy dyskusyjnej clojure i zacząłem przeglądać wiadomości, które mogłyby mi pomóc w zrozumieniu, jak zastosować Clojure w aplikacjach JEE. Czytałem kolejne wątki i tak schodził mi mile czas :) Jak zwykle, możnaby powiedzieć. Kilka postów o Clojure, o wydaniu Clojure 1.2 Beta 1 i różnych mniej istotnych problemach, aż tu nagle po lekturze jednego, nieoczekiwanie, w mojej głowie pojawiła się odpowiedź! Właśnie zrozumiałem, że Clojure to w pewnym momencie bajtkod Javy, a więc wszystko sprowadza się do takiego przygotowania środowiska, aby składając aplikację, skrypty Clojure były już w postaci bajtkodu. To było to! Po chwili miałem plan działania, aby stworzyć aplikację webową z logiką biznesową napisaną w Clojure.
Plan działania:
- Stworzyć projekt zbiorczy z Apache Maven
- Stworzyć projekt biblioteki pomocniczej w Clojure z Apache Maven
- Stworzyć klasę w Clojure, która wyświetla ciąg znaków - Witaj ...
- Stworzyć projekt aplikacji webowej z Apache Maven
- Stworzyć servlet, który wyświetla ciąg znaków z klasy w Clojure
- Zbudować paczkę war do wdrożenia na serwer i jej uruchomienie
- Imprezka?
Wymagane oprogramowanie:
- Eclipse Helios (3.6) for Java EE Developers - zintegrowane środowisko programistyczne, które wykorzystam w zastępstwie NetBeans IDE 6.9. Po ostatnich doświadczeniach z nim, postanowiłem spróbować innego IDE i przekonano mnie do Eclipse (zamiast planowanej IntelliJ IDEA).
- m2eclipse - wtyczka Eclipse umożliwiająca pracę z Apache Maven
- Apache Tomcat 7.0 - lekki serwer aplikacyjny JEE6, który stanowi środowisko uruchomieniowe dla naszej aplikacji webowej
- (opcjonalnie) counterclockwise - wtyczka Eclipse udostępniająca wsparcie dla Clojure
Może pojawić się jeszcze pytanie o powód użycia Apache Maven, skoro korzystam z Eclipse. Wybór padł na Apache Maven z uwagi na dwie kwestie - możliwość zestawienia projektu wielomodułowego i zarządzania nim poza IDE (dosyć wątpliwy argument za Mavenem mając Eclipse IDE, w którym moglibyśmy wykonać wszystkie potrzebne rzeczy) oraz wykorzystanie wtyczki clojure-maven-plugin, która pozwala na obsługę kroku kompilacji kodu w Clojure do bajtkodu (kluczowy aspekt całego rozwiązania).
W połączeniu z wtyczką Eclipse - counterclockwise możemy zbudować bardzo pomocne środowisko do programowania w Clojure. Z counterclockwise (CCW) mamy wsparcie dla programowania w Clojure w Eclipse, a z clojure-maven-plugin w Apache Maven. Obecnie oba wydają się być nie do pobicia. Nie obyło się jednak bez zgrzytów - widoczność klas Clojure w javowych nie jest doskonała i podczas integracji obu zdarza się, że Eclipse nie podpowiada typów Clojure w klasach "czystojavowych".
Po tym przydługim wstępie jesteśmy gotowi merytorycznie do zbudowania naszej pierwszej aplikacji webowej z Clojure i JEE6 w Eclipse z Mavenem i uruchomić ją na Tomcacie.
Kompletny projekt jest dostępny jako clojure-webapp.zip.
Utworzenie projektu zbiorczego w Eclipse i Apache Maven - clojure-webapp
Uruchomienie asystenta nowego projektu z pomocą m2eclipse
Obsługą projektów mavenowych w Eclipse zajmuje się wtyczka m2eclipse. Zakładam, że jej instalację mamy już za sobą.
Zakładamy projekt główny clojure-webapp. Wciskamy Cmd+n (File > New > Other...) i wybieramy Maven Project w kategorii Maven.
Zatwierdzamy wybór przyciskiem Next >.
Domyślna konfiguracja jest wystarczająca, więc wciskamy przycisk Next >.
Z listy archetypów (szablonów projektowych) wybieramy maven-archetype-quickstart.
Wciskamy przycisk Next >.
Podajemy dane projektu:
- Group Id: pl.jaceklaskowski.clojure
- Artifact Id: clojure-webapp
- Package: pl.jaceklaskowski.clojure
Wciskamy przycisk Finish. Projekt powinien wyglądać, jak na zdjęciu poniżej.
Zmiana konfiguracji projektu - edycja pom.xml
Zmieniamy konfigurację projektu edytując plik pom.xml - zmieniamy Packaging na pom.
Zapisujemy zmiany.
Opcjonalnie kasujemy katalogi źródłowe - src/main/java i src/test/java, które nie mają znaczenia w projekcie typu pom.
Wprowadzamy kilka zmian w konfiguracji używanej wersji Javy (konfiguracja wtyczki maven-compiler-plugin) oraz repozytoria, gdzie znajdują się wykorzystywane zależności.
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>pl.jaceklaskowski.clojure</groupId> <artifactId>clojure-webapp</artifactId> <packaging>pom</packaging> <version>0.0.1-SNAPSHOT</version> <name>clojure-webapp</name> <url>http://jaceklaskowski.pl/wiki/Clojure_w_aplikacji_webowej_Java_EE_6_z_Eclipse_Helios,_Apache_Maven_i_Apache_Tomcat</url> <build> <pluginManagement> <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> </pluginManagement> </build> <repositories> <repository> <id>java.net</id> <url>http://download.java.net/maven/2</url> </repository> <repository> <id>glassfish - java.net</id> <url>http://download.java.net/maven/glassfish</url> </repository> </repositories> <dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> </project>
Zmiana wersji Javy w projekcie w Eclipse
Nie zapominamy o zmianie wersji JRE używanego w projekcie - domyślnie jest to wersja 1.4, a potrzebujemy co najmniej 1.6. Na poziomie Mavena jest to już zrealizowane przez konfigurację wtyczki maven-compiler-plugin i najwyraźniej ta informacja nie jest przekazywana Eclipse.
Z menu kontekstowego projektu clojure-webapp wybieramy Build Path > Configure Build Path...
Utworzenie projektu biblioteki pomocniczej w Clojure - clojure-utils
Utworzenie projektu potomnego clojure-utils w Eclipse
Wybieramy projekt clojure-webapp i wciskamy Cmd+n. Wybieramy Maven Module w kategorii Maven.
Next > i podajemy nazwę nowego podprojektu clojure-utils, w którym będziemy programowali logikę naszej aplikacji webowej w Clojure.
Ponownie Next > i w panelu Select an Archetype wybieramy maven-archetype-quickstart. I jeszcze raz Next >.
Podajemy parametry projektu.
Zatwierdzamy przyciskiem Finish.
UWAGA: W ten sposób możemy doświadczyć błędu m2eclipse, który objawia się tym, że po utworzeniu projektu potomnego clojure-utils projekt macierzysty staje się niepoprawny - pom.xml jest niepełny. Wystarczy uzupełnić konfigurację projektu edytując plik clojure-webapp/pom.xml i uzupełniając Group Id, Artifact Id, Version or Packaging.
Docelowo widok Package Explorer powinien przedstawiać się jak poniżej.
Edycja pliku pom.xml projektu clojure-utils
W pliku pom.xml wprowadzamy zmiany dotyczące wykorzystywanej wersji biblioteki junit (dziedziczona jest z projektu nadrzędnego clojure-webapp) oraz włączamy wtyczkę clojure-maven-plugin z zadeklarowaniem zależności od Clojure 1.1.0. Wtyczka clojure-maven-plugin oraz biblioteka clojure 1.1.0 znajdują się w centralnym repozytorium Mavena, więc jedynym wymaganiem jest podłączenie do Sieci.
Zmiany można wprowadzić korzystając z edytora POM w Eclipse (dostarczany przez m2eclipse), albo po prostu edytować plik jak typowy plik XMLowy (w zakładce pom.xml edytora POM).
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>clojure-webapp</artifactId> <groupId>pl.jaceklaskowski.clojure</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>pl.jaceklaskowski.clojure</groupId> <artifactId>clojure-utils</artifactId> <version>0.0.1-SNAPSHOT</version> <name>clojure-webapp :: Projekt pomocniczy z Clojure</name> <url>http://www.jaceklaskowski.pl/wiki</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>com.theoryinpractise</groupId> <artifactId>clojure-maven-plugin</artifactId> <version>1.3.3</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test</id> <phase>test</phase> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.clojure</groupId> <artifactId>clojure</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
Utworzenie klasy w Clojure - pl.jaceklaskowski.clojure.witaj.clj
Obsługą Clojure w projekcie zajmie się wtyczka clojure-maven-plugin. Dzięki niej będziemy mogli zapomnieć o temacie kompilacji kodu pisanego w Clojure, czy uruchomieniu odpowiednich klas testowych pisanych w tym języku. Wtyczka znajduje się w oficjalnym repozytorium Mavena, więc wystarczy ją zadeklarować w pom.xml, przypisać do odpowiednich etapów w domyślnym cyklu mavenowym przy budowaniu i tyle.
Włączenie obsługi projektu przez wtyczkę counterclockwise (CCW)
Z tak przygotowanym projektem mavenowym możemy rozpocząć programowanie w Clojure. Tworzenie skryptów jest znacznie uproszczone, kiedy włączymy wcześniej obsługę projektu przez wtyczkę counterclockwise (CCW).
Po włączeniu wsparcia wtyczki CCW pojawi się komunikat...
...oraz pliki jar dla clojure oraz clojure-contrib.
Niech Cię nie zmyli, że raz dodane biblioteki Clojure do pom.xml to włączenie ich w CCW, albo odwrotnie. Jeśli życzymy sobie, aby bibilioteki Clojure trafiły do pliku pom.xml, którym zarządza Maven, to dodanie ich za pomocą CCW nic nie da. W obecnej sytuacji mamy wsparcie dla Clojure oferowane przez dwa środowiska - Eclipse (counterclockwise) i Apache Maven (clojure-maven-plugin). Obie nie wiedzą o sobie (a chciałoby się, aby jednak wiedziały), więc biblioteki Clojure dodawane są dwukrotnie (i w zależności od wersji wtyczek potencjalnie w różnych wersjach - rozwojowa wersja CCW dodaje rozwojową wersję clojure).
Utworzenie struktury katalogowej dla skryptów Clojure - src/main/clojure
W domyślnej konfiguracji clojure-maven-plugin skrypty Clojure znajdują się w katalogu src/main/clojure. Musimy go założyć i tam będziemy tworzyć nasz skrypt witaj.clj (rozszerzenie .clj ma znaczenie). Skrypt należy do przestrzeni (coś na wzór pakietu w Javie) pl.jaceklaskowski.clojure.witaj, a więc (podobnie jak miałoby to miejsce w Javie) umieszczamy go w katalogu pl/jaceklaskowski/clojure/witaj.
Najpierw tworzymy katalog źródłowy - Cmd+n, a później Source Folder w kategorii Java.
Definiujemy katalog src/main/clojure.
Opcjonalnie możemy skasować katalogi src/main/java oraz src/main/test, których nie będziemy potrzebować.
Utworzenie pakietu pl.jaceklaskowski.clojure
W katalogu src/main/clojure utworzymy coś na wzór pakietu w Javie - pl.jaceklaskowski.clojure. Cmd+n i wybieramy Package w kategorii Java.
DOBRA RADA: Nie tworzymy struktury katalogowej przez wybór Folder w kategorii General, gdyż faktycznie utworzony katalog będzie nazywał się pl.jaceklaskowski.clojure, a to nie odpowiada naszym oczekiwaniom.
Podajemy nazwę pakietu.
Zatwierdzamy przyciskiem Finish.
Powinniśmy otrzymać strukturę katalogową jak na poniższym obrazku.
Utworzenie skryptu Clojure - witaj.clj
Z tak stworzonym katalogiem (ok, ok, pakietem) możemy stworzyć w nim skrypt Clojure. Mając zaznaczony pakiet pl.jaceklaskowski.clojure, wciskamy Cmd+n i wybieramy Clojure File w kategorii Clojure.
Wciskamy przycisk Next > i podajemy nazwę pliku - witaj.clj.
Zatwierdzamy przyciskiem Finish.
Zmieniamy skrypt witaj.clj tak, aby prezentował się jak poniżej.
(ns pl.jaceklaskowski.clojure.witaj (:gen-class :methods [[przywitajSie [String] String]])) (defn -przywitajSie [this imie] (str "Witaj " imie "!"))
Zgodnie z kontraktem integracji Clojure z JVM opisane w Ahead-of-time Compilation and Class Generation, generowanie klasy na podstawie definicji :gen-class polega na umieszczeniu jej w pakiecie odpowiadającym wartości elementu ns oraz utworzeniu metod publicznych - tych, które określiliśmy w :methods *oraz* tych poprzedzonych myślnikiem. Zainteresowani mogą przejrzeć bajtkod dla poznania wnętrza generowanej klasy.
devmac:clojure-utils jacek$ javap -classpath target/classes pl.jaceklaskowski.clojure.witaj public class pl.jaceklaskowski.clojure.witaj extends java.lang.Object{ public static {}; public pl.jaceklaskowski.clojure.witaj(); public java.lang.Object clone(); public int hashCode(); public java.lang.String toString(); public boolean equals(java.lang.Object); public java.lang.String przywitajSie(java.lang.String); public static void main(java.lang.String[]); }
Kompletna struktura katalogowa projektu clojure-utils
Ostatecznie struktura projektu clojure-utils powinna przedstawiać się jak na poniższym obrazku.
(nieudane) Uruchomienie skryptu przez counterclockwise z Clojure REPL
Działanie skryptu Clojure możemy sprawdzić przy pomocy wbudowanej obsługi CCW - uruchomienia skryptu w Clojure REPL.
I tutaj kolejna uwaga - dodawane biblioteki Clojure mogą różnić się wersjami i CCW nie dodaje clojure-src.jar oraz clojure.jar, jeśli wcześniej zdefiniujemy zależność w pom.xml od Clojure 1.1.0. To jednak kończy się błędem uruchomienia Clojure REPL, właśnie ze względu na rozbieżność wersji bibliotek.
Exception in thread "main" java.lang.VerifyError: class clojure.contrib.repl_ln$loading__4354__auto__ overrides final method meta.()Lclojure/lang/IPersistentMap; at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) at java.lang.ClassLoader.defineClass(ClassLoader.java:616) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141) at java.net.URLClassLoader.defineClass(URLClassLoader.java:283) at java.net.URLClassLoader.access$000(URLClassLoader.java:58) at java.net.URLClassLoader$1.run(URLClassLoader.java:197) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:248) at clojure.contrib.repl_ln__init.load(Unknown Source) at clojure.contrib.repl_ln__init.<clinit>(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:247) at clojure.lang.RT.loadClassForName(RT.java:1516) at clojure.lang.RT.load(RT.java:389) at clojure.lang.RT.load(RT.java:371) at clojure.core$load__6449$fn__6458.invoke(core.clj:4171) at clojure.core$load__6449.doInvoke(core.clj:4170) at clojure.lang.RestFn.invoke(RestFn.java:413) at clojure.lang.Var.invoke(Var.java:359) at clojure.contrib.repl_ln.<clinit>(Unknown Source)
Możliwym rozwiązaniem jest wcześniejsze przetestowanie skryptów Clojure, a dopiero później zdefiniowanie zależności w pom.xml, albo (dosyć karkołomne, ale spełnia oczekiwania CCW i clojure-maven-plugin) stworzenie dwóch projektów dla Clojure - jeden z pom.xml, który kompiluje drugi ze skryptami Clojure. W takiej konfiguracji włączamy obsługę Clojure przez CCW w Eclipse jedynie dla drugiego. Propozycje ciekawszych rozwiązań mile widziane.
(udane) Uruchomienie skryptu przez clojure-maven-plugin z clojure:repl
Dla tych wszystkich, którzy dotrwali do tego miejsca i są żądni wiedzy nt. Clojure poprzednie nieudane uruchomienie wynika z niezgodności bibliotek i bez "fikołów" nie da się tego łatwo obsłużyć. Jeśli jednak masz zainstalowanego Apache Maven istnieje możliwość skorzystania z niego do uruchomienia Clojure REPL z linii poleceń. Wykorzystujemy do tego możliwości clojure-maven-plugin i jego zadań clojure:compile oraz clojure:repl.
devmac:clojure-utils jacek$ mvn clean clojure:compile clojure:repl
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building clojure-utils
[INFO] task-segment: [clean, clojure:compile, clojure:repl]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean {execution: default-clean}]
[INFO] Deleting directory /Users/jacek/Documents/eclipse-workspace/clojure-webapp/clojure-utils/target
[INFO] [clojure:compile {execution: default-cli}]
Compiling pl.jaceklaskowski.clojure.witaj to /Users/jacek/Documents/eclipse-workspace/clojure-webapp/clojure-utils/target/classes
[INFO] Preparing clojure:repl
[INFO] [resources:resources {execution: default-resources}]
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/jacek/Documents/eclipse-workspace/clojure-webapp/clojure-utils/src/main/resources
[INFO] [compiler:compile {execution: default-compile}]
[INFO] Nothing to compile - all classes are up to date
[INFO] [clojure:compile {execution: compile}]
Compiling pl.jaceklaskowski.clojure.witaj to /Users/jacek/Documents/eclipse-workspace/clojure-webapp/clojure-utils/target/classes
[INFO] [clojure:repl {execution: default-cli}]
Clojure 1.1.0
user=> *clojure-version*
{:major 1, :minor 1, :incremental 0, :qualifier ""}
user=> (.przywitajSie (new pl.jaceklaskowski.clojure.witaj) "Jacek")
"Witaj Jacek!"Utworzenie projektu aplikacji webowej z Apache Maven - webapp
Pamiętając nasze problemy z utworzeniem projektu biblioteki pomocniczej jako modułu mavenowego, tym razem skorzystamy z innej możliwości tworzenia modułu - przez główny plik pom.xml.
Otwieramy pom.xml z projektu głównego clojure-webapp, w którym wciskamy ikonkę w górnym, prawym rogu sekcji Modules (proszę nie pomylić tego z przyciskiem Create..., który z niewyjaśnionych dla mnie przyczyn nie działa w obecnej wersji, tworząc jedynie projekt ze znakiem zapytania).
Podajemy nazwę projektu webapp, aby w kolejnym kroku wskazać na archetyp maven-archetype-webapp do utworzenia struktury projektu aplikacji webowej zarządzanej przez Maven.
Wciskamy przycisk Next >
I kończymy Finish.
W utworzonym projekcie webapp wprowadzamy zmiany w jego pom.xml - konfigurację wtyczki wtyczka maven-war-plugin dla aplikacji webowej oraz (opcjonalnie) samą nazwę projektu.
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>clojure-webapp</artifactId> <groupId>pl.jaceklaskowski.clojure</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <groupId>pl.jaceklaskowski.clojure</groupId> <artifactId>webapp</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>clojure-webapp :: Aplikacja webowa</name> <url>http://www.jaceklaskowski.pl/wiki</url> <build> <finalName>webapp</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.1-beta-1</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
Struktura projektów powinna przedstawiać się jak poniżej.
Utworzenie servletu pl.jaceklaskowski.clojure.WitajServlet
Zadeklarowanie zależności clojure-utils
Rozpoczynamy od zadeklarowania zależności między projektem aplikacji webowej webapp a clojure-utils.
Otwieramy pom.xml projektu webapp. W zakładce Dependencies, w sekcji Dependencies wciskamy przycisk Add.
Wpisujemy clojure-utils w polu Enter groupId, artifactId or sha1 prefix or pattern (*) i wybieramy zależność pl.jaceklaskowski.clojure.clojure-utils-0.0.1-SNAPSHOT.jar.
Zatwierdzamy wybór przyciskiem OK.
Zadeklarowanie zależności Java Servlet API 3.0 - geronimo-servlet_3.0_spec
Podobnie jak wcześniej z clojure-utils, deklarujemy zależność projektową org.apache.geronimo.specs.geronimo-servlet_3.0_spec-1.0.jar z klasami Java Servlet 3.0 (to jest ta część artykułu, która uzasadnia użycie terminu Java EE 6 w tytule :-)). Istotnym jest, aby wybrać provided w polu Scope (u dołu okienka dialogowego Select Dependency), gdyż wskaże tę zależność jako potrzebną w projekcie, ale dostarczaną przez środowisko (w naszym przypadku serwer aplikacyjny Apache Tomcat), a więc niepotrzebną do umieszczenia w produkcie projektu, czyli pliku war.
Z niewiadomych mi przyczyn dodanie tej zależności kończy się błędem braku w repozytorium (z którego jak rozumiem wcześniej została pobrana do wybrania!). Rozwiązaniem jest zmiana wartości Type na jar.
Utworzenie servletu pl.jaceklaskowski.clojure.WitajServlet w Eclipse
Rozpoczynamy od założenia katalogu źródłowego src/main/java, w którym utworzymy servlet.
Podajemy src/main/java w polu Folder name.
Zatwierdzamy przyciskiem Finish.
Cmd+n i w kategorii Web wybieramy Servlet.
Wciskamy przycisk Next >. Podajemy dane nowego servletu.
Wciskamy przycisk Finish.
Modyfikujemy servlet tak, aby korzystał z udogodnień JEE6, czyli skorzystamy z adnotacji @WebServlet (mimo, że servlet już został automatycznie zarejestrowany przez Eclipse w deskryptorze web.xml - skasujemy go za moment).
Do klasy WitajServlet dodajemy adnotację @WebServlet("/witaj"), która instruuje serwer aplikacyjny, że ma do czynienia z servletem związanym (zmapowanym) z adresem /witaj. Dodatkowo, w metodzie doGet, wywołujemy naszą klasę pisaną w Clojure. Wynik wypisujemy do przeglądarki.
I tu jest właśnie nasza integracja między światem Java EE 6 (klasa javowa, która jest servletem) a Clojure (w postaci skryptu witaj.clj). Skoro oba to docelowo bajtkod (podobnie jak wiele z bibliotek wykorzystywanych przez nas bez dostępu do źródeł) ich integracja jest taka, jaką znamy od wieków! Zdumiewające, jak wiele czasu musiało upłynąć, aby zrozumieć to. Wierzę, że artykuł sprawi, teraz ta możliwość jest znana szerszemu gronu.
Ostatecznie nasza klasa servletu WitajServlet przedstawia się następująco:
package pl.jaceklaskowski.clojure; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/witaj") public class WitajServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String imie = request.getParameter("imie"); if (imie == null || imie.length() == 0) { imie = "Jacek"; } response.getWriter().print(new pl.jaceklaskowski.clojure.witaj().przywitajSie(imie)); } }
Niestety, z nieznanych mi powodów, nie mamy co liczyć na wsparcie Eclipse odnośnie klas generowanych na podstawie skryptów Clojure. Tu po prostu należy wpisać poprawne wywołanie bez zwracania uwagi na błędy zgłaszane przez Eclipse.
Uruchomienie aplikacji webowej z Clojure na Apache Tomcat
Uruchomienie aplikacji webowej webapp z poziomu Eclipse
Podczas tworzenia servletu WitajServlet, Eclipse automatycznie tworzy deskryptor web.xml na poziomie J2EE 1.4 (Java Servlet 2.3). Wdrożenie aplikacji z tak zdefiniowanym deskryptorem nie instruuje Tomcata do prześwietlenia dostępnych klas w aplikacji webowej i rozpoznanie naszego servletu na poziomie Java EE 6 korzystając z adnotacji @WebServlet.
W projekcie webapp zmieniamy zawartość pliku src/main/webapp/WEB-INF/web.xml tak, aby prezentowała się jak poniżej.
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> </web-app>
Po tej zmianie warto odświeżyć (nawet kilkukrotnie!) konfigurację projektu webapp poprzez menu kontekstowe Maven > Update Project Configuration. W ten sposób w projekcie pojawi się katalog JAX-WS Web Services (!), co świadczy o pomyślnej (?) współpracy Eclipse z m2eclipse.
Zaznaczamy servlet WitajServlet i z menu kontekstowego (pod prawym klawiszem myszki) wybieramy Run As > Run on Server.
Wybieramy istniejący serwer Apache Tomcat lub definiujemy własny (pozostawiam ten krok jako zadanie domowe). Wciskamy przycisk Finish.
I tu może oczekiwać nas niespodzianka. Wszystko zależy od włączonych opcji podczas tworzenia aplikacji z Clojure, w projekcie clojure-utils.
Jeśli korzystaliśmy ze wsparcia wtyczki counterclockwise musimy wykonać kilka czynności "czyszczących" - usunięcie clojure-contrib.jar ze ścieżki klas w projekcie clojure-utils. Należy również wyłączyć katalog classes jako katalog z klasami projektu.
Tym razem uruchomienie aplikacji webowej kończy się pomyślnie.
Uruchomienie aplikacji webowej webapp z poziomu Apache Maven - ręczne wdrożenie WARa
Kolejną opcją uruchomienia aplikacji webapp jest uruchomienie budowania WARa przez Apache Maven. Poniżej zaprezentowane zostało tworzenie aplikacji z poziomu linii poleceń, jednakże możnaby pokusić się o uruchomienie podobnej sesji z wewnątrz Eclipse.
devmac:clojure-webapp jacek$ mvn clean package [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Unnamed - pl.jaceklaskowski.clojure:clojure-webapp:pom:0.0.1-SNAPSHOT [INFO] clojure-utils [INFO] webapp Maven Webapp ... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] ------------------------------------------------------------------------ [INFO] Unnamed - pl.jaceklaskowski.clojure:clojure-webapp:pom:0.0.1-SNAPSHOT SUCCESS [1.994s] [INFO] clojure-utils ......................................... SUCCESS [5.486s] [INFO] webapp Maven Webapp ................................... SUCCESS [8.156s] [INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------
Uruchomienie servletu to wdrożenie paczki, jak to ma miejsce z innymi. Uruchamiamy konsolę administracyjną i rozpoczynamy instalację. Plik war do wdrożenia to webapp/target/webapp.war.
Uruchomienie aplikacji webowej webapp z poziomu Apache Maven - jetty-maven-plugin
Możemy również skorzystać ze wsparcia Maven i Jetty przez wykorzystanie wtyczki jetty-maven-plugin, które pozwalają na uruchomienie aplikacji webowej "w miejscu".
Dodajemy poniższą konfigurację do pom.xml projektu webapp.
<plugins> ... <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> </plugin> </plugins>
Uruchamiamy mvn install w projekcie głównym, aby następnie uruchomić mvn jetty:run w webapp (nie zapomnijmy wcześniej wyłączyć Tomcata w Eclipse, bo doświadczymy konfliktu portu 8080).
devmac:clojure-webapp jacek$ mvn install [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Unnamed - pl.jaceklaskowski.clojure:clojure-webapp:pom:0.0.1-SNAPSHOT [INFO] clojure-utils [INFO] webapp Maven Webapp ... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] ------------------------------------------------------------------------ [INFO] Unnamed - pl.jaceklaskowski.clojure:clojure-webapp:pom:0.0.1-SNAPSHOT SUCCESS [2.587s] [INFO] clojure-utils ......................................... SUCCESS [4.947s] [INFO] webapp Maven Webapp ................................... SUCCESS [1.145s] [INFO] ------------------------------------------------------------------------ [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ devmac:clojure-webapp jacek$ cd webapp/ devmac:webapp jacek$ mvn jetty:run [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Building webapp Maven Webapp [INFO] task-segment: [jetty:run] [INFO] ------------------------------------------------------------------------ ... [INFO] [jetty:run {execution: default-cli}] [INFO] Configuring Jetty for project: webapp Maven Webapp [INFO] webAppSourceDirectory /Users/jacek/Documents/eclipse-workspace/clojure-webapp/webapp/src/main/webapp does not exist. Defaulting to /Users/jacek/Documents/eclipse-workspace/clojure-webapp/webapp/src/main/webapp [INFO] Reload Mechanic: automatic [INFO] Classes = /Users/jacek/Documents/eclipse-workspace/clojure-webapp/webapp/target/classes [INFO] Context path = / [INFO] Tmp directory = /Users/jacek/Documents/eclipse-workspace/clojure-webapp/webapp/target/tmp [INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] web.xml file = null [INFO] Webapp directory = /Users/jacek/Documents/eclipse-workspace/clojure-webapp/webapp/src/main/webapp [INFO] Starting jetty 8.0.0.M1 ... 2010-07-18 22:39:38.831:INFO::jetty-8.0.0.M1 2010-07-18 22:39:39.148:INFO::No Transaction manager found - if your webapp requires one, please configure one. 2010-07-18 22:39:40.435:INFO::Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server
Teraz wystarczy otworzyć przeglądarkę, wskazać adres http://localhost:8080/witaj i kolejny raz delektować się Clojure w aplikacji webowej!
Imprezka? Zapracowałeś/-aś sobie! :-)











































