- COMP.CS.140
- 2. Tervetuloa Ohjelmointi 3: rajapinnat ja tekniikat -kurssille
- 2.3 Java: ohjelmointiympäristö
- 2.3.4 Java-projektin hallinta Mavenilla
Java-projektin hallinta Mavenilla¶
Käytämme tällä kurssilla Java-ohjelmien toteuttamiseen ja hallintaan Apache Maven -nimistä työkalua. Maven lienee yleisin Java-projekteissa käytetty käännöstyökalu, mutta myös esimerkiksi Gradle on nykyisin varsin suosittu. Mavenin roolia Java-projektin hallinnassa voisi verrata 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. Mavenia tullaan käyttämään kaikkien ohjelmointitehtävien toteuttamiseen.
Maven on JDK:n ja Git-versionhallinnan tapaan NetBeansin kaltaisista kehitystyökaluista erillinen
kokonaisuus. Netbeans sisältää Mavenin jo valmiiksi Java SE -liitännäisen kautta, jonka avulla
Maven-projekteja voi luoda NetBeansista käsin. Jos Mavenia haluaa käyttää komentoriviltä,
tarvitaan Mavenin erillinen asennus. Maven on saatavilla kotisivujensa
kautta. Jos Maven on kunnolla asennettu, pitäisi sitä pystyä käyttämään komentoriviltä 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 alihakemistoon src/main/java
. Esimerkiksi
kooditiedoston SomeClass.java
polku olisi src/main/java
.
Jos projektihakemiston nimeksi on valittu esimerkiksi test
, on projektin hakemistorakenne
tässä tapauksessa seuraava:
test
│─ pom.xml
└───src
└───main
└───java
└───SomeClass.java
Kuten tiedostopääte vihjaa, projektitiedosto pom.xml
kuvaa projektin ominaisuudet XML-muodossa.
Ellei XML ole vielä tuttu käsite, tutustu sen perusominaisuuksiin esimerkiksi
Wikipedian artikkelista.
Alla on esimerkkinä melko, joskaan ei aivan, minimaalinen Mavenin projektitiedosto:
<?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, joka 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.artifactId
-elementti, joka kertoo projektin nimen.version
-elementti, joka kertoo projektin version. Tämä voisi siis kasvaa sitä mukaa, kun ohjelma kehittyy. Esimerkiksi version 1.0 jälkeen voisi tulla versio 1.1 tai versio 2.0.
Yllä annettiin myös packaging
-elementti, jolla voi määrittää millaiseen muotoon Maven paketoi
käännetyn ohjelman. Arvo jar
tarkoittaa, että ohjelman tavukooditiedostot ja muut resurssit
asetetaan yhteen JAR-tiedostoon. 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. 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,
mutta alempia on mahdollista käyttää.
NetBeans
Maven-projekti luodaan NetBeansissa valisemalla File | New Project...
(tai pienen plusmerkin
sisältävä kansiokuvake) ja sitten Java with Maven
ja Java Application
.
NetBeans kysyy tiedot suurimmalle osalle yllä mainituista elementeistä ja luo sitten
jokseenkin yllä kuvattua muotoa olevan projektitiedoston, src/main/java
-hakemiston
ja pakkaushakemiston sekä pakkaushakemistoon main
-metodin sisältävän ajoluokan,
jonka voi halutessaan poistaa. Projektin sisältö näkyy teemoittain ryhmiteltynä
NetBeansin käyttöliittymän vasemmassa yläkulmassa olevalla Projects
-välilehdellä.
Projektitiedosto pom.xml
on Project files
-kohdan alla ja Source Packages
-kohdan alla on annettu projektin pakkaus ja sen sisältämät tiedostot.
Projektin voi luoda ilman pakkausmääreitä Package:
-kentän tyhjentämällä. NetBeans
ei tee tällöin pakkaushakemistoa ja sijoittaa valmiin ajoluokan pakkausmääreettä
src/main/java
-hakemistoon, joka näkyy Project
-välilehdellä Project Packages | <default packages>
-kohdassa. Myös muiden lähdekooditiedostojen tulee olla tässä tapauksessa
src/main/java
-hakemistossa. Toimi näin ensimmäisten viikkojen harjoitustehtävissä,
joille ei ole kiinnitetty lähdekoodille pakkausta tai pakkauksia.
NetBeans lisää versionumeron perään "-SNAPSHOT"
-merkkijonon, joka ilmaisee projektin olevan
kehitysvaiheessa (epävakaa). Merkkijonon voi poistaa tällä kurssilla, koska projektit ovat
sen verran pieniä, että niille voi antaa lopullisen versionumeron heti luotaessa. Näin
esimerkiksi versio 1.0-SNAPSHOT
voi olla lyhyesti vain 1.0
. Jatkossa oletetaan,
että projekteille annetaan versionumerot ilman "-SNAPSHOT"
-lopuketta.
Maven-projektin kääntäminen¶
Maven-projektin voi kääntää joko Mavenia tukevan IDE:n komennolla tai komentoriviltä. IDE vaatii usein lisäosan asentamisen, jotta Mavenia voidaan käyttää IDE:stä käsin.
Maven käsittelee käännöstä oletusarvoisesti monivaiheisena projektin koostamisprosessina
(build
), johon kuuluu muun muassa 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 yhdeksi tiedostoksi. Paketti sisältää ohjelman tavukoodin ja on usein JAR-muotoinen.install
: Tallentaa paketin Mavenin paikalliseen tietovarastoon, joka sijoitetaan käyttäjän kotihakemiston alihakemistoon.m2/repository
. Maven tulostaa oletusarvoisesti 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 projektihakemiston juuren alihakemistoon
target/classes
. 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
.
Mavenin oletusprosessin lisäksi huomion arvoinen prosessi on clean
, joka poistaa target
-alihakemiston sisältöineen. Komento mvn clean
ei etene käännösvaiheeseen, vaan vain “siivoaa”
projektin poistamalla build
-prosessin tuottamat tiedostot.
NetBeans
Maven-projektin kääntäminen Netbeansin Run | Build project
-komennolla tai kääntäminen ja ajaminen
Run | Run Project
-komennolla vastaa komentoa mvn install
. Koska install
on myöhäisempi
vaihe kuin package
, sekin siten jo esimerkiksi paketoi ohjelman alihakemistoon target
.
NetBeansin Run | Clean and Build Project
-valinta suorittaa clean
-prosessin ja
aloittaa sitten build
-prosessin. Tämä on hyödyllistä, jos projekti on tarpeen kääntää
uudestaan aivan alusta alkaen.
Projektin kääntämiselle ja ajamiselle on omat kuvakkeensa työkalupakissa.
Maven-projektin suorittaminen¶
Mavenin build
-käännösprosessiin ei kuulu omana vaiheenaan projektin suorittaminen. IDE:eissä
on toki omat keinonsa suorittaa projekti ja Maveniin on olemassa projektin suorittamiseen tarkoitettu
Exec Maven
-liintännäinen, jota NetBeans hyödyntää, kun projekti ajetaan. Tämä liitännäinen
otetaan käyttöön automaattisesti, jos projekti halutaan suorittaa Mavenilla komentoriviltä
(tästä tietoa alempana NetBeans-otsikon alla).
Ehkä yksinkertaisempi tapa suorittaa projekti komentoriviltä on kuitenkin käyttää Mavenin
käännösprosessinsa package
-vaiheessa luomia JAR-tiedostoja suoraan Java-tulkilla ilman
Mavenin apua. Ennen asian tarkempaa pohdintaa on syytä tutustua lähemmin JAR-tiedostoihin.
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 paketoida komentorivillä
jo valmiiksi käännettyjä luokkatiedostoja ja muita tiedostoja JAR-tiedostoksi. Emme käsittele tätä
kuitenkaan tarkemmin, vaan keskitymme Mavenin luomien JAR-tiedostojen 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 komennolla
java -jar program.jar
, jos ohjelma on paketoitu JAR-tiedostoon program.jar
.
JAR-tiedostoja käytetään yleisesti luokkakirjastojen jakamiseen. 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 -classpath /some/directory/library.jar MyClass
suorittaisi luokan MyClass
niin, että Java etsii suorituksen aikana tarvittavia tiedostoja
tavanomaisten Javan luokkakirjastojen lisäksi myös hakemistossa /some/directory
olevasta
JAR-tiedostosta library.jar
. Luokkapolkumääritys antaa useita polkuja ja JAR-tiedostoja,
jolloin ne vain erotellaan toisistaan kaksoispisteellä (Linux, Mac) tai puolipisteellä (Windows).
Esimerkiksi Linuxissa suoritus java -classpath /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
.
Mavenin pluginit¶
Mavenin projektitiedostoon on mahdollista (ja melko tavallista) myös lisätä build
-elementti, jonka alla
tyypillisesti on käännöksessä käytettäviä liitännäisiä määrittävä plugins
-elementti. Kurssin kannalta oleellinen
on erityisesti onejar-liitännäinen, jota tarvitaan useimmissa tehtävissä. Liitännäisen saa käyttöön esimerkiksi
seuraavanlaisella projektitiedostolla:
<?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>
<build>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>ExampleMain</mainClass>
<onejarVersion>0.97</onejarVersion>
<attachToBuild>true</attachToBuild>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Onejar-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 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
pakkaukseen com.example
määritetystä luokasta SomeClass
.