Maven kezdőlépések
Frissítés: 2021. december 20.
Bevezetés
Ebben a posztban egy rövid bevezetőt találsz a Mavennel kapcsolatban. Nem magyarázom a fogalmakat, csupán jelzem, hogy mely fogalmakat kell ismerni.
A Mavennel kapcsolatban a legtöbbet megfogalmazott kritika az, hogy a tanulási görbéje nem elég meredek, azaz ahhoz, hogy jól használjuk, sokat kell tudni róla. Ezt tükrözik sajnos a publikus repository-kban található silány minőségű artifactok is, amiből látszik, hogy ugyan sokan használják a Mavent, de kevesen ismerik. Ezzel azonban a többi fejlesztő dolgát keserítik meg.
Források
Ezért azt javaslom, hogy amennyiben valaki Mavent akar használni,
és pom.xml
-t akar írni,
egy-két bevezető jellegű cikk után olvasson el két könyvet.
Egyik a Maven by Example, a másik a Maven: The Complete Reference, mely a Sonatype-nál ingyen elolvasható, és egyik szerzője Jason Van Zyl, a Maven megalkotója.
Sonatype-nak ezen kívül magyar vonatkozása is van, hiszen a Nexus elődjét Cservenák Tamás fejlesztette Proximity néven, és eztán kérte fel a Sonatype, hogy készítsen egy hasonló terméket, Nexus néven.
Fogalmak
A Maven definíció szerint “software project management and comprehension
tool”. Tehát nem egy egyszerű build tool, melyben az Anthoz hasonlóan
leírjuk az egymás utáni lépéseket, hanem annál sokkal fejlettebb,
magasabb szintű megoldás. Alapja a project object model (POM), melyet
XML-ben leírva tudunk megadni (pom.xml
), és ez alapján a Maven képes
további leírások nélkül elvégezni a build folyamatot, különböző
riportok, dokumentációk, gyakorlatilag a teljes projekt website-jának
legenerálását. A Maven nem titkolt célja, hogy a Java fejlesztők körében
egyfajta szabvány legyen, mely a konvenciók megtartását előnybe helyezi
az egyéni konfigurációkkal szemben (convention over configuration).
Sajnos a Javaban nincs szabvány arra, hogy hogyan kell
egy projektet felépíteni, a Maven ezt az űrt próbálja betölteni. Azaz ha
tartjuk magunkat egy projekt struktúrához, azaz a különböző
állományainkat a megfelelő könyvtárakban helyezzük el, a Maven képes lesz
elvégezni a build folyamatot, a források lefordítását, a különböző
erőforrás állományok megfelelő helyre másolását, és az alkalmazásunk
összecsomagolását.
A Maven alapfogalma a projekt, mely a koordináták alapján van
azonosítva, melyek a következők: groupId, artifactId, version,
packaging, classifier. Pl. a Log4J esetén a groupId log4j
,
artifactId log4j
és a version 1.2.16
. A többi megadása opcionális. A groupId
megadásánál elterjedt az egyedi azonosítás miatt a domain név megadása,
mint a Java package-eknél (pl. org.springframework
). A projektek
egymástól öröklődhetnek, valamint van lehetőség több modulból álló
projektek megadására is (mindegyik modul saját pom.xml
-lel). A
pom.xml
-ben lehet megadni a projektre jellemző olyan információkat is,
mint a licence, szervezet, fejlesztők, közreműködők, valamint olyan
fejlesztési környezet információkat is, mint a verziókezelő, issue
management, continuous integration eszköz elérése, stb. Ráadásul a
projektre jellemző POM, az ún. effective POM több részből épül fel.
Egyrészt a Super POM, mely a Maven adott verziójának a része, a szülő POM-jából,
valamint a projekthez tartozó POM-ból aggregálva.
A Maven nagy előnye ezen kívül, hogy beépített mechanizmus van a különböző projekt függőségek definiálására és feloldására (pl. tranzitív függőség, A projekt függ B-től, B projekt függ C-től, akkor a C-re is szükség van). Itt fontos ismerni a scope fogalmát. Sokat segíthet a függőségi fa kirajzolása. A függőségek megadásánál használhatunk pontos verziószámokat, de akár verzióintervallumokat is (range). Megadhatóak opcionális függőségek is, melyeket nem muszáj igénybe vennünk.
A Maven build folyamat kimenetele az artifact. Ezen artifact 3rd party library esetén a JAR állomány, webes alkalmazás esetén a WAR állomány, nagyvállalati alkalmazás esetén az EAR állomány, stb. A Maven megkülönbözteti a release és snapshot fogalmát, ahol az előző a kiadható verzió, míg az utóbbi a fejlesztés közben használatos verzió.
Az artifactokat egy központi helyen, ún. repository-ban helyezi el,
hierarchikus rendben. A legtöbb kritikai itt éri a Mavent, hogy miért
is nem jó a függő library-k tárolása egyszerűen a verziókövető
rendszerben, miért kell ehhez egy külön repository-t használni. A Maven
használhat publikus repository-kat, valamint használ az adott gépen egy
lokális repository-t is, ez alapesetben a felhasználó home könyvtárának
.m2/repository
könyvtárában található. Ezen kívül a repository
managerek elterjedésével (pl. Nexus vagy Artifactory) érdemes
kialakítani egy cégen belül használatos belső repository-t is, melyben
tárolhatóak a saját projektek állományai, valamint egyfajta proxy-ként
működnek a publikus repository-k felé. Az alapértelmezett publikus
repository a Maven Central Repository, mely elérhető a
https://repo1.maven.org/maven2/ címen. A repository-k is
csoportosíthatók, hogy release vagy snapshot verziókat tárolnak-e.
A Maven teljesen moduláris felépítésű, minden ún. Maven pluginben van
megvalósítva. Ezen Maven pluginek is artifactok. Ezért van az, hogy a
Maven telepítőcsomagja alig pár mega, az összes többi funkcionalitást
biztosító plugint (artifact) akkor tölt le, amikor szükség van rá.
Persze emiatt az első futtatás tovább tarthat, hiszen ekkor nagy
mennyiségű artifactot kell letöltenie. A pom.xml
-ben ezen plugineket
konfigurálhatjuk is. A pluginek tulajdonképpen összetartozó goalok
gyűjteménye, ahol egy goal egy egyedi, önmagában is futtatható funkció.
Ilyen pl. a compiler plugin compile
goalja, ami a forrás állományok
fordításáért felelős. Valamint a Mavenben vannak definiálva ún.
életciklusok (lifecycle). Pl. build lifecycle (default), site és clean
lifecycle. Minden lifecycle fázisokból áll, és minden fázishoz
alapértelmezetten tartozik egy vagy több plugin goal. Pl. a build
lifecycle egyik fázisa package
, mely a jar plugin jar
goalját hívja meg
bizonyos esetekben. Persze ezt magunk személyre is szabhatjuk. Első
feladat megérteni a Maven tanulásakor, mikor kiadunk egy parancsot, mi
is fut le, milyen életciklus milyen fázis, milyen plugin milyen goalja.
A pluginek kaphatnak paramétereket.
A Maven előnye, hogy nagyban megkönnyíti a csapattagok kommunikációját,
valamint a fejlesztésbe bekapcsolódást, ugyanis képes kigenerálni
különböző riportokat (pom.xml
-ben definiálandó), dokumentációt, a
projekt site-ját (természetesen nagymértékben testreszabható módon).
Ezért különösen elosztott, open source fejlesztésre különösen alkalmas.
A Mavenben vannak ún. profilok is, melyek a build folyamat
portabilitását biztosítják különböző build környezetekben. Pl. lehet,
hogy másképp kell, hogy működjön a build folyamat különböző
architektúrákon, JDK verziókon és operációs rendszereken, teszt és élest
környezetben, sőt akár különböző fejlesztőeszközök
használatakor.
Lehetőség van arra, hogy mi adjuk meg parancssorból a profilt (-P
kapcsolóval), de van profile activation is, ahol valamely környezeti
beállítás hatására automatikusan egy profil kerül kiválasztásra.
A másik érdekesség az assembly-k használata. A Maven beépítetten ismeri
a gyakoribb csomagolási módokat, a packagingtől függően JAR, WAR, EAR
(bin
). Képes arra is, hogy olyan distributiont készítsen, amibe bele
vannak csomagolva a függő osztályok is (jar-with-dependencies
), valamint
képes a teljes projektet becsomagolni (project
), valamint csak a
forráskódot kiadni (src
). Ezen kívül saját assembly-t is definiálhatunk
assembly descriptor megadásával.
A Mavenben a személyes kedvencem az archetype. Az archetype egy projekt sablon, melyből létre lehet hozni egy üres projektet különböző paraméterek megadásával. Ha gyakran indítunk projektet, érdemes egy ilyent gyártanunk, mely a megfelelő kiindulási alapot biztosítja. Vannak beépített archetype-ok is, ezelből is sok ötletet lehet meríteni. Ezek is artifactok, azaz a repository-ban is elhelyezhetők.
A Mavenben ezen kívül lehetőség van saját pluginek implementálására is. Egy goalt egy mojo-ban implementálhatunk (Maven plain Old Java Object). Egy plugin mojo-k gyűjteménye.
A Maven könnyen integrálható az összes continuous integration eszközzel.
Tippek és személyes tapasztalatok
Ide a személyes tapasztalataimat próbálom összegyűjteni, és azokat a problémákat és megoldásaikat, melyekkel egy Maven felhasználó először találkozik. Ezek egy részével a FAQ is foglalkozik.
Java verzió
Alapesetben a Maven compiler plugin a Java 1.4-re fordít, ahhoz, hogy az
1.5 újdonságait ki tudjuk használni, át kell ezt állítani, pl. az
1.6-os verzióra a pom.xml
-ben. Ezt a property-t felolvassa a Maven
compiler plugin.
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
Kevés memória
Amennyiben a Maven build vagy site generálás hibát dob
(java.lang.OutOfMemoryError: Java heap space
), hogy nincs elég memória,
akkor be kell állítanunk a MAVEN_OPTS
környezeti változót.
MAVEN_OPTS="-Xmx1024m -Xms512m"
Karakterkódolás
Az ékezetes karakterek használata itt is problémát okoz, így hogy ezt elkerüljük, mindenütt deklarálni kell a karakterkódolást. Javasolt az UTF-8 használata.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Ezt a property-t szintén felolvassa a Maven compiler plugin.
Amennyiben nem adjuk meg, a Maven warningot ír, hogy nincs megadva a karakterkódolás, emiatt a build környezetfüggő, mert az operációs rendszer default kódolását használja.
Hiányzó artifact
Amennyiben egy artifact hiányzik a repository-ból, a következő paranccsal tudjuk oda installálni:
mvn install:install-file
-Dfile=
-DgroupId=
-DartifactId=
-Dversion=
-Dpackaging=
-DgeneratePom=true
Azaz pl az Oracle jdbc driver esetén:
mvn install:install-file
-Dfile=ojdbc6-11.2.0.1.0.jar
-DgroupId=com.oracle
-DartifactId=ojdbc6
-Dversion=11.2.0.1.0
-Dpackaging=jar
-DgeneratePom=true
Utána persze ezen koordinátákkal kell hivatkozni rá a függőség megadásánál.
Debug mód
Futtassuk a Mavent a -X
kapcsolóval.
Teszt esetek
A Maven Surefire pluginja tud futtatni JUnit és TestNG
teszt eseteket is. A kizárólag teszteléshez szükséges függőségeket test
scope-pal kell elhelyezni a függőségek között. Meg lehet adni, hogy
amennyiben egy teszt eset hibázik, a build még folytatódjon
(Surefire plugin testFailureIgnore
tulajdonsága). Valamint azt is
meg lehet adni, hogy ne futtassa le a teszt eseteket
(–Dmaven.test.skip=true
vagy -DskipTests=true
parancssori kapcsolóval,
de a pom.xml
-ben is megadható Surefire plugin skipTests
property-jének
állításával). Ha csak egy teszt esetet akarunk futtatni, használjuk a
-Dtest=MyTest
kapcsolót (csomag megadása nélkül).
Artifact neve
Alapesetben a Maven artifact neve a koordinátákból jön, azonban war
állomány esetén ez nem lenne szerencsés, így felüldefiniálható a pom.xml
-ben
a finalName
taggel.
Riportok generálása
A site lifecycle része a riportok generálása is. Azonban, ha mi a site
többi részére vagyunk kíváncsiak, és nem akarjuk a riportokat
kigeneráltatni, akkor ezt megtehetjük a -DgenerateReports=false
kapcsoló használatával.
Repository manager
Egy cégen belül mindenképp alkalmazzunk repository manager eszközt. Ez egyrészt tartalmazza a saját artifactjainkat, másrészt egy proxy a publikus repositry-k felé. Webes felületen menedzselhetőek (artifact keresés és feltöltés), valamint mindenféle jogosultsági szinteket tudunk megadni, és képesek archiválni a repository-t. Érdemes az Artifactory és a Nexus közül választani.
Log4j 1.2.15
A repository-k minőségét jellemzi a Log4J 1.2.15 verziója, melyben a
javax.mail:mail
, javax.jms:jms
, com.sun.jdmk:jmxtools
és a
com.sun.jmx:jmxri
artifactok kötelező függőségként voltak megadva. Ezen
jar-ok használata pedig nem kötelező, csak akkor szükséges, ha a
hozzájuk tartozó megfelelő appendereket akarjuk használni, a JMX meg
már része a JDK-nak. Szerencsére ezt azóta
javították.
IDE integráció - NetBeans
A Maven NetBeans integrációját gyakran használtam, és igen jónak tartom, nem ütköztem vele problémába. Kicsit féltem tőle, mert elsődleges szempont a gyors fejleszt-tesztel iterációs ciklus, és régebbi verzióval voltak gondjaim. Most ilyenről nem számolhatok be. Nem jelentősen lassabb a beépített Antra épülő build folyamatnál, hiszen mindegyik egy külső, megfelelően integrált eszköz. A MavenBestPractices cikk rengeteg jó tanácsot ad a NetBeans felhasználóknak.
IDE integráció - Eclipse
Az Eclipse esetén az m2eclipse hatékonyságáról hallottam rossz híreket, különösen nagy, több modulból álló projektek esetén, így azt nem próbáltam, helyette a maven-eclipse-plugin plugin-t használtam, mely a POM alapján képes módosítani az Eclipse projekt állományokat. A recept egyszerű:
- Hozzuk létre az Eclipse workspace-t
- A
mvn eclipse:configure-workspace -Declipse.workspace=C:/workspace
paranccsal konfiguráljuk a workspace-t (aC:\workspace\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.jdt.core.prefs
állományban létrehoz egyorg.eclipse.jdt.core.classpathVariable.M2_REPO=C:\Documents and Settings\jtechlog\.m2\repository
bejegyzést, szóval létrehoz egyM2_REPO
változót, mely a repository helyére mutat) - A
mvn eclipse:eclipse
paranccsal generáltassuk le a projekt állományokat, melyekkel már megnyitható a projekt Eclipse-ben - Amikor webes projektet hoztam létre, még a következő konfigurációt
is el kellett helyeznem a
pom.xml
-ben.
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<wtpmanifest>true</wtpmanifest>
<wtpapplicationxml>true</wtpapplicationxml>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
Számomra meglepően jól működött az, hogyha változott a pom.xml
-ben valami,
csak újrafuttattam az eclipse:eclipse goal
parancsot, majd az Eclipse-ben
újraolvastattam a projektet. Ha a NetBeans nem Ant alapú lenne, ott is
jobban preferálnám ezt a módot.
Code complete
Különösen meglepő volt számomra, hogy mind a NetBeansben, mint az
Eclipse-ben elérhető a pom.xml
code complete, sőt, működik a dependency
megadásnál is, ugyanis automatikusan felajánlja az elérhető artifactok
koordinátáit. A NetBeans esetén alul a státuszsorban látszik, hogy a
hálózathoz kapcsolódik, és átviszi a repository indexét.
Provided
Ha szükségünk van egy JAR-ra a fordításhoz, de nem akarjuk, hogy pl.
webes alkalmazásnál a WAR-ba kerüljön, mert futtatáskor a konténer úgyis
biztosítja azt a classpath-on ( ilyen pl. a servlet.jar
, mail.jar
, stb.),
akkor használjuk a provided
scope-ot.
Hiányzó artifact
Abban az esetben, ha nem tudjuk a pontos koordinátákat a függőség megadásakor, érdemes valamilyen keresőt használni. Nekem ezek jöttek be:
Létező NetBeans projektről átállás
Két projektben is a NetBeans projekt állományai mellé felvettem egy
pom.xml
-t is, hogy a Maven bevezethető legyen, de párhuzamosan mindkét build
folyamat működjön. Ez azért körülményes, mert a könyvtárstruktúra nem a
Maven konvencióinak felel meg. A cél az volt, hogy bitre megegyező war
állományt állítson elő a NetBeans és a Maven. Az időm 90%-ában azon
dolgoztam, hogy pont azokat a JAR-okat, és pont annyit tegyen oda, mint
az eredeti projektben voltak. Az, hogy a könyvtárnevek eltértek, semmi
gondot nem okozott.
Egy kis statisztika:
Technológia: | Spring, Struts, JSP |
JAR-ok száma az eredeti projektben: | 35 |
Dependency tag-ek a POM-ban: | 30 |
Exclusions tag-ek nélkül a JAR-ok száma: | 62 |
Exclusions tag-ek a POM-ban: | 17 (!!!) |
Egy repository-ban sem talált JAR-ok száma, melyeket kézzel kellett telepíteni: | 3 |
Ahhoz, hogy a NetBeanses projekt azonnal leforduljon a Mavennel is, a
következő konfigurációkat kellett a pom.xml
-ben megtenni:
<build>
<!-- WAR neve -->
<finalName>app</finalName>
<sourceDirectory>src/java</sourceDirectory>
<plugins>
<!-- Fentebb említett 1.6-os Java és UTF-8 kódolások -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>web</warSourceDirectory>
</configuration>
</plugin>
</plugins>
</build>