- COMP.CS.140
- 8. Pakkaukset
- 8.3 Java-projektin hallinta Mavenilla
Java-projektin hallinta Mavenilla¶
Keskitymme tästä eteenpäin käsittelemään Javan perusominaisuuksien sijaan korkeamman tason apuvälineitä Java-ohjelman toteuttamiseen ja hallintaan. Eräs varsin perustavanlaatuinen tarve koskee koodin käännösprosessin ja ulkoisten kirjastojen hallintaa. Tähän tarkoitukseen on olemassa laaja kirjo apuvälineitä. Tässä on valittu tarkasteltavaksi Apache Maven-niminen työkalu, jolla voi määrittää esimerkiksi sen, miten ohjelma käännetään ja mitä ulkoisia kirjastoja ohjelma tarvitsee. Maven lienee yleisin Java-projekteissa käytetty käännöstyökalu, mutta myös esim. Gradle on nykyisin varsin suosittu. Mavenin roolia Java-projektin hallinnassa voisi verrata esim. npm-pakettienhallintaohjelman rooliin Node.js-viitekehystä käyttävässä JavaScript-projektissa. Käsittelemme Mavenia varsin suppeasti pidättäytyen lähinnä kurssin kannalta oleellisimmissa ominaisuuksissa.
Netbeans sisältää Mavenin jo valmiiksi, mutta sen voi asentaa myös erikseen esimerkiksi
Mavenin kotisivuilta tai Linuxissa järjestelmän
pakettienhallintaohjelmalla. Jos Maven on kunnolla asennettu, pitäisi se pystyä suorittamaan
komentorivillä komennolla mvn
.
Maven-projektin rakenne¶
Maven-projekti koostuu pääasiassa kahdesta asiasta: projektin juurihakemistossa sijaitsevasta
Maven-projektin ominaisuudet määrittävästä projektitiedostosta nimeltä pom.xml
, ja
alihakemistossa src
sijaitsevasta projektin lähdekoodista. Java-lähdekoodit sijoitetaan
oletuksena tarkemmin ottaen alihakemistoon src/main/java
, noudattaen muuten tavanomaista tapaa
sijoittaa kooditiedostot pakkauksia vastaaviin alihakemistoihin. Esim. pakkauksen com.example
kooditiedoston SomeClass.java
polku olisi src/main/java/com/example/SomeClass.java
.
Kuten tiedostopääte vihjaa, projektitiedosto pom.xml
kuvaa projektin ominaisuudet XML-muodossa.
Ellei XML ole vielä tuttu käsite, tutustu sen perusominaisuuksiin esim.
Wikipedian artikkelista.
Alla on esimerkkinä melko (joskaan ei aivan) minimaalinen Mavenin projektitiotiedosto:
<?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>fi.tuni.prog3</groupId>
<artifactId>example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
pom.xml
:n täytyy sisältää ainakin seuraavat osat:
project
-juurielementti (alkaa<project>
-alkutagilla ja päättyy</project>
-lopputagiin). Alla luetellut elementit sijaitsevat juurielementin sisällä.modelVersion
-elementti, jonka arvon tulee nykyisin aina olla 4.0.0.Tämä kuvaa, mitä Mavenin standardia (tai “mallia”) projektitiedosto vastaa. Nykyinen on 4.0.0.
groupId
-elementti, joka kertoo projektin ryhmän.Maven voi ryhmitellä projekteja tämän perusteella (hieman samoin kuin pakkaukset ryhmittelevät lähdekooditiedostoja).
artifactId
-elementti, joka kertoo projektin nimen.version
-elementti, joka kertoo kuvaa projektin version.Tämä voisi siis kasvaa sitä mukaa, kun ohjelma kehittyy (esim. version 1.0 jälkeen voisi tulla versio 1.1 tai versio 2.0).
Yllä oli lisäksi packaging
-elementti, jolla voi määrittää millaiseen muotoon Maven paketoi
käännetyn ohjelman; arvo jar
tarkoittaa, että ohjelma luokkatiedostot yms. resurssit asetetaan
yhteen ns. JAR-tiedostoon (tämä on myös Mavenin oletus). Tästä kohta lisää. Yllä määritetään
lisäksi lopussa properties
-elementin sisällä, että projektin lähdekooditiedostot käyttävät
UTF-8 merkistökoodausta, lähdekoodin tulkitaan noudattavan Javan versiota 17, ja kääntäjä tuottaa
Javan version 17 mukaisia luokkatiedostoja. Nämä Javan versioita koskevat arvot tulee tietenkin
asettaa oman ympäristön kannalta toimiviksi (ne eivät voi olla käännösympäristön tarjoaman Javan
versiota korkeampia; alempia on mahdollista käyttää).
Jos luot Netbeansissa uuden projektin ja valitset Java with Maven/Java Application, luo Netbeans valmiiksi jokseenkin yllä kuvattua muotoa olevan projektitiedoston. Se löytyy vasemmalla Projects-välilehdellä kansion “Project files” alta.
Maven-projektin kääntäminen¶
Maven-projektin voi kääntää joko Mavenia tukevan IDE:n komennolla tai suoraan itse komentoriviltä. Jotkut IDE:t (kuten Netbeans) tukevat Mavenia suoraan, ja toiset (esim. VS Code) vaativat lisäosan asentamisen.
Maven käsittelee käännöstä oletusarvoisesti monivaiheisena prosessina, johon kuuluu mm. seuraavat vaiheet:
compile
: Kääntää projektin alihakemistossasrc
olevat lähdekoodit.test
: Suorittaa käännetylle ohjelmalle automaattiset testit (jos sellaisia on määritetty).package
: Paketoi käännetyn ohjelman helpommin jaettavaan muotoon, kuten JAR-tiedostoon.install
: Tallentaa paketin Mavenin paikalliseen tietovarastoon.Esim. Linuxissa paketti löytyisi yleensä käyttäjän kotihakemiston alihakemistosta
.m2/repository
. Maven oletusarvoisesti tulostaa tiedon siitä, mihin ja millä nimellä paketti tarkkaan ottaen kopioitiin.
Vaiheita on enemmänkin, mutta emme käsittele niitä tässä tarkemmin.
Maven-projektin voi kääntää komentorivillä muotoa mvn phase
olevalla komennolla, missä
phase
kertoo, mihin käännösvaiheeseen asti haluamme suorittaa käännöksen. Vaiheet seuraavat
toinen toistaan järjestyksessä, ja myöhemmän vaiheen suorittaminen tarkoittaa, että suoritetaan
myös kaikki sitä edeltävät vaiheet. Esimerkiksi mvn compile
kääntää projektin, mvn test
sekä kääntää että testaa, ja mvn package
sekä kääntää, testaa että paketoi.
Maven asettaa käännetyt luokkatiedostot oletusarvoisesti alihakemistoon target/classes
. Kuten
tavallisestikin, luokkatiedostot sijoittuvat niille määritettyjen pakkausten mukaiseen
hakemistorakenteeseen. Jos projekti paketoidaan esimerkiksi JAR-tiedostoon, asettaa Maven
lopputuloksen alihakemiston target
juureen. Tuloksena syntyvän tiedoston nimi johdetaan
projektitiedostossa määritetyistä tiedoista: se on yleensä muotoa artifactId-version.jar
.
Maven-projektin kääntäminen Netbeansin Run/Build project -komennolla vastaa yleensä komentoa
mvn install
. Koska install
on myöhäisempi vaihe kuin package
, sekin siten jo
esimerkiksi paketoi ohjelman alihakemistoon target
.
JAR-tiedostot (alias Java Archive)¶
JAR-tiedostot ovat eräs Java-kääntäjän tukema mahdollisuus paketoida ohjelma (tai luokkakirjasto) yhteen tiedostoon, jotta ohjelma olisi helpompi jakaa esimerkiksi loppukäyttäjille. JAR-tiedosto sisältää kaikki ohjelman luokkatiedostot, ja mahdollisesti muitakin sen tarvitsemia resursseja. JAR-tiedostot ovat oikeastaan vain erilaista tiedostopäätettä käyttäviä ZIP-tiedostoja, ja niiden sisältöä voi siten sinänsä halutessaan käsitellä tavallisilla ZIP-tiedostojen käsittelyohjelmilla.
Java-kääntäjän ohessa tulee apuohjelma jar
, jolla voi halutessaan itse komentorivillä paketoida
jo valmiiksi käännettyjä luokkatiedostoja yms. JAR-tiedostoksi. Emme käsittele tätä kuitenkaan
tarkemmin, vaan keskitymme Mavenin käyttöön.
Kun ohjelma on paketoitu JAR-tiedostoon, sen voi suorittaa antamalla Java-virtuaalikoneelle
lisävalitsimen -jar
ja sen perässä suoritettavan JAR-tiedoston nimen: esimerkiksi tapaan
java -jar program.jar
, jos ohjelma on paketoitu JAR-tiedostoon program.jar
.
JAR-tiedostoja käytetään yleisesti luokkakirjastojen levittämiseen. Jos haluamme suorittaa ohjelman,
joka tarvitsee vaikkapa JAR-tiedoston library.jar
tarjoamia luokkia tai rajapintoja, tulee
library.jar
asettaa johonkin sellaiseen paikkaan, mistä Java löytää sen, ja tarvittaessa antaa
tiedostoa vastaava luokkapolkumääritys. Esimerkiksi java -cp /some/directory/library.jar MyClass
suorittaisi luokan MyClass
niin, että Java etsii suorituksen aikana tarvittavia luokkia yms.
tavanomaisten Javan luokkakirjastojen lisäksi myös hakemistossa /some/directory
olevasta
JAR-tiedostosta library.jar
. Luokkapolkumääritys voi luetella useita polkuja ja JAR-tiedostoja:
ne vain erotellaan toisistaan kaksoispisteellä (Linux, Mac) tai puolipisteellä (Windows). Esim.
Linuxissa suoritus java -cp /another/directory/:.:/some/directory/library.jar MyClass
ohjaisi
Java-virtuaalikoneen etsimään tarvittavia luokkatiedostoja hakemistosta /another/directory
,
nykyhakemistosta .
, ja hakemistossa /some/directory
olevasta JAR-tiedostosta
library.jar
.
Ulkoisten luokkakirjastojen nouto Mavenilla¶
Eräs merkittävä motiivi Mavenin käyttöön on sen kyky noutaa ulkoisia kirjastoja projektin käyttöön
internetistä. Javan oma luokkakirjasto on laaja, mutta ei luonnollisestikaan sisällä kaikkia
mahdollisesti tarvitsemiamme apuvälineitä. Jos on olemassa jokin ulkoinen (muu kuin Javan omaan
luokkakirjastoon sisältyvä) kirjasto, jonka toimintoja haluamme käyttää, on yleensä varsin
todennäköistä, että kyseinen kirjasto löytyy Mavenin jopa miljoonia kirjastoja sisältävästä
kirjastohakemistosta. Tällöin kirjasto voidaan ottaa projektissa käyttöön yksinkertaisesti
lisäämällä pom.xml
-tiedostoon kyseistä kirjastoa vastaava riippuvuusmääritys. Kun tämän
jälkeen projektin kääntää, noutaa Maven kyseisen kirjaston automaattisesti internetistä ja voimme
alkaa käyttämään kyseisen kirjaston luokkia ja rajapintoja koodissamme hyvin samaan tapaan kuin
olemme toistaiseksi käyttäneet Javan oman luokkakirjaston luokkia ja rajapintoja.
Alempana on esimerkki siitä, miten riippuvuusmääritys tehdään: projektitiedostoon on lisätty
dependencies
-elementti, jonka sisällä olevat dependency
-elementit luettelevat projektin
riippuvuudet. Alla on vain yksi riippuvuus: jdom2-kirjasto, joka tarjoaa apuvälineitä XML-datan
käsittelyyn. Riippuvuuden määrittelyssä annetaan groupId
, artifactId
ja version
. Nämä
riittävät kuvaamaan Mavenille, mistä luokkakirjastosta ja sen versiosta on tarkkaan ottaen kyse.
Tämä samalla ehkä antaa esimerkin siitä, miksi Mavenin projektitiedostoon vaaditaan kyseiset
tiedot: jos oma projektimme julkaistaisiin Mavenin keskushakemistossa, jotta muut Mavenin käyttäjät
voisivat helposti käyttää sitä ulkoisena luokkakirjastona, toimisivat meidän omalle projektillemme
määritetyt groupId
, artifactId
ja version
luokkakirjastomme yksilöivinä riippuvuuden
kuvaavina arvoina.
Mavenin keskushakemisto
Herää ehkä kysymys, että kuinka osaamme luoda oikeanlaisen riippuvuusmäärityksen? Kirjaston (jonka olemme löytäneet esim. internet-hakukoneen avulla tms.) dokumentaatiossa on toisinaan kuvattu sitä vastaava Mavenin riippuvuusmääritys. Jos kirjasto ylipäänsä on Mavenin saatavilla, sen tietojen pitäisi myös löytyä Mavenin keskushakemistosta, johon on tarjolla hakusivu https://search.maven.org/. Keskushakemisto esimerkiksi luettelee saatavilla olevien kirjastojen eri versiot (joista yleensä kannattanee valita uusin) sekä näyttää valitsemaame kirjaston versiota vastaavan riippuvuusmäärityksen.
<?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>fi.tuni.prog3</groupId>
<artifactId>example</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>fi.tuni.prog3.example.ExampleMain</mainClass>
<onejarVersion>0.97</onejarVersion>
<attachToBuild>true</attachToBuild>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Edellä on jdom2-kirjaston käyttöönoton lisäksi yksi toinenkin muutos aiempaan projektitiedostoon:
loppuun on lisätty build
-elementti, jonka alla on käännöksessä käytettäviä liitännäisiä
määrittävä plugins
-elementti. Tässä on tarkemmin ottaen yksi liitännäinen (“plugin”):
yksi plugin-elementti, joka ottaa käyttöön onejar-liitännäisen. Kyseinen liitännäinen tarjoaa
sellaisen mukavan ominaisuuden, että sen myötä Maven paketoi JAR-tiedostoon projektin omien
luokkatiedostojen yms. resurssien lisäksi myös sen tarvitsemat ulkoiset luokkakirjastot. Nimittäin
Mavenin luomat JAR-tiedostot eivät tavallisesti sisältäisi niitä, ja tällöin ulkoisia kirjastoja
käyttävää ohjelmaa ei voisikaan suorittaa suoraan pelkän yhden JAR-tiedoston varassa: sen oheen
tarvittaisiin erikseen myös ohjelman tarvitsemat kirjastot. Sen sijaan onejar-liitännäistä
käyttämällä saadaan luotua itseriittoinen JAR-tiedosto. Onejaria käyttäessä tuloksena on kaksi
JAR-tiedostoa: tavallinen sekä riippuvuudet sisältävä versio. Jälkimmäisen nimeen on lisätty osa
“one-jar”. Esimerkiksi edellä annetun projektitiedoston tapauksessa komento mvn package
loisi
JAR-tiedostot target/example-1.0.jar
ja target/example-1.0.one-jar.jar
.
Onejar-liitännäisen määrityksessä on yksi kohta, joka pitää yleensä määrittää
projektikohtaisesti eri tavalla: mainClass
-elementin sisältö. Kyseinen arvo määrittää projektin
pääluokan eli sen luokan, jonka määrittämästä main
-funktiosta ohjelman suoritus alkaa. Yllä
määritettiin, että projektin pääluokka olisi fi.tuni.prog3.example.ExampleMain
. Tällainen
määritys tarvitaan, jotta JAR-tiedoston sisältämän ohjelman voisi suorittaa tapaan
java -jar program.jar
ilman erillistä parametria, joka kertoisi mistä luokasta suoritus
aloitetaan. Projektin ei toki ole pakko sisältää main
-funktiota, jos se on tarkoitettu muiden
ohjelmien käyttämäksi luokkakirjastoksi.
Jos käytät onejar-liitännäistä määrittämättä pääluokkaa, tai haluat aloittaa suorituksen jostain
muusta saman projektin luokasta, suoritettavan luokan voi määrittää ohjelmaa ajettaessa erillisellä
valitsimella -Done-jar.main.class=MainClass
, missä MainClass
ilmaisee luokan (tarvittaessa
pakkauksineen päivineen). Esimerkiksi
java -Done-jar.main.class=com.example.SomeClass -jar program.jar
aloittaisi suorituksen
JAR-tiedoston program.jar
sisältämästä pakkaukseen com.example
määritetystä luokasta
SomeClass
.