24 mai, 2009

Introducere în Apache Maven 2, Partea 4 - Dependenţele proiectelor

Posibilitatea de a localiza uşor un artifact într-un repozitoriu pe baza coordonatelor Maven, ne oferă posibilitatea de a defini dependenţe către alte artifacte în POM-ul proiectului. Dacă veţi analiza POM-ul care a fost arătat în articolul precedent Introducere în Apache Maven 2, Partea 3 - Project Object Model (POM), în paragraful Coordonatele Maven, veţi vedea că există o secţiune care se ocupă de dependenţe, şi că în această secţiune se conţine o singură dependenţă către JUnit.

Maven poate gestiona atât dependenţe interne, cît şi externe. O dependenţă externă pentru un proiect Java ar putea fi o bibliotecă, cum ar fi Log4J sau Spring Framework. O dependenţă internă este reprezentată prin unul din proiectele dvs. care depinde de un alt proiect al dvs. De exemplu, un proiect cu o aplicaţie web, care depinde de un alt proiect care conţine service clasele sau logica de persistenţă.

Pentru a înţelege mai bine, mai jos sînt arătate câteva exemple de dependenţe:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.6.2</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  ...
</project>
[Click mai jos pentru a citi articolul în întregime]

Prima întrebare pe care o să vă o puneţi este: De unde am ştiut noi exact care sînt valorile pentru groupId şi artifactId pentru aceste artifacte? Puteţi localiza toate artifactele Maven folosind pagina web http://www.mvnrepository.com. Acest site oferă o interfaţă de căutare pentru repozitoriul Maven. Cu ajutorul lui puteţi căuta artifactele de care aveţi nevoie pentru a le adăuga ca dependenţe în proiectul dvs. Aveţi posibilitatea să căutaţi un artifact după groupId, artifactId sau chiar descriere. La căutare, o să vă fie afişat artifactul cu lista tuturor versiunilor cunoscute de repozitoriul central Maven. Făcînd click pe detaliile pentru o anumită versiune, veţi fi redirecţionat către o altă pagină care va conţine elementul <dependency> pe care îl puteţi copia şi insera în propriul pom.xml. Unele dintre aceste dependenţe sînt atât de des utilizate (de exemplu JUnit), încît le veţi memoriza foarte rapid groupId şi artifactId.

După cum se vede din fragmentul de POM de mai sus, o dependenţă se declară folosind groupId, artifactId şi version ale artifactului, şi scope (domeniul de aplicare) al acestei dependenţe.

Maven prevede diferite domenii de aplicare(scope) ale dependenţelor. Domeniul de aplicare dirijează care dependenţe sînt disponibile, şi în care classpath. Totodată el dirijează care dependenţe sînt incluse în distribuţie o dată cu aplicaţia. Să examinăm fiecare domeniu de aplicare în detaliu:

  • compile - este domeniul de aplicare implicit. Toate dependenţele au domeniul de aplicare compile în cazul în care domeniul de aplicare nu este specificat. Dependenţele cu domeniul de aplicare compile sînt disponibile în toate classpath-urile, de asemenea ele sînt ambalate(packaged) odată cu aplicaţia la distribuire.
  • provided - sînt folosite atunci când aşteptaţi ca JDK, container-ul sau server-ul să vă ofere aceste dependenţe. De exemplu, dacă dezvoltaţi o aplicaţie web, veţi avea nevoie de Servlet API disponibil în classpath-ul de compilare pentru a compila un servlet, dar nu doriţi să includeţi Servlet API la ambalare în WAR, deoarece jar-ul cu Servlet API este furnizat de server-ul sau servlet container-ul dvs. Dependenţele provided sînt disponibile doar în classpath-ul de compilare, nu runtime. De asemenea ele nu sînt ambalate odată cu aplicaţia la distribuire.
  • runtime - sînt necesare pentru executarea şi testare sistemului, dar nu sînt necesare pentru compilare. De exemplu, aţi putea avea nevoie de jar-ul cu JDBC API la momentul compilării şi de implementarea driver-ului JDBC numai la runtime.
  • test - nu sînt necesare în timpul funcţionării normale a aplicaţiei şi sînt disponibile numai în perioada de compilare şi execuţie a testelor.
  • system - este similar cu provided cu excepţia faptului că va trebui să oferiţi o cale explicită către fişierul jar în sistemul de fişiere local. Acest lucru este destinat pentru a permite compilarea cu folosirea bibliotecilor native. Dacă declaraţi domeniului de aplicare system, atunci trebuie să specificaţi de asemenea şi elementul systemPath.

Pe lîngă specificarea unei versiuni fixe, puteţi de asemenea specifica o serie de versiuni care ar putea satisface o anumită dependenţă. Puteţi face acest lucru prin plasarea unui sau mai multor numere de versiuni între următoarele caractere:

(, ) - Cuantificatori exclusivi

[, ] - Cuantificatori inclusivi

De exemplu, dacă doriţi să accesaţi orice versiune JUnit mai mare sau egală decît 3.8, dar mai mică decît 4.0, dependenţa dumneavoastră ar arăta aşa cum este arătat mai jos:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>[3.8,4.0)</version>
  <scope>test</scope>
</dependency>

Dacă doriţi să depindeţi de orice versiune de JUnit nu mai mare decît 3.8.1, atunci trebuie să specificaţi doar o limită superioară inclusiv. Versiunea înainte de, sau după virgulă nu este necesară, şi înseamnă +/- infinit.

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>[,3.8.1]</version>
  <scope>test</scope>
</dependency>

Notă: Versiunea [1.2] înseamnă doar versiunea 1.2 şi nimic altceva. Când se declară o versiune normală, cum ar fi versiunea 3.8.1 de Junit, intern, aceasta este reprezentată ca: "permite orice, dar prefera 3.8.1". Acest lucru înseamnă că atunci când este detectat un conflict, Maven este autorizat să folosească algoritmi de conflict pentru a alege cea mai buna versiune. Dacă se specifică [3.8.1], aceasta înseamnă că doar 3.8.1 va fi folosit şi nimic altceva. Dacă în altă parte există o dependenţă care a specificat [3.8.2], atunci veţi obţine o eroare de conflict la build.


Dependenţe Tranzitive

O dependenţă tranzitivă este o dependenţă de către o altă dependenţă. Dacă un proiect A depinde de un alt proiect B, care la rîndul său depinde de proiectul C, atunci proiectul C este considerat o dependenţă tranzitivă a proiectului A. Dacă proiectul C depinde la rîndul său de proiectul D, atunci şi proiectul D de asemenea este considerat o dependenţă tranzitivă a proiectului A.

Maven Transitive Dependencies

Maven poate gestiona dependenţele tranzitive şi scuti developer-ul de necesitatea de a găsi toate dependenţele necesare pentru a compila şi rula o aplicaţie. Puteţi doar să declaraţi o dependenţă de vre-un framework sau bibliotecă fără să vă faceţi griji pentru depistarea tuturor dependenţelor pe care acestea le au.

Maven realizează acest lucru prin construirea unui graf de dependenţe şi rezolvarea tuturor conflictelor şi suprapunerilor care ar putea apărea. De exemplu, dacă Maven găseşte două proiecte care depind de aceleaşi groupId şi artifactId, acesta va selecta în mod automat care dependenţă să o folosească, întotdeauna favorizînd cea mai recentă versiune a dependenţei. Deşi sună convenabil, există unele cazuri în care dependenţele tranzitive pot cauza unele probleme de configurare. În astfel de scenarii, puteţi utiliza excluderea de dependenţe.

Mai jos este arătat un exemplu în care se adaugă o dependenţă de proiectul A, dar se exclude dependenţa tranzitivă de proiectul B şi se înlocuieşte cu o dependenţă de proiectul X:

<dependencies>
  <dependency>
    <groupId>com.company.project</groupId>
    <artifactId>project-a</artifactId>
    <version>1.0</version>
    <exclusions>
      <exclusion>
        <groupId>com.company.project</groupId>
        <artifactId>project-b</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>com.company.project</groupId>
    <artifactId>project-x</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

După cum puteţi observa nu există nici un semn că dependenţa de proiectul X este o înlocuire. În cazul acesta proiectul X este o bibliotecă care asigură acelaşi API ca şi proiectul B. Pentru a înlocui o dependenţă tranzitivă cu o altă dependenţă, trebui să excludeţi dependenţa tranzitivă şi să declaraţi o altă dependenţă în loc.

De menţionat, că Maven nu descărca doar fişierul JAR al dependenţei, Maven descarcă de asemenea şi fişierul POM al dependenţei. Faptul că Maven descarcă fişierele POM adăugător faţă de artifacte îi permite să suporte dependenţele tranzitive, deoarece Maven consultă POM-urile dependenţelor pentru a găsi oricare alte dependenţe tranzitive. Aceste dependenţe tranzitive sînt apoi adaugate ca dependenţe a proiectului curent.


Dependenţe Opţionale

Dacă aveţi nevoie de câteva biblioteci pentru a compila proiectul dvs, dar nu doriţi ca toate bibliotecile să apară ca dependenţe tranzitive pentru proiectele care vor utiliza proiectul dvs ca dependenţă, atunci puteţi utiliza dependenţe opţionale aşa cum este arătat mai jos. De obicei aceasta situaţie poate apărea atunci cînd aplicaţia dvs suportă diferite implementări ale unui şi acelaşi API şi dvs lăsaţi utilizatorii să-şi aleagă singuri implementarea pe care o doresc.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company.project</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>org.othercompany</groupId>
      <artifactId>project-a</artifactId>
      <version>1.2.0</version>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>net.bigcompany</groupId>
      <artifactId>project-b</artifactId>
      <version>2</version>
      <optional>true</optional>
    </dependency>
  </dependencies>
</project>

Odată ce aţi declarat aceste dependenţe ca opţionale, sînteţi obligat să le includeţi în mod explicit în cadrul proiectului care depinde de my-project. De exemplu, dacă a-ţi scris o aplicaţie care depinde de my-project şi doriţi să folosiţi project-b ca implementare, atunci va trebui să adăugaţi următoarea configurare pentru proiectului dvs:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company.project</groupId>
  <artifactId>my-application</artifactId>
  <version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>com.company.project</groupId>
      <artifactId>my-project</artifactId>
      <version>1.0.0</version>
    </dependency>
    <dependency>
      <groupId>net.bigcompany</groupId>
      <artifactId>project-b</artifactId>
      <version>2</version>
    </dependency>
  </dependencies>
</project>


Referinţe către Proprietăţi

POM-ul poate include referinţe la proprietăţi precedate de semnul dolarului şi înconjurate de două acolade. Spre exemplu, priviţi la următorul POM:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company.project</groupId>
  <artifactId>project-a</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <build>
    <finalName>${project.groupId}-${project.artifactId}</finalName>
  </build>
</project>

Când Maven citeşte POM-ul, el înlocuieşte referinţele la proprietăţi cu valorile acestora. În exemplul de mai sus finalName va fi evaluat în com.company.project-project-a. Aici noi am personalizat numele fişierului JAR generat prin adăugarea elementului finalName în configurarea de build a proiectului. Cu finalName egal cu com.company.project-proiect-a, build-ul va produce un fişier JAR în target/com.company.project-project-a.jar.

Maven pune la dispoziţie trei variabile implicite care pot fi folosite pentru a accesa variabilele de mediu şi sistem, informaţii din POM şi setările Maven:

  • env - expune variabile de mediu şi sistem care la rîndul lor sînt expuse de către sistemul de operare sau shell. De exemplu, o referinţă la ${env.PATH} într-un POM Maven va fi înlocuită cu valoarea variabilei de mediu $PATH (sau %PATH% în Windows).
  • project - expune POM-ul în întregime. Puteţi utiliza punctul (.) pentru a crea calea de referinţă către valoarea unui element din POM. De exemplu, în această secţiune am folosit groupId şi artifactId pentru a seta elementul finalName. Sintaxa pentru aceste referinţe a fost: ${project.groupId}-${project.artifactId}.
  • settings - expune informaţiile despre setările Maven. Puteţi utiliza punctul (.) pentru a crea calea de referinţă către valoarea unui element din fişierul settings.xml. De exemplu ${settings.offline} face referinţă la valoarea din elementul offline în <user_home>/.m2/settings.xml.

În plus faţă de cele trei variabile implicite, puteţi face referinţă la proprietăţile de sistem Java precum şi orice proprietăţi personalizate definite în POM sau într-un profil de build:

  • Proprietăţile de sistem Java - toate proprietăţile accesibile prin metoda getProperties() din java.lang.System sînt expuse ca POM proprietăţi. Câteva exemple de de proprietăţi sînt: ${user.name}, ${user.home}, ${java.home}, ${os.name}, etc. O listă completă a proprietăţilor de sistem Java pot fi găsite în Javadoc pentru clasa java.lang.System.
  • Proprietăţile personalizate - pot fi setate cu ajutorul elementului properties în pom.xml sau settings.xml, sau pot fi încărcate din fişiere externe. Dacă aţi stabilit o proprietate numită myproperty în pom.xml, atunci la această proprietate se poate face referinţă cu ${myproperty}. Mai jos este arătată sintaxa pentru definirea ${myproperty} = myvalue în POM:
    <project>
      ...
      <properties>
        <myproperty>myvalue</myproperty>
      </properties>
      ...
    </project>
    

Atît pentru astăzi. În următorul post vom descrie Plugin-urile şi Goal-urile Maven.


Trimiteţi un comentariu

  Blogger template adapted from The Professional Template by Ourblogtemplates.com 2008

Header image adapted from Free Web Page Headers