Maven Definitive Guide De

  • 8,084 views
Uploaded on

 

More in: Travel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
8,084
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
125
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Thomas Locher (Sonatype, Inc.), Tim O'Brien (Sonatype, Inc.), John Casey (Sonatype, Inc.), Brian Fox (Sonatype, Inc.), Bruce Snyder (Sonatype, Inc.), Jason Van Zyl (Sonatype, Inc.), Eric Redmond () Copyright © 2006-2008
  • 2. Copyright ....................................................................................................... xii 1. Creative Commons BY-ND-NC ......................................................... xii Vorwort zur deutschen Ausgabe: 0.5 ............................................................ xiv Vorwort .......................................................................................................... xv 1. Anleitung zu diesem Buch .................................................................. xv 2. Ihr Feedback ....................................................................................... xvi 3. Typographische Konventionen ......................................................... xvii 4. Konventionen in Bezug auf Maven .................................................. xvii 5. Danksagung ......................................................................................xviii 1. Einführung von Apache Maven ................................................................... 1 1.1. Maven ... Was ist das? ........................................................................ 1 1.2. Konvention über Konfiguration ......................................................... 2 1.3. Die gemeinsame Schnittstelle ............................................................ 3 1.4. Universelle Wiederverwendung durch Maven-Plugins ..................... 5 1.5. Konzeptionelles Modell eines "Projekts" .......................................... 6 1.6. Ist Maven eine Alternative zu XYZ? ................................................. 8 1.7. Ein Vergleich von Maven und Ant .................................................... 9 1.8. Zusammenfassung ............................................................................ 14 2. Installieren und Ausführen von Maven ...................................................... 15 2.1. Überprüfen der Java-Installation ...................................................... 15 2.2. Herunterladen von Maven ................................................................ 16 2.3. Installation von Maven ..................................................................... 16 2.3.1. Maven Installation unter Mac OSX ....................................... 17 2.3.2. Maven Installation unter Microsoft Windows ....................... 18 2.3.3. Maven Installation unter Linux .............................................. 19 2.3.4. Maven Installation unter FreeBSD oder OpenBSD ............... 19 2.4. Testen einer Maven Installation ....................................................... 19 2.5. Spezielle Installationshinweise ........................................................ 20 2.5.1. Benutzerdefinierte Konfiguration und-Repository ................ 21 2.5.2. Aktualisieren einer Maven-Installation .................................. 22 2.5.3. Upgrade einer Maven-Installation von Maven 1.x auf Maven 2.x ..................................................................................................... 23 2.6. Maven De-Installieren ...................................................................... 24 ii
  • 3. Maven: The Definitive Guide 2.7. Hilfe bekommen beim Arbeiten mit Maven .................................... 24 2.8. Das Maven Hilfe Plugin ................................................................... 26 2.8.1. Beschreibung eines Maven Plugin ......................................... 27 2.9. Die Apache Software Lizenz ........................................................... 29 I. Maven by Example ..................................................................................... 31 3. Ein einfaches Maven Projekt .............................................................. 33 3.1. Einleitung .................................................................................. 33 3.1.1. Das Herunterladen der Beispiele dieses Kapitels ............ 33 3.2. Erstellen eines einfachen Projekts ............................................. 34 3.3. Der Aufbau eines einfachen Projekts ........................................ 36 3.4. Einfaches Projekt Objekt Modell (POM) .................................. 37 3.5. Kern Konzepte .......................................................................... 39 3.5.1. Maven Plugins und Ziele ................................................. 39 3.5.2. Maven Lifecycle .............................................................. 42 3.5.3. Maven Koordinaten ......................................................... 46 3.5.4. Maven Repositories ......................................................... 49 3.5.5. Maven Abhängigkeits-Management (Dependency Management) ............................................................................. 52 3.5.6. Site-Generierung und Reporting ..................................... 55 3.6. Zusammenfassung ..................................................................... 56 4. Anpassen eines Maven Projektes ........................................................ 57 4.1. Einleitung .................................................................................. 57 4.1.1. Herunterladen der Beispiele dieses Kapitels ................... 57 4.2. Eine kurze Einführung in das "Simple Weather" Projekt ......... 57 4.2.1. Yahoo! Wetter Dienst RSS ............................................. 58 4.3. Erstellen des "Simple Weather" Projektes ................................ 59 4.4. Anpassen der Projektinformationen .......................................... 60 4.5. Einfügen neuer Abhängigkeiten ................................................ 62 4.6. Quellcode von "Simple Weather" ............................................. 64 4.7. Resourcen Hinzufügen .............................................................. 71 4.8. Ausführen des "Simple Weather" Programms .......................... 72 4.8.1. Das Exec Maven Plugin .................................................. 74 4.8.2. Erkundung der Projekt Abhängigkeiten .......................... 74 4.9. Erstellen von Unit-Tests ............................................................ 77 iii
  • 4. Maven: The Definitive Guide 4.10. Hinzufügen von Gebietsbezogenen Unit Tests ....................... 80 4.11. Hinzufügen einer Unit-Test Ressource ................................... 81 4.12. Ausführen von Unit-Tests ....................................................... 83 4.12.1. Ignorieren fehlgeschlagener Unit Tests ......................... 84 4.12.2. Überspringen von Unit-Tests ........................................ 86 4.13. Builden einer paketierten, Befehlszeilen orientierten Anwendung ...................................................................................... 87 4.13.1. Anbinden des Assembly Goals zur Packetierungs Phase 89 5. Eine einfache Web-Anwendung ......................................................... 91 5.1. Einleitung .................................................................................. 91 5.1.1. Herunterladen der Beispiele dieses Kapitels ................... 91 5.2. Eine kurze Einführung in die "Simple Web" Anwendung ........ 91 5.3. Erstellen des "Simple Web" Projekts ........................................ 92 5.4. Konfigurieren des Jetty-Plugins ................................................ 93 5.5. Das Hinzufügen eines einfachen Servlets ................................. 96 5.6. Das Hinzufügen von J2EE-Abhängigkeiten ............................. 98 5.7. Zusammenfassung ................................................................... 100 6. Ein multi-modulares Projekt ............................................................. 101 6.1. Einleitung ................................................................................ 101 6.1.1. Herunterladen der Beispiele dieses Kapitels ................. 101 6.2. Das "Simple Parent" Projekt (parent=Elternteil) .................... 102 6.3. Das "Simple Weather" Modul ................................................. 103 6.4. Das "Simple-Web" Anwendungs-Modul ................................ 106 6.5. Erstellung des Multi-Projekt-Moduls ...................................... 109 6.6. Starten der Web-Anwendung .................................................. 110 7. Multi-module Enterprise Project ....................................................... 112 7.1. Einleitung ................................................................................ 112 7.1.1. Herunterladen der Beispiele dieses Kapitels ................. 112 7.1.2. Multi-Modul Enterprise Projekt .................................... 113 7.1.3. Technologie des Beispiels ............................................. 116 7.2. Das "Simple Parent" Projekt - Top Level ............................... 117 7.3. Das Simple Model" Modul - Das Objektmodell ..................... 119 7.4. Das "Simple Weather" Modul - Die Dienste .......................... 125 7.5. Das "Simple Persist" Modul - Die Datenabstraktion .............. 129 iv
  • 5. Maven: The Definitive Guide 7.6. Das "Simple Web" Modul - Das Web-Interface ..................... 139 7.7. Aufrufen der Web-Anwendung ............................................... 153 7.8. Das "Simple Command" Modul - Das Kommandozeilen Modul ......................................................................................................... 154 7.9. Aufrufen der Kommandozeilen-Anwendung .......................... 162 7.10. Fazit ....................................................................................... 164 7.10.1. Programmierung gegen Interface-Projekte ................. 166 8. Optimirung und Überarbeitung der POMs ........................................ 168 8.1. Einführung ............................................................................... 168 8.2. POM Bereinigung ................................................................... 169 8.3. Optimirung der Abhängigkeiten ............................................. 170 8.4. Optimirung der Plugins ........................................................... 176 8.5. Optimierung unter Zuhilfenahmen des Maven Dependency Plugin ......................................................................................................... 178 8.6. Abschliessende POMs ............................................................. 182 8.7. Fazit ......................................................................................... 190 II. Maven Reference ..................................................................................... 192 9. The Project Object Model ................................................................. 193 9.1. Introduction ............................................................................. 193 9.2. The POM ................................................................................. 193 9.2.1. The Super POM ............................................................. 196 9.2.2. The Simplest POM ........................................................ 200 9.2.3. The Effective POM ....................................................... 201 9.2.4. Real POMs .................................................................... 201 9.3. POM Syntax ............................................................................ 202 9.3.1. Project Versions ............................................................ 202 9.3.2. Property References ...................................................... 205 9.4. Project Dependencies .............................................................. 207 9.4.1. Dependency Scope ........................................................ 208 9.4.2. Optional Dependencies ................................................. 210 9.4.3. Dependency Version Ranges ........................................ 211 9.4.4. Transitive Dependencies ............................................... 213 9.4.5. Conflict Resolution ....................................................... 215 9.4.6. Dependency Management ............................................. 217 v
  • 6. Maven: The Definitive Guide 9.5. Project Relationships ............................................................... 219 9.5.1. More on Coordinates ..................................................... 220 9.5.2. Multi-module Projects ................................................... 221 9.5.3. Project Inheritance ......................................................... 223 9.6. POM Best Practices ................................................................. 227 9.6.1. Grouping Dependencies ................................................ 227 9.6.2. Multi-module vs. Inheritance ........................................ 229 10. Der Build Lebenszyklus .................................................................. 237 10.1. Einführung ............................................................................. 237 10.1.1. Lebenszyklus: clean .................................................... 237 10.1.2. Standard Lebenszyklus: default .................................. 241 10.1.3. Lebenszyklus: site ....................................................... 243 10.2. Package-spezifische Lebenszyklen ....................................... 244 10.2.1. jar ................................................................................. 245 10.2.2. pom .............................................................................. 246 10.2.3. plugin ........................................................................... 246 10.2.4. ejb ................................................................................ 247 10.2.5. war ............................................................................... 248 10.2.6. ear ................................................................................ 248 10.2.7. Andere Packetierungs Typen ...................................... 249 10.3. Gebräuchliche Lebenszyklus Goals ...................................... 251 10.3.1. Ressourcen Verarbeiten ............................................... 251 10.3.2. compile ........................................................................ 255 10.3.3. Verarbeiten von Test Ressourcen ................................ 257 10.3.4. Kompilieren der Test Klassen (testCompile) .............. 258 10.3.5. test ............................................................................... 258 10.3.6. install ........................................................................... 260 10.3.7. deploy .......................................................................... 260 11. Build Profiles .................................................................................. 262 11.1. What Are They For? .............................................................. 262 11.1.1. What is Build Portability ............................................. 262 11.1.2. Selecting an Appropriate Level of Portability ............. 264 11.2. Portability through Maven Profiles ....................................... 265 11.2.1. Overriding a Project Object Model ............................. 268 vi
  • 7. Maven: The Definitive Guide 11.3. Profile Activation .................................................................. 269 11.3.1. Activation Configuration ............................................. 271 11.3.2. Activation by the Absence of a Property .................... 272 11.4. Listing Active Profiles .......................................................... 273 11.5. Tips and Tricks ...................................................................... 274 11.5.1. Common Environments ............................................... 274 11.5.2. Protecting Secrets ........................................................ 276 11.5.3. Platform Classifiers ..................................................... 278 11.6. Summary ............................................................................... 280 12. Maven Assemblies .......................................................................... 282 12.1. Introduction ........................................................................... 282 12.2. Assembly Basics ................................................................... 283 12.2.1. Predefined Assembly Descriptors ............................... 284 12.2.2. Building an Assembly ................................................. 285 12.2.3. Assemblies as Dependencies ....................................... 288 12.2.4. Assembling Assemblies via Assembly Dependencies 289 12.3. Overview of the Assembly Descriptor .................................. 293 12.4. The Assembly Descriptor ...................................................... 296 12.4.1. Property References in Assembly Descriptors ............ 296 12.4.2. Required Assembly Information ................................. 296 12.5. Controlling the Contents of an Assembly ............................. 298 12.5.1. Files Section .............................................................. 298 12.5.2. FileSets Section ......................................................... 299 12.5.3. Default Exclusion Patterns for fileSets .................... 302 12.5.4. dependencySets Section ............................................. 303 12.5.5. moduleSets Sections ................................................... 317 12.5.6. Repositories Section .................................................... 325 12.5.7. Managing the Assembly’s Root Directory .................. 326 12.5.8. componentDescriptors and containerDescriptorHandlers ............................................. 327 12.6. Best Practices ........................................................................ 328 12.6.1. Standard, Reusable Assembly Descriptors .................. 328 12.6.2. Distribution (Aggregating) Assemblies ...................... 332 12.7. Summary ............................................................................... 337 vii
  • 8. Maven: The Definitive Guide 13. Properties and Ressource Filterung ................................................. 338 13.1. Einleitung .............................................................................. 338 13.2. Maven Properties .................................................................. 338 13.2.1. Maven Projekt Einstellungen ...................................... 339 13.2.2. Properties der Maven Einstellungen (settings.xml) .... 342 13.2.3. Properties der Umgebungsvariablen ........................... 342 13.2.4. Java System Properties ................................................ 343 13.2.5. Benuzerdefinierte Properties ....................................... 345 13.3. Ressource Filterung ............................................................... 346 14. Maven in Eclipse: m2eclipse .......................................................... 351 14.1. Einführung ............................................................................. 351 14.2. m2eclipse ............................................................................... 351 14.3. Installation des m2eclipse Plugins ........................................ 352 14.3.1. Installieren der Voraussetzungen ................................ 353 14.3.2. Installation von m2eclipse ........................................... 355 14.4. Aufschalten der Maven Konsole ........................................... 356 14.5. Erstellen eines Maven Projekts ............................................. 357 14.5.1. Auschecken eines Maven Projektes von einem SCM Repository ............................................................................... 358 14.5.2. Erstellen eines Maven Projekts auf der Basis eines Maven Archetyps ................................................................................ 360 14.5.3. Erstellen eines Maven Moduls .................................... 364 14.6. Erstellen einer Maven POM Datei ........................................ 367 14.7. Importieren von Maven Projekten ........................................ 371 14.7.1. Importiren eines Maven Projektes ............................... 373 14.7.2. Materialisieren eines Maven Projektes ....................... 375 14.8. Starten von Maven Builds ..................................................... 379 14.9. Mit Maven Projekten arbeiten ............................................... 382 14.9.1. Zufügen und Updaten von Abhängigkeiten und Plugins 384 14.9.2. Erstellen eines Maven Modules .................................. 387 14.9.3. Herunterladen der Quelldatei(en) ................................ 387 14.9.4. Öffnen von Projektseiten ............................................. 387 14.9.5. Auflösen von Abhängigkeiten ..................................... 388 14.10. Arbeiten mit den Maven Repositorien ................................ 388 viii
  • 9. Maven: The Definitive Guide 14.10.1. Suchen von Maven Artefakten sowie Java Klassen .. 389 14.10.2. Indizierung von Maven Repositorien ........................ 394 14.11. Der neue graphische POM Editor ....................................... 398 14.12. Projektabhängigkeiten mit m2eclipse analysieren .............. 404 14.13. Maven Einstellungen ........................................................... 409 14.14. Zusammenfassung ............................................................... 417 15. Site Generation ................................................................................ 419 15.1. Introduction ........................................................................... 419 15.2. Building a Project Site with Maven ...................................... 420 15.3. Customizing the Site Descriptor ........................................... 422 15.3.1. Customizing the Header Graphics ............................... 423 15.3.2. Customizing the Navigation Menu ............................. 424 15.4. Site Directory Structure ......................................................... 426 15.5. Writing Project Documentation ............................................ 427 15.5.1. APT Example .............................................................. 427 15.5.2. FML Example ............................................................. 428 15.6. Deploying Your Project Website .......................................... 429 15.6.1. Configuring Server Authentication ............................. 430 15.6.2. Configuring File and Directory Modes ....................... 431 15.7. Customizing Site Appearance ............................................... 432 15.7.1. Customizing the Site CSS ........................................... 432 15.7.2. Create a Custom Site Template ................................... 433 15.7.3. Reusable Website Skins .............................................. 438 15.7.4. Creating a Custom Theme CSS ................................... 440 15.7.5. Customizing Site Templates in a Skin ........................ 441 15.8. Tips and Tricks ...................................................................... 443 15.8.1. Inject XHTML into HEAD ......................................... 443 15.8.2. Add Links under Your Site Logo ................................ 443 15.8.3. Add Breadcrumbs to Your Site ................................... 444 15.8.4. Add the Project Version .............................................. 445 15.8.5. Modify the Publication Date Format and Location ..... 446 15.8.6. Using Doxia Macros .................................................... 447 16. Repository Management with Nexus .............................................. 449 17. Writing Plugins ............................................................................... 451 ix
  • 10. Maven: The Definitive Guide 17.1. Introduction ........................................................................... 451 17.2. Programming Maven ............................................................. 451 17.2.1. What is Inversion of Control? ..................................... 452 17.2.2. Introduction to Plexus ................................................. 453 17.2.3. Why Plexus? ................................................................ 454 17.2.4. What is a Plugin? ......................................................... 455 17.3. Plugin Descriptor .................................................................. 456 17.3.1. Top-level Plugin Descriptor Elements ........................ 458 17.3.2. Mojo Configuration ..................................................... 459 17.3.3. Plugin Dependencies ................................................... 463 17.4. Writing a Custom Plugin ....................................................... 463 17.4.1. Creating a Plugin Project ............................................. 463 17.4.2. A Simple Java Mojo .................................................... 464 17.4.3. Configuring a Plugin Prefix ........................................ 466 17.4.4. Logging from a Plugin ................................................ 470 17.4.5. Mojo Class Annotations .............................................. 471 17.4.6. When a Mojo Fails ...................................................... 473 17.5. Mojo Parameters ................................................................... 474 17.5.1. Supplying Values for Mojo Parameters ...................... 474 17.5.2. Multi-valued Mojo Parameters .................................... 477 17.5.3. Depending on Plexus Components ............................. 479 17.5.4. Mojo Parameter Annotations ...................................... 479 17.6. Plugins and the Maven Lifecycle .......................................... 481 17.6.1. Executing a Parallel Lifecycle ..................................... 481 17.6.2. Creating a Custom Lifecycle ....................................... 482 17.6.3. Overriding the Default Lifecycle ................................ 484 18. Writing Plugins in Alternative Languages ...................................... 487 18.1. Writing Plugins in Ant .......................................................... 487 18.2. Creating an Ant Plugin .......................................................... 487 18.3. Writing Plugins in JRuby ...................................................... 490 18.3.1. Creating a JRuby Plugin .............................................. 491 18.3.2. Ruby Mojo Implementations ....................................... 493 18.3.3. Logging from a Ruby Mojo ........................................ 496 18.3.4. Raising a MojoError .................................................... 497 x
  • 11. Maven: The Definitive Guide 18.3.5. Referencing Plexus Components from JRuby ............ 497 18.4. Writing Plugins in Groovy .................................................... 498 18.4.1. Creating a Groovy Plugin ............................................ 499 19. Using Maven Archetypes ................................................................ 501 19.1. Introduction to Maven Archetypes ........................................ 501 19.2. Using Archetypes .................................................................. 502 19.2.1. Using an Archetype from the Command Line ............ 502 19.2.2. Using the Interactive generate Goal ............................ 503 19.2.3. Using an Archetype from m2eclipse ........................... 506 19.3. Available Archetypes ............................................................ 506 19.3.1. Common Maven Archetypes ....................................... 506 19.3.2. Notable Third-Party Archetypes ................................. 508 19.4. Publishing Archetypes .......................................................... 511 A. Appendix: Detailinformationen zur settings.xml-Datei .......................... 514 A.1. Übersicht ....................................................................................... 514 A.2. Die Details der settings.xml Datei ................................................ 515 A.2.1. Einfache Wertangaben ........................................................ 515 A.2.2. Servers ................................................................................. 516 A.2.3. Spiegelrepositorien .............................................................. 517 A.2.4. Proxies ................................................................................. 518 A.2.5. Profiles ................................................................................ 520 A.2.6. Activation ............................................................................ 520 A.2.7. Properties ............................................................................. 522 A.2.8. Repositories ......................................................................... 523 A.2.9. Plugin Repositories ............................................................. 525 A.2.10. Aktive Profile .................................................................... 526 B. Appendix: Alternativen zu den Sun Spezifikationen .............................. 528 xi
  • 12. Copyright Copyright 2008 Sonatype, Inc. Online version published by Sonatype, Inc., 654 High Street, Suite 220, Palo Alto, CA, 94301. Print version published by O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. Nutshell Handbook, the Nutshell Handbook logo, and the O'Reilly logo are registered trademarks of O'Reilly Media, Inc. The Developer's Notebook series designations, the look of a laboratory notebook, and related trade dress are trademarks of O'Reilly Media, Inc. Java(TM) and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc., in the United States and other countries. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Sonatype, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and authors assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. 1. Creative Commons BY-ND-NC This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States license. For more information about this license, see http://creativecommons.org/licenses/by-nc-nd/3.0/us/. You are free to share, copy, distribute, display, and perform the work under the following conditions: • You must attribute the work to Sonatype, Inc. with a link to http://www.sonatype.com. xii
  • 13. Copyright • You may not use this work for commercial purposes. • You may not alter, transform, or build upon this work. If you redistribute this work on a web page, you must include the following link with the URL in the about attribute listed on a single line (remove the backslashes and join all URL parameters): <div xmlns:cc="http://creativecommons.org/ns#" about="http://creativecommons.org/license/results-one?q_1=2&q_1=1 &field_commercial=n&field_derivatives=n&field_jurisdiction=us &field_format=StillImage&field_worktitle=Maven%3A+Guide &field_attribute_to_name=Sonatype%2C+Inc. &field_attribute_to_url=http%3A%2F%2Fwww.sonatype.com &field_sourceurl=http%3A%2F%2Fwww.sonatype.com%2Fbook &lang=en_US&language=en_US&n_questions=3"> <a rel="cc:attributionURL" property="cc:attributionName" href="http://www.sonatype.com">Sonatype, Inc.</a> / <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/3.0/us/"> CC BY-NC-ND 3.0</a> </div> When downloaded or distributed in a jurisdiction other than the United States of America, this work shall be covered by the appropriate ported version of Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 license for the specific jurisdiction. If the Creative Commons Attribution-Noncommercial-No Derivative Works version 3.0 license is not available for a specific jurisdiction, this work shall be covered under the Creative Commons Attribution-Noncommercial-No Derivate Works version 2.5 license for the jurisdiction in which the work was downloaded or distributed. A comprehensive list of jurisdictions for which a Creative Commons license is available can be found on the Creative Commons International web site at http://creativecommons.org/international. If no ported version of the Creative Commons license exists for a particular jurisdiction, this work shall be covered by the generic, unported Creative Commons Attribution-Noncommercial-No Derivative Works version 3.0 license available from http://creativecommons.org/licenses/by-nc-nd/3.0/. xiii
  • 14. Vorwort zur deutschen Ausgabe: 0.5 Vor Ihnen liegt die deutsche Übersetzung der "Definitiv Guide to Apache Maven". Ein Werk das, wie das englische Original, zum Ziel hat Brücken zu schlagen. Brücken nicht nur zwischen Technikern und Techniken, sondern auch Brücken zwischen unseren verschiedenen Kulturen. In meiner täglichen Erfahrung gibt es eine grosse Zahl Arbeitsgruppen und Teams welche mehr als eine, oft sogar so viele Nationalitäten wie Mitglieder umfassen. Das Kommunikationsmedium ist meist der kleinste gemeinsame Nenner: (Sowie-)Englisch. Wie oft habe ich nach tage- und nächtelangem Suchen feststellen müssen, dass der Fehler eigentlich ganz offensichtlich war, wenn Man(n) nur die (englische) Anleitung verstanden hätte! Maven ist ein mächtiges Tool, häufiger missverstanden und nur rudimentär eingesetzt - insbesondere weil die Zusammenhänge unklar, die Dokumentation in Englisch schwierig und lückenhaft ist. Da ich vom Wert dieses Werkzeugs überzeugt bin habe ich alles gegeben dieses Werkzeug auch dem deutschen Sprachraum "native" zugänglich zu machen. Ich erhoffe mir hiervon auch, Entwickler zu ermutigen sich in der grossen bestehenden Maven Community einzubringen, Themen aufzugreifen und weiter zu führen. Diesen Entwicklern möchte ich Sicherheit bieten indem ich Ihnen eine Referenz zur Seite stelle, die sie verstehen. Mit Blick auf unsere multinationalen Gruppen habe ich versucht das Werk so nahe am englischen Original zu belassen als möglich. Sie werden also auf Abschnittebene vergleichen können. Sollten Sie unsicher sein was Ihr Kollege tatsächlich meint, so zücken Sie Ihr deutsches Exemplar, sehen nach welchen Abschnitt im englischen Original Ihr Kollege referenziert - et voilà - lesen Sie in Ihrer Muttersprache was gemeint ist. Thomas Locher Zürich, im Januar 2008 PS: Habe ich Ihnen bereits empfohlen die englische Originalausgabe zu kaufen? xiv
  • 15. Vorwort Maven is ein Build Werkzeug, ein Projekt Management Werkzeug ein abstrakter Container um Build Aufträge abzuarbeiten. Es stellt ein Werkzeug dar, welches unumgänglich ist für alle Projekte welche über die einfachsten Anforderungen hinauswachsen und sich plötzlich in der Lage finden konsistent eine grosse Zahl voneinander abhängiger Bibliotheken zu Builden, welche sich auf dutzende oder gar hunderte Komponenten (auch) von Drittparteien abstützen. Es ist ein Werkzeug, das dazu beigetragen hat einen Grossteil des Verwaltungsaufwandes für Komponenten aus dem Alltag von Millionen von Fachspezialisten zu eliminieren. Es hat vielen Organisationen geholfen sich weiterzuentwickeln, hinweg über die Phase des täglichen Kampf mit dem Build Management, hin zu einer neuen Phase in welcher der Aufwand einen Build zu erhalten und zu pflegen nicht mehr eine Grenzgrösse des Software Designs darstellt. Dieses Buch ist ein erster Versuch eines umfassenden Werkes bezüglich Maven. Es baut auf die gesammelten Erfahrungen und der Arbeit aller Autoren vorgängiger Titel bezüglich Maven auf und Sie sollten dieses Werk nicht als fertiges Werkstück ansehen sondern vielmehr als der erster Wurf einer langen Reihe von Updates welche dieser Ausgabe folgen werden. Auch wenn es Maven bereits einige Jahre gibt, so empfinden die Autoren dieses Werkes dennoch, dass Maven erst gerade begonnen hat die kühnen Versprechen einzulösen welche auf den Weg gegeben wurden. Die Autoren -wie auch die Firma- welche hinter diesem Buch stehen, Sonatype glauben fest daran, dass dieses Werk dazu beitragen wird, eine neue Welle der Innovation und Entwicklung in Umfeld von Maven und dessen Ökosystem loszutreten. 1. Anleitung zu diesem Buch Nehmen Sie dieses Buch in die Hand und lesen Sie einige Seiten des Textes. Am Ende der Seite werden Sie, sollten Sie die HTML Ausgabe lesen, entweder den Link zur nächsten Seite folgen wollen, oder im Fall der gedruckten Ausgabe werden Sie die Ecke heben und weiterblättern. Sollten Sie vor einem Computer xv
  • 16. Vorwort sitzen, so können Sie sicherlich das eine oder andere Beispiel durchspielen um dem Text zu folgen. Was Sie nicht tun sollten, auch nicht in Wut, ist das Buch in die Richtung eines Mitmenschen zu werfen - es ist einfach zu schwer! Dieses Buch gliedert sich in drei Abschnitte: eine Einführung, einen Beispielteil und eine Referenz. Die Einführung besteht aus zwei Kapiteln: Einführung und Installation. Der Beispielteil führt Maven ein, indem einige echte Anwendungsbeispiele eingeführt werden und Ihnen anhand der Struktur der Beispiele weitere Motivation und Erklärung gegeben werden. Sollte Maven für Sie neu sein, so fangen Sie an, den Beispielteil zu lesen. Die Referenz ist weniger als Einführung gedacht, sondern mehr als Nachschlagewerk. Jedes Kapitel des Referenzteils ziehlt genau auf ein Thema ab und geht hierbei auf die tiefstmöglichen Details ein. So wird das Kapitel über die Erstellung von Plugins anhand von einigen Beispielen und einer Anzahl Listen und Tabellen abgehandelt. Beide Abschnitte bieten Erklärungen und doch werden grundverschiedene Wege eingeschlagen. Während der beispielhafte Teil sich auf den Zusammenhang innerhalb des Maven Projekt stützt, konzentriert sich der Referenzteil jeweils auf ein einziges Thema. Sie können natürlich auch im Buch herumspringen; der beispielhafte Teil ist in keiner Weise Voraussetzung für den Referenzteil, aber Sie werden den Referenzteil besser verstehen, sollten Sie bereits den beispielhaften Teil gelesen haben. Maven wird am Besten anhand von Beispielen gelernt, sobald Sie die Beispiele durchgeabeitet haben werden Sie eine gute Referenzquelle brauchen, um sich Maven Ihren Bedürfnissen anzupassen. 2. Ihr Feedback Wir haben dieses Buch nicht geschrieben, um unserem Herausgeber ein Word Dokument zu senden, eine Veröffentlichung zu feiern und einander zu Beglückwünschen was für eine gute Arbeit wir geleistet haben. Dieses Buch ist nicht "fertig", ja dieses Buch wird wohl nie "fertig" werden. Das behandelte Thema ändert sich ständig, weitet sich aus; und so verstehen wir dieses Buch als ein ständig weitergehender Dialog mit der Maven Community. Dieses Buch herauszugeben bedeutet, dass die wahre Arbeit erst gerade begonnen hat, und Sie, der Leser, spielen eine zentrale Rolle dieses Buch zu erweitern, verbessern und xvi
  • 17. Vorwort aktuell zu halten. Sollten Sie etwas bemerken das fehlerhaft ist: ein Rechtschreibfehler, schlechter Code, eine offenkundige Lüge, dann teilen Sie uns dies mit! Schreiben Sie uns eine Email an: book@sonatype.com. Die weitergehende Rolle dieses Buches ist abhängig von Ihrem Feedback. Wir sind interessiert daran zu erfahren, was funktioniert und was nicht. Wir würden gerne erfahren, sollte dieses Buch Informationen enthalten welche Sie nicht verstehen. Ganz besonders wollen wir erfahren, sollten Sie den Eindruck haben dises Buch wäre schrecklich. Positive und negative Rückmeldungen sind uns willkommen. Wir nehmen uns das Recht heraus nicht jeder Meinung zuzustimmen, aber in jedem Fall bekommen Sie eine gebührende Antwort. 3. Typographische Konventionen Dieses Buch verwendet die folgenden typographischen Konventionen: Kursiv Wird für wichtige Begriffe, Programm- und Dateinamen, URLs, Ordner und Verzeichnispfade und zur Hervorhebung/Einführung von Begriffen verwendet. Nichtproportonalschrift Wird für Programmcode verwendet (Java Klassennamen, -methoden, Variablen, Properties, Datentypen, Datenbank Elemente, und andere Codebeispiele welche in den Text eingestreut sind). Fette Nichtproportonalschrift Wird verwendet für Befehle welche Sie auf der Kommandozeile eingeben, sowie um neue Codefragmente in fortlaufenden Beispielen hervorzuheben. Kursive Nichtproportonalschrift Wird verwendet um Ausgabetext zu kennzeichnen. 4. Konventionen in Bezug auf Maven xvii
  • 18. Vorwort Dieses Buch folgt gewissen Konventionen bezüglich Namensgebung und Schriftsatz bezüglich Apache Maven. Compiler Plugin Maven Plugins werden gross geschrieben. Goal create Maven Goal Namen werden in nichtproportionaler Schrift wiedergegeben. "plugin" Maven kreist stark um die Nutzung von Plugins, aber Sie werden keine umfassende Definition von Plugin in einem Wörterbuch finden. Dieses Buch benutzt den Terminus Plugin, da er einprägsam ist und auch weil er sich in der Maven Community so eingebürgert hat. Maven Lebenszyklus, Maven Standard Verzeichnis Layout, Maven Plugin, Projekt Objekt Modell Die Kernkonzepte von Maven werden im Text gross geschrieben, insbesondere wenn darauf Bezug genommen wird. Goal Parameter Ein Parameter eines Maven Goal wird in nichtproportionalschrift wiedergegeben. Phase compile Lebenszyklusphasen werden in nichtproportionalschrift wiedergegeben. 5. Danksagung Sonatype möchte sich insbesondere bei allen Mitwirkenden bedanken: die folgende Liste ist in keiner Weise abschliessend oder vollständig und alle haben mitgewirkt um die Qualität dieses Werkes zu verbessern. Vielen Dank an Mangalaganesh Balasubramanian, Bertrand Florat, Chad Gorshing, Ali Colabawala, Bozhidar Batzov sowie Mark Stewart. Besonderen Dank auch an Joel Costigliola für die Hilfe und Korrektur des Kapitels bezüglich Spring Web. Stan Guillory war schon xviii
  • 19. Vorwort so etwas wie ein Teilautor, zählt man die grosse Zahl der Korrekturen und Verbesserungsvorschläge welcher er einbrachte. Vielen Dank Stan. Speziellen Dank auch an Richard Coatsby von Bamboo der die Rolle des einstweiligen Grammatik-Beraters aufgenommen hat. Vielen Dank an alle partizipierenden Teilautoren einschliesslich Eric Redmond. Vielen Dank auch an die folgenden Mitwirkenden welche Fehler entweder in Form einer Email oder zum zugehörigen Get Satisfaction Site gemeldet haben: Paco Soberón, Ray Krueger, Steinar Cook, Henning Saul, Anders Hammar, "george_007", "ksangani", Niko Mahle, Arun Kumar, Harold Shinsato, "mimil", "-thrawn-", Matt Gumbley. Sollten Sie Ihren Get Satisfaction Benutzernamen in dieser Liste aufgeführt sehen und würden diesen gerne durch Ihren Namen ersetzt wissen, so senden Sie uns eine kurze Mail an book@sonatype.com. xix
  • 20. Chapter 1. Einführung von Apache Maven Es gibt zwar schon eine Reihe Online Veröffentlichungen zum Thema Apache Maven, aber was bis heute fehlt, ist eine umfassende, gut geschriebenen Einführung von Maven, welche zugleich als altgediente Referenz herhalten kann. Was wir hier versucht haben, ist eine solche gesamthafte Einführung gepaart mit einer umfassenden Referenz zu erstellen. 1.1. Maven ... Was ist das? Die Antwort auf diese Frage hängt ganz von Ihrer Perspektive ab. Die große Mehrheit der Benutzer wird Maven als "Build"-Werkzeug einordnen: ein Werkzeug um aus Quellcode einsatzfähige Artefakte (Programme) zu erzeugen. Build Ingenieure und Projektmanager mögen Maven als etwas umfassenderes sehen: ein (technisches) Projektmanagement-Tool. Wo liegt der Unterschied? Ein Build-Werkzeug wie Ant ist ausschließlich auf die Vorverarbeitung, Kompilierung, Prüfung und Verteilung ausgelegt. Ein Projekt-Management-Tool wie Maven liefert eine Obermenge von Funktionen welche in einem Build- und Paketier-Werkzeug zur Anwendung kommen. Neben der Bereitstellung von Build Funktionalitäten, bietet Maven auch die Möglichkeit Berichte/Auswertungen (Reports) anzufertigen, Websites zu generieren und den Kontakt zwischen den Mitgliedern einer Arbeitsgruppe/eines Teams zu ermöglichen. Hier eine formale Definition von Apache Maven: Maven ist ein Projektmanagement-Tool welches ein umfassendes Projektmodell beinhaltet und eine Reihe von Normen bereitstellt. Darüber hinaus verfügt es über einen definierten Projekt-Lebenszyklus, ein Abhängigkeits-Management-System sowie Logik, welche die Ausführung von Plugin-Goals in definierten Phasen eines Lebenszykluses ermöglicht. Wenn Sie Maven einsetzen, beschreiben Sie Ihr Projekt in einem genau definierten Projekt Objekt Modell, im weiteren Text POM genannt. Maven kann dann auf dieses Modell überspannende Regelwerke aus der Menge gemeinsam genutzter (oder auch massgeschneiderter) Plugins anwenden. Lassen Sie sich nicht von der Tatsache dass Maven ein "Projektmanagement" 1
  • 21. Einführung von Apache Maven Werkzeug darstellt, erschrecken. Sollten Sie lediglich nach einem Build-Tool Ausschau gehalten haben, so wird Maven Ihnen dies ebenfalls bieten. In der Tat, die ersten Kapitel dieses Buches befassen sich mit dem häufigsten Anwendungsfall: Der Verwendung von Maven um Ihr Projekt zu kompilieren, testen und zu verteilen. 1.2. Konvention über Konfiguration Konvention über Konfiguration ist ein einfaches Konzept: Systeme, Bibliotheken und Frameworks sollten von vernünftige Standardwerten ausgehen, ohne unnötige Konfigurationen zu benötigen - Systeme sollten "einfach funktionieren". Gängige Frameworks wie Ruby on Rails und EJB3 haben damit begonnen, sich an diesen Grundsätzen zu orientieren. Dies in Reaktion auf die Komplexität der Konfiguration von Frameworks wie etwa der anfänglichen EJB 2.1 Spezifikationen. Das Konzept 'Konvention über Konfiguration' wird am Beispiel der EJB 3 Persistenz schön veranschaulicht: alles, was Sie tun müssen, um ein bestimmtes Bean persistent zu speichern ist, dieses mit einer @Entitiy-Annotation zu versehen. Das Framework übernimmt Tabellen-und Spaltennamen basierend auf den Namen der Klasse und den Namen der Attribute. Es besteht die Möglichkeit, mittels sogenannter 'Hooks' - wenn nötig - die gesetzten Standardwerte zu übersteuern. In den meisten Fällen werden Sie feststellen, dass der Einsatz der vom Framework bereitgestellten Namen zu einer schnelleren Projektdurchführung führt. Maven greift dieses Konzept auf und stellt vernünftige Standard-Verhalten für Projekte bereit. Ohne weitere Anpassungen, wird davon ausgegangen, dass sich Quellcode im Verzeichnis ${basedir}/src/main/java und Ressourcen sich im Verzeichnis ${basedir}/src/main/resources befinden. Von Tests wird davon ausgegangen, dass diese sich im Verzeichnis ${basedir}/src/test befinden, sowie ein Projekt standardmäßig ein JAR-Archive bereitstellt. Maven geht davon aus, dass Sie den kompilierten Byte-Code unter ${basedir}/target/classes ablegen wollen und anschließend ein ausführbares JAR-Archive erstellen, welches im Verzeichnis ${basedir}/target abgelegt wird. Zwar mag diese Lösung trivial anmuten, jedoch bedenken Sie bitte die Tatsache, dass die meisten Ant-basierten Projekte die Lage dieser Verzeichnisse in jedem Teilprojekt festlegen müssen. Die 2
  • 22. Einführung von Apache Maven Umsetzung von Konvention über Konfiguration in Maven geht noch viel weiter als nur einfache Verzeichnis Standorte festzulegen, Maven-Core-Plugins kennen ein einheitliches System von Konventionen für die Kompilierung von Quellcode, der Paketierung sowie Verteilung der Artefakten, der Generierung von (Dokumentations-) Web-Seiten, und vielen anderen Prozessen. Die Stärke von Maven beruht auf der Tatsache, dass Maven mit einer 'vorbestimmten Meinung' ausgelegt ist: Maven hat einen definierten Lebenszyklus sowie eine Reihe von Plugins welche unter gemeinsamen Annahmen in der Lage sind, Software zu erstellen. Sollten Sie nach den Konventionen arbeiten, erfordert Maven fast keinen Aufwand - einfach Ihre Quellen in das richtige Verzeichnis legen - Maven kümmert sich um den Rest. Ein Nachteil der Verwendung von Systemen welche dem Grundsatz der Konvention über Konfiguration folgen ist, dass Endbenutzer oftmals die Gefahr sehen, dass sie gezwungen werden eine bestimmte Methode anzuwenden - einem bestimmten Ansatz folgen müssen. Zwar ist es sicherlich richtig, dass der Kern von Maven auf einigen wenigen Grundannahmen basiert, welche nicht in Frage gestellt werden sollten, dennoch kann man die meisten Vorgaben auf die jeweilig vorherrschenden Bedürfnisse anpassen und konfigurieren. So kann zum Beispiel die Lage des Projekt-Quellcodes sowie dessen Ressourcen konfiguriert werden, oder die Namen der resultierenden JAR-Archive angepasst werden. Durch die Entwicklung von massgefertigten Plugins, kann fast jedes Verhalten an die Bedürfnisse Ihrer Umgebung angepasst werden. Sollten Sie der Konvention nicht folgen wollen, ermöglicht Maven es Ihnen Voreinstellungen anzupassen um Ihren spezifischen Anforderungen Rechnung zu tragen. 1.3. Die gemeinsame Schnittstelle In der Zeit vor Maven, das eine gemeinsame Schnittstelle für den Build von Software bereitstellte, hatte gewöhnlich jedes einzelne Projekt eine Person, welche sich der Verwaltung des völlig eigenen Build Systems widmete. Entwickler mussten einige Zeit aufwenden, um neben der tatsächlichen Entwicklung herauszufinden, welche Eigenheiten beim Build einer neuen Software zu berücksichtigen waren, zu welcher sie beitragen wollten. Im Jahr 2001, gab es 3
  • 23. Einführung von Apache Maven völlig unterschiedliche Build Konzepte/Systeme welche für den Build eines Projekts wie Turbine, POI oder Tomcat zur Anwendung kamen. Jedesmal, wenn ein neues Source Code-Analyse-Tool zur statischen Analyse von Quellcode veröffentlicht wurde, oder wenn jemand ein neues Unit-Test Framework herausgab welches man einsetzen wollte/sollte, musste man alles stehen und liegen lassen, um herauszufinden, wie dieses neue Werkzeug in das Projekt und dessen Build Umgebung einzupassen wäre. Wie konnte man Unit-Tests laufen lassen? Es gab tausend verschiedene Antworten. Dieses Umfeld war geprägt von tausenden endlosen Auseinandersetzungen bezüglich der 'richtigen' Werkzeuge und Build-Vorgehen. Das Zeitalter vor Maven war ein Zeitalter der Ineffizienz: das Zeitalter der "Build-Ingenieure". Heute setzen die meisten Open-Source-Entwickler bereits auf Maven, oder nutzen dieses um neue Software-Projekten aufzusetzen. Dieser Übergang ist weniger ein Wechsel von einem Build Werkzeug zu einem anderen, sondern viel mehr eine Entwicklung, hin zum Einsatz einer gemeinsamen Build-Schnittstelle für Projekte. So wie Software-Systeme modularer wurden, wurden Build Werkzeuge komplexer und die Zahl der (Teil-)Projekte wuchs in den Himmel. In der Zeit vor Maven, mussten Sie, wollten Sie etwa ein Projekt wie Apache ActiveMQ oder Apache ServiceMix aus Subversion auschecken und aus den Quellen erstellen, mit mindestens einer Stunde Aufwand rechnen, die Sie damit verbrachten herauszufinden, wie das Build-System des einzelnen Projekts funktionierte. Was waren die Voraussetzungen, um das Projekt zu builden? Welche Bibliotheken muss man herunterladen? Wo bekomme ich diese? Welche Targets kann ich im Build ausführen? Im besten Fall dauerte es ein paar Minuten um herauszufinden wie ein neues Projekt zu builden war, - und im schlimmsten Fall (wie die alte Servlet-API-Umsetzung des Jakarta-Projekts), war ein Projekt aufbauen so schwierig, das es mehrere Stunden dauerte nur um an einen Punkt zu gelangen, an dem ein neuer Mitarbeiter den Quellcode bearbeiten konnte und das Projekt kompilierte. Heutzutage machen Sie einen Projekt Check-Out und führen mvn install aus. Während Maven eine Reihe von Vorteilen einschließlich der Verwaltung von Abhängigkeiten und Wiederverwendung von gemeinsamer Logik zum Build durch Plugins bietet, ist der Hauptgrund für den Erfolg der, dass es gelungen ist eine 4
  • 24. Einführung von Apache Maven einheitliche Schnittstelle für den Build von Software bereitzustellen. Wenn Sie sehen, dass ein Projekt wie Apache Wicket Maven verwendet, können Sie davon ausgehen, dass Sie in der Lage sein werden, Projekt ohne weiten Aufwand auszuchecken und aus den Quellen mittels mvn zu bauen und zu installieren. Sie wissen, wo der Zündschlüssel hinkommt, Sie wissen, dass das Gas-Pedal auf der rechten Seite und die Bremse auf der Linken ist. 1.4. Universelle Wiederverwendung durch Maven-Plugins Der Herzstück von Maven ist ziemlich dumm. Es weiss nicht viel mehr als ein paar XML-Dokumenten zu parsen sowie einen Lebenszyklus und ein paar Plugins zu verwalten. Maven wurde so konzipiert, dass die meiste Verantwortung auf eine Anzahl Maven-Plugins delegiert werden kann, welche den Lebenszyklus von Maven beeinflussen sowie gewisse Goals erreichen können. Der Großteil der Arbeit geschieht in Maven Goals. Hier passieren Dinge wie die Kompilierung von Quellcode, der Paketierung von Bytecode, die Erstellung von Websites, und jede andere Aufgabe die um einen Build zu erfüllen notwenig ist. Die Maven Installation nach dem Download von Apache weiß noch nicht viel über die Paketierung eines WAR-Archivs oder dem Ausführen eines JUnit-Tests; der größte Teil der Intelligenz von Maven ist in den Plugins enthalten und diese bezieht Maven aus dem Maven-Repository. In der Tat, das erste Mal, als Sie einen Aufruf wie z.B. mvn install auf Ihrer neuen Maven Installation absetzten, holte sich Maven die meisten der Core Maven Plugins aus dem zentralen Maven-Repository. Das ist mehr als nur ein Trick, um die Download-Größe von Maven in der Verteilung zu beeinflussen, dieses Verhalten ist es, das es erlaubt, durch die Erweiterung eines Plugins die Fähigkeiten des Builds zu verändern. Die Tatsache, dass Maven sowohl die Abhängigkeiten wie auch die Plugins aus einem Remote-Repository lädt ermöglicht universelle Wiederverwendung von Build-Logik. Das Maven Surefire Plugin ist das Plugin, welches für die Ausführung von Unit-Tests verantwortlich zeichnet. Irgendwo zwischen Version 1.0 und der aktuell 5
  • 25. Einführung von Apache Maven verbreiteten Version hat jemand beschlossen, dieses neben der Unterstützung von JUnit auch um die Unterstützung für das TestNG Unit-Test Framework zu erweitern. Dies geschah in einer Art und Weise, dass die Abwärtskompatibilität erhalten blieb. Für die Benutzer des Surefire Plugin im Einsatz von JUnit3 Tests veränderte sich nichts, weiterhin werden die Tests kompiliert und ausgeführt, wie dies zuvor der Fall war. Aber Sie erhalten eine neue Funktionalität, denn sollten Sie nun Unit Tests nach dem TestNG Framework ausführen wollen, so haben Sie nun auch diese Möglichkeit, dank der Bemühungen der Maintainer des Surefire Plugin. Das Plugin erhielt auch die Fähigkeit annotierte JUnit4 Unit Tests zu unterstützen. Alle diese Erweiterungen wurden bereitgestellt, ohne dass Sie Maven aktiv aktualisieren, oder neue Software installieren mussten. Und ganz zentral, Sie mussten nichts an Ihrem Projekt ändern, abgesehen von einer Versionsnummer für ein Plugin in einem POM. Es ist dieser Mechanismus welche sich auf wesentlich mehr als nur das Surefire Plugin auswirkt: Projekte werden mittels einem Compiler Plugin kompiliert, mittels JAR-Plugin in JAR-Archive gepackt, es gibt es Plugins zum Erstellen von Berichten, Plugins zur Ausführung von JRuby und Groovy-Code, sowie Plugins zum Veröffentlichen von Websites auf Remote-Servern. Maven hat gemeinsame Aufgaben in Plug-Ins herausgelöst welche zentral gewartet sowie universell eingesetzt werden. Wann immer Veränderungen in einem Bereich des Buildens umgesetzt werden, wann immer ein neues Unit-Test Framework freigegeben oder ein neues Werkzeug bereit gestellt wird. Sie sind nicht gezwungen, diese Änderungen in ihren Build einzupflegen um dies zu unterstützen. Sie profitieren von der Tatsache, dass Plugins von einem zentral gewarteten Remote-Repository heruntergeladen werden. Das ist die wahre Bedeutung von universeller Wiederverwendung durch Maven Plugins. 1.5. Konzeptionelles Modell eines "Projekts" Maven unterhält ein (abstraktes) Modell eines Projekts, Sie verarbeiten also nicht nur Quellcode Dateien in Binärdateien, Sie entwickeln zugleich eine Beschreibung des Software-Projekts und ordnen diesem eine Reihe einzigartiger Koordinaten zu. Sie beschreiben die Attribute des Projekts: Wie ist die Projektlizenzierung 6
  • 26. Einführung von Apache Maven geregelt? Wer entwickelt und trägt zum Projekt bei? Zu welchen anderen Projekten bestehen Abhängigkeiten? Maven ist mehr als nur ein "Build-Tool", es ist mehr als nur eine Verbesserung von Werkzeugen wie make™ und Ant™; es ist eine Plattform, umfasst eine neue Semantik bezüglich Software-Projekten und Software-Entwicklung. Diese Definition eines Modells für jedes Projekt ermöglicht Funktionen wie: Dependency Management / Abhängigkeits Verwaltung Da ein Projekt einzigartig mittels einer Gruppenkennung (groupId), Artefaktenkennung (artifactId) und Version (version) identifiziert wird, ist nun möglich, diese Koordinaten zur Abhängigkeitsverwaltung einzusetzen. Remote-Repositories Mit Blick auf das Abhängigkeitsmanagement ist es nun möglich diese im Maven Projekt Objekt Modell eingeführten Koordinaten einzusetzen um Maven Repositorien aufzubauen. Universelle Wiederverwendung der Build-Logik Plugins werden ausgerichtet auf das Projekt Objekt Model (POM) gebaut, sie sind nicht dafür ausgelegt auf bestimmte Dateien an bestimmten Orten zuzugreifen. Alles wird in das Modell abstrahiert, Plugin-Konfiguration und Anpassung geschieht im Modell. Tool Portabilität / Integration Werkzeuge wie Eclipse, NetBeans oder IntelliJ haben jetzt einen gemeinsamen Ort um auf Projektinformationen zuzugreifen. Vor dem Aufkommen von Maven, gab es für jede IDE eine spezifische Art und Weise in welcher diese Daten abgelegt wurden, was im Wesentlichen einem benutzerdefinierten POM entspricht. Maven standardisiert diese Beschreibung und während jeder IDE weiterhin deren eigenes Datenablagesystem unterhalten kann, lässt sich dieses nun leicht aus dem Modell heraus generieren. Einfache Suche und Filterung von Projekt-Artefakten Werkzeuge wie Nexus ermöglichen es Ihnen schnell und einfach Archive auf der Basis der im POM enthaltenen Daten zu indexieren und zu durchsuchen. 7
  • 27. Einführung von Apache Maven Maven hat eine Grundlage für die Anfänge einer konsistenten semantischen Beschreibung eines Software-Projekts geschaffen. 1.6. Ist Maven eine Alternative zu XYZ? Natürlich, Maven stellt eine Alternative zu Ant dar, aber Apache Ant ist nach wie vor ein großartiges, weit verbreitetes Werkzeug. Es stellt seit Jahren den amtierende Champion der Java Build Tools, und Sie können auch weiterhin Ant-Build-Skripte einfach in Ihr Maven Projekt integrieren. Das ist auch eine gebräuchliche Art, Maven einzusetzen. Andererseits, jetzt da mehr und mehr Open-Source-Projekte sich auf Maven zubewegen und Maven als Projekt Management Plattform einsetzen, haben aktive Entwickler begonnen zu erkennen, dass Maven nicht nur die Aufgabe der Build Verwaltung unterstützt, sondern insgesamt zur Förderung einer gemeinsamen Schnittstelle zwischen Entwicklern und Software Projekte beiträgt. Maven hat viele Ausprägungen eine Plattform als nur eines Werkzeugs. Wenn Sie die Auffassung vertreten würden, Maven als eine Alternative zu Ant anzusehen, so würden Sie Äpfel mit Birnen vergleichen. Maven umfasst mehr als nur ein einen Werkzeugkasten um Projekte zu Builden. Dies ist das zentrale Argument, welches alle Vergleiche der Art Maven/Ant, Maven/Buildr, Maven/Gradle unerheblich macht. Maven ist nicht bestimmt von der Mechanik Ihres Build-Systems, es fusst nicht auf dem Skripting der verschiedenen Aufgaben Ihres Builds, sondern es geht weit mehr um die Förderung einer Reihe von Standards, einer gemeinsamen Schnittstelle, eines Leben-Zykluses, einem Standard-Repository Format, einem Standard-Verzeichnis Layout, usw. Es geht sicherlich nicht darum, welches POM-Format zum Einsatz kommt, ob XML, YAML, Ruby oder Maven. Maven bietet weit mehr als dies: Maven und bezieht sich auf viel mehr als nur das Werkzeug. Wenn dieses Buch von Maven spricht, so bezieht sich dies auf die Zusammenstellung von Software, Systemen und Standards, welche Maven unterstützt. Buildr, Ivy, Gradle alle diese Werkzeuge interagieren mit den Repository-Format welches zu definieren Maven beigetragen hat. Sie könnten genauso einen Build auf der Basis von Buildr einzig unter Zuhilfenahme von einem Tool wie Nexus aufbauen, Nexus wird in vorgestellt in Kapitel 16: Repository-Manager. 8
  • 28. Einführung von Apache Maven Während Maven eine Alternative für viele dieser Tools darstellt, muss die Gemeinschaft der Open Source Entwickler darüber hinaus kommen können, Technologie als ein kontinuierliches Nullsummenspiel zwischen unfreundlichen Konkurrenten in einer kapitalistischen Wirtschaft zu sehen. Dies mag im Wettbewerb von Großunternehmen und Ihrem Bezug zu einander sinnvoll erscheinen, hat aber wenig Relevanz auf die Art und Weise, wie Open-Source-Communities arbeiten. Die Überschrift "Wer gewinnt? Ant oder Maven?" ist nicht sehr konstruktiv. Zwingen Sie uns, diese Frage zu beantworten, werden wir uns definitiv auf die Seite von Maven schlagen und darlegen, dass Maven eine gegenüber Ant überlegene Alternative darstellt die darüberhinaus die Grundlage für eine Technologie des Buildes legt. Gleichzeitig bitten wir zu berücksichtigen, dass die Grenzen von Maven in ständiger Bewegung sind, die Maven Gemeinde ist immerzu versucht, neue Wege zu mehr Ökumene, mehr Interoperabilität, größerer Gemeinschaft zu schaffen. Die Kernkomponenten von Maven sind der deklarative Build, die Abhängigkeitsverwaltung (Dependency Management), Repository Verwaltung und breite Wiederverwendbarkeit durch den Einsatz von Plugins. Es sind aber die spezifische Inkarnationen dieser Ideen zu einem gegebenen Zeitpunkt weniger wichtig, als das Ziel dass die Open-Source-Community in Zusammenarbeit zur Verringerung der Ineffizienz von "Enterprise Scale Builds" beizutragen. 1.7. Ein Vergleich von Maven und Ant Während der vorangegangene Abschnitt Ihnen verständlich gemacht haben sollte, dass die Autoren dieses Buches keinerlei Interesse an der Schaffung oder Vertiefung einer Fehde zwischen Apache Ant und Apache Maven haben, ist uns die Tatsache bewusst, dass die meisten Organisationen eine Entscheidung zwischen Ant und Maven treffen (müssen). In diesem Abschnitt werden wir daher diese beiden Werkzeuge gegenüberstellen. Ant läuft beim Build-Prozess zu seiner vollen Grösse auf: Es ist ein Build-System nach dem Vorbild von make mit Targets und Abhängigkeiten. Jeder Target besteht aus einer Reihe von Anweisungen, die in XML beschrieben werden. Es gibt eine Task copy, eine Task javac sowie eine Task JAR. Wenn Sie Ant benutzen, müssen 9
  • 29. Einführung von Apache Maven Sie Ant mit der speziellen, spezifischen Anweisungen für die Zusammenstellung und dem Packaging Ihres Projektes aufrufen. Sehen Sie sich das folgende Beispiel einer einfachen Ant build.xml-Datei an: Example 1.1. Eine einfache Ant build.xml-Datei <project name="my-project" default="dist" basedir="."> <description> simple example build file </description> <!-- set global properties for this build --> <property name="src" location="src/main/java"/> <property name="build" location="target/classes"/> <property name="dist" location="target"/> <target name="init"> <!-- Create the time stamp --> <tstamp/> <!-- Create the build directory structure used by compile --> <mkdir dir="${build}"/> </target> <target name="compile" depends="init" description="compile the source " > <!-- Compile the java code from ${src} into ${build} --> <javac srcdir="${src}" destdir="${build}"/> </target> <target name="dist" depends="compile" description="generate the distribution" > <!-- Create the distribution directory --> <mkdir dir="${dist}/lib"/> <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file --> <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/> </target> <target name="clean" description="clean up" > <!-- Delete the ${build} and ${dist} directory trees --> <delete dir="${build}"/> <delete dir="${dist}"/> </target> </project> An diesem einfachen Beispiel eines Ant Skriptes, können Sie sehen, wie genau man definieren muss, was man von Ant erwartet. Ein Target ist javac, hier werden 10
  • 30. Einführung von Apache Maven die Quelldateien in Binärdaten verarbeitet, dabei ist es wichtig, dass man genau angibt, wo die Quell- sowie Zieldateien abgelegt werden (/src/main/java resp. /target/classes). Ebenfalls müssen Sie Ant anweisen, aus den resultierenden Dateien ein JAR-Archiv zu erstellen. Während einige der neueren Entwicklungen dazu beitragen, dass Ant weniger prozedural orientiert arbeitet, ist die Entwickler-Erfahrung dennoch die, einer in XML abgefassten prozeduralen Programmiersprache. Vergleichen Sie das Vorgehen von Maven mit dem vorigen Beispiel: In Maven, um ein JAR-Archive aus einer Reihe von Java Quellcode Dateien zu erstellen, ist alles, was Sie tun müssen, ein einfaches POM (pom.xml Datei) zu generieren. Platzieren Sie den Quellcode in ${basedir}/src/main/java und führen Sie dann mvn install von der Befehlszeile aus . Das Beispiel der Maven pom.xml Datei, welches zum selben Ergebnis führt wie zuvor das Ant-Skript sehen Sie unten. Example 1.2. Muster einer Maven pom.xml-Datei <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0</version> </project> Das ist alles was Sie in der zugehörigen pom.xml Datei benötigen. Der Aufruf von mvn install wird die Resourcen sowie Quelldateien kompilieren, bestehende Unit-Tests ausführen, JAR-Archive erstellen und dieses in einem lokalen Repository anderen Projekten zur Verfügung stellen. Ohne die pom.xml Datei abzuändern, können Sie durch den Aufruf von mvn site im Zielverzeichnis eine index.html wiederfinden, welche den Link zu einer Dokumentations-Web-Seite auf der Basis von JavaDoc sowie einigen Standard Reports bereitstellt. Zugegeben, dies ist das einfachste, mögliche Beispiel-Projekt. Ein Projekt, das nur Quellcode enthält und ein JAR-Archive erzeugt. Ein Projekt welches der Maven Konvention unterliegt und keinerlei Abhängigkeiten oder Anpassungen berücksichtigt. Sobald wir die Verhaltensweisen der Plugins anpassen wird unsere pom.xml Datei wachsen. In den grössten Projekten werden Sie komplexe Maven 11
  • 31. Einführung von Apache Maven POMs finden, welche eine grosse Anzahl Plugin-Anpassungen sowie Abhängigkeitserklärungen enthalten. Aber, selbst wenn das POM Ihres Projekts erheblich wächst, enthält dies eine ganz andere Art von Daten und Informationen denn das Skript einer entsprechenden Ant-Datei. Maven POMs enthalten Deklarationen wie: "Dies ist eine JAR-Projekt", und "Der Quellcode ist unter /src/main/java abgelegt". Wohingegen Ant-Build-Dateien explizite Anweisung enthalten: "Dies ist ein Projekt", "Die Quelle ist in /src/main/java", "javac gegen dieses Verzeichnis ausführen", "Ablegen der Ergebnisse in /target/classses", "Erstellen eines JAR-Archives aus der. ...". Wo Ant genauen Prozess-Anweisungen bedurfte, gab es etwas innerhalb des Maven Builds, das einfach "wusste", wo der Quellcode zu finden ist und wie dieser zu verarbeiten ist. Die Unterschiede zwischen den Ant und Maven in diesem Beispiel sind: Apache Ant • Ant kennt keine formalen Konventionen bezüglich einer gemeinsamen Projekt Verzeichnis-Struktur. Sie müssen Ant genau sagen, wo sich die Quelldateien befinden und wo die Ausgabe abgelegt werden soll. Informelle Konventionen haben sich im Laufe der Zeit herausgebildet, aber diese wurden nicht im Produkt kodifiziert. • Ant ist Prozedural, Sie müssen Ant genau sagen, was zu tun ist und wann dies zu tun. Sie mussten definieren: erst kompilieren, dann kopieren, dann paketieren. • Ant kennt keinen Lebenszyklus, Sie mussten Targets definieren und deren Abhängigkeiten. Sie mussten die Abfolge der Schritte manuell für jeden Target festlegen. Apache Maven • Maven arbeitet nach Konventionen. Da Sie diese beachteten, wusste Maven wo Ihr Quellcode sich befand. Es stellt den Bytecode unter target/classes und es erzeugte ein JAR-Archive in /target. 12
  • 32. Einführung von Apache Maven • Maven ist deklarative. Alles was Sie tun musste, war eine pom.xml Datei zu erzeugen sowie Ihre Quelle im Standard-Verzeichnis ablegen. Maven kümmerte sich um den Rest. • Maven kennt einen Lebenszyklus, welchen Sie mit dem Aufruf von mvn install angestossen haben. Dieser Aufruf bringt Maven dazu, eine Abfolge von Schritten abzuarbeiten, bis das Ende des Lebenszykluses erreicht ist. Als Nebeneffekt dieser Reise durch den Lebenszyklus führt Maven eine Reihe von Standard-Plugin-Goals aus, diese erledigen Arbeiten wie z.B. kompilieren oder dem Erstellen eines JAR-Archives. Maven hat eine eingebaute Intelligenz bezüglich der gemeinsamen Projektaufgaben in Form von Maven-Plugins. Sollten Sie Unit Tests ausführen wollen, ist alles was Sie tun müssen, die Tests zu schreiben und unter ${basedir}/src/test/java abzulegen, führen Sie eine Test bezogene Abhängigkeit zu entweder TestNG oder JUnit ein, und starten Sie mvn test. Sollten Sie anstelle eines JAR-Archives eine Web Anwendung erstellen, so ist alles was Sie tun müssen den Projekttyp auf "war" umstellen und Ihr Applikationsverzeichnis (docroot) auf ${basedir}/src/main/webapp setzen. Klar, können Sie all dies auch mit Ant bewerkstelligen, aber Sie werden alle Anweisungen von Grund auf festhalten. In Ant würden Sie erst einmal herausfinden, wo die JUnit-JAR-Datei sich befindet, dann müssten Sie einen Klassenpfad erstellen welcher auch die JUnit-Archive Datei enthält, und anschliessend Ant mitteilen wo sich der Quellcode befindet. Schliesslich würden Sie einen Target erstellen die Quellen der Test Dateien zu kompilieren und zu guter Letzt die Tests mit JUnit auszuführen. Ohne Unterstützung von Technologien wie antlibs und Ivy (und auch mit diesen unterstützenden Technologien) stellt sich bei Ant das Gefühl eines prozeduralen Build Ablaufs ein. Eine effiziente Zusammenstellung einiger Maven POMs in einem Projekt welches sich an die Maven-Konventionen hält erzeugt überraschend wenig XML im Vergleich zur Ant-Alternative. Ein weiterer Vorteil von Maven ist die Abhängigkeit von weithin benutzten Maven-Plugins. Heutzutage benutzt jeder das Maven Surefire Plugin für Unit-Tests, sobald jemand ein weiteres Unit Test Framework einbindet, können Sie neue Fähigkeiten in Ihrem eigenen Build durch 13
  • 33. Einführung von Apache Maven die einfache Änderung der Version eines einzelnen, bestimmten Maven Plugins nutzen. Die Entscheidung über die Verwendung Maven oder Ant ist heutzutage nicht binär. Ant hat noch immer einen Platz in in komplexen Builds. Wenn Ihr aktueller Build-Prozess einige sehr individuelle Prozesse enthält, oder wenn Sie etliche Ant-Skripte geschrieben haben um einen bestimmten Prozess in einer bestimmten Weise, die sich nicht an die Normen Maven hält, abzudecken, können Sie diese Skripte dennoch mit Maven abarbeiten. Ant wird als Kernkomponente in Form eines Plugins von Maven zur Verfügung gestellt. Benuzerdefinierte Maven-Plugins können in Ant geschrieben werden und Maven Projekte können so konfiguriert werden, das Ant-Skripte innerhalb des Maven-Projekt-Lebenszykluses abgearbeitet werden. 1.8. Zusammenfassung Diese Einführung wurde absichtlich kurz gehalten. Wir haben Ihnen umrissen, was Maven ist und wie es zu einem guten Build-Prozess beiträgt, sowie über die Zeit hinweg diesen verbessern hilft. Im nächste Kapitel werden wir am Beispiel eines einfachen Projektes aufzeigen welche phänomenalen Aufgaben mit dem kleinstmöglich Aufwand an Konfiguration geleistet werden können. 14
  • 34. Chapter 2. Installieren und Ausführen von Maven Dieses Kapitel enthält detaillierte Anweisungen für die Installation von Maven auf einer Reihe von unterschiedlichen Plattformen. Statt ein bestimmtes Mass der Vertrautheit mit der Installation von Software und dem Einstellen von Umgebungsvariablen vorauszusetzen, haben wir uns entschieden, so detailliert wie möglich zu sein, um Probleme welche von unvollständigen Installation herrühren von vornherein zu minimieren. die einzige Voraussetzung welche angenommen wird ist die, einer bestehenden, geeigneten und vollständigen Java Umgebung, eines Java Development Kit (JDK). Sollten Sie nur Interesse an der Installation haben, so können Sie nach dem Lesen der Abschnitte "2.2 Herunterladen von Maven" sowie "2.3 Installation von Maven" zu den weiterführenden Kapiteln des Buches übergehen. Wenn Sie an einer detaillierten Beschreibung interessiert sind, so gibt Ihnen dieses Kapitel die entsprechenden Informationen sowie eine Erklärung der Bedeutung der Apache Software License, Version 2.0. 2.1. Überprüfen der Java-Installation Obschon Maven auch auf Java 1.4 unterstützt ist, geht dieses Buch davon aus, dass sie mindestens auf Java 5 aufsetzen. Wählen Sie den neusten stabilen JDK welcher für Ihre Plattform erhältlich ist. Alle in diesem Buch aufgeführten Beispiele sind unter Java 5 oder Java 6 lauffähig. % java -version java version "1.6.0_02" Java(TM) SE Runtime Environment (build 1.6.0_02-b06) Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing) Maven ist unter allen Java-kompatibel zertifiziert Entwicklungsumgebungen, sowie einigen weiteren, nicht-zertifizierten Implementierungen von Java lauffähig. Die Beispiele in diesem Buch wurden gegen die offizielle Java Development Kit Implementation wie diese von Sun Microsystems Webseite (http://java.sun.com) 15
  • 35. Installieren und Ausführen von Maven heruntergeladen werden kann, geschrieben und getestet. Sollten Sie mit einer Linux-Distribution arbeiten, müssen Sie eventuell die Java Entwicklungsumgebung selbst herunterladen und installieren. Bitte stellen Sie sicher, dass diese auch zum Einsatz kommt: durch Aufruf von "java -fullversion ". Nun, da Sun Microsystem den Quellcode von Java unter einer Open Source Lizenz offengelegt hat, tritt diesbezüglich hoffentlich schon bald eine Besserung ein und es werden auch auf puristisch Linux-Distributionen die originalen Sun JRE sowie JDKs installiert. Bis zu diesem Tag müssen Sie selbst Hand anlegen, Java herunterladen und installieren. 2.2. Herunterladen von Maven Sie können Maven von der Apache Maven-Projekt-Website unter http://maven.apache.org/download.html herunterladen. Beim Herunterladen von Maven, wählen Sie die neuste Version von Apache Maven vom Website. Die aktuell neuste Version zur Zeit als dieses Buch geschrieben wurde war Maven 2.0.9. Sollten Sie die Apache Software Lizenz noch nicht kennen, so machen sie sich - vor dem Einsatz des Produktes - mit deren Bedingungen vertraut. Weitergehende Informationen diesbezüglich finden Sie in Abschnitt "2.8 Die Apache Software Lizenz". 2.3. Installation von Maven Die Unterschiede zwischen den Betriebssystemen wie Mac OS X und Microsoft Windows sind gross, daneben gibt auch subtile Unterschiede zwischen den verschiedenen Versionen von Windows oder Varianten von Linux zu beachten. Zum Glück ist der Prozess der Installation von Maven auf all diesen Betriebssystemen relativ einfach und schmerzlos. Die folgenden Abschnitte geben einen Überblick über die empfohlene Best-Practice der Installation von Maven unter einer Vielzahl von Betriebssystemen. 16
  • 36. Installieren und Ausführen von Maven 2.3.1. Maven Installation unter Mac OSX Am Anfang benötigen Sie zunächst die Software, diese bekommen Sie unter: http://maven.apache.org/download.html. Laden Sie die aktuelle Version von Maven in einem für Sie passenden Format herunter. Wählen Sie einen geeigneten Platz als "Heimat" und entpacken Sie das Archiv dort. Wenn Sie zum Beispiel das Verzeichnis /usr/local/apache-maven-2.0.9 gewählt haben, so macht es Sinn einen symbolischen Link zu erzeugen. Dies erleichtert die Arbeit und erspart die Anpassung von Umgebungsvariablen im Falle eines Upgrades der Version. /usr/local % cd /usr/local /usr/local % ln -s apache-maven-2.0.9 maven /usr/local % export M2_HOME=/usr/local/maven /usr/local % export PATH=${M2_HOME}/bin:${PATH} Ist Maven einmal installiert ist, müssen Sie noch ein paar Schritte unternehmen, damit Maven korrekt funktionieren kann. Zu aller erst sollten Sie das bin-Verzeichnis Ihrer Maven Distribution (in diesem Beispiel /usr/local/Apache-Maven/bin) in den Befehlspfad aufnehmen. Ausserdem sollten Sie noch die Umgebungsvariable M2_HOME auf das Maven-Top-Level-Verzeichnis setzen (in diesem Beispiel /usr/local/Maven). Note Installations-Anweisungen sind die gleichen für OSX Tiger und OSX Leopard. Es wurde berichtet, dass Maven 2.0.6 mit einer Vorschau-Version von XCode ausgeliefert wird. Sollten Sie XCode installiert haben, führen Sie mvn von der Befehlszeile aus, und überprüfen Sie die Verfügbarkeit. XCode installiert Maven unter /usr/share/Maven. Wir empfehlen die Installation der jüngsten Version von Maven 2.0.9, da hier eine Reihe von Bugfixes und Verbesserungen eingeflossen sind. Um die Konfiguration beider Umgebungsvariablen bei jedem Start zu setzen, ist es notwendig diese in einem Skript abzulegen welches bei jedem Systemstart abgearbeitet wird. Fügen Sie daher die folgenden Zeilen der Datei .bash_login an: export M2_HOME=/usr/local/maven 17
  • 37. Installieren und Ausführen von Maven export PATH=${M2_HOME}/bin:${PATH} Mit dem Anfügen dieser Zeilen wird Maven Ihrer Umgebung zugefügt, und Sie bekommen die Möglichkeit Maven von der Kommandozeile zu starten. Note Diese Installationsanleitung geht davon aus, dass Sie unter der bash-Shell arbeiten. 2.3.1.1. Maven Installation unter Mac OSX mit MacPorts Sollten Sie MacPorts einsetzen, so können Sie Maven installieren, indem Sie die folgenden Befehle auf der Kommandozeile eingeben: $ sudo port install maven2 Password: ****** ---> Fetching maven2 ---> Attempting to fetch apache-maven-2.0.9-bin.tar.bz2 from http://www.apache.org/dist/mav ---> Verifying checksum(s) for maven2 ---> Extracting maven2 ---> Configuring maven2 ---> Building maven2 with target all ---> Staging maven2 into destroot ---> Installing maven2 2.0.9_0 ---> Activating maven2 2.0.9_0 ---> Cleaning maven2 Für weiterführende Informationen bezüglich der Maven 2 Portierung verweisen wir auf Portfile. Bezüglich weiterführender Informationen zu MacPorts und wie dies zu installieren ist: MacPorts Projekt Seite (Engl.). 2.3.2. Maven Installation unter Microsoft Windows Die Installation von Maven unter Windows ist sehr ähnlich wie jene unter Mac OSX, die wichtigsten Unterschiede finden sich bezüglich des Installationsortes und der Festlegung der Umgebungsvariablen. Dieses Buch geht von einem Maven Installations-Verzeichnis von "C:Program FilesMaven-2.0.9" aus, aber es wird keinen Unterschied machen, sollten Sie Maven in ein anderes Verzeichnis installieren, solange Sie die Umgebungsvariablen entsprechend anpassen. Nach 18
  • 38. Installieren und Ausführen von Maven dem Entpacken des Maven Installationspaketes müssen Sie zwei Umgebungsvariablen - PATH und M2_HOME - setzen. Um dies zu bewerkstelligen, geben Sie auf der Kommandozeile folgende Befehlen ein: C:Userstobrien > set M2_HOME=c:Program Filesapache-maven-2.0.9 C:Userstobrien > set PATH=%PATH%;%M2_HOME%bin Das Setzen der Umgebungsvariablen in der Kommando-Zeile erlaubt Ihnen die Ausführung von Maven während Ihrer aktuellen Sitzung und zwingt Sie diese bei jedem Neustart erneut zu setzen. Sie sollten diese Variablen daher den Systemeinstellungen zufügen, öffnen Sie hierzu das Menue "Systemeinstellungen" von dort "System" und wählen Sie "". 2.3.3. Maven Installation unter Linux Zur Installation von Maven auf einer Linux-Maschine, folgen Sie der Vorgehensweise in Abschnitt 2.3.2: "Installation von Maven auf Mac OSX". 2.3.4. Maven Installation unter FreeBSD oder OpenBSD Zur Installation von Maven auf einem FreeBSD oder OpenBSD Maschine, folgen Sie der Vorgehensweise in Abschnitt 2.3.2: "Installation von Maven auf Mac OSX" 2.4. Testen einer Maven Installation Ist Maven erst einmal installiert, können Sie die Version überprüfen, indem Sie mvn -v in der Befehlszeile eingeben. Bei einer korrekten Installation von Maven sollte eine Ausgabe wie folgt erscheinen: $ mvn -v Maven 2.0.9 Sollten Sie diese Ausgabe erhalten, wissen Sie dass Maven verfügbar und bereit ist um eingesetzt zu werden. Wenn nicht, hat Ihr Betriebssystem den mvn Befehl nicht gefunden. Überprüfen Sie ob die PATH-Umgebungsvariable sowie M2_HOME 19
  • 39. Installieren und Ausführen von Maven (Gross/Kleinschreibung beachten!) richtig gesetzt sind. 2.5. Spezielle Installationshinweise Das herunter zu ladende Maven Paket hat eine Grösse von etwa 1,5 MiB [1], diese Schlankheit hat es erreicht, da der Kern von Maven mit dem Ziel entwickelt wurde, Plugins und Abhängigkeiten von einem Remote-Repository dynamisch und auf Abruf zuzuladen. Wenn Sie Anfangen mit Maven zu arbeiten, lädt Maven zunächst Plugins in ein lokales Repository. Dieses wird im Abschnitt 2.5.1 "Benutzerdefinierte Konfiguration und -Repository" beschrieben. Im Fall, dass Sie neugierig sind, lassen Sie uns einen Blick darauf werfen, was sich im Maven Installationsverzeichnis befindet. 1 /usr/local/maven $ ls -p1 LICENSE.txt NOTICE.txt README.txt bin/ boot/ conf/ lib/ Die Datei LICENSE.txt enthält die Software-Lizenz für Apache Maven. Diese Lizenz ist in einigen Details in Abschnitt 2.8 "Über die Apache Software Lizenz" beschrieben. NOTICE.txt enthält einige Hinweise zu Abhängigkeiten von Bibliotheken, von denen Maven abhängig ist. README.txt enthält Installationsanweisungen. Das Verzeichnis /bin enthält das "mvn"-Skript, mit welchem Maven aufgerufen wird. /boot enthält eine JAR-Datei (classworlds-1.1.jar), welche den Class-Loader unter welchem Maven ausgeführt wird bereitstellt. /conf enthält eine Datei settings.xml, diese wird eingesetzt um globale Einstellungen festzulegen um das Verhalten von Maven zu definieren und anzupassen. Sollten Sie Maven Ihren Gegebenheiten anpassen 1 Haben Sie je eine 200-GB-Festplatte gekauft, nur um herauszufinden, dass diese weniger als 200 GiB fasst, wenn Sie diese installiert haben? Computer verstehen Gibibytes, aber Einzelhändler verkaufen Produkte mit Gigabyte. MiB steht für die Mebibyte und ist definiert als 2^20 oder 1024^2. Diese binäre Präfix Standards sind von der IEEE, CIPM, und und IEC übernommen. Für weitere Informationen über Kibibytes, Mebibytes, Gibibytes und Tebibytes siehe auch http://en.wikipedia.org/wiki/Mebibyte. 20
  • 40. Installieren und Ausführen von Maven müssen, hat es sich herausgebildet, dass man die Einstellungen der settings.xml durch eine Datei settings.xml im Verzeichnis ~/.m2 übersteuert. /lib enthält eine einzige JAR-Datei (Maven-2.0.9-uber.jar), welche die Hauptimplementierung (Core) von Maven enthält. Note Mit der Ausnahme einer shared Unix Installation, sollten Sie auf die Anpassung der settings.xml unter M2_HOME/conf vermeiden. Die Anpassung der globalen settings.xml-Datei der eigentlichen Maven Installation ist gewöhnlich unnötig und erschwert den upgrade zwischen den Maven Installationen unnötigerweise, da Sie daran denken müssen, die angepasste settings.xml wieder von der bisherigen zur neuen Maven Installation zurückzukopieren. Sollten Sie dennoch gezwungen sein, die settings.xml anzupassen, so sollten Sie die 'persönliche' settings.xml unter ~/.m2/settings.xml anpassen. 2.5.1. Benutzerdefinierte Konfiguration und-Repository Sobald Sie beginnen sich mit Maven ausgiebig auseinander zu setzen, werden Sie feststellen, dass Maven einige lokale, benutzerspezifische Konfigurationsdateien und ein lokales Archiv in Ihrem Home-Verzeichnis angelegt hat. Unter ~/.m2 befinden sich: ~/.m2/settings.xml Eine Datei mit der benutzerspezifischen Konfiguration der Authentifizierung, Repositorien und anderen Informationen, um das Verhalten von Maven zu steuern. ~/.m2/repository/ Dieses Verzeichnis enthält Ihr lokales Maven-Repository. Wenn Sie eine Abhängigkeit von einem Remote-Maven-Repository herunterladen, so wird diese in ihrem lokalen Repository zwischengespeichert. 21
  • 41. Installieren und Ausführen von Maven Note Unter Unix (und OSX), bezeichnet die Tilde "~" Ihr Home-Verzeichnis (d.h. ~/bin bezieht sich auf /home/tobrien/bin). Unter Windows, werden wir uns ebenfalls mit ~ auf Ihr Home-Verzeichnis beziehen. Unter Windows XP ist Ihr Home-Verzeichnis C:Dokumente und Einstellungen<Benutzername>, unter Windows Vista wird Ihr Home-Verzeichnis mit C:Users<Benutzername> bezeichnet. Im Folgenden bitten wir Sie entsprechend Ihres Systems den Pfad anzupassen. 2.5.2. Aktualisieren einer Maven-Installation So Sie unter Unix/OSX arbeiten und die Installation wie oben unter Abschnitt 2.3.2 "Installation von Maven auf Mac OSX" oder Abschnitt 2.3.3 "Installation von Maven auf Linux" beschrieben ausgeführt haben, so ist es ein Kinderspiel eine Maveninstallation zu Aktualisieren: Installieren Sie einfach die neuere Version von Maven (/usr/local/maven-2.future) neben der bestehenden Version von Maven (/usr/local/maven-2.0.9). Ändern Sie dann den symbolischen Link /usr/local/Maven von /usr/local/maven-2.0.9/ auf /usr/local/maven-2.future. Fertig! Da Ihre M2_HOME Variable bereits auf /usr/local/Maven zeigt, müssen keine weiteren Anpassungen ausgeführt werden. Unter Windows, entpacken Sie einfach die neue Version von Maven unter C:Program FilesMaven-2.future und aktualisieren Sie Ihre M2_HOME Variable über die Systemsteuerungen. Note Sollten Sie die globale Datei settings.xml von /M2_HOME/conf angepasst haben, so müssen Sie diese Änderungen in das /conf-Verzeichnis der neuen Installation Übertragen. 22
  • 42. Installieren und Ausführen von Maven 2.5.3. Upgrade einer Maven-Installation von Maven 1.x auf Maven 2.x Beim Upgrade von <term>Maven 1</term> auf <term>Maven 2</term> werden Sie eine ganz neue POM Struktur sowie ein neues Repository benutzen. Haben Sie bereits ein benutzerspezifisches Maven 1 Repository für massgeschneiderte Artefakte erstellt, so können Sie mit Hilfe des Nexus Repository Manager diese so darstellen, dass sie von Maven 2 Anwendungen benutzt werden kann. Bezüglich weiterer Informationen zum Nexus Repository Manager verweisen wir auf Kapitel 16: Repository Manager. Zusätzlich zum Einsatz von Werkzeugen wie Nexus, können Sie Referenzen auf Repositorien so einstellen, dass diese das vorgängige Format unterstützen. Bezüglich weiterer Informationen zum Konfiguration einer Referenz auf ein Maven 1 Repository siehe auch Abschnitt A.2.8: "Repositorien". Sollten Sie eine Anzahl bestehender Maven 1 Projekte unterhalten, interessieren Sie sich bestimmt für das Maven One Plugin. Das Plugin wurde entworfen, um Projekte von Maven 1 auf Maven 2 zu portieren. Ein bestehendes Maven 1 Projekt können sie portieren in dem Sie das Goal one:convert wie folgt aufrufen: $ cd my-project $ mvn one:convert one:convert wird eine Maven 1 project.xml Datei einlesen und in eine pom.xml-Datei konvertieren welches zu Maven 2 kompatibel ist. Sollten Sie allerdings ein Maven 1 Build Skript mittels Jelly angepasst haben, so müssen Sie andere Wege gehen. Während Maven 1 Jelly basierte Anpassungen favorisierte, ist der bevorzugte Weg der Anpassung von Maven 2 Skripten mittels benutzerdefinierten Plugins, skriptbaren Plugins oder dem Maven Antrun Plugin. Das Wichtigste was es bei der Migration von Maven 1 auf Maven 2 zu beachten gilt ist, dass Maven 2 auf einem grundsätzlich verschiedenen Unterbau aufbaut. Maven 2 setzt auf Lebenszyklusphasen und definiert das Verhältnis zwischen Plugins auf eine andere Art und Weise. Sollten Sie eine derartige Migration 23
  • 43. Installieren und Ausführen von Maven vornehmen, so müssen Sie Zeit einplanen, um sich mit den Unterschieden der zwei Versionen vertraut machen. Obschon es nahe liegt sich mit der neuen POM Struktur auseinanderzusetzen, sollten Sie sich zunächst auf das Lebenszykluskonzept konzentrieren. Sobald Sie das Lebenszykluskonzept verstanden haben, steht Ihnen nichts mehr im Weg, Maven zur vollen Stärke einzusetzen. 2.6. Maven De-Installieren Die meisten Installationsanleitungen für Maven beinhalten das Entpacken des Maven Bündel in ein Verzeichnis und das Setzen einiger Umgebungsvariablen. Sollten Sie also Maven von Ihrem Arbeitsplatz entfernen müssen, so müssen Sie lediglich das Maven Installationsverzeichnis löschen und die Umgebungsvariablen entfernen. Sie sollten ebenfalls das Verzeichnis ~/.m2 entfernen, da dies Ihr lokales Repository enthält. 2.7. Hilfe bekommen beim Arbeiten mit Maven Während dieses Buch das Ziel hat, ein umfassendes Nachschlagewerk zu sein, wird es Themen geben welche wir verpasst haben oder besondere Umstände und Situationen sowie Tipps, welche nicht abgedeckt werden. Der Kern von Maven ist sehr einfach, die eigentliche Arbeit geschieht in den Maven Plugins; und deren gibt es zu viele um alle in diesem Buch abdecken zu können. Sie werden zwangsläufig auf Schwierigkeiten stoßen und Eigenheiten finden, welche in diesem Buch nicht berücksichtigt wurden. In diesen Fällen empfehlen wir Ihnen die Suche nach Antworten an folgenden Orten: http://maven.apache.org Dies sollte der erste Ausgangspunk einer jeden Suche sein. Der Maven-Website enthält eine Fülle von Informationen und Dokumentation. Jedes offizielle Plugin hat ein paar Seiten Dokumentation und es gibt eine Reihe von "Quick Start"-Dokumenten, die zusätzlich zum Inhalt dieses Buches hilfreich sein 24
  • 44. Installieren und Ausführen von Maven werden. Während die Maven Website eine Fülle von Informationen enthält, kann es aber auch frustrierend, verwirrend und überwältigend sein, dort zu suchen. Die Seite enthält ein spezielles Google-Suchfeld welches die wichtigsten Informationsquellen bezüglich Maven durchsucht. Diese Suchmaske liefert bessere Ergebnisse als eine generische Google-Suche. Maven User Mailing List Die Maven User Mailing List ist der Ort für Nutzer um Fragen zu stellen. Bevor Sie sich mit einer Frage an die User Mailing List wenden, durchsuchen Sie bitte vorangegangene Diskussionen zum betreffenden Thema. Es zeugt von schlechtem Stil eine Frage stellen, die bereits zuvor gestellt wurde, ohne vorher zu prüfen, ob in den Archiven bereits eine Antwort schlummert. Es gibt eine Reihe von nützlichen Mailing-List Archiv Browsern um Ihnen die Arbeit zu erleichtern. Für unsere Recherchen hat sich Nabble als am nützlichsten erwiesen. Sie können die User Mailing List Archive hier finden: http://www.nabble.com/Maven---Users-f178.html. Eine Beschreibung, wie Sie der User Mailingliste beitreten können finden sie hier http://maven.apache.org/mail-lists.html. http://www.sonatype.com Sonatype unterhält eine Online-Kopie dieses Buches und andere Tutorials im Zusammenhang mit Apache Maven. Note Trotz der größten Bemühungen von einigen sehr engagierten Maven Mitwirkenden, ist die Maven Website schlecht organisiert und voller unvollständiger (und manchmal) irreführender Auszügen und Dokumentationen. In der Gemeinschaft der Maven Entwickler gibt es leider einen Mangel gemeinsamer Standards der Plugin-Dokumentation. Einige Plugins sind sehr gut dokumentiert, während andere nicht einmal die elementarsten Anweisungen für den Gebrauch ausweisen. Oft der erfolgversprechendste Ansatz die Suche nach einer Lösung in den Archiven der User Mailing Lists. Und wenn Sie wirklich helfen wollen unterbreiten Sie einen Patch der Maven Website (oder diesem Buch). 25
  • 45. Installieren und Ausführen von Maven 2.8. Das Maven Hilfe Plugin Im Laufe des Buches werden wir Maven-Plugins Einführen, sprechen von Maven Project Object Model (POM)-Dateien, Einstellungen und Profilen. Es werden Zeiten kommen, in denen Sie ein Werkzeug benötigen, um Ihnen dabei zu helfen, den Sinn einiger in Maven genutzter Modelle zu erkennen sowie die Zielsetzungen bestimmter Plugins nachzuvollziehen. Das Maven Hilfe Plugin ermöglicht Ihnen die in Maven aktiven Profile auszugeben, das tatsächlich aktive Projekt Objekt Modell (POM) anzusehen sowie die Attribute der Maven Plugins darzustellen. Note Für einen konzeptionellen Überblick über das POM und Plug-Ins verweisen wir Sie auf Kapitel 3, Ein einfaches Maven-Projekt. Das Hilfe-Maven Plugin hat vier Goals. Die ersten drei Goals: -active-profiles, effective-pom, sowie effective-settings beschreiben je ein bestimmtes Projekt und müssen daher im Hauptverzeichnis des Projekts aufgerufen werden. Das letzte Goal - describe - ist etwas komplexer, es gibt Informationen über ein Plug-In oder dessen Zielsetzung aus. help:active-profiles Gibt die Profile (project, user, global) aus, welche für diesen Build aktiv sind help:effective-pom Zeigt das tatsächlich eintretende POM für den gegenwärtigen Build unter Berücksichtigung der aktiven Profile aus help:effective-settings Gibt die Einstellungen des Projekts wieder, unter Berücksichtigung von Profilerweiterungen sowie der Vererbung aus den globalen Einstellungen auf die Benutzer spezifischen Einstellungen help:describe Beschreibt die Attribute eines Plugins. Dieses Goal muss einzig nicht unter 26
  • 46. Installieren und Ausführen von Maven einem bestehendes Projekt Verzeichnis aufgerufen werden. Es muss mindestens die groupId und artifactId des Plugin welches Sie beschreiben wollen gegeben werden 2.8.1. Beschreibung eines Maven Plugin Sobald Sie beginnen mit Maven zu arbeiten, werden Sie die meiste Zeit damit verbringen zu versuchen mehr Informationen zu Maven-Plugins zu bekommen: Wie funktionieren Plugins? Was sind die Konfigurations-Parameter? Was sind die Goals? Das Goal help:describe werden Sie regelmässig benutzen um an diese Informationen zu gelangen. Mit dem plugin-Parameter können Sie spezifizieren welches Plugin Sie untersuchen möchten. Hierzu geben sie entweder das Plugin-Prefix an (z.B. maven-help-plugin als help) oder die groupId:artifact[:version], wobei die Version optional ist. hier ein Beispiel; der folgende Befehl verwendet das Hilfe Plugin Goal describe um Informationen über das Maven Hilfe Plugin auszugeben. $ mvn help:describe -Dplugin=help ... Group Id: org.apache.maven.plugins Artifact Id: maven-help-plugin Version: 2.0.1 Goal Prefix: help Description: The Maven Help plugin provides goals aimed at helping to make sense out of the build environment. It includes the ability to view the effective POM and settings files, after inheritance and active profiles have been applied, as well as a describe a particular plugin goal to give usage information. ... Die Ausgabe des Goals describe mit dem Plugin-Parameter gibt die Koordinaten des Plugins aus, dessen Goal, Prefix und eine kurze Beschreibung. Obwohl diese Daten hilfreich sind, werden Sie in der Regel ausführlichere Informationen suchen. Um dies zu erreichen, führen Sie das Goal help:describe mit dem Parameter full aus, wie im folgenden beschrieben: $ mvn help:describe -Dplugin=help -Dfull ... Group Id: org.apache.maven.plugins Artifact Id: maven-help-plugin 27
  • 47. Installieren und Ausführen von Maven Version: 2.0.1 Goal Prefix: help Description: The Maven Help plugin provides goals aimed at helping to make sense out of the build environment. It includes the ability to view the effective POM and settings files, after inheritance and active profiles have been applied, as well as a describe a particular plugin goal to give usage information. Mojos: =============================================== Goal: 'active-profiles' =============================================== Description: Lists the profiles which are currently active for this build. Implementation: org.apache.maven.plugins.help.ActiveProfilesMojo Language: java Parameters: ----------------------------------------------- [0] Name: output Type: java.io.File Required: false Directly editable: true Description: This is an optional parameter for a file destination for the output of this mojo...the listing of active profiles per project. ----------------------------------------------- [1] Name: projects Type: java.util.List Required: true Directly editable: false Description: This is the list of projects currently slated to be built by Maven. ----------------------------------------------- This mojo doesn't have any component requirements. =============================================== ... removed the other goals ... Diese Option ist ideal um alle Goals eines Plugins zu finden, sowie deren 28
  • 48. Installieren und Ausführen von Maven Parameter. Aber manchmal ist dies weit mehr Informationen als notwendig. Um Informationen über ein einziges Goal zu erlangen, setzen Sie mit dem Plugin-Parameter auch den mojo-Parameter zu. Der folgende Aufruf listet alle Informationen über das Goal compile des Compiler-Plugins. $ mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull Note Was? Ein Mojo? In Maven, ein Plugin Goal ist bekannt als "Mojo". 2.9. Die Apache Software Lizenz Apache Maven ist unter der Apache Software Lizenz, Version 2.0 freigegeben. Wenn Sie die Lizenz lesen möchten, lesen Sie diese Lizenz abgelegt in ${M2_HOME}/LICENSE oder lesen Sie diese auf der Webseiten der Open-Source-Initiative http://www.opensource.org/licenses/apache2.0.php nach. Es gibt eine gute Chance, dass Sie, wenn Sie dieses Buch lesen, kein Rechtsanwalt sind. Wenn Sie sich fragen, was die Apache Lizenz, Version 2.0 bedeutet, dann hat die Apache Software Foundation hat eine sehr hilfreich "Häufig gestellte Fragen (FAQ)"- Seite bezüglich der Lizenz zusammengestellt. Sie finden diese unter http://www.apache.org/foundation/licence-FAQ.html. Da die FAQ in Englisch gefasst sind, hier nur eine Beispielhafte Übersetzung der Frage "Ich bin kein Jurist. Was bedeutet das alles ?" Die Apache Lizenz, Version 2.0, Erlaubt Ihnen • die Apache Software kostenlos herunterladen und zu verwenden. Ganz oder teilweise, für persönliche, Firmen-interne oder kommerzielle Nutzung einzusetzen. • Apache Software in oder-Distributionen oder Pakete welche Sie erstellen einzusetzen Verbietet Ihnen: 29
  • 49. Installieren und Ausführen von Maven • Apache Software oder davon abgeleitete Software ohne korrekte Angabe der Herkunft zu verbreiten; • die Benutzung jeglicher Marken der Apache Software Stiftung in einer solchen Weise zu dass daraus abzuleiten ist, dass die Stiftung Ihr Produkt unterstützt • die Benutzung jeglicher Marken der Apache Software Stiftung in einer solchen Weise zu dass daraus abzuleiten ist, dass Sie die Apache Software erschaffen haben. Zwingt Sie nicht • die Source der Apache Software selbst, oder irgendwelcher davon abgeleiteten Produkte welche Sie daraus hergestellt haben einer Distribution oder einem Produkt welches Sie zusammengestellt haben beizulegen. • Änderungen welche sie an der Software der Apache Software Stiftung angebracht haben dieser zurückzugeben (Solcher Feedback wird ermutigt) 30
  • 50. Part I. Maven by Example Das erste Buch zum Thema Maven welches erschienen ist, war das "Maven Developer's Notebook" von O'Reilly, dieses Buch, führte Maven in einer Reihe von einfachen Schritten ein. Die Idee hinter dem Developer's Notebook-Serie war, dass Entwickler am Besten lernen, wenn sie neben einem anderen Entwickler sitzen, und dem Denkprozess folgen, welcher ein anderer Entwickler beschreitet um sich eine neue Materie anzueignen und zu nutzen. Obschon die Serie der Developer's Notebooks erfolgreich war, gibt es eine entscheidende Kehrseite am Notebook-Format: Notebooks sind, par Definition, Ziel orientiert. Sie geleiten durch eine Abfolge von Schritten zu einem bestimmten Ziel. Größere Nachschlagewerke oder "Animal" Bücher sind dazu da, eine umfassende Materialsammlung über das gesamte Thema darzustellen. Beide Bücher haben Vor-und Nachteile, aber die Veröffentlichung des einen ohne das andere ist ein Rezept für zukünftige Probleme. Zur Verdeutlichung des Problems, nehmen Sie einmal an, dass zehntausend Menschen nachdem sie "A Developer's Notebook" gelesen haben, alle wissen werden, wie man ein einfaches Projekt aufsetzt, dem Beispiel halber ein Maven Projekt welches aus einer Reihe von Quelldateien ein WAR-Archive generiert. Aber, sobald diese Personen tiefer gehende Informationen nachschlagen möchten oder die eine oder andere Besonderheit zum Beispiel des "Assembly" Plugins erfahren wollen, stecken sie in so etwas wie einer Sackgasse. Da es kein gut geschriebenes Referenzmanual für Maven gibt, müssen sie nun auf die Plugin-Dokumentation auf dem Maven Site zurückgreifen oder alternativ sich durch eine Reihe von Mailinglisten quälen. Wenn die Leute erst einmal in der Maven Dokumentation graben, fangen sie an, tausende von schlecht geschrieben HTML-Dokumente des Maven Websites zu lesen, geschrieben von Hunderten von verschiedenen Entwicklern, jeder mit einer anderen Vorstellung davon, was es bedeutet, ein Plugin zu dokumentieren: Hunderte von Entwicklern mit unterschiedlichen sprachlichen Stilen, Ausdrücken und Muttersprachen. Trotz der größten Bemühungen von Hunderten von gut meinenden Freiwilligen, ist die Lektüre der Plugin-Dokumentation auf der Website Maven, ist im besten Fall frustrierend - und im schlimmsten Fall ein Grund Maven aufzugeben. Oftmals 31
  • 51. bleiben Maven Nutzer stecken, nur weil sie auf dem Maven Site schlicht keine Antwort auf ihr Problem finden können. Während das erste Maven Developer's Notebook neue Benutzer zu Maven hinzog, und diese mit grundlegenden Kenntnissen zur Nutzung von Maven ausstattete, wuchs schon bald Frustration in dieser Gruppe, denn sie konnten beim besten Willen kein prägnantes, gut geschriebenes Referenz-Handbuch finden. Dieser Mangel eines aussagekräftiges (oder definierenden) Referenz-Handbuches hat Maven für einige Jahre zurückgehalten; es wurde etwas wie eine dämpfende Kraft in der Maven User-Community. Dieses Buch zielt darauf ab, diese Situation zu ändern, indem sie sowohl ein Update des ursprünglichen Maven Entwickler Notebook in Teil I, "Maven by Example" darstellt, und auch den ersten Versuch eines umfassenden Nachschlagewerks in Teil II, "Maven-Referenz". Was haben Sie in Ihren Händen halten, (oder auf dem Bildschirm sehen) sind eigentlich zwei Bücher in einem. In diesem Teil des Buches, werden wir den erzählenden, fortschreitenden Stil des Developer's Notebooks beibehalten, denn es ist wertvolles Material, welches dem Einsteiger hilft, anhand von Beispielen zu lernen, eben "Maven by Example". In der ersten Hälfte des Buches wird Maven an Hand von Beispielen eingeführt, um dann in Teil II, der "Maven-Referenz" Lücken zu schliessen, uns in Details zu ergeben und fortgeschrittene Themen einzuführen welche sonst unter Umständen den neuen Benutzer von Maven irritieren und vom Weg abbringen könnte. Während Teil II: Maven-Referenz auf einen Tabellenverweis oder einem vom Beispiel entkoppelten Programmausdruck setzt, setzt Teil I: Maven by Example auf die Kraft einer guten Vorbildwirkung sowie einer durchgehenden Storyline. Mit Abschluss von Teil I: Maven by Example, sollten Sie das Rüstzeug haben, das Sie brauchen, um mit Maven für ein paar Monate auszukommen. Wahrscheinlich werden sie nur auf Teil II: Maven-Referenz zurückkommen, um eine Anpassung eines Maven-Plugins vorzunehmen, oder wenn Sie weitere Details zu den einzelnen Plugins benötigen. 32
  • 52. Chapter 3. Ein einfaches Maven Projekt 3.1. Einleitung In diesem Kapitel führen wir ein einfaches Projekt ein, welches wir von Grund auf mit dem Maven Archetype Plugin erstellen. Diese Anwendung bietet uns die Gelegenheit einige grundlegende Konzepte von Maven einzuführen, während Sie einfach der Entwicklung des Projektes folgen. Sollten Sie bereits zuvor Maven eingesetzt haben, werden Sie festgestellt haben, dass Maven mit sehr wenigen Details gut zurecht kommt. Ihr Build wird "einfach funktionieren", und Sie brauchen sich nur um Details zu kümmern, wenn es notwendig ist, das Standardverhalten zu ändern oder ein angepasstes Plugin zu erstellen. Allerdings, sollten Sie gezwungen sein sich um die Details von Maven zu kümmern, so ist ein gutes Verständnis der grundlegenden Konzepte von wesentlicher Bedeutung. Dieses Kapitel zielt darauf ab, das einfachste mögliche Maven-Projekt einzuführen und daran Ihnen einige der grundlegenden Konzepte, welche die Maven zu einer soliden Build Plattform machen, vorzustellen. Nach dem Lesen dieses Kapitels werden Sie ein grundlegendes Verständnis des Build Lifecycles, des Maven Repositories, der Abhängigkeits-Verwaltung (Dependency Management) und dem Projekt Objekt Modell (POM) haben. 3.1.1. Das Herunterladen der Beispiele dieses Kapitels Dieses Kapitel entwickelt ein sehr einfaches Beispiel, welches dazu dient einige Kern-Konzepte von Maven vorzustellen. Indem Sie den Schritten des Kapitels folgen, sollte es nicht notwendig werden die Beispiel herunterzuladen um den von Maven erstellten Quellcode einzusehen. Um dieses Beispiel zu erstellen werden wir das Archetype Maven Plugin einsetzen. Dieses Kapitel ändert das Projekt in keiner Weise. Sollten Sie es vorziehen, dieses Kapitel anhand der letzendlichen Quellcode-Beispiele zu lesen, so kann das Beispielprojekt dieses Kapitels zusammen mit den anderen Beispielen dieses Buchs von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder 33
  • 53. Ein einfaches Maven Projekt http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch03. Darin finden Sie ein Verzeichnis mit dem Namen /simple welches den Quellcode für dieses Kapitel enthält. 3.2. Erstellen eines einfachen Projekts Um ein neues Projekt unter Maven zu beginnen, setzen Sie das Archetype Maven-Plugin von der Befehlszeile aus ein. $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 -DartifactId=simple -DpackageName=org.sonatype.mavenbook [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for updates from central [INFO] ----------------------------------------------------------------------- [INFO] Building Maven Default Project [INFO] task-segment: [archetype:create] (aggregator-style) [INFO] -------------------------------------------------------------------- [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch03 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: /Users/tobrien/svnw/sonatype/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: simple [INFO] * End of debug info from resources from generated POM * [INFO] Archetype created in dir: /Users/tobrien/svnw/sonatype/examples/simple mvn ist der Aufruf von Maven2; archetype:create bezeichnet ein Maven Goal. So Sie Ant kennen, ist ein Maven Goal gleichbedeutend mit einem Ant Target. Beide beschreiben eine Arbeitseinheit eines Builds. Die -Dname=wert Wertepaare stellen Argumente dar, welche an das Goal weitergereicht werden und nehmen die Form von -D Eigenschaft an, ähnlich deren welche Sie der Java Virtual Machine über die Befehlszeile weitergeben. Der Zweck des Goals archetype:create ist, schnell ein Projekt auf der Grundlage eines Archetypen zu erstellen. Ein Archetyp in diesem Kontext ist definiert als "ein originalgetreues Modell oder Typmuster nach dem andere, ähnliche Dinge gemustert sind, ein Prototyp" [1][2]. Von Maven 34
  • 54. Ein einfaches Maven Projekt werden etliche Archetypen bereitgehalten, diese reichen von der einfachen Swing Anwendung bis hin zur komplexen Web-Applikation. In diesem Kapitel werden wir den aller einfachsten Archtetypen nutzen um ein einfaches Gerüst eines Starter-Projekts zu erstellen. Das Plugin hat das Präfix "archetype" und das entsprechende Goal ist "create". Sobald wir das Projekt generiert haben, sehen Sie sich das Verzeichnis /simple und die von Maven darin generierte Verzeichnisstruktur näher an: simple/‚ simple/pom.xmlƒ /src/ /src/main/„ /main/java /src/test/… /test/java Der erzeugte Verzeichnisbaum hält sich an das Maven Standard Layout, wir werden später auf die Einzelheiten eingehen, aber für den Moment konzentrieren wir uns auf die wenigen grundsätzlichen Verzeichnisse: ‚ Das Archetype Maven Plugin erstellt ein Verzeichnis, das mit der artifactId einhergeht: /simple. Dieses Verzeichnis ist bekannt als das Projekt Basis-Verzeichnis. ƒ Jedes Maven-Projekt benötigt was als als Projekt Objekt Modell (POM) bekannt ist, in einer Datei mit dem Namen pom.xml. Diese Datei beschreibt das Projekt, konfiguriert Plugins, und definiert Abhängigkeiten. „ Unser Projekt-Quellcode und zugehörige Ressourcen werden unter /src/main abgelegt. Im Falle unseres einfachen Java-Projekt wird dies aus ein paar Java-Klassen und einigen Property-Dateien bestehen. In einem anderen Projekt könnte dies das Root-Dokument einer Web-Anwendung sein, oder Konfigurationsdateien für einen Applikations-Server. In einem Java-Projekt werden die Java-Klassen in /src/main/java sowie Ressoucen in /src/main/resources abgelegt. … Unsere Projekt-Testfälle befinden sich unter /src/test. In diesem Verzeichnis werden Java-Klassen wie z.B. JUnit oder TestNG Tests (/src/test/java) abgelegt, sowie Klassenpfad relevante Ressourcen für Tests unter /src/test/resources. 35
  • 55. Ein einfaches Maven Projekt Das Maven Archetype Plugin erzeugt eine einzige Klasse com.sonatype.maven.App. Dies ist eine dreizeilige Java-Klasse mit einer statischen main()-Funktion welche eine Nachricht ausgibt: package org.sonatype.mavenbook; /** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } Der einfachste Maven Archetyp generiert das einfachst mögliche Programm: ein Programm, welches "Hallo Welt!" auf der Standard-Ausgabe ausgibt. 3.3. Der Aufbau eines einfachen Projekts Sobald Sie das Projekt mit dem Maven Archetype Plugin erstellt haben, indem Sie die Anweisungen aus Abschnitt 3.2: Erstellen eines einfachen Projekts ausführen, werden Sie es builden und als Anwendung paketieren wollen. Um dies zu bewerkstelligen, rufen Sie im Verzeichnis in welchem sich die pom.xml-Datei befindet den Befehl mvn install von der Befehlszeile auf. $ mvn install [INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------- [INFO] Building simple [INFO] task-segment: [install] [INFO] ---------------------------------------------------------------------- [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Compiling 1 source file to /simple/target/classes [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Compiling 1 source file to /simple/target/test-classes [INFO] [surefire:test] [INFO] Surefire report directory: /simple/target/surefire-reports 36
  • 56. Ein einfaches Maven Projekt ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.sonatype.mavenbook.AppTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [jar:jar] [INFO] Building jar: /simple/target/simple-1.0-SNAPSHOT.jar [INFO] [install:install] [INFO] Installing /simple/target/simple-1.0-SNAPSHOT.jar to ~/.m2/repository/com/sonatype/maven/ch03/simple/1.0-SNAPSHOT/ simple-1.0-SNAPSHOT.jar Sie haben soeben das einfachste mögliche Maven Projekt erstellt, kompiliert, getestet, paketiert und installiert. Um sich zu überzeugen, dass die Anwendung tatsächlich lauffähig ist, starten Sie sie von der Befehlszeile: $ java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App Hello World! 3.4. Einfaches Projekt Objekt Modell (POM) Bei der Ausführung von Maven richtet dieses sich nach dem Projekt Objekt Modell (POM), um an Informationen über das Projekt zu gelangen. Das POM liefert Antworten auf Fragen wie: Welche Art von Projekte ist es? Wie lautet der Projektname? Gibt es irgendwelche Anpassungen am Build bezüglich diesem Projekt? Hier die grundlegende pom.xml-Datei wie sie vom Archetype Maven Plugin Goal create erstellt wurde. Example 3.1. Einfachstes Projekt pom.xml Datei <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch03</groupId> <artifactId>simple</artifactId> 37
  • 57. Ein einfaches Maven Projekt <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>simple</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> Diese pom.xml-Datei stellt das elementarste POM dar mit welchem Sie je in einem Maven Projekt zu tun haben werden. In der Regel ist eine POM-Datei wesentlich komplexer: Abhängigkeiten werden definiert und Plugin Verhalten angepasst. Die ersten Elemente- groupId, artifactId, packaging sowie version sind als Maven-Koordinaten bekannt. Sie sind der eindeutigen Identifizierung eines Projekts gewidmet. name und url sind beschreibende Elemente des POM, welche in einer einfach lesbare Art den Namen des Projektes sowie einer zugeordneten Projekt-Website wiedergeben. Zuletzt wird im dependency Element eine einzige Beziehung als "Test-Scoped" Abhängigkeit zum JUnit-Test Framework unter dem Namen JUnit definiert. Diese Themen werden im Abschnitt 3.5: "Kernkonzepte" ausgeführt. Zum jetzigen Zeitpunkt ist alles was sie wissen müssen, dass die pom.xml-Datei Maven zum Leben erweckt. Bei der Ausführung von Maven wird dieses gesteuert durch eine Kombination von Einstellungen innerhalb der pom.xml-Datei, einer Art "Super"-POM welche sich im Installationsverzeichnis von Maven befindet, sowie (möglicherweise) einigen benutzerspezifischen Einstellungen. Um sich das "tatsächliche" POM, oder das POM, gegen welches Maven ausgeführt wird anzusehen, geben Sie im Stammverzeichnis Ihres Projekts den folgenden Befehl ein: $ mvn help:effective-pom Mit der Ausführung bekommen Sie ein wesentlich umfangreicheres POM, das auch die Maven Standardeinstellungen enthält 38
  • 58. Ein einfaches Maven Projekt 3.5. Kern Konzepte Nachdem Sie nun zum ersten Mal Maven eingesetzt haben, ist ein guter Zeitpunkt gekommen einige der Kernkonzepte von Maven einzuführen. Im vorigen Beispiel haben Sie, aufbauend auf ein einfaches POM, ein einfaches Projekt generiert, welches im Standard Layout von Maven (der Konvention folgend) strukturiert die Quelldateien abgelegt hat. Sie haben dann Maven mit einer Lebenszyklusphase als Argument aufgerufen und damit ausgelöst, dass Maven eine Anzahl Plugin Goals abgearbeitet hat. Zuletzt haben Sie einen Maven Artifact in Ihr lokales Repository installiert. Moment! Was ist ein "Lebenszyklus"? Was ist ein "lokales Repository"? Die folgenden Abschnitte beschreiben einige der für Maven zentralen Konzepte. 3.5.1. Maven Plugins und Ziele Im vorigen Abschnitt haben wir Maven mit zwei unterschiedlichen Kommandozeilen-Argumenten aufgerufen. Der erste Befehl war ein einziges Plugin Goal, das Goal create des Archetype Maven Plugin. Der zweite Aufruf von Maven war hingegen gekoppelt mit einer Lifecycle-Phase - install. Um ein einzelnes (bestimmtes) Maven Plugin Goal auszuführen, benutzten wir die Syntax mvn archetype:create, hierbei ist archetype die Kennung eines Plugins und create die Kennung eines Goals. Wann immer Maven ein Plugin Goal ausführt, gibt es die Plugin- sowie die Goal-Kennung auf der Standardausgabe aus: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 -DartifactId=simple -DpackageName=org.sonatype.mavenbook ... [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central ... Ein Maven-Plugin ist eine Sammlung von einem oder mehreren Goals. Beispiele von Maven-Plugins sind einfache Core-Plugins wie das JAR-Plugin welches das Goal zur Erzeugung von JAR-Archiven beinhaltet; das Compiler-Plugin mit den Goals Source Code kompilieren oder Unit Test Quellcode zu kompilieren oder das Surefire Plugin welches Goals zum Abarbeiten von Unit Tests sowie der Report 39
  • 59. Ein einfaches Maven Projekt Generierung diesbezüglich beinhaltet. Andere, stärker spezialisierte Maven-Plugins sind z. B. das Hibernate3 Plugin zur Integration mit der beliebten Hibernate Persistenz Bibliothek, dem JRuby Plugin, welches erlaubt Ruby als Teil des Builds auszuführen sowie Maven Plugins in Ruby zu schreiben. Maven sieht auch vor benutzerdefinierte Plugins einzubinden. Ein benutzerdefiniertes Plugin kann in Java geschrieben sein, oder aber auch in einer von vielen anderen Sprachen darunter Ant, Groovy, BeanShell, und, wie bereits erwähnt, Ruby. Figure 3.1. Ein Plugin enthält Goals Ein Goal ist eine spezifische Aufgabe, welche alleinstehend, oder zusammen mit anderen Goals als Teil eines größeren Builds ausgeführt werden kann. Ein Goal ist eine "Arbeitseinheit" in Maven. Beispiele von Goals sind das Goal compile des Compiler-Plugin, es kompiliert den gesamten Quellcode für ein Projekt, oder das Goal test des Surefire Plugin welches Unit-Tests ausführen kann. Goals werden durch Konfigurations-Parameter bestimmt, welche erlauben das Verhalten anzupassen. Ein Beispiel, das Goal compile des Compiler-Plugin definiert eine Reihe von Konfigurations-Parameter, mit denen Sie die gewünschte JDK-Version Version bestimmen, sowie über den Einsatz von Compiler-Optimierungen bestimmen können. Im vorigen Beispiel haben wir über die Befehlszeile die Parameter groupId und artifactId an das Goal create des Archetype Plugin weitergegeben (-DgroupId=com.sonatype.maven.ch03 sowie -DartifactId=simple). Darüber hinaus haben wir den Parameter packageName als com.sonatype.maven an das Goal create weitergegeben. Hätten wir den Parameter packageName ausgelassen, wäre die Voreinstellung com.sonatype.maven.ch03 40
  • 60. Ein einfaches Maven Projekt zum Zug gekommen. Note Im weiteren Text werden wir häufig, wenn wir auf ein Plugin Goal verweisen eine verkürzte Notation benutzen: pluginId:goalId; Ein Beispiel: um sich auf das Goal create des Archetype Maven Plugin zu beziehen schreiben wir archetype:create. Goals legen Parameter fest, für welche vernünftige Standardwerte definiert sein können. Im vorangehenden Beispiel archtetype:create, wurde der Archetype nicht spezifiziert, es wurde lediglich eine groupId sowie eine artifactId weitergegeben. Hier kommen wir das erste mal mit Konvention vor Konfiguration in Berührung. Die Konvention, oder der Standard, des Goals create ist die Generierung eines einfachen Projektes namens Quickstart. Das Goal create definiert einen Parameter archetype:artifactId welcher auf den Standardwert maven-archetype-quickstart gesetzt ist. Der Archetyp 'Quickstart' erzeugt eine minimale Projekthülse, mit zugehörigem POM sowie einer einzigen Klasse. Das Archetype Plugin ist wesentlich mächtiger als dieses Beispiel andeutet, jedoch ist es eine gute Möglichkeit, um neue Projekte schnell zu starten. Später in diesem Buch, zeigen wir Ihnen, wie das Archetype Plugin benutzt werden kann, um komplexere Projekte wie Web-Anwendungen zu generieren, und wie Sie das Archetype Plugin benutzen können um Ihren eigenen Satz von Projekten zu definieren. Der Kern von Maven hat nur wenig zu tun mit den spezifischen Aufgaben welche am Build Ihres Projektes beteiligt sind. Von sich aus weiss Maven nicht wie man Ihre Quelldateien kompiliert oder gar ein JAR-Archive packt. Maven delegiert all diese Arbeiten an Maven-Plugins wie das Compiler Plugin oder das JAR-Plugin. Diese können, sobald sie gebraucht werden, aus dem zentralen Maven Repository heruntergeladen und periodisch aktualisiert werden. Wenn Sie Maven herunterladen, so bekommen Sie lediglich den Kern oder Core von Maven. Dieser besteht aus einer sehr grundlegenden Shell, welche nur in der Lage ist die Befehlszeile zu analysieren, den Klassenpfad zu verwalten, eine POM-Datei zu analysieren und nach Bedarf Plugins herunterzuladen. Indem das Compiler Plugin 41
  • 61. Ein einfaches Maven Projekt vom Core getrennt gehalten wurde und über einen Update Mechanismus verfügt, ist es ein Leichtes für den Benutzer, auch die neusten Compiler Optionen zum Einsatz zu bringen. Auf diese Weise ermöglichen Maven-Plugins universelle Wiederverwendbarkeit von übergreifender Buildlogik, Sie definieren nicht einen Kompilierungs-Task innerhalb des Builds, Sie benutzen das Compiler Plugin wie es von jedem Maven Benutzer eingesetzt wird. Wird das Plugin weiterentwickelt, kann jedes Projekt auf der Basis von Maven sofort den Nutzen daraus ziehen (und sollten Sie das Compiler Plugin nicht mögen, so können Sie es übersteuern und Ihr eigenes zum Einsatz bringen!). 3.5.2. Maven Lifecycle Der zweite Aufruf des vorangegangenen Abschnitts war mvn package. Bei diesem Aufruf wurde kein Plugin Goal angegeben, statt dessen wurde eine Lebenszyklusphase angegeben. Eine Phase ist ein Schritt in dem was Maven den "Build Lifecycle" nennt. Der Build Lebenszyklus ist eine geordnete Folge von Phasen welche am Aufbau eines Projekts beteiligt sind. Maven unterstützen kann eine Reihe ganz unterschiedlicher Lebenszyklen. Der am häufigsten zum Einsatz kommende ist der Standard-Lebenszyklus. Dieser beginnt mit eine Phase der Validierung der grundlegenden Integrität des Projekts und endet mit einer Phase der Bereitstellung von einem Projekt für die Produktion. Lifecycle Phasen sind bewusst vage gehalten, im einzelnen lediglich definiert als Validierung, Test oder Verteilung kann eine Phase für verschiedene Projekte ganz unterschiedliche Bedeutung haben. Zur Verdeutlichung ein Beispiel: die Phase package bedeutet in einem Projekt welches ein JAR-Archive erstellt: 'erstelle ein JAR-Archive', hingegen in einem Projekt welches eine Webanwendung zum Ziel hat: 'Erstelle ein WAR-Archive'. Abbildung 3.2 "Ein Lebenszyklus besteht aus einer Abfolge von Phasen" zeigt eine vereinfachte Darstellung des Maven Standard Lebenszykluses. Plugin-Goals können an Lebenszyklus-Phasen gebunden werden. Mit dem Durchschreiten des Lebenszykluses arbeitet Maven die Goals der einzelnen Phasen ab. An jeder Phase können keine (0) oder mehrere Goals gebunden sein. Im vorangegangenen Abschnitt, beim Aufruf von mvn package, haben Sie vielleicht bemerkt, dass mehr als ein Ziel ausgeführt wurde. Sehen Sie sich die Ausgabe nach 42
  • 62. Ein einfaches Maven Projekt dem Ausführen von mvn package genau an, Sie werden sehen, dass verschiedene Goals ausgeführt wurden. Als dieses einfache Beispiel die Phase package erreichte, führte es das Goal jar des Jar Plugins aus. Da unser einfaches Schnellstart-Projekt (standardmäßig) den Paketierungstyp jar setzt, wird das Goal jar:jar an die Phase package gebunden. Figure 3.2. Ein Goal wird an eine Phase gebunden Wir wissen nun, dass die Phase package für ein Projekt mit jar-Paketierung ein JAR-Archive erstellen wird. Aber wie steht es um die vorangehenden Goals, deren wie etwa compiler:compile und surefire:test? Diese Goals werden von Maven beim Durchschreiten der vorhergehenden Phasen des Lebenszykluses abgearbeitet. Der Aufruf einer bestimmten Phase löst aus, dass alle vorgängigen Phasen in entsprechender Reihenfolge abgearbeitet werden. Mit dem Abschluss der spezifizierten Phase endet die Bearbeitung. Jede Phase entspricht keinem oder mehreren abzuarbeitenden Goals, und da wir keine weitere Plugin-Konfiguration oder Anpassung durchgeführt haben, bindet dieses Beispiel lediglich eine Reihe von Standard-Plugin-Goals an den Standard-Lebenszyklus. Läuft Maven entlang des definierten Standard-Lebenszykluses bis zur Phase package, werden die folgenden Goals in der angegebenen Reihenfolge ausgeführt: resources:resources Das Goal resources des Resouces Plugin wird an die Phase resources gebunden. Dieses Goal kopiert alle Ressourcen von /src/main/resources und allen anderen, konfigurierten Ressource- Verzeichnissen in das Ausgabe-Verzeichnis. compiler:compile 43
  • 63. Ein einfaches Maven Projekt Das Goal compile des Compiler Plugin wird an die Phase compile gebunden. Dieses Ziel kompiliert den gesamten Quellcode von /src/main/java oder jedem anderen konfigurierten Quellen-Verzeichnisse in das Ausgabe-Verzeichnis. resources:testResources Das Goal testResources des Resources Plugin wird an die Phase test-resources gebunden. Dieses Goal kopiert alle Ressourcen von /src/test/resources und jedem anderen, konfigurierten Test-Ressource Verzeichnis in ein Test Ausgabe Verzeichnis. compiler:testCompile Das Goal testCompile des Compiler-Plugin wird an die Phase test-compile gebunden. Dieses Goal kompiliert die Testfälle von /src/test/java und allen anderen konfigurierten Test-Quellen-Verzeichnissen in ein Test Ausgabe Verzeichnis. surefire:test Das Goal test des Surefire Plugin wird an die Phase test gebunden. Dieses Ziel führt alle Tests aus und generiert Ausgabedateien der detaillierten Ergebnissen. Standardmäßig wird dieses Goal den Build beenden, sollte ein Test scheitern. jar:jar Das Goal jar des JAR-Plugin wird an die Phase package gebunden. Dieses Goal paketiert das Ausgabeverzeichnis und erstellt ein JAR-Archiv. 44
  • 64. Ein einfaches Maven Projekt 45
  • 65. Ein einfaches Maven Projekt Figure 3.3. Gebundene Goals werden mit den zugehörigen Phasen ausgeführt. (Anmerkung: Es bestehen mehr Phasen als oben abgebildet, dies ist ein Auszug der Möglichkeiten) Wir fassen zusammen: beim Ausführen von mvn package durchläuft Maven alle Lebenszyklus Phasen bis und mit package, und führt hierbei in Abfolge alle Goals aus, welche zugehörig an Phasen gebunden sind. Anstelle der Angabe eines Maven Lifecycle Goals, können Sie das gleiche Ergebnis erreichen, indem Sie eine Abfolge von Plugin Goals angeben: mvn resources:resources compiler:compile resources:testResources compiler:testCompile surefire:test jar:jar install:install Die Ausführung der Phase package ist der Verwaltung aller beteiligten Goals eines bestimmten Builds vorzuziehen. Ein derartiges Vorgehen erlaubt den Projekten auch die Einhaltung genau definierter Standards. Es ist der Lebenszyklus, welcher einem Entwickler erlaubt von von einem Maven-Projekt zu einem anderen zu springen, ohne sich um die Details der einzelnen Builds zu kümmern. Schaffen Sie es ein Maven-Projekt zu builden, so schaffen Sie dies für alle! 3.5.3. Maven Koordinaten Das Archetype Plugin erstellte ein Projekt mit einer Datei namens pom.xml. Dies ist das Projekt Objekt Modell (POM), eine deklarative Beschreibung eines Projekts. Wenn Maven ein Goal ausführt hat jedes Goal Zugang zu den im Projekt POM definierten Informationen. Wenn das Goal jar:jar ein JAR-Archive erstellen soll, sieht es in der POM Datei nach um herauszufinden wie das JAR-Archive heissen soll. Soll das Goal compiler:compiler Java Quelldateien kompilieren greift das Goal auf die POM Dateien zurück, um zu sehen ob dort Compiler Eigenschaften definiert sind. Goals werden immer im Kontext eines POMs ausgeführt. Goals sind Aktionen welche wir auf ein Projekt anwenden möchten, 46
  • 66. Ein einfaches Maven Projekt und Projekte sind in POM Dateien abgebildet. Das POM bezeichnet ein Projekt, stellt einen Satz von Identifikationsmerkmale (Koordinaten) für ein Projekt, und definiert die Beziehungen zwischen diesem Projekt und andern Projekten durch die Angabe von Abhängigkeiten, Eltern und Voraussetzungen. Ein POM kann auch Plugin-Verhalten beeinflussen sowie Informationen über die Entwicklergemeinde und an einem Projekt beteiligten Entwicklern bereitstellen. Maven Koordinaten stellen eine Anzahl Identifikatoren, welche zur eindeutigen Identifikation eines Projekts, einer Abhängigkeit oder eines Maven Plugins genutzt werden können. Werfen Sie einen Blick auf das folgende POM. Figure 3.4. Maven Projekt Koordinaten Wir haben die Maven Koordinaten für dieses Projekt: groupId, artifactId, version und packaging vorgestellt. Die Einheit dieser Identifikatoren bildet die Koordinaten eines Projekts[3]. Genau wie in jedem anderen Koordinatensystem, fixieren diese Koordinaten einen bestimmten Punkt im Raum: vom Allgemeinen zum Spezifischen. Maven bestimmt ein Projekt mit dessen Koordinaten, so sich ein 47
  • 67. Ein einfaches Maven Projekt Projekt auf ein anderes bezieht, entweder als Abhängigkeit, Plugin oder auch elterliche Projekt Referenz. Maven Koordinaten werden gewöhnlich durch einen Doppelpunkt getrennt in folgender Form wiedergegeben: groupId:artifactId:version:packaging. Die Koordinate des im obigen Beispiel einer pom.xml-Datei eines Projektes sind daher folgendermassen wiedergegeben: mavenbook:my-app:jar:1.0-SNAPSHOT. Diese Notation gilt auch für Projekt-Abhängigkeiten; unser Projekt basiert auf JUnit Version 3.8.1, es enthält daher eine Abhängigkeit zu junit:junit:jar:3.8.1 . 1 groupId Die "Gruppe" (Firma, Team, Organisation, Projekt, oder anderweitige Gruppe). Es gilt die ungeschriebene Regel, dass die groupId in der Reverse-Domain-Name Notation der Organisation die das Projekt erstellt, wiedergegeben wird. Projekte von Sonatype hätte eine groupId welche mit com.sonatype beginnt, Projekte der Apache Software Foundation hätte eine groupId welche mit org.apache beginnt. artifactId Eine eindeutige hierarchische Kennung unter der groupId, identifiziert ein einzelnes Projekt. version Eine bestimmte Version eines Projekts. Freigegebene Projekte haben eine bestimmte, festgelegte Versionskennung. Diese bezieht sich immer auf genau eine Version des Projekts. Projekte in der aktiven Entwicklung können eine spezielle Kennung bekommen, diese markiert die angegebene Version als eine "Momentaufnahme". Der Identifikator packaging ist ebenfalls ein wichtiger Bestandteil der Maven Koordinaten, aber nicht Teil der eindeutigen Projektkennung. Eine Projektdarstellung der Art groupId:artifactId:version bezeichnet ein Projekt einzigartig (kanonisch). Es ist nicht möglich ein weiteres Projekt mit dem gleichen 1 Es gibt eine weitere, fünfte Koordinate: classifier. Diese kommt nur selten zum Einsatz und wir werden die Einführung daher auf später im Buch zurückstellen. Nehmen Sie sich die Freiheit diese Koordinate im Moment zu ignorieren. 48
  • 68. Ein einfaches Maven Projekt Bezeichnern zu erstellen. packaging Die Art in welcher das Projekt 'gepackt' ist, dies ist auf den Standardwert 'jar' gesetzt. Ein Projekt vom Packaging Typ 'jar' wird als JAR-Archive bereitgestellt, eines des Types 'war' in Form eines WAR-Archives. Diese vier Elemente bestimmen den Schlüssel um ein Projekt eindeutig im weiten Raum "mavenisierter" Projekte aufzufinden. Maven-Repositories (öffentliche, private und lokale) sind nach diesen Identifikatoren organisiert. Sobald ein Projekt im lokalen Maven-Repository eingestellt ist, steht es augenblicklich für alle weiteren Projekte (welche gegen dieses Repository arbeiten) bereit, eingebunden zu werden. Alles was zu tun bleibt, ist die Koordinaten in einem anderen Projekt als Abhängigkeit einzutragen. Figure 3.5. Der 'Maven'-Raum stellt ein Koordinatensystem für Projekte 3.5.4. Maven Repositories Beim ersten Ausführen von Maven, werden Sie feststellen, dass Maven eine Reihe von Dateien von einem Maven Remote Repository herunterlädt. Sollte die 49
  • 69. Ein einfaches Maven Projekt Erstellung des einführenden Projektes das erste Mal sein, dass Sie Maven aufrufen, so ist das erste was passiert, dass Maven die neuste Version des Resource Plugins herunterlädt, ausgelöst durch das Goal resources:resource. Maven lädt Artefakte und Plugins aus einem Remote Repository sobald diese benötigt werden. Einer der Gründe, weshalb der Maven Download ist so klein ist (1,5 MiB) ist, dass Maven nicht mit allerlei Plugins ausgeliefert wird. Ausgeliefert wird nur das Allernötigste, alles weitere holt sich Maven bei Bedarf aus einem Remote-Repository. Maven kommt hierzu mit einer Standardeinstellung eines Remote Repositories (http://repo1.maven.org/maven2) von welchem es versuchen wird die Core Maven-Plugins und deren Abhängigkeiten herunterzuladen. Oftmals werden Sie an einem Projekt arbeiten, welches auf Bibliotheken aufbaut, welche weder frei noch öffentlich zu verbreiten sind. In einem solchen Fall müssen Sie entweder ein benutzerdefiniertes Repository innerhalb Ihrer Organisation aufbauen oder die Abhängigkeiten manuell herunterladen und installieren. Die Standard (Remote) Repositorien können durch Verweise auf benutzerdefinierte Maven Repositorien ersetzt oder ergänzt werden. Es gibt eine Reihe Produkte welche Organisationen dabei unterstützen eigene Repositorien zu verwalten sowie Spiegel-Repositorien von öffentlichen Maven-Repositorien zu erstellen. Was macht ein Maven Repository zu einem Maven Repository? Ein Maven-Repository zeichnet sich durch seine Struktur aus, ein Repository ist eine Sammlung von Projekt Artefakten in einer Struktur und Form welche für Maven leicht leicht nachvollziehbar ist. Die Artefakte in einem Maven-Repository sind in einer Verzeichnis-Struktur abgelegt, welche eng mit den Maven Projekt Koordinaten korrespondieren. Sie können sich diese Struktur ansehen: öffnen Sie mit einem Web-Browser das zentrale Maven Repository auf http://repo1.maven.org/maven2/. Sie können dort ein Artefakt mit den Koordinaten org.apache.commons:commons-email:1.1 im Verzeichnis /org/apache/commons/commons-email/1.1/ in einer Datei namens "commons-email.jar" vorfinden. Der Standard für ein Maven-Repository ist es, einen Artefakten in folgender Struktur relativ zum Wurzelverzeichnis des Repositories abzulegen: /<groupId>/< artifactId>/<version>/< artifactId>-<version>.<packaging> 50
  • 70. Ein einfaches Maven Projekt Maven lädt Artefakte und Plugins von einem Remote-Repository auf Ihren lokalen Rechner herunter und speichert diese Artefakte in Ihrem lokalen Maven-Repository. Ist ein Artefakt einmal von einem Remote Repository in das lokale Repository heruntergeladen, so wird Maven kein weiteres mal auf das Remote Repository zugreifen, da Maven immer zuerst auf das lokale Repository zugreifen wird, vor es anderswo sucht. Unter Windows XP befindet sich Ihr lokales Repository wahrscheinlich unter C:Dokumente und EinstellungenBenutzername.m2Repository, unter Windows Vista befindet sich Ihr lokales Repository unter C:BenutzerBenutzername.m2Repository auf Unix-Systemen werden Sie Ihr lokales Maven-Repository unter ~/.m2/repository wiederfinden. Sobald Sie ein Projekt wie das aus dem vorherigen Abschnitt erstellen, wird in der Phase install ein Goal ausgeführt welches den neuen Artifakten in Ihrem lokalen Maven Repository ablegt. Sie sollten nun in der Lage sein, das Artefakt Ihres eingängigen Projektes in Ihrem lokalen Maven Repository wiederzufinden. Beim Aufruf von mvn install installiert Maven den Projekt Artefakt in Ihrem lokalen Repository. Probieren Sie es aus. $ mvn install ... [INFO] [install:install] [INFO] Installing .../simple-1.0-SNAPSHOT.jar to ~/.m2/repository/com/sonatype/maven/simple/1.0-SNAPSHOT/ simple-1.0-SNAPSHOT.jar ... Betrachten Sie die Ausgabe dieses Befehls, Sie können sehen, wie Maven unseres Projekt's JAR-Archiv im lokalen Maven-Repository ablegt. Maven nutzt das lokale Repository um Artefakte zwischen lokalen Projekten zu teilen. Sagen wir, Sie entwickeln zwei Projekte, Projekt A und Projekt B, dabei ist Projekt B von einem Artefakt von Maven-Projekt A abhängig. So wird Maven den Artefakt beim Build von Projekt B aus Ihrem lokalen Repository holen. Maven Repositorien sind beides, ein lokaler Zwischenspeicher (Cache) von Artefakten welche von einem Remote Repository heruntergeladen wurden, wie auch ein Mechanismus welcher erlaubt, dass Ihre Projekte zueinander abhängig sein können. 51
  • 71. Ein einfaches Maven Projekt 3.5.5. Maven Abhängigkeits-Management (Dependency Management) In dem einfachen Beispiel dieses Kapitels, löst Maven die Koordinaten der JUnit-Abhängigkeit junit:junit:3.8.1 in einen Pfad /junit/junit/3.8.1/junit-3.8.1.jar des Maven Repositories auf. Die Möglichkeit, Artefakte in einem Repository basierend auf Maven Koordinaten zu lokalisieren gibt uns die Möglichkeit Abhängigkeiten in einem Projekt POM zu definieren. Sehen Sie sich die pom.xml-Datei genauer an. Sie werden einen Abschnitt finden, welcher sich mit den Abhängigkeiten (dependencies) befasst, dieser Abschnitt enthält eine einzige Abhängigkeit: JUnit. Ein komplexeres Projekt würde mehr als eine einzelne Abhängigkeit beinhalten, oder auch Abhängigkeiten welche von anderen Artefakten abhängen. Dies ist eine der mächtigsten Funktionen von Maven: die Unterstützung von transitiven Abhängigkeiten. Nehmen wir an, Ihr Projekt hängt von einer Bibliothek ab, welche wiederum von fünf oder zehn anderen Bibliotheken abhängig ist (etwa wie Spring oder Hibernate). Statt alle diese Abhängigkeiten nachzuverfolgen und in Ihrem pom.xml explizit aufzulisten können Sie schlicht diejenige Abhängigkeit aufnehmen, welche Sie beabsichtigen. Maven wird die implizit abhängigen Bibliotheken für Sie auflösen. Maven wird auch auftretende Konflikte auflösen, diesbezüglich bietet Maven die Möglichkeit das Verhalten anzupassen sowie gewisse transitive Abhängigkeiten auszuschliessen. Werfen wir einen Blick auf eine Abhängigkeit, welche im vorangegangenen Beispiel in Ihr lokales Repository heruntergeladen wurde. Sehen Sie in Ihrem lokalen Repository-Pfad unter ~/.m2/repository/junit/junit/3.8.1/ nach. Wie Ihnen nach der Lektüre des Kapitels bekannt ist, gibt es eine Datei junit-3.8.1.jar sowie eine junit-3.8.1.pom-Datei und zusätzlich eine Anzahl Dateien; Prüfsummen welche Maven nutzt, um die Echtheit eines heruntergeladenen Artefakts zu überprüfen. Beachten Sie, dass Maven nicht nur das JUnit JAR-Archive herunterlädt. Maven lädt zusätzlich das POM des Artefakts herunter. Die Tatsache, dass Maven zusätzlich zum Artefakt auch die zugehörige POM Dateien herunterlädt ist von zentraler Bedeutung für die Unterstützung von transitiven Abhängigkeiten durch Maven. 52
  • 72. Ein einfaches Maven Projekt Sie werden feststellen, dass Maven bei der Installation Ihres Projekt-Artefaktes in das lokale Maven-Repository zugleich eine leicht modifizierte Version der zugehörigen pom.xml-Datei im selben Verzeichnis des JAR-Archives ablegt. Zugleich eine POM-Datei im Repository abzulegen gibt anderen Projekten Informationen zu diesem Projekt, als Wichtigstes dessen Abhängigkeiten. Projekt B, abhängig von Projekt A, hängt auch von den Abhängigkeiten des Projekts A ab. Wenn Maven eine Abhängigkeit zu einem Artefakt anhand einer Reihe von Artefaktkoordinaten auflöst, so ruft Maven die POM-Dateien auf und nimmt sich deren Abhängigkeiten an, um bestehende transitive Abhängigkeiten aufzulösen. Jene transitiven Abhängigkeiten werden sodann den Projektabhängigkeiten zugefügt. Eine Abhängigkeit in Maven ist nicht nur eben ein JAR-Archiv. Es ist eine POM-Datei, welche weitere Abhängigkeiten von anderen Artefakten erklären kann. Solche Abhängigkeiten der Abhängigkeiten nennt man transitive Abhängigkeiten und sie werden durch die Tatsache, dass das Maven-Repository mehr als nur den Bytecode speichert ermöglicht. Maven speichert Meta-Daten bezüglich der Artefakte. Die folgende Abbildung zeigt ein mögliches Szenario transitiver Abhängigkeiten. 53
  • 73. Ein einfaches Maven Projekt Figure 3.6. Wie Maven transitive Abhängigkeiten auflöst / Resolving transitive Dependencies In der vorangegangenen Abbildung, ist ein Projekt abhängig von den Projekten B und C. Projekt B baut auf Projekt D auf, Projekt C baut auf Projekt E auf. Die Gesamtmenge aller Abhängigkeiten von A sind Projekte B, C, D, E, jedoch musste Projekt A lediglich die Projekte B und C deklarieren. Transitive Abhängigkeiten können sich immer dann als nützlich erweisen, wenn Ihr Projekt sich auf ein anderes Projekt abstützt, welches weitere Abhängigkeiten deklariert (z.B. Hibernate, Apache Struts, oder das Spring Framework). Maven bietet Ihnen auch die Möglichkeit, transitive Abhängigkeiten von der Aufnahme in den Klassenpfad auszuschliessen. Maven kennt auch das Konzept des Gültigkeitsbereichs (Scope). Die pom.xml-Datei des Beispielprojektes kennt nur eine einzige Abhängigkeit junit:junit:jar:3.8.1 in einem Gültigkeitsbereich test. Wenn eine Abhängigkeit bezüglich eines Gültigkeitsbereich test definiert ist, so bedeutet dies, dass diese nicht dem Goal compile des Compiler Plugins zur Verfügung steht. 54
  • 74. Ein einfaches Maven Projekt Die Abhängigkeit wird dem Klassenpfad nur für die Goals compiler:testCompile sowie surefire:test zugefügt. Bei der Erstellung eines JAR-Archivs für ein Projekt werden Abhängigkeiten nicht mit eingeschlossen, es wird lediglich gegen diese kompiliert. Beim Erstellen eines WAR- oder EAR-Archives können Sie konfigurieren, ob Sie Abhängigkeiten in den erstellten Artefakt einschliessen möchten. Ebenfalls möglich ist der Ausschluss bestimmter Abhängigkeiten abhängig vom gewählten Gültigkeitsbereich. Der Gültigkeitsbereich provided bestimmt, dass die Abhängigkeit benötigt wird um zu kompilieren, aber nicht in den Erstellten Artefakt eingebunden werden soll. Dieser Gültigkeitsbereich (provided) ist nützlich, wenn Sie eine Web Applikation erstellen; Sie müssen z.B. gegen die Servlet Spezifikation kompilieren, jedoch werden Sie vermeiden, das Servlet API JAR-Archiv in das /WEB-INF/lib Verzeichnis Ihrer Webanwendung abzulegen. 3.5.6. Site-Generierung und Reporting Eine weitere wichtige Möglichkeit ist die Fähigkeit von Maven Dokumentationen und Reports zu Generieren. Führen Sie im Stammverzeichnis der Beispielanwendung folgenden Aufruf aus: $ mvn site Der Aufruf wird Lebenszyklus Phase site auslösen. Im Gegensatz zu den Standard-Build Lifecycle Phasen welche sich mit der Verwaltung von Codegeneration, der Manipulation von Ressourcen, Kompilierung, Packetierung und so weiter befassen betrifft diese Phase ausschließlich die Bearbeitung von Website-Content im Rahmen des /src/site Verzeichnisses und der Generierung von Reports. Nach Abschluss diesen Befehls, sollten Sie einen Projekt-Website im Verzeichnis /target/site vorfinden. Nach dem Laden der Datei /target/site/index.html sollten Sie das Skelett eines Projekt (Web-)Sites sehen. Diese Shell enthält bereits einige Berichte unter der Rubrik "Projekt Reports" der linken Navigation im Menü, sowie Informationen über das Projekt, dessen Abhängigkeiten und der Entwickler im Zusammenhang mit dem Projekt in der Rubrik "Projekt-Information". Die Projekt-Website dieses Beispiels ist größtenteils 55
  • 75. Ein einfaches Maven Projekt leer, da das POM nur sehr wenige Informationen über die reinen Koordinatenpunkte hinaus enthält: den Namen und die URL des Projektsites sowie der einzigen Test Abhängigkeit. Sie werden feststellen, dass auf dieser Website bereits einige Standard-Berichte bereitstehen, so gibt es einen Bericht, welcher den Ausgang der Unit-Tests zusammenfasst. Dieser Report spiegelt alle im Projekt erfolgten Unit Tests wieder, sowie im einzelnen deren Erfolg oder Misserfolg. Ein weiterer Report erzeugt die JavaDoc des Projekt API's. Maven bietet eine vollständige Palette von konfigurierbaren Reports, wie zum Beispiel dem Clover Report, welcher Aufschluss über die erfolgte Testabdeckung gibt, der JXR Report, ein Report welcher querverweisende HTML-Seiten generiert und speziell für Code-Reviews nützlich ist, der PMD Report, welcher, aufbauend auf eine Quellcodeanalyse, Aufschluss über eine Reihe von problematischen Kodierungen gibt, sowie der JDepend Report, welcher die Abhängigkeiten zwischen verschiedenen Packages einer Quellcodebasis analysiert. Die Reports der Website werden in der pom.xml Datei eines Builds konfiguriert. 3.6. Zusammenfassung Wir haben ein einfaches Projekt erstellt, in ein JAR-Archive gepackaged und diese JAR-Archive im lokalen Repository installiert um es anderen Projekten zur Verfügung zu stellen; sodann haben wir eine Projektwebsite der zugehörigen Dokumentation generiert. Wir konnten das alles bewerkstelligen, ohne auch nur einen einzige Zeile Code zu schreiben. Darüberhinaus haben wir Zeit aufgewandt einige der Kernkonzepte von Maven zu erläutern. Im nächsten Kapitel werden wir damit beginnen, die pom.xml-Datei anzupassen, weitere Abhängigkeiten einzufügen sowie einige Unit Tests zu konfigurieren. 56
  • 76. Chapter 4. Anpassen eines Maven Projektes 4.1. Einleitung Dieses Kapitel baut auf das vorangehende Kapitel (3), insbesondere auf das eingeführte "Simple Maven Projekt" auf. Wir erstellen mit dem Archetype Maven Plugin ein einfaches Projekt, fügen einige Abhängigkeiten ein, erstellen Quellcode und passen das Projekt unseren Bedürfnissen an. Am Ende dieses Kapitels, werden Sie wissen, wie man es am besten angeht, reale Projekte mit Maven zu erstellen. 4.1.1. Herunterladen der Beispiele dieses Kapitels In diesem Kapitel werden wir mit Ihnen ein einfaches, nützliches Programm zur Interaktion mit einem Yahoo! Wetter Web-Service erstellen. Während Sie in der Lage sein sollten, der Entwicklung dieses Kapitels ohne Beispiel-Quellcode zu folgen, empfehlen wir das Herunterladen einer Kopie der Quellcodes als Referenz. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Unterverzeichnis /ch04. Darin befindet sich ein Verzeichnis mit dem Namen /simple weather welches den Quellcode für dieses Kapitel enthält. 4.2. Eine kurze Einführung in das "Simple Weather" Projekt Bevor wir loslegen, lassen Sie uns einen Schritt zurücktreten und das Wetter Projekt vorstellen. Was ist das, das 'Simple Weather' Projekt? Das einfache 57
  • 77. Anpassen eines Maven Projektes Wetterdienst-Projekt ist ein Beispiel, das speziell konstruiert wurde um einige der Funktionen von Maven vorzustellen. Es handelt sich um eine Anwendung, welche beispielhaft die Art der Anwendung vorstellt, welche Sie zu bauen angefragt werden. Die 'Simple Weather' Anwendung ist ein ein grundlegendes Beispiel einer befehlszeilenorientierten Anwendung, welche auf eine angegebene Postleitzahl Daten vom Yahoo! RSS WetterDienst abruft. Die Anwendung analysiert den Rückgabewert und gibt diesen auf der Konsole aus. Es gab eine Reihe von Gründen welche uns bewogen dieses Beispiel auszuwählen: zum einen ist es unkompliziert, der Benutzer macht eine Eingabe über die Befehlszeile; diese nehmen wir auf, schicken die Anfrage an den Yahoo! Wetter Dienst (RSS), analysieren das Ergebnis und geben zuletzt einige Daten auf den Bildschirm aus. Dieses Beispiel besteht aus einer einfachen main()-Klasse und einigen Hilfsklassen, es gibt kein Enterprise Framework einzuführen, lediglich einfaches XML-Parsen sowie einige Logging-Anweisungen. Zum anderen gibt dieses Beispiel uns eine gute Gelegenheit einige interessante Bibliotheken wie Velocity, Dom4J und Log4J einzuführen - Obwohl dieses Buch sich auf die Einführung von Maven konzentriert, werden wir uns nicht scheuen, die Gelegenheit zur Einführung interessanter Utilities wahrzunehmen. Schließlich ist es ein Beispiel, das in einem einzigen Kapitel eingeführt, entwickelt, und deployed werden kann. 4.2.1. Yahoo! Wetter Dienst RSS Bevor Sie diese Anwendung bauen, sollten Sie etwas mehr über die Yahoo! Wetter Dienste (RSS-Feeds) erfahren. Allem voraus weisen wir darauf hin, dass der Dienst unter den folgenden Konditionen angeboten wird: "Die Datendienste werden zur Nutzung von Einzelpersonen sowie Non-Profit-Organisationen kostenlos, im Rahmen von persönlichen, nicht kommerziellen Anwendungen angeboten. Sie sind angehalten auf die Nutzung der Yahoo! Wetter Dienste im Rahmen Ihres Angebotes hinzuweisen." Mit anderen Worten, wenn Sie daran dachten, diese Dienste in Ihren 58
  • 78. Anpassen eines Maven Projektes kommerziellen Webauftritt zu integrieren, dann gehen Sie bitte noch einmal über die Bücher: die Nutzung der Dienste ist lediglich für den privaten, nicht kommerziellen Gebrauch gestattet. Die Nutzung zu welcher wir in diesem Kapitel anregen ist beschränkt auf die persönliche Weiterbildung. Weitere Informationen bezüglich der Yahoo! Wetter Dienst Konditionen finden Sie in der Yahoo Wetter! API-Dokumentation: http://developer.yahoo.com/weather/. 4.3. Erstellen des "Simple Weather" Projektes In einem ersten Schritt, lassen Sie uns mit Hilfe des Archetype Maven Plugin ein grundlegendes Skelett der Anwendung erstellen. Führen Sie den folgenden Aufruf aus, um ein neues Projekt zu generieren: $ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch04 -DartifactId=simple-weather -DpackageName=org.sonatype.mavenbook -Dversion=1.0 [INFO] [archetype:create] [INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: checking for updates from central [INFO] ------------------------------------------------------------------ [INFO] Using following parameters for creating Archetype: maven-archetype-quickstart:RELEASE [INFO] ------------------------------------------------------------------ [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: ~/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0 [INFO] Parameter: artifactId, Value: simple-weather [INFO] *** End of debug info from resources from generated POM *** [INFO] Archetype created in dir: ~/examples/simple-weather Sobald das Archetype Maven Plugin das Projekt erstellt hat, wechseln Sie in das Verzeichnis der "Simple Weather" Anwendung und werfen Sie einen Blick auf die pom.xml Datei. Sie sollten das folgende XML-Dokument vorfinden: Example 4.1. Erstes POM des "Simple Weather" Projekts <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 59
  • 79. Anpassen eines Maven Projektes http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch04</groupId> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>simple-weather2</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> </project> Bitte beachten Sie, dass wir den Parameter version und das Goal archetype:create übergeben haben. Hiermit wird der Standardwert des 1.0-SNAPSHOT überschrieben. In diesem Projekt entwickeln wir die Version 1.0 des "Simple Weather" Beispielprogramms, wie Sie leicht am Element version der pom.xml-Datei ablesen können. 4.4. Anpassen der Projektinformationen Bevor wir mit dem Schreiben von Quellcode loslegen, Lassen Sie uns die Projektinformationen ein wenig Anpassen. Wir wollen etliche Informationen bezüglich der Lizenzrechte, der Organisation sowie der beteiligten Entwickler einfügen. Dies alles sind Standard-Informationen welche wir erwarten, dass diese in den meisten Projekten gebraucht werden. Die folgende Auflistung zeigt den 60
  • 80. Anpassen eines Maven Projektes Ausschnitt der XML-Datei, der die Informationen zur Organisation, der Lizenzierung sowie bezüglich der Entwickler bereitstellt. Example 4.2. Informationen bezüglich der Organisation, rechtlicher Belange sowie der Entwickler in der pom.xml-Datei <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/maven-v4_0_0.xsd"> ... <name>simple-weather</name> <url>http://www.sonatype.com</url> <licenses> <license> <name>Apache 2</name> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> <distribution>repo</distribution> <comments>A business-friendly OSS license</comments> </license> </licenses> <organization> <name>Sonatype</name> <url>http://www.sonatype.com</url> </organization> <developers> <developer> <id>jason</id> <name>Jason Van Zyl</name> <email>jason@maven.org</email> <url>http://www.sonatype.com</url> <organization>Sonatype</organization> <organizationUrl>http://www.sonatype.com</organizationUrl> <roles> <role>developer</role> </roles> <timezone>-6</timezone> </developer> </developers> ... </project> Die Auslassungspunkte ("...") in Beispiel 4.2, "Informationen bezüglich der Organisation, rechtlicher Belange sowie der Entwickler in der pom.xml-Datei" 61
  • 81. Anpassen eines Maven Projektes sind eine Abkürzung für ein stark gekürztes Listing. Wenn Sie in diesem Buch auf eine pom.xml Datei stossen, welche direkt hinter dem Element-Tag project mit "..." beginnt und mit "..." direkt vor dem Element project aufhört, so zeigt dies an, dass wir nicht die gesamte pom.xml-Datei wiedergeben. In diesem Fall wurden die Tag-Elemente licenses, organization und developers vor dem Element dependencies eingefügt. 4.5. Einfügen neuer Abhängigkeiten Die einfache "Simpel Weather" Anwendung umfasst folgende drei Aufgaben: Abrufen von XML-Daten vom Yahoo! Wetter Dienst, analysieren des zurückgegebenen XML von Yahoo und schliesslich die formatierte Ausgabe auf der Konsole. Um diese Aufgaben zu erledigen, müssen wir einige neue Abhängigkeiten zu unserem Projekt pom.xml hinzufügen. Um die XML-Antwort des Yahoo! Dienstes zu analysieren werden wir <term>Dom4J</term> und <term>Jaxen</term> einsetzen. Um die Ausgabe des Kommandozeilenprogramms zu formatieren setzen wir <term>Velocity</term> ein und es ist ebenfalls notwendig, eine Abhängigkeit zu <term>Log4J</term> hinzuzufügen, um Ereignisse zu loggen. Nachdem wir diese Abhängigkeiten einbringen, sieht unsere Beispiel pom.xml-Datei wie folgt aus: Example 4.3. Hinzufügen der Abhängigkeiten von Dom4J, Jaxen, Velocity und Log4J <project> [...] <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> 62
  • 82. Anpassen eines Maven Projektes </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> [...] </project> Wie Sie sehen, haben wir zum bestehenden Element der Test bezogenen Abhängigkeit JUnit, vier weitere Elemente dependency eingefügt. Nach dem Einfügen der Abhängigkeiten sowie dem Aufruf von mvn install werden Sie sehen, wie Maven diese Abhängigkeiten wie auch eine Reihe transitiver Abhängigkeiten in Ihr lokales Maven Repository herunterlädt. Nun wie wussten wir die Koordinaten dieser Abhängigkeiten? "Weiss" man schlicht die entsprechenden Werte für groupId und artifactId? Manche Artefakte sind so weit verbreitet (z.B. Log4J), dass Sie, wann immer Sie sie brauchen, sich an die Werte der groupId und artifactId erinnern werden. Velocity, Dom4J und Jaxen haben wir unter Einsatz einer hilfreichen Website (http://www.mvnrepository.com) aufgelöst. Diese Website bietet eine Maske zur Suche über das Maven-Repository an. Sie können die Site benutzen um Abhängigkeiten herauszufinden. Um dies zu verifizieren, versuchen Sie es selbst: laden Sie die Seite http://www.mvnrepository.com und suchen Sie nach einigen häufig verwendeten 63
  • 83. Anpassen eines Maven Projektes Bibliotheken wie Hibernate oder dem Spring Framework. Wenn Sie auf dieser Site nach einem Artefakt suchen, werden Ihnen der artefactId-Wert sowie alle im zentralen Repository verfügbaren version-Werte ausgegeben. Ein Klick auf die Details einer bestimmten Version lädt eine Seite, welche das Element dependency wiedergibt, dieses können Sie kopieren und in die pom.xml Datei Ihres eigenen Projekt einfügen. Wenn Sie eine Abhängigkeit finden müssen, so lohnt es sich, mvnrepository.com zu benutzen. Auch werden Sie dann werden festestellen, dass bestimmte Bibliotheken über mehr als eine groupId verfügen. Dieses Werkzeug wird Ihnen helfen das Maven-Repository zu verstehen. 4.6. Quellcode von "Simple Weather" Das "Simple Weather" Beispielprogramm besteht aus fünf Java-Klassen: org.sonatype.mavenbook.weather.Main Diese Klasse enthält eine statische main()-Funktion, es ist der Ausgangspunkt für dieses System. org.sonatype.mavenbook.weather.Weather Die Weather-Klasse ist ein einfaches Java-Bean, welches die Koordinaten unseres Wetterberichtes, sowie eine Anzahl wichtiger Daten wie z.B Temperatur und Luftfeuchtigkeit vorhält. org.sonatype.mavenbook.weather.YahooRetriever Die YahooRetriever Klasse stellt eine Verbindung zum Yahoo! Wetter Dienst her und liefert einen InputStream der Daten des Dienstes. org.sonatype.mavenbook.weather.YahooParser Die YahooParser Klasse analysiert das zurückgegebene XML vom Yahoo! Wetter Dienst und wandelt es in ein Weather-Objekt um. org.sonatype.mavenbook.weather.WeatherFormatter Die WeatherFormatter Klasse setzt auf einem Weather Objekt auf, erstellt einen VelocityContext, und führt ein Velocity Template (Vorlage) daruaf aus. 64
  • 84. Anpassen eines Maven Projektes Wir wollen an diese Stelle nicht näher auf den Quellcode des Beispiels eingehen, werden aber alle erforderlichen Quellen des Beispiels im Buch bereitstellen, so dass Sie "Simple Weather" zum Laufen bekommen. Wir gehen davon aus, dass die meisten von Ihnen den Quellcode heruntergeladen haben, wollen aber mit den Beispielen des Buches aber auch jene Leser berücksichtigen, welche den Beispielen innerhalb des Kapitels Schritt-für-Schritt folgen. Die folgenden Abschnitte legen den Quellcode der Klassen des "Simple Weather" Projekt dar. Diese Klassen sollten alle in das gleiche Paket com.sonatype.maven.weather abgelegt werden. Lassen Sie uns die App sowie die AppTest Klassen welche das Goal archetype:create erzeugt hat, entfernen und unsere neuen Klassen dem Package zufügen. In einem Maven-Projekt, liegen alle Quellen eines Projektes unter /src/main/java. Aus dem Basis-Verzeichnis des neuen Projekts heraus, führen Sie die folgenden Befehle aus: $ cd src/test/java/org/sonatype/mavenbook $ rm AppTest.java $ cd ../../../../../.. $ cd src/main/java/org/sonatype/mavenbook $ rm App.java $ mkdir weather $ cd weather Sie haben ein neues Paket com.sonatype.maven.weather erstellt. Nun sollten wir etliche Klassen in diesem Verzeichnis erstellen. Benutzen Sie hierfür Ihren Lieblings-Text-Editor und erstellen Sie eine neue Datei mit dem Namen Weather.java mit folgendem Inhalt: Example 4.4. "Simple Weather" Wetter Objekt Modell package org.sonatype.mavenbook.weather; public class Weather { private String city; private String region; private String country; private String condition; private String temp; private String chill; private String humidity; 65
  • 85. Anpassen eines Maven Projektes public Weather() {} public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getTemp() { return temp; } public void setTemp(String temp) { this.temp = temp; } public String getChill() { return chill; } public void setChill(String chill) { this.chill = chill; } public String getHumidity() { return humidity; } public void setHumidity(String humidity) { this.humidity = humidity; } } Die Wetter Klasse definiert ein einfaches Bean, welches die Informationen vorhält, welche aus den vom Yahoo! Wetter-Dienst zurückgegebenen Daten geparsed werden. Der Yahoo! Wetter-Dienst bietet eine Fülle an Informationen, ausgehend von den Sonnenaufgangs und -untergangszeiten, bis hin zur Windrichtung sowie -geschwindigkeit. Im Ansinnen, die Beispiele des Buchs so einfach wie möglich zu gestalten, enthält unser Weeather-Objekt-Modell nur die Temperatur, Chill, Feuchtigkeit und sowie eine textuelle Beschreibung der gegenwärtigen Bedingungen. Im gleichen Verzeichnis erstellen Sie nun eine Datei mit dem Namen Main.java. Diese Haupt-Klasse wird die statische main()-Funktion beinhalten, welche der Einstiegspunkt dieses Beispiels ist. Example 4.5. Einfache Weather-Hauptklasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import org.apache.log4j.PropertyConfigurator; 66
  • 86. Anpassen eines Maven Projektes public class Main { public static void main(String[] args) throws Exception { // Configure Log4J PropertyConfigurator.configure(Main.class.getClassLoader() .getResource("log4j.properties")); // Read the Zip Code from the Command-line (if none supplied, use 60202) String zipcode = "60202"; try { zipcode = args[0]); } catch( Exception e ) {} // Start the program new Main(zipcode).start(); } private String zip; public Main(String zip) { this.zip = zip; } public void start() throws Exception { // Retrieve Data InputStream dataIn = new YahooRetriever().retrieve( zip ); // Parse Data Weather weather = new YahooParser().parse( dataIn ); // Format (Print) Data System.out.print( new WeatherFormatter().format( weather ) ); } } Die oben gezeigte main()-Funktion konfiguriert Log4J indem es eine Ressource (log4j.properties) auf dem Klassenpfad sucht, um anschliessend einen ZIP-Code/eine Postleitzahl von der Befehlszeile zu lesen. Wird eine während des Leseversuchs eine Exception geworfen, wird standardmässig auf die Postleitzahl 60202 zurückgegriffen. Sobald dem Programm eine Postleitzahl zugeführt wird, wird main() instanziert und deren start()-Methode aufgerufen. Die start()-Methode ruft die YahooRetriever Klasse auf, um das XML-Wetter abzurufen. Die Klasse YahooRetriever liefert einen InputStream, welcher an die Klasse YahooParser weitergeleitet wird. YahooParser analysiert das zurückgegebene Yahoo! Wetter XML und erstellt ein Weather-Objekt. Schließlich nimmt der WeatherFormatter 67
  • 87. Anpassen eines Maven Projektes das Weather Objekt und spuckt eine formatierte Zeichenfolge aus, welche auf der Standardausgabe ausgegeben wird. Erstellen Sie nun im gleichen Verzeichnis eine Datei mit dem Namen YahooRetriever.java mit folgendem Inhalt: Example 4.6. "Simple Weather" YahooRetriever Klasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import org.apache.log4j.Logger; public class YahooRetriever { private static Logger log = Logger.getLogger(YahooRetriever.class); public InputStream retrieve(int zipcode) throws Exception { log.info( "Retrieving Weather Data" ); String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode; URLConnection conn = new URL(url).openConnection(); return conn.getInputStream(); } } Diese einfache Klasse öffnet eine URLConnection auf die Yahoo! Wetter-API und liefert einen InputStream. Um den InputStream verarbeiten zu können müssen wir im gleichen Verzeichnis eine Datei YahooParser.java erstellen. Example 4.7. "Simple Weather" YahooParser Klasse package org.sonatype.mavenbook.weather; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.io.SAXReader; public class YahooParser { 68
  • 88. Anpassen eines Maven Projektes private static Logger log = Logger.getLogger(YahooParser.class); public Weather parse(InputStream inputStream) throws Exception { Weather weather = new Weather(); log.info( "Creating XML Reader" ); SAXReader xmlReader = createXmlReader(); Document doc = xmlReader.read( inputStream ); log.info( "Parsing XML Response" ); weather.setCity( doc.valueOf("/rss/channel/y:location/@city") ); weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") ); weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") ); weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") ); weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") ); weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") ); weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") ); return weather; } private SAXReader createXmlReader() { Map<String,String> uris = new HashMap<String,String>(); uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" ); DocumentFactory factory = new DocumentFactory(); factory.setXPathNamespaceURIs( uris ); SAXReader xmlReader = new SAXReader(); xmlReader.setDocumentFactory( factory ); return xmlReader; } } Der YahooParser ist die komplexeste Klasse in diesem Beispiel. Wir werden nicht in die Einzelheiten von Dom4J oder Jaxen eingehen, aber die Klasse benötigt dennoch ein wenig Erklärung. Die parse()-Methode von YahooParser nimmt einen InputStream auf und gibt ein Weather-Objekt zurück. Um dies zu tun, muss sie das XML-Dokument per DOM4J analysieren. Da wir an Elementen des Yahoo!Wetter Namespaces interessiert sind, ist es notwendig einen Namespace bewussten SAXReader in der Methode createXmlReader() zu erstellen. Sobald wir diesen Reader erstellt haben und damit das Dokument analysiert haben, bekommen wir ein org.dom4j.Document-Objekt zurück. Statt durch alle Kind-Elemente zu iterieren, adressieren wir die für uns relevanten Informationen direkt mit Hilfe 69
  • 89. Anpassen eines Maven Projektes eines XPath-Ausdrucks. Dom4J liefert in diesem Beispiel das XML-Parsing und Jaxen die XPath-Funktionalität. Haben wir einmal ein Weather-Objekt erzeugt, müssen wir unsere Ausgabe für den Gebrauch lesbar formatieren. Erstellen Sie im Verzeichnis der andern Klassen eine Datei mit dem Namen WeatherFormatter.java. Example 4.8. "Simple Weather" WeatherFormatter Klasse package org.sonatype.mavenbook.weather; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import org.apache.log4j.Logger; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; public class WeatherFormatter { private static Logger log = Logger.getLogger(WeatherFormatter.class); public String format( Weather weather ) throws Exception { log.info( "Formatting Weather Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader() .getResourceAsStream("output.vm")); VelocityContext context = new VelocityContext(); context.put("weather", weather ); StringWriter writer = new StringWriter(); Velocity.evaluate(context, writer, "", reader); return writer.toString(); } } Die Klasse WeatherFormatter verwendet Velocity, um basierend auf einer Vorlage, die Ausgabe darzustellen. Die format()-Methode nimmt eine Weather Bean auf und gibt eine formatierte Zeichenkette zurück. Das erste, was die format()-Methode macht, ist eine Velocity Vorlage namens poutput.vm vom Klassenpfad zu laden. Wir erstellen dann einen VelocityContext welcher mit einem einzigen Weather Objekt namens weather bestückt wird. Ein StringWriter wird erstellt, um die Ergebnisse mit der Vorlage zu fusionieren. Die Vorlage wird mit 70
  • 90. Anpassen eines Maven Projektes einem Aufruf an Velocity.evaluate() ausgewertet und die Ergebnisse als String zurückgegeben. Bevor wir dieses Beispiel ausführen können, müssen wir noch einige Ressourcen auf unserem Klassenpfad erstellen. 4.7. Resourcen Hinzufügen Dieses Projekt hängt von zwei Klassenpfad Ressourcen ab: Die main()-Klasse konfiguriert Log4J mit log4j.properties vom Klassenpfad, und die WeatherFormatter Klasse greift auf eine Velocity Vorlage namens output.vm zurück. Beide Ressourcen müssen im Standard-Package (oder dem Wurzelverzeichnis des Klassenpfades) vorhanden sein. Um diese Ressourcen zuzufügen, müssen wir im Wurzelverzeichnis ein neues Verzeichnis /project-src/main/resources erstellen. Da das Verzeichnis nicht nicht vom Goal archetype:create geschaffen wurde, müssen wir dieses durch folgende Befehle aus dem Projekt Verzeichnis heraus erstellen: $ cd src/main $ mkdir resources $ cd resources Sobald das Ressourcen-Verzeichnis erstellt ist, können wir die beiden Ressourcen zufügen. Zunächst fügen Sie die Datei log4j.properties in das Ressourcen-Verzeichnis ein. Example 4.9. "Simple Weather" Log4J Konfigurationsdatei # Set root category priority to INFO and its only appender to CONSOLE. log4j.rootCategory=INFO, CONSOLE # CONSOLE is set to be a ConsoleAppender using a PatternLayout. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=INFO log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n Diese Datei log4j.properties konfiguriert Log4J alle Log-Nachrichten mit 71
  • 91. Anpassen eines Maven Projektes einem PatternLayout an die Standard-Ausgabe zu senden. Schließlich brauchen wir eine Velocity Vorlage output.vm um die Ausgabe der Applikation darzustellen. Erstellen Sie output.vm im Ressourcen-Verzeichnis. Example 4.10. "Simple Weather" Output-Velocity-Template ********************************* Current Weather Conditions for: ${weather.city}, ${weather.region}, ${weather.country} Temperature: ${weather.temp} Condition: ${weather.condition} Humidity: ${weather.humidity} Wind Chill: ${weather.chill} ********************************* Diese Vorlage enthält eine Reihe von Verweisen auf eine Variable namens weather. Die Variable weather ist das Weather Bean, welches an den WeatherFormatter gegeben wurde, die Syntax ${weather.temp} ist eine Kurzform um die Werte der Weather Bean aufzurufen und anzuzeigen. Nun, da wir allen Projekt-Code an den richtigen Stellen haben, können wir Maven benutzen, um dieses Beispiel aufzurufen. 4.8. Ausführen des "Simple Weather" Programms Mit dem Exec Maven Plugin des Codehouse-Mojo-Projekts können wir das Programm ausführen. Um die main()-Klasse auszuführen, geben Sie den folgenden Befehl aus dem Projekt-Basis-Verzeichnis ein: $ mvn install /$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main ... [INFO] [exec:java] 0 INFO YahooRetriever - Retrieving Weather Data 134 INFO YahooParser - Creating XML Reader 333 INFO YahooParser - Parsing XML Response 420 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: 72
  • 92. Anpassen eines Maven Projektes Evanston, IL, US Temperature: 45 Condition: Cloudy Humidity: 76 Wind Chill: 38 ********************************* ... We didn’t supply a command-line argument to the Main class, so we ended up with the default zip code, 60202. To supply a zip code, we would use the -Dexec.args argument and pass in a zip code: $ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main -Dexec.args="70112" ... [INFO] [exec:java] 0 INFO YahooRetriever - Retrieving Weather Data 134 INFO YahooParser - Creating XML Reader 333 INFO YahooParser - Parsing XML Response 420 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New Orleans, LA, US Temperature: 82 Condition: Fair Humidity: 71 Wind Chill: 82 ********************************* [INFO] Finished at: Sun Aug 31 09:33:34 CDT 2008 ... Da wir kein Kommandozeilen-Argument angegeben haben, wurde die Anwendung mit der Standard-Postleitzahl "60202" ausgeführt. Wie Sie sehen können, haben wir das "Simple Weather" Tool erfolgreich aufgerufen, Daten von Yahoo! Wetter abgerufen, das Ergebnis analysiert, formatiert und die sich daraus ergebenden Daten mit Hilfe von Velocity ausgegeben. Wir haben all dies erreicht, ohne dabei viel mehr zu tun als unseres Projekt's Source-Code erstellen, sowie minimale Konfiguration an der pom.xml vorzunehmen. Beachten Sie, dass kein "Build-Prozess" beteiligt war. Wir mussten nicht festlegen wie oder wo die Java-Compiler unseren Quellecode kompiliert, wir haben nichts zu tun, um das Build-System zu finden, anzugeben wie der Bytecode ausgeführt werden soll um die Beispiel-Anwendung auszuführen. Alles, was notwendig war, um etliche Abhängigkeiten einzufügen war, die entsprechenden Maven Koordinaten zu finden 73
  • 93. Anpassen eines Maven Projektes und in der pom.xml Datei einzutragen. 4.8.1. Das Exec Maven Plugin Der Exec-Plugin ermöglicht Ihnen die Ausführung von Java-Klassen und andere Skripten. Es ist keines der Kern- oder Core-Maven Plugins, aber es steht Ihnen vom Mojo Projekt gehostet von Codehaus zur Verfügung. Für eine vollständige Beschreibung der Exec-Plugins, führen Sie: $ mvn help:describe -Dplugin=exec -Dfull aus Es wird alle Goals, welche das Exec Maven Plugin zur Verfügung stellt, auflisten. Das Help Plugin, wird auch alle gültigen Parameter des Exec-Plugins aufzeigen, sollten Sie dessen Verhalten anpassen wollen. Um dem Exec Plugin Kommandozeilenargumente mitzugeben, sehen Sie in der Dokumentation des Plugins nach, welche von help:describe wiedergegeben wird. Obschon das Exec-Plugin nützlich ist, solltens Sie es nicht ausserhalb der laufenden Tests während der Entwicklungsphase zur Ausführung von Anwendungen benutzen. Als robustere Lösung, verwenden Sie das Maven Plugin Assembly welches im Abschnitt 4.13: "Erstellen einer packetierten Kommandozeilen Anwendung" eingeführt wird. 4.8.2. Erkundung der Projekt Abhängigkeiten Das Exec-Plugin ermöglicht es uns, die Anwendung auszuführen, ohne die Abhängigkeiten explizit in den Klassenpfad aufzunehmen. In jedem anderen Build-System, wäre es notwendig geworden, alle Abhängigkeiten in eine Art /lib-Verzeichnis zu kopieren, welches eine Sammlung von JAR-Dateien enthält. Dann hätten wir ein einfaches Skript, welches den Bytecode des Programms sowie alle unsere Abhängigkeiten in den Klassenpfad einschliesst. Nur dann könnten wir java com.sonatype.maven.weather.Main ausführen. Das Exec Plugin nutzt die Tatsache, dass Maven bereits um die Erstellung und Verwaltung Ihres Klassenpfades und der Abhängigkeiten weiss. 74
  • 94. Anpassen eines Maven Projektes Während dies ist zwar bequem ist, ist es dennoch schön zu wissen, was genau in Ihren Klassenpfad eingeschlossen wird. Während des Projekt direkt nur von wenigen Bibliotheken wie Dom4J, Log4J, Jaxen, und Velocity abhängt, stützen diese sich auf weitere, transitive Abhängigkeiten. Um herauszufinden, wie Ihr Klassenpfad tatsächlich aussieht, können Sie das Dependency Maven Plugin zur Ausgabe einer Liste der aufgelösten Abhängigkeiten benutzen. Um die Liste der Abhängigkeiten des "Simple Weather" Projekts auszugeben, führen Sie das Goal dependency:resolve aus. $ mvn dependency:resolve ... [INFO] [dependency:resolve] [INFO] [INFO] The following files have been resolved: [INFO] com.ibm.icu:icu4j:jar:2.6.1 (scope = compile) [INFO] commons-collections:commons-collections:jar:3.1 (scope = compile) [INFO] commons-lang:commons-lang:jar:2.1 (scope = compile) [INFO] dom4j:dom4j:jar:1.6.1 (scope = compile) [INFO] jaxen:jaxen:jar:1.1.1 (scope = compile) [INFO] jdom:jdom:jar:1.0 (scope = compile) [INFO] junit:junit:jar:3.8.1 (scope = test) [INFO] log4j:log4j:jar:1.2.14 (scope = compile) [INFO] oro:oro:jar:2.0.8 (scope = compile) [INFO] velocity:velocity:jar:1.5 (scope = compile) [INFO] xalan:xalan:jar:2.6.0 (scope = compile) [INFO] xerces:xercesImpl:jar:2.6.2 (scope = compile) [INFO] xerces:xmlParserAPIs:jar:2.6.2 (scope = compile) [INFO] xml-apis:xml-apis:jar:1.0.b2 (scope = compile) [INFO] xom:xom:jar:1.0 (scope = compile) Wie Sie sehen, hat unser Projekt eine große Zahl von Abhängigkeiten. Auch wenn wir nur direkte Abhängigkeiten von vier Bibliotheken angegeben haben, scheinen insgesamt 15 Abhängigkeiten zu bestehen. Dom4J hängt von Xerces und dem XML-Parser-APIs ab, Jaxen hängt davon ab, dass Xalan sich im Klassenpfad befindet. Das Dependency Maven Plugin wird ihnen die endgültige Kombination von Abhängigkeiten unter welchen Ihr Projekt kompiliert wird ausgeben. Wollen Sie den gesamten Baum aller Abhängigkeiten sehen, so können Sie das Goal dependency:tree aufrufen: $ mvn dependency:tree ... [INFO] [dependency:tree] [INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 [INFO] +- log4j:log4j:jar:1.2.14:compile 75
  • 95. Anpassen eines Maven Projektes [INFO] +- dom4j:dom4j:jar:1.6.1:compile [INFO] | - xml-apis:xml-apis:jar:1.0.b2:compile [INFO] +- jaxen:jaxen:jar:1.1.1:compile [INFO] | +- jdom:jdom:jar:1.0:compile [INFO] | +- xerces:xercesImpl:jar:2.6.2:compile [INFO] | - xom:xom:jar:1.0:compile [INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile [INFO] | +- xalan:xalan:jar:2.6.0:compile [INFO] | - com.ibm.icu:icu4j:jar:2.6.1:compile [INFO] +- velocity:velocity:jar:1.5:compile [INFO] | +- commons-collections:commons-collections:jar:3.1:compile [INFO] | +- commons-lang:commons-lang:jar:2.1:compile [INFO] | - oro:oro:jar:2.0.8:compile [INFO] +- org.apache.commons:commons-io:jar:1.3.2:test [INFO] - junit:junit:jar:3.8.1:test ... Sollten Sie wirklich abenteuerlich sein, oder einfach nur die volle Liste der Abhängigkeiten sehen wollen, einschliesslich aller Artefakte, die aufgrund von Konflikten oder anderen Gründen abgelehnt wurden, führen Sie Maven mit dem Debug-Flag aus. $ mvn install -X ... [DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null) [DEBUG] log4j:log4j:jar:1.2.14:compile (selected for compile) [DEBUG] dom4j:dom4j:jar:1.6.1:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1.1:compile (selected for compile) [DEBUG] jaxen:jaxen:jar:1.1-beta-6:compile (removed - ) [DEBUG] jaxen:jaxen:jar:1.0-FCS:compile (removed - ) [DEBUG] jdom:jdom:jar:1.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer: 1.0.b2) [DEBUG] xerces:xercesImpl:jar:2.6.2:compile (selected for compile) [DEBUG] xom:xom:jar:1.0:compile (selected for compile) [DEBUG] xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile) [DEBUG] xalan:xalan:jar:2.6.0:compile (selected for compile) [DEBUG] xml-apis:xml-apis:1.0.b2. [DEBUG] com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile) [DEBUG] velocity:velocity:jar:1.5:compile (selected for compile) [DEBUG] commons-collections:commons-collections:jar:3.1:compile (selected for compile) [DEBUG] commons-lang:commons-lang:jar:2.1:compile (selected for compile) [DEBUG] oro:oro:jar:2.0.8:compile (selected for compile) [DEBUG] junit:junit:jar:3.8.1:test (selected for test) In der Debug-Ausgabe sehen wir die Eingeweide des Abhängigkeit-Management-Systems bei der Arbeit. Was Sie hier sehen, ist der vollständige Baum aller Abhängigkeiten für dieses Projekt. Maven listet die vollen 76
  • 96. Anpassen eines Maven Projektes Maven Koordinaten für alle Ihre Projekt-Abhängigkeiten und die Abhängigkeiten derer Abhängigkeiten (und die Abhängigkeiten der Abhängigkeiten der Abhängigkeiten). Sie können ersehen, dass "Simple Weather" von Jaxen abhängt, welches von xom abhängig ist was wiederum auf icu4j aufbaut. Aus dieser Ausgabe können Sie erkennen, dass Maven einen Graphen der Abhängigkeiten erstellt, hierbei Duplikate eliminiert und etwaige Versionskonflikte löst. Sollten Sie bezüglich Abhängigkeiten auf Probleme stossen, so ist es oft hilfreich ein wenig tiefer im Baum den Abhängigkeiten zu schürfen. Durch Einschalten der Debugausgabe können Sie den Maven Abhängigkeits-Mechanismus bei der Arbeit sehen. 4.9. Erstellen von Unit-Tests Maven hat eine eingebaute Unterstützung für Unit-Tests. Testen ist ein Teil des ursprünglichen Maven Lebenszyklus. Lassen Sie uns ein paar Unit-Tests zum "Simple Weather" Projekt hinzufügen. Zuerst werden wir ein Package com.sonatype.maven.weather unter /src/test/java erstellen. $ cd src/test/java $ cd org/sonatype/mavenbook $ mkdir -p weather/yahoo $ cd weather/yahoo An dieser Stelle werden wir zwei Unit-Tests erstellen. Der erste Unit Test wird die Klasse YahooParser, der zweite die WeatherFormatter Klasse testen. Erstellen Sie eine Datei mit dem Namen YahooParserTest.java mit folgendem Inhalt im Package weather: Example 4.11. "Simple Weather" YahooParserTest Unit-Test package org.sonatype.mavenbook.weather.yahoo; import java.io.InputStream; import junit.framework.TestCase; import org.sonatype.mavenbook.weather.Weather; import org.sonatype.mavenbook.weather.YahooParser; 77
  • 97. Anpassen eines Maven Projektes public class YahooParserTest extends TestCase { public YahooParserTest(String name) { super(name); } public void testParser() throws Exception { InputStream nyData = getClass().getClassLoader().getResourceAsStream("ny-weather.xml"); Weather weather = new YahooParser().parse( nyData ); assertEquals( "New York", weather.getCity() ); assertEquals( "NY", weather.getRegion() ); assertEquals( "US", weather.getCountry() ); assertEquals( "39", weather.getTemp() ); assertEquals( "Fair", weather.getCondition() ); assertEquals( "39", weather.getChill() ); assertEquals( "67", weather.getHumidity() ); } } Dieser YahooParserTest erweitert die von JUnit definierte Klasse TestCase. Es folgt das übliche Muster für einen JUnit-Test: ein Konstruktor, der ein einziges String Argument annimmt und den Konstruktor der Superklasse aufruft und eine Reihe von public Methoden welche im Namen mit "test" beginnen, welche als Unit-Tests aufgerufen werden. Wir definieren ein einfaches Testvorgehen: testParser, welches den YahooParser überprüft, indem es ein XML-Dokument mit bekannten Werten auswertet. Das Test XML-Dokument heisst ny-weather.xml und wird aus dem Klassenpfad geladen. Wir fügen Test Ressourcen in Abschnitt 4.11: "Hinzufügen von Unit Test Ressourcen" an. In unserem Verzeichnisaufbau des Maven-Projekts befindet sich die Datei ny-weather.xml im Verzeichnis der Test-Ressourcen -- ${basedir}/src/test/resources unter /com/sonatype/maven/wetter/yahoo/ny-weather.xml . Die Datei wird als InputStream eingelesen und an die parse()-Methode des YahooParser geleitet. Die parse()-Methode liefert ein Weather Objekt zurück,welches über eine Anzahl von assertEquals() Aufrufen, eine innerhalb der Klasse TestCase definierte Methode, geprüft wird. Im gleichen Verzeichnis erstellen Sie eine Datei mit dem Namen WeatherFormatterTest.java. 78
  • 98. Anpassen eines Maven Projektes Example 4.12. "Simple Weather" WeatherFormatterTest Unit-Test package org.sonatype.mavenbook.weather.yahoo; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.sonatype.mavenbook.weather.Weather; import org.sonatype.mavenbook.weather.WeatherFormatter; import org.sonatype.mavenbook.weather.YahooParser; import junit.framework.TestCase; public class WeatherFormatterTest extends TestCase { public WeatherFormatterTest(String name) { super(name); } public void testFormat() throws Exception { InputStream nyData = getClass().getClassLoader().getResourceAsStream("ny-weather.xml"); Weather weather = new YahooParser().parse( nyData ); String formattedResult = new WeatherFormatter().format( weather ); InputStream expected = getClass().getClassLoader().getResourceAsStream("format-expected.dat"); assertEquals( IOUtils.toString( expected ).trim(), formattedResult.trim() ); } } Der zweite Unit Test dieses einfachen Projekts testet die Klasse WeatherFormatter. Wie bereits die Klasse YahooParserTest, erweitert auch die Klasse WeatherFormatterTest die Klasse TestCase des JUnit Frameworks. Die einzige Testfunktion liest die gleiche Testressource von ${basedir}/src/test/resources umter dem /com/sonatype/Maven/Wetter/yahoo-Verzeichnis unter Einbezug des Unit Test Klassenpfades. Wir fügen Test Ressourcen wie im Abschnitt 4.11: "Hinzufügen Unit Test Ressourcen" beschrieben an. WeatherFormatterTest zieht diese Beispiel-Eingabedatei durch den YahooParser an, welcher ein Weather Objekt zurückgibt an, dieses Objekt wird dann mit dem WeatherFormatter ausgegeben. Da WeatherFormatter eine Zeichenkette ausgibt, müssen wir gegen eine gesetzte Eingabe testen. Unser "gesetzter" Input wurde einer Text-Datei mit 79
  • 99. Anpassen eines Maven Projektes dem Namen format-expected.dat erfasst, welche sich im gleichen Verzeichnis wie ny-weather.xml befindet. Um die Test-Ausgabe mit der erwarteten Ausgabe zu vergleichen, wird diese als InputStream eingelesen und unter Einbezug der Klasse IOUtils aus Apache Commons IO in einen String umgewandelt. Diese Zeichenfolge wird dann mittels assertEquals() mit der Testausgabe verglichen. 4.10. Hinzufügen von Gebietsbezogenen Unit Tests In der Klasse WeatherFormatterTest benutzten wir ein Dienstprogramm aus Apache Commons IO- die Klasse IOUtils. IOUtils bietet eine Reihe von hilfreichen statische Funktionen, welche den Grossteil der Arbeit der Input/Output-Operationen übernehmen. In diesem speziellen Unit Test benutzen wir IOUtils.toString() um die format-expected.dat Klassenpfadressource in einen String zu verwandeln. Wir hätten dies auch ohne den Einsatz von Commons IO bewerkstelligen können, aber es hätte zusätzliche sechs oder sieben Zeilen Code bedingt um mit den verschiedenen InputStreamReader- und StringWriter-Objekten umzugehen. Der Hauptgrund für den Einsatz der Commons IO Bibliothek war, uns eine Ausrede zu geben, Test-scoped (abgegrenzte) Abhängigkeiten am Beispiel von Commons IO einzuführen. Eine Test-scoped Abhängigkeit ist eine Abhängigkeit, welche nur auf während der Testkompilierung und Testausführung auf dem Klassenpfad eingeschlossen ist. Sollte Ihr Projekt als war- oder ear-Archiv packetiert werden, so würde eine Test-scoped Abhängigkeit nicht in das Projekt-Output-Archiv einbezogen werden. Um eine Test-scoped Abhängigkeit zu erstellen, fügen Sie das folgende dependency-Element in Ihrem Projekt in das Element dependencies ein . Example 4.13. Das Hinzufügen einer Test-scoped Abhängigkeit <project> ... <dependencies> ... <dependency> <groupId>org.apache.commons</groupId> 80
  • 100. Anpassen eines Maven Projektes <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> ... </dependencies> </project> Nachdem Sie diese Abhängigkeit der pom.xml Datei zugefügt haben, führen Sie mvn dependency:resolve aus: Sie sollten sehen, dass commons-io nun als eine Abhängigkeit mit Gebietsbezug test aufgeführt wird. Noch eine Kleinigkeit wird benötigt, bevor wir bereit sind diese Projekt Unit-Tests auszuführen. Wir müssen die Klassenpfad Ressourcen erstellen, von welchen diese Unit-Tests abhängen. Geltungsbereiche werden ausführlich im Abschnitt 9.4.1: "Geltungsbereiche von Abhängigkeiten" dargestellt. 4.11. Hinzufügen einer Unit-Test Ressource Ein Unit Test hat Zugriff auf eine Reihe von testspezifischen Ressourcen. Häufig werden Sie Dateien mit den erwarteten Ergebnissen und Dummy-Dateien mit Eingabetexten auf dem Testklassenpfad ablegen. In diesem Projekt legen wir ein Test XML-Dokument für den YahooParserTest namens ny-weather.xml sowie eine Datei mit der erwarteten Ausgabe des WeatherFormatter unter format-expected.dat ab. Um Test Ressourcen einzufügen, müssen Sie zunächst das Verzeichnis /src/test/resources erstellen. Dies ist das Standard-Verzeichnis, in welchem Maven nach Unit-Test Ressourcen sucht. Erstellen Sie dieses Verzeichnis indem Sie die folgenden Befehle aus Ihrem Projekt-Basis-Verzeichnis heraus ausführen. $ cd src/test $ mkdir resources $ cd resources Sobald Sie das Ressourcen-Verzeichnis erstellt haben, erstellen Sie eine Datei mit dem Namen format-expected.dat in diesem Verzeichnis. 81
  • 101. Anpassen eines Maven Projektes Example 4.14. "Simple Weather" WeatherFormatterTest erwartete Ausgabe ********************************* Current Weather Conditions for: New York, NY, US Temperature: 39 Condition: Fair Humidity: 67 Wind Chill: 39 ********************************* Diese Datei sollte vertraut aussehen. Es ist die gleiche Ausgabe, welche generiert wurde, als Sie das "Simple Weather" Projekt mit dem Exec Maven Exec starteten. Die zweite Datei welche Sie benötigen werden und daher im Ressourcen-Verzeichnis erstellen sollten ist ny-weather.xml. Example 4.15. "Simple Weather" YahooParserTest XML-Eingabe <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>Yahoo! Weather - New York, NY</title> <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link> <description>Yahoo! Weather for New York, NY</description> <language>en-us</language> <lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate> <ttl>60</ttl> <yweather:location city="New York" region="NY" country="US" /> <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" /> <yweather:wind chill="39" direction="0" speed="0" /> <yweather:atmosphere humidity="67" visibility="1609" pressure="30.18" rising="1" /> <yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" /> <image> <title>Yahoo! Weather</title> <width>142</width> <height>18</height> <link>http://weather.yahoo.com/</link> <url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url> </image> <item> <title>Conditions for New York, NY at 8:51 pm EDT</title> 82
  • 102. Anpassen eines Maven Projektes <geo:lat>40.67</geo:lat> <geo:long>-73.94</geo:long> <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link> <pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate> <yweather:condition text="Fair" code="33" temp="39" date="Sat, 10 Nov 2007 8:51 pm EDT" /> <description><![CDATA[ <img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br /> <b>Current Conditions:</b><br /> Fair, 39 F<BR /><BR /> <b>Forecast:</b><BR /> Sat - Partly Cloudy. High: 45 Low: 32<br /> Sun - Sunny. High: 50 Low: 38<br /> <br /> ]]></description> <yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45" text="Partly Cloudy" code="29" /> <yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50" text="Sunny" code="32" /> <guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid> </item> </channel> </rss> Diese Datei enthält ein Test XML-Dokument für den YahooParserTest. Wir speichern diese Datei, um den YahooParser zu testen ohne eine XML-Antwort vom Yahoo! Wetterdienst abzurufen. 4.12. Ausführen von Unit-Tests Nun, da die Unit Tests zu Ihrem Projekt bestehen, führen wir diese aus! Sie brauchen nichts weiter zu tun um die Unit Tests anzustossen: die Testphase ist ein normaler Bestandteil des Maven Lifecycles. Die Maven Tests werden automatisch ausgeführt sobald Sie mvn package oder mvn install aufrufen. Wenn Sie möchten, dass alle Phasen des Lebenszyklus bis einschließlich der Testphase ausgeführt werden, rufen Sie mvn test auf. $ mvn test ... [INFO] [surefire:test] [INFO] Surefire report directory: ~/examples/simple-weather/target/ surefire-reports 83
  • 103. Anpassen eines Maven Projektes ------------------------------------------------------- T E S T S ------------------------------------------------------- Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest 0 INFO YahooParser - Creating XML Reader 177 INFO YahooParser - Parsing XML Response 239 INFO WeatherFormatter - Formatting Weather Data Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest 475 INFO YahooParser - Creating XML Reader 483 INFO YahooParser - Parsing XML Response Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 Der Aufruf von mvn test von der Befehlszeile veranlasst Maven zur Ausführung aller Phasen des Lebenszyklus' bis und mit der Testphase. Das Surefire Maven Plugin hat ein Goal test, welches an die Testphase gebunden ist. Dieses Goal test führt alle Unit-Tests des Projektes aus, welche sich unter /src/test/java befinden und einen Dateinamen von **/Test*.java, **/*Test.java sowie **/*TestCase.java haben. Für unser Projekt können Sie sehen, dass das Surefire Plugin mit dem Goal test die Unittests WeatherFormatterTest und YahooParserTest ausgeführt hat. Bei der Ausführung des Surefire Maven Plugins werden nicht nur die Tests ausgeführt, es werden auch XML- und Text-Berichte erzeugt und unter dem Verzeichnis ${basedir}/target/surefire-reports abgelegt. Sollten Ihre Tests fehlschlagen, so können Sie in diesem Verzeichnis von den Unit Tests angelegte Detailsangaben wie Fehlermeldungen oder Stack Traces der Unit Tests finden. 4.12.1. Ignorieren fehlgeschlagener Unit Tests Oft werden Sie an einem System arbeiten dessen Unit Tests scheitern. Sollten Sie Test-Driven Development (TDD) praktizieren, so ist die Anzahl der gescheiterten Tests ein Mass dafür, wie nahe der Vollendung Ihr Projekt ist. Sollten Sie trotz gescheiterter Unit-Tests dennoch gerne ein Produkt erstellen, so müssen Sie Maven anweisen, diese Unit Tests zu ignorieren. Das Standardverhalten von Maven ist es, nach einem gescheiterten Test den Build abzubrechen. Um den Build auch bei 84
  • 104. Anpassen eines Maven Projektes auftreten gescheiterter Tests weiterzuführen, müssen sie den Konfigurationseintrag testFailureIgnore des Surefire Maven Plugins auf "true" setzen. Example 4.16. Ignorieren von gescheiterten Unit-Test Fällen <project> [...] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </build> [...] </project> Die Plugin Dokumentation (http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) zeigt, dass dieser Parameter einen Ausdruck erklärt: Example 4.17. Plugin-Parameter-Ausdrücke testFailureIgnore Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on occasion. * Type: boolean * Required: No * Expression: ${maven.test.failure.ignore} // testFailureIgnore Setzen Sie diesen Parameter auf "true" um scheiternde Testfälle // Die Benutzung des Parameters wird NICHT EMPFOHLEN, kann aber zu Zeiten recht nütz Dieser Ausdruck kann von der Befehlszeile mit dem 'D-Parameter' gesetzt werden: $ mvn test -Dmaven.test.failure.ignore=true 85
  • 105. Anpassen eines Maven Projektes 4.12.2. Überspringen von Unit-Tests Sie können Maven auch so konfigurieren dass bestimmte Unit Tests ganz übersprungen werden. Vielleicht arbeiten Sie an einem sehr großen System, in welchem die Unit-Tests Minuten in Anspruch nehmen und Sie wollen nicht warten bis alle Unit-Tests abgeschlossen sind, um ein Produkt zu erstellen. Möglicherweise müssen Sie mit einem Legacy-System arbeiten, welches eine Reihe von scheiternden Unit-Tests umfasst. Anstelle der Behebung der Unit-Test Fehlern möchten Sie lediglich ein JAR-Archive erstellen. Maven sieht für diese Fälle vor, und stellt die Fähigkeit mit dem Parameter skip des Surefire Plugins zum überspringen von Unit-Tests bereit. Um beim Aufruf von der Befehlszeile die Unit Tests zu überspringen, fügen Sie einfach den Parameter maven.test.skip Ihrem angestrebten Goal an: $ mvn install -Dmaven.test.skip=true ... [INFO] [compiler:testCompile] [INFO] Not compiling test sources [INFO] [surefire:test] [INFO] Tests are skipped. ... Wenn das Surefire Plugin das Goal test erreicht, überspringt es die Unit-Tests, sollte der Parameter maven.test.skip auf "true" gesetzt sein. Eine weitere Art Maven zu konfigurieren Unit Tests zu überspringen ist, diese Konfiguration in Ihrer Projekt pom.xml-Datei vorzunehmen. Um dies zu tun, würde man ein Element plugin Ihrer Build Konfiguration zufügen. Example 4.18. Überspringen von Unit-Tests <project> [...] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> 86
  • 106. Anpassen eines Maven Projektes </build> [...] </project> 4.13. Builden einer paketierten, Befehlszeilen orientierten Anwendung In Abschnitt 4.8, "Ausführen des "Simple Weather" Programms", haben wir die "Simple Weather" Anwendung unter Verwendung des Exec Maven Plugins ausgeführt. Auch wenn das Exec Plugin die Anwedung ausgeführt und und Ausgaben erzeugt hat, sollte man Maven nicht als Container zur Ausführung Ihrer Anwendungen ansehen. Sollten Sie Ihre Anwendung an andere weitergeben wollen, so werden sie dies sicher als JAR-Archive, oder als Archiv in ZIP oder GZIP Format tun wollen. Das folgende Vorgehen gibt einen Überblick über den Prozess mit Hilfe einer vordefinierter Assembly Definitionsdatei sowie dem Assembly Maven Plugin ein verteilbares JAR Archive zu erstellen, welches den Anwendungs-(Byte) Code sowie alle notwendigen Abhängigkeiten enthält. Das Assembly Maven Plugin ist ein Plugin welches Sie zur Zusammenstellung beliebiger Pakete zur Verteilung Ihrer Anwendung benutzen können. Sie können das Assembly Plugin dazu einsetzen, das Resultat in beliebiger Form zusammenzustellen, indem Sie eine darauf zugeschnittene Assembly Definitionsdatei erstellen. In einem späteren Kapitel werden wir Ihnen zeigen wie man eine zugeschnittene Assembly Definitionsdatei erstellt, um die "Simple Weather" in einem komplexeren Archiv zu deployen. In diesem Kapitel werden wir das vordefinierte Format jar-with-dependencies einsetzen. Um das Assembly Maven Plugin einzusetzen, müssen wir die folgende Anpassung an der bestehenden Konfiguration in der pom.xml-Datei vornehmen: Example 4.19. Konfigurieren der Assembly Maven Definition <project> [...] <build> <plugins> 87
  • 107. Anpassen eines Maven Projektes <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build> [...] </project> Sobald Sie die Konfigurationsänderung hinzugefügt haben, starten Sie den Build mit dem Aufruf mvn assembly:assembly. $ mvn install assembly:assembly ... [INFO] [jar:jar] [INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar [INFO] [assembly:assembly] [INFO] Processing DependencySet (output=) [INFO] Expanding: .m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into /tmp/archived-file-set.1437961776.tmp [INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/ commons-lang-2.1.jar into /tmp/archived-file-set.305257225.tmp ... (Maven Expands all dependencies into a temporary directory) ... [INFO] Building jar: ~/examples/simple-weather/target/ simple-weather-1.0-jar-with-dependencies.jar Nach dem Abarbeiten befindet sich unser Ergebnis im Verzeichnis /target in der Datei simple-weather-1.0-jar-with-dependencies.jar. Wieder können wir die main()-Klasse ausführen. Um die "Simple Weather" aufzurufen, geben sie folgenden Befehl auf der Kommandozeile im Projekt-Basis-Verzeichnis ein: $ cd target $ java -cp simple-weather-1.0-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 10002 0 INFO YahooRetriever - Retrieving Weather Data 221 INFO YahooParser - Creating XML Reader 399 INFO YahooParser - Parsing XML Response 474 INFO WeatherFormatter - Formatting Weather Data ********************************* Current Weather Conditions for: New York, NY, US 88
  • 108. Anpassen eines Maven Projektes Temperature: 44 Condition: Fair Humidity: 40 Wind Chill: 40 ********************************* Das Format 'jar-with-dependencies' erstellt ein einziges JAR-Archiv, welches allen Bytecode der "Simple Weather" Anwendung sowie den entpackten Bytecode aller Abhängigkeiten enthält. Dieses etwas unkonventionelle Format eines Archivs kommt in einer 9 MiB Datei mit rund 5290 Klassen daher, aber dies ist ein einfaches Format, um Anwendungen welche Sie mit Maven entwickelt haben zu verteilen. Später in diesem Buch, werden wir Ihnen zeigen, wie Sie eine benutzerdefinierte Assembly Definitionsdatei erstellen um eine gewöhnliche Standard-Distribution zu erstellen. 4.13.1. Anbinden des Assembly Goals zur Packetierungs Phase Unter Maven 1 wurde ein Build massgeschneidert indem man eine Anzahl von Plugin Goals direkt aneinanderknüpfte. Jedes Plugin Goal hatte Vorbedingungen und definierte einen Bezug zu anderen Plugin Goals. Mit der Ankunft von Maven 2 wurde das Lebenszykluskonzept eingeführt, und Plugin Goals sind nun zu einer Anzahl Phasen zugeordnet, welche in einem Standard Lebenszyklus bestehen. Das Lebenszykluskonzept ist eine solide Grundlage, welche es vereinfacht, die Abfolge der Plugin Goals zu bestimmen und zu koordinieren welche Goals in einem bestimmten Build ausgeführt werden. Unter Maven 1 war der Bezug zwischen den Goals direkt, unter Maven 2 besteht der Bezug von Plugin Goals immer zu einer Menge von gemeinsamen Lebenszyklus Phasen. Obschon es zulässig ist, ein Goal direkt - wie beschrieben - von der Kommandozeile auszuführen, steht es eher im Einklang mit der Architektur von Maven 2, das Assembly mit dem Aufruf des Goals assembly:assembly während einer Phase des Maven Lebenszykluses aufzurufen. Die folgenden Plugin-Konfiguration konfiguriert das Maven Plugin Assembly, um das angefügte Goal während der Phase package des Maven Standard 89
  • 109. Anpassen eines Maven Projektes Lebenszykluses auszuführen. Das angefügte Goal führt das gleiche aus, wie schon das Goal assembly. Um das Goal assembly:attached der Phase package zuzuordnen benutzen wir das Element executions innerhalb des Elements plugin der Sektion build des Projekt POMs. Example 4.20. Anbinden des Assembly Goals zur Packetierungs Phase <project> [...] <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <executions> <execution> <id>simple-command</id> <phase>package</phase> <goals> <goal>attached</goal> </goals> </execution> </executions> </plugins> </build> [...] </project> Sobald Sie diese Änderung Ihres POMs angefügt haben müssen Sie nur noch mvn package aufrufen, um Ihr Assembly zu generieren. Die Konfiguration der execution wird sicherstellen, dass das Goal assembly:attached ausgeführt wird, sobald Maven in die Lebenszyklusphase package eintritt. 90
  • 110. Chapter 5. Eine einfache Web-Anwendung 5.1. Einleitung In diesem Kapitel erstellen wir mit Hilfe des Archetype Maven Plugin eine einfache Web-Anwendung. Wir führen diese Web-Applikation ein, benutzen hierfür einen Servlet-Container Jetty, fügen einige Abhängigkeiten ein, schreiben ein einfaches Servlet und erzeugen ein WAR-Archiv. Am Ende dieses Kapitels, werden Sie in der Lage sein Maven zur Beschleunigung der Entwicklung von Web-Applikationen einzusetzen. 5.1.1. Herunterladen der Beispiele dieses Kapitels Das Beispiel in diesem Kapitel wird mit dem Archetype Maven Plugin erzeugt. Während Sie in der Lage sein sollten, der Entwicklung dieses Kapitels ohne Beispiel-Quellcode zu folgen, empfehlen wir das Herunterladen einer Kopie der Quellcodes als Referenz. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch05. Darin finden Sie ein Verzeichnis mit dem Namen /simple web welches den Quellcode für dieses Kapitel enthält. 5.2. Eine kurze Einführung in die "Simple Web" Anwendung Wir haben dieses Kapitel gezielt auf Plain-Old Web Applikationen (POWA) konzentriert - ein Servlet und eine JSP-Seite. Wir werden Ihnen in den nächsten 20 Seiten sicher nicht zeigen wie Sie Ihre Struts 2, Tapestry, Wicket, JSF, oder Waffle Anwendung erstellen, auch nicht wie Sie die Integration eines IoC Containers wie 91
  • 111. Eine einfache Web-Anwendung Plexus, Guice oder Spring Framework bewerkstelligen. Das Ziel dieses Kapitels ist es, Ihnen die grundlegenden Möglichkeiten welche Maven zur Entwicklung von Web-Anwendungen bietet vorzustellen. Nicht mehr und nicht weniger! Später in diesem Buch, werden wir einen Blick auf die Entwicklung von zwei Web-Anwendungen werfen: einer, welche Hibernate, Velocity sowie das Spring Framework nutzt, und eine andere unter Verwendungen von Plexus. 5.3. Erstellen des "Simple Web" Projekts Um das Web Applikationsprojekt zu erstellen, führen Sie mvn archetype:create mit einer artifactId und einer groupId aus. Wählen Sie die archetypeArtifactId maven-archetype-webapp. Bei der Ausführung wird die entsprechende Verzeichnis-Struktur und das grundlegende Maven POM erstellt. ~/examples$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch05 -DartifactId=simple-webapp -DpackageName=org.sonatype.mavenbook -DarchetypeArtifactId=maven-archetype-webapp [INFO] [archetype:create] [INFO] ---------------------------------------------------------------------- [INFO] Using parameters for creating Archetype: maven-archetype-webapp:RELEASE [INFO] ---------------------------------------------------------------------- [INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch05 [INFO] Parameter: packageName, Value: org.sonatype.mavenbook [INFO] Parameter: basedir, Value: ~/examples [INFO] Parameter: package, Value: org.sonatype.mavenbook [INFO] Parameter: version, Value: 1.0-SNAPSHOT [INFO] Parameter: artifactId, Value: simple-webapp [INFO] *************** End of debug info from resources from generated POM ** [INFO] Archetype created in dir: ~/examples/simple-webapp Sobald das Archetype Maven Plugin das Projekt erstellt hat, gehen Sie in das Verzeichnis /simple-web und werfen Sie einen Blick auf die dort abgelegte pom.xml-Datei. Sie sollten das folgenden XML-Dokument vorfinden: Example 5.1. Erstes POM des "Simple Web" Projekt <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> 92
  • 112. Eine einfache Web-Anwendung <groupId>org.sonatype.mavenbook.ch05</groupId> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>simple-webapp Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build> </project> Beachten Sie dass das Element packaging den Wert 'war' enthält. Ein Projekt des Types war package, wird eine WAR-Datei im Zielverzeichnis erstellen, das standardmäßig den Namen ${artifactId}-${version}.war trägt. Für dieses Projekt, wird das standardmäßige WAR in /target/simple-webapp-1.0-SNAPSHOT.war abgelegt. Im "Simple Web" Projekt haben wir den Namen der WAR-Datei vorbestimmt, indem wir ein Element finalName in die Projekt Build Konfiguration eingefügt haben. Mit dem finalName von 'simple-webapp' generiert die Phase package ein WAR-Archiv in /target/simple-webapp.war. 5.4. Konfigurieren des Jetty-Plugins Sobald Sie Ihre Web Anwendung kompiliert, getestet und verpackt haben, werden Sie sie wahrscheinlich einen Servlet-Container einsetzen wollen und diese mit der 93
  • 113. Eine einfache Web-Anwendung vom Archetype Maven Plugin erstellten index.jsp-Datei testen wollen. Normalerweise wäre der Ablauf hierzu entsprechend: Herunterladen eines Containers wie etwas Jetty oder Apache Tomcat, Auspacken einer Distribution, Kopieren Ihres Anwendungs WAR-Archivs in ein /webapp-Verzeichnis, schliesslich starten des Containers. Obschon man das alles tun kann, gibt es keine Notwendigkeit, dies zu tun. Stattdessen können Sie das Jetty Maven Plugin einsetzen, und Ihre Web-Applikation aus Maven heraus starten. Um dies zu tun, müssen wir das Jetty Maven Plugin in unserem Projekt POM konfigurieren. Fügen Sie das folgende Plugin-Element Ihrer Projekt Build Konfiguration an: Example 5.2. Konfigurieren des Jetty-Plugins <project> [...] <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins> </build> [...] </project> Nachdem Sie das Jetty Plugin in der Projekt pom.xml-Datei konfiguriert haben. können Sie das Goal jetty:run aufrufen, um Ihre Web Anwendung im Jetty Container zu starten. Rufen sie mvn jetty:run wie folgt auf: ~/examples$ mvn jetty:run ... [INFO] [jetty:run] [INFO] Configuring Jetty for project: simple-webapp Maven Webapp [INFO] Webapp source directory = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp [INFO] web.xml file = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp/WEB-INF/web.xml [INFO] Classes = ~/svnw/sonatype/examples/simple-webapp/target/classes 2007-11-17 22:11:50.532::INFO: Logging to STDERR via org.mortbay.log.StdErrLog [INFO] Context path = /simple-webapp [INFO] Tmp directory = determined at runtime 94
  • 114. Eine einfache Web-Anwendung [INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] Webapp directory = ~/svnw/sonatype/examples/simple-webapp/src/main/webapp [INFO] Starting jetty 6.1.6rc1 ... 2007-11-17 22:11:50.673::INFO: jetty-6.1.6rc1 2007-11-17 22:11:50.846::INFO: No Transaction manager found 2007-11-17 22:11:51.057::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Nach erfolgreichem Starten des Servlet-Containers durch Maven, laden Sie die URL http://localhost:8080/simple-webapp/ in einen Web-Browser. Die durch Archetype generierte index.jsp-Datei ist trivial: sie enthält eine Überschrift mit dem Text: "Hallo Welt!". Maven erwartet, dass das Document Root der Web-Applikation in /src/main/webapp liegt. In diesem Verzeichnis finden Sie auch die Datei index.jsp welche im Beispiel 5.3: "Inhalt von /src/main/webapp/index.jsp" aufgelistet wird. Example 5.3. Inhalt von /src/main/webapp/index.jsp <html> <body> <h2>Hello World!</h2> </body> </html> Unter /src/main/webapp/WEB-INF finden Sie den kleinsten möglichen Web-Applikations Beschrieb; der web.xml. Example 5.4. Inhalt von src/main/webapp/WEB-INF/web.xml <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> </web-app> 95
  • 115. Eine einfache Web-Anwendung 5.5. Das Hinzufügen eines einfachen Servlets Eine Web-Applikation mit einer einzigen JSP-Seite und keinen konfigurierten Servlets ist so gut wie nutzlos. Lassen Sie uns ein einfaches Servlet hinzufügen und die entsprechenden Änderungen in der pom.xml- sowie der web.xml-Datei vornehmen. Zu Beginn müssen wir ein neues Package namens com.sonatype.maven.web im Verzeichnis /src/main/java erstellen $ mkdir -p src/main/java/org/sonatype/mavenbook/web $ cd src/main/java/org/sonatype/mavenbook/web Sobald Sie dieses Package kreiert haben, wechseln Sie in das Verzeichnis (/src/main/java/com/sonatype/maven/web) und erstellen Sie eine Klasse SimpleServlet in einer Datei SimpleServlet.java, welche den folgenden Quellcode enthält. Example 5.5. SimpleServlet Klasse package org.sonatype.mavenbook.web; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SimpleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println( "SimpleServlet Executed" ); out.flush(); out.close(); } } Unsere Klasse SimpleServlet ist eben dies, ein einfaches Servlet, welches eine einfache Nachricht auf den responseWriter schreibt. Um dieses Servlet Ihrer Web-Anwendung hinzuzufügen und auf einen Anfragepfad abzubilden, fügen Sie die folgenden Servlet-und Servlet-Mapping-Elemente zu Ihrer Projekt 96
  • 116. Eine einfache Web-Anwendung web.xml-Datei an. Die web.xml-Datei finden Sie unter /src/main/webapp/WEB-INF. Example 5.6. Mapping the Simple Servlet <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>simple</servlet-name> <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simple</servlet-name> <url-pattern>/simple</url-pattern> </servlet-mapping> </web-app> Nun ist alles vorhanden, um das Servlet zu testen, die Klasse befindet sich unter /src/main/java und die web.xml-Datei wurde aktualisiert. Bevor wir das Jetty-Plugin starten, kompilieren Sie Ihr Projekt, durch den Aufruf von mvn compile: ~/examples$ mvn compile ... [INFO] [compiler:compile] [INFO] Compiling 1 source file to ~/examples/ch05/simple-webapp/target/classes [INFO] ------------------------------------------------------------------------ [ERROR] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Compilation failure /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[4,0] package javax.servlet does not exist /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[5,0] package javax.servlet.http does not exist /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[7,35] cannot find symbol symbol: class HttpServlet public class SimpleServlet extends HttpServlet { /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[8,22] 97
  • 117. Eine einfache Web-Anwendung cannot find symbol symbol : class HttpServletRequest location: class org.sonatype.mavenbook.web.SimpleServlet /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[9,22] cannot find symbol symbol : class HttpServletResponse location: class org.sonatype.mavenbook.web.SimpleServlet /src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[10,15] cannot find symbol symbol : class ServletException location: class org.sonatype.mavenbook.web.SimpleServlet Die Erstellung schlägt fehl, Ihr Maven-Projekt weist keine Abhängigkeit zum Servlet-API aus. Im nächsten Abschnitt werden wir diese hinzufügen und das Servlet-API in das Projekt POM eintragen. 5.6. Das Hinzufügen von J2EE-Abhängigkeiten Um ein Servlet zu schreiben benötigen Sie die Servlet-API-Spezifikation. Um die Servlet-API-Spezifikation als eine Abhängigkeit zu Ihrem Projekt POM hinzuzufügen, fügen Sie das folgende dependency Element Ihrer pom.xml an. Example 5.7. Einfügen der Servlet-Spezifikation 2.4 als Abhängigkeit <project> [...] <dependencies> [...] <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> </dependencies> [...] </project> Es ist auch darauf hinzuweisen, dass wir für diese Abhängigkeit den Geltungsbereich provided verwendet haben. Dies sagt Maven, dass das Archiv 98
  • 118. Eine einfache Web-Anwendung seitens des Containers bereitgestellt wird, und nicht in das war-Archiv einfliessen soll. Wenn Sie daran interessiert waren, ein benutzerdefiniertes JSP-Tag für dieses einfache Web-Anwendung zu erstellen, so bräuchten Sie eine Abhängigkeit zur JSP-Spezifikation 2.0. Verwenden Sie die folgende Konfiguration, um diese Abhängigkeit zu schaffen. Example 5.8. Hinzufügen der 2.0 JSP-Spezifikation als Abhängigkeit <project> [...] <dependencies> [...] <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies> [...] </project> Sobald Sie die Servlet Spezifikationsimplementierung als Abhängigkeit hinzugefügt haben, führen Sie mvn clean install gefolgt von mvn jetty:run aus. [tobrien@t1 simple-webapp]$ mvn clean install ... [tobrien@t1 simple-webapp]$ mvn jetty:run [INFO] [jetty:run] ... 2007-12-14 16:18:31.305::INFO: jetty-6.1.6rc1 2007-12-14 16:18:31.453::INFO: No Transaction manager found 2007-12-14 16:18:32.745::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server An diesem Punkt sollten Sie in der Lage sein, die Ausgabe des SimpleServlet zu erhalten. Von der Befehlszeile können Sie mittels curl die Ausgabe des Servlet zur Standard-Ausgabe umleiten: ~/examples$ curl http://localhost:8080/simple-webapp/simple SimpleServlet Executed 99
  • 119. Eine einfache Web-Anwendung 5.7. Zusammenfassung Nach dem Lesen dieses Kapitels sollten Sie in der Lage sein eine einfache Web-Applikation zu erstellen. In diesem Kapitel wurde nicht näher auf die Millionen verschiedener Möglichkeiten eingegangen wie man eine komplette Web-Anwendung erschafft. Weitere Kapitel werden einen umfassenderen Überblick über Projekte geben, bei denen einige der populären Web-Frameworks und Technologien zum Einsatz kommen. 100
  • 120. Chapter 6. Ein multi-modulares Projekt 6.1. Einleitung In diesem Kapitel werden wir ein Multi-Modul Projekt erstellen, welches die Beispiele der beiden vorangegangenen Kapitel verbindet. Die "Simple Weather" Anwendung von Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: "Anpassen eines Maven Projekts") wird verbunden mit der "Simple Web" Applikation von Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: "Eine einfache Web-Anwendung"), mit dem Ergebnis einer Web Anwendung welche die Wetterdaten zu einer gegebenen Postleitzahl abruft und die Wettervorhersage auf einer Webseite darstellt. Am Ende dieses Kapitels werden Sie in der Lage sein, unter Verwendung von Maven komplexe, multi-modulare Projekte zu entwickeln. 6.1.1. Herunterladen der Beispiele dieses Kapitels Das multi-modulare Projekt welches wir in diesem Kapitel erstellen werden, besteht aus modifizierten Versionen der Projekte der vorhergehenden Kapitel: 4 (Kapitel 4: "Anpassen eines Maven Projekts") sowie 5 (Kapitel 5: "Eine einfache Web-Anwendung"). Wir werden dieses multi-modulare Projekt nicht mit dem Archetype Maven Plugin generieren. Wir empfehlen Ihnen eine Kopie der Beispiel-Code als zusätzliche Referenz beim Lesen der Inhalte in diesem Kapitel herunterzuladen. Das Beispielprojekt dieses Kapitels zusammen mit den anderen Beispielen aus diesem Buch kann von http://books.sonatype.com/maven-book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie dann zum Verzeichnis /ch06. Darin finden Sie ein Verzeichnis mit dem Namen /simple-parent welches das multi-modulare Projekt dieses Kapitels enthält. Innerhalb des Verzeichnisses /simple-parent werden Sie auf eine pom.xml-Datei sowie zwei Unterverzeichnisse /simple weather und /simple web app stossen welche den Quellcode für dieses Kapitel enthalten. 101
  • 121. Ein multi-modulares Projekt 6.2. Das "Simple Parent" Projekt (parent=Elternteil) Ein multi modulares Projekt wird durch ein "Eltern"-POM und dessen referenzierende Submodule definiert. Im Verzeichnis /simple-parent finden Sie die "Eltern" POM Datei pom.xml, auch das 'top-level' POM genannt, Example 6.1. simple-parent Projekt POM <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 6 Simple Parent Project</name> <modules> <module>simple-weather</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> 102
  • 122. Ein multi-modulares Projekt </dependency> </dependencies> </project> Wie Sie sehen, definiert das top-level POM die Maven Koordinaten groupId als com.sonatype.maven, artifactId als simple-parent und version als 1.0. Das top level POM definiert keinen Ausgabetyp JAR- oder WAR-Archiv wie das bislang der Fall, sondern bezieht sich auf andere Maven Projekte. Das entsprechende Element package eines top level POM welches lediglich ein Projekt Objekt Modell darstellt, ist 'pom'. Der nächste Abschnitt in der pom.xml-Datei führt die Sub-Module auf. Diese Module werden im Element module aufgeführt, und befinden sich je in einem eigenen Unterverzeichnis. Maven kennt diese Struktur und sucht in den Unterverzeichnissen nach weiteren pom.xml-Dateien. Diese werden dann in die Liste der Maven Projekte des Builds aufgenommen. Zuletzt setzen wir eine Anzahl Einstellungen welche von allen Untermodulen geerbt werden. Das "Simple Parent" Modul setzt die Zielplattform aller Untermodule als Java 5 Plattform. Da das Compiler-Plugin standardmässig an den Lebenszyklus gekettet ist, können wir den Abschnitt pluginManagement hierzu benutzen. Wir werden pluginManagement später im Detail beleuchten, für den Moment ist es einfacher, diese Trennung zwischen Bereitstellung von Plugin Standard-Konfigurationen und dem tatsächlichen 'binden' der Plugins darzustellen wenn sie auf diese Weise getrennt werden. Das Element dependency fügt als globale Abhängigkeiten JUnit 3.8.1 ein. Sowohl die Konfiguration sowie die Abhängigkeiten werden von allen Untermodulen geerbt. Der Einsatz der POM Vererbung erlaubt es Ihnen, gemeinsam genutzte Abhängigkeiten, wie z.B. JUnit oder Log4J, universell zu definieren. 6.3. Das "Simple Weather" Modul Das erste Untermodul welches wir uns genauer ansehen wollen, ist das Untermodul "Simple Weather". Dieses Modul enthält alle Klassen, welche zum Abrufen und Verarbeiten des Yahoo! Wetterdienstes benötigt werden. 103
  • 123. Ein multi-modulares Projekt Example 6.2. POM des Untermodul "Simple-Weather" <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Chapter 6 Simple Weather API</name> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> 104
  • 124. Ein multi-modulares Projekt <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Im POM des Moduls "Simple Weather" sehen wir, wie dieses Modul die elterlichen Koordinaten in Form von Maven Koordianten referenziert. Das elterliche POM enthielt die Koordinaten groupId com.sonatype.maven, artifactId simple-parent sowie version 1.0 Beachten Sie hier, dass wir weder die Koordinate groupID noch version definieren müssen, diese sind bereits im "elterlichen" POM definiert. Example 6.3. Die Klasse WeatherService package org.sonatype.mavenbook.weather; import java.io.InputStream; public class WeatherService { public WeatherService() {} public String retrieveForecast( String zip ) throws Exception { // Retrieve Data InputStream dataIn = new YahooRetriever().retrieve( zip ); // Parse Data Weather weather = new YahooParser().parse( dataIn ); // Format (Print) Data return new WeatherFormatter().format( weather ); } } Die Klasse WeatherService ist unter /src/main/java/de/sonatype/Maven/Weather definiert, und ruft lediglich die drei in Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: "Anpassen eines Maven Projekts") definierten Objekte auf. Im Beispiel dieses Kapitels, erstellen 105
  • 125. Ein multi-modulares Projekt wir ein separates Projekt, welches Service-Objekte bereitstellt, die vom Web-Applikations Projekt angesprochen werden. Dies ist ein gewöhnliches Pattern von Enterprise Anwendungen. Oftmals besteht eine komplexe Anwendung aus weit mehr als einer einzigen (einfachen) Web-Applikation. Eine solche Anwendung könnte aus einer Reihe von Web Anwendungen sowie weiteren Befehlszeilenanwendungen bestehen. Sie werden in diesem Fall die gemeinsame Logik in einer Service Klasse zusammenfassen und für eine Anzahl Projekte als Dienst bereitstellen. Dies gibt uns eine Rechtfertigung für die Erstellung einer Klasse WeatherService. Sie können daran sehen, wie die "Simple Web" Anwendung auf die bereitgestellten Service Objekte welche in "Simple Weather" bereitgestellt werden zugreift. Die Methode retrieveForecast() hat einen Parameter Postleitzahl. Dieser wird an die Methode retrieve() der Klasse YahooRetriever gegeben, welche das entsprechende XML-Fragment beim Yahoo Wetter Dienst einholt. Das erhaltene XML wird an die Methode parse() übergeben, welche ein Objekt Weather zurückgibt. Das Objekt Weather wird dann in Form einer lesbaren, formatierten Zeichenkette ausgegeben. 6.4. Das "Simple-Web" Anwendungs-Modul Das Untermodul "Simple Web" ist das zweite, referenzierte Sub-Modul des Projekts. Das darin enthaltene Web-Anwendungsprojekt ist vom Untermodul WeatherService abhängig und es enthält einige einfache Servlets zur Präsentation der Ergebnisse der Yahoo! Wetterdienst Abfrage. Example 6.4. POM des Untermodul "Simple-Webapp" <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> 106
  • 126. Ein multi-modulares Projekt <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>simple-webapp Maven Webapp</name> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch06</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> </plugin> </plugins> </build> </project> Das "Simple Web" Modul definiert ein einfaches Servlet, welches eine Postleitzahl aus einem HTTP-Request ausliest, den weatherService aus Example 6.3, “Die Klasse WeatherService” (Beispiel 6.3: "Die klasse WeatherService") aufruft, und das Resultat auf den Writer schreibt. Example 6.5. Simple-Webapp WeatherServlet package org.sonatype.mavenbook.web; import org.sonatype.mavenbook.weather.WeatherService; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class WeatherServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String zip = request.getParameter("zip" ); WeatherService weatherService = new WeatherService(); 107
  • 127. Ein multi-modulares Projekt PrintWriter out = response.getWriter(); try { out.println( weatherService.retrieveForecast( zip ) ); } catch( Exception e ) { out.println( "Error Retrieving Forecast: " + e.getMessage() ); } out.flush(); out.close(); } } Im WeatherServlet, instanziieren wir die Klasse "SimpleWeather" des SimpleWeather Projekts. Die Postleitzahl welche in den Anfrage-Parametern übermittelt wird, geht an die Methode retrieveForecast(), der zurückgegebene Text wird auf den Writer geleitet. Schlussendlich besteht die web.xml-Datei der "Simple Web" Anwendung, welche unter /src/main/webapp/WEB-INF zu finden ist, und alles zusammenbindet. Die Servlet-und Servlet-Mapping-Elemente definieren hierbei einen Pfad von /weather auf das WeatherServlet. Example 6.6. web.xml der Simple-Webapp <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>simple</servlet-name> <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class> </servlet> <servlet> <servlet-name>weather</servlet-name> <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>simple</servlet-name> <url-pattern>/simple</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>weather</servlet-name> <url-pattern>/weather</url-pattern> </servlet-mapping> </web-app> 108
  • 128. Ein multi-modulares Projekt 6.5. Erstellung des Multi-Projekt-Moduls Mit dem "Simple Weather" Projekt welches den allgemeinen Code zur Interaktion mit dem Yahoo! Wetterdienst erstellt, sowie der "Simple Web" Anwendung welche die Webanwendung in Form eines Servlets bereitstellt, es ist an der Zeit, zu Kompilieren und Packetieren, sowie Bereitstellen der Anwendung in Form eines WAR-Archivs. Um das zu bewerkstelligen, müssen Sie die beiden Projekte in der richtigen Reihenfolge kompilieren und installieren. Da "Simple Web" von "Simple Weather" abhängig ist, muss diese zuerst kompiliert werden und als JAR-Archive bereitgestellt werden. Tun Sie dies durch den Aufruf von mvn clean install aus dem Verzeichnis der Mutterapplikation heraus auf der Befehlszeile. ~/examples/ch06/simple-parent$ mvn clean install [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Simple Parent Project [INFO] simple-weather [INFO] simple-webapp Maven Webapp [INFO] ---------------------------------------------------------------------- [INFO] Building simple-weather [INFO] task-segment: [clean, install] [INFO] ---------------------------------------------------------------------- [...] [INFO] [install:install] [INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar [INFO] ---------------------------------------------------------------------- [INFO] Building simple-webapp Maven Webapp [INFO] task-segment: [clean, install] [INFO] ---------------------------------------------------------------------- [...] [INFO] [install:install] [INFO] Installing simple-webapp.war to simple-webapp-1.0.war [INFO] [INFO] ---------------------------------------------------------------------- [INFO] Reactor Summary: [INFO] ---------------------------------------------------------------------- [INFO] Simple Parent Project ............................... SUCCESS [3.041s] [INFO] simple-weather ...................................... SUCCESS [4.802s] [INFO] simple-webapp Maven Webapp .......................... SUCCESS [3.065s] [INFO] ---------------------------------------------------------------------- Wenn Maven gegen ein Projekt mit Submodulen ausgeführt wird, lädt Maven zunächst das höchststehende POM, dann lokalisiert es alle abhängigen POM-Dateien. Maven bringt alle Komponenten POM-Inhalte in das, was man den 109
  • 129. Ein multi-modulares Projekt Maven Reaktor nennt, innerhalb werden die Komponenten POM auf deren Abhängigkeiten analysiert. Der Reaktor definiert die Reihenfolge der Komponenten und stellt deren Abarbeitung und Installation sicher. Note Der Reaktor wird die Reihenfolge der Module gemäß der Definition in der POM erhalten, soweit dies möglich ist. Eine hilfreiches mentales Modell hierfür ist das Bild, dass Module mit Abhängigkeiten von Geschwister-Projekten solange in der Reihenfolge "nach hinten" gerückt werden, bis deren Abhängigkeitsverhältnisse befriedigt sind. In seltenen Fällen kann es nützlich sein, in die Reihenfolge der Module einzugreifen, - zum Beispiel, wenn Sie ein häufig instabiles Modul gegen Anfang des Build abarbeiten möchten. Nachdem der Reaktor ausgearbeitet hat, in in welcher Reihenfolge die Projekte abgearbeitet werden müssen, führt Maven die angegebenen Goals für die jeweiligen Module des Multi-Moduls aus. In diesem Beispiel können Sie erkennen, wie Maven "Simple Weather" vor "Simple Web" stellt, und auf dem jeweiligem Modul einen Befehl mvn clean install abarbeitet. Note Beim Ausführen von Maven von der Befehlszeile sollten Sie die Lebenszyklusphase clean vor allen anderen Phasen spezifizieren. Mit dem Aufruf von clean stellen Sie sicher, dass alle alten Output-Artefakte gelöscht werden, vor die Anwendung kompiliert und paketiert wird. clean auszuführen ist nicht notwendig, aber es stellt sicher dass Sie einen "sauberen Build" erstellen. 6.6. Starten der Web-Anwendung Nach der Installation des Multi-Modul-Projekts mittels mvn clean install aus dem Stammverzeichnis von "Simple Project" heraus, können Sie direkt in das 110
  • 130. Ein multi-modulares Projekt Unterverzeichnis /Simple Web wechseln und dort das Goal run des Jetty Plugins aufrufen: ~/examples/ch06/simple-parent/simple-webapp $ mvn jetty:run [INFO] ---------------------------------------------------------------------- [INFO] Building simple-webapp Maven Webapp [INFO] task-segment: [jetty:run] [INFO] ---------------------------------------------------------------------- [...] [INFO] [jetty:run] [INFO] Configuring Jetty for project: simple-webapp Maven Webapp [...] [INFO] Webapp directory = ~/examples/ch06/simple-parent/ simple-webapp/src/main/webapp [INFO] Starting jetty 6.1.6rc1 ... 2007-11-18 1:58:26.980::INFO: jetty-6.1.6rc1 2007-11-18 1:58:26.125::INFO: No Transaction manager found 2007-11-18 1:58:27.633::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Sobald Jetty mit dem Startvorgang abgeschlossen hat, laden Sie http://localhost:8080/simple-webapp/weather?zip=01201 in einen Browser und Sie sollten sehen, wie die Wettervorhersage formatiert ausgegeben wird. 111
  • 131. Chapter 7. Multi-module Enterprise Project 7.1. Einleitung In diesem Kapitel werden wir, aufbauend auf die Beispiele der Kapitel 6: Ein Multi-Projekt-Modul sowie Kapitel 5. Eine einfache Web-Anwendung, ein Multi-Modul-Projekt entwickeln. In dieses Beispiel werden wir das Spring Framework und auch Hibernate einbinden, um eine einfache Web-Anwendung und gleichzeitig ein einfaches Befehlszeilen-Dienstprogramm zum Lesen von Daten des Yahoo! Wetter Dienstes zu erstellen. Den Quellcode des "Simple Weather" Beispiels aus Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: Anpassen eines Maven Projekts) werden wir mit dem Quellcode aus Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: Eine einfache Web-Anwendung) kombinieren. Im Laufe der Entwicklung dieses Multi Modul Projekts werden wir Fragenstellungen bezüglich des Einsatzes von Maven erörtern und verschiedene Möglichkeiten der Umsetzung darlegen, um den Aufbau modularer Projekte zu fördern und die Wiederverwendung von Komponenten aufzuzeigen. 7.1.1. Herunterladen der Beispiele dieses Kapitels Das multi-modulare Projekt welches wir in diesem Kapitel erstellen werden, besteht aus modifizierten Versionen der Projekte der vorhergehenden Kapitel; Chapter 4, Anpassen eines Maven Projektes (Kapitel 4: Anpassen eines Maven Projekts) sowie Chapter 5, Eine einfache Web-Anwendung (Kapitel 5: Eine einfache Web-Anwendung). Wir werden dieses multi-modulare Projekt nicht mit dem Archetype Maven Plugin generieren. Wir empfehlen Ihnen eine Kopie der Beispiel-Code als zusätzliche Referenz beim Lesen der Inhalte in diesem Kapitel herunterzuladen. Das Beispielprojekt dieses Kapitel zusammen mit den anderen Beispielen dieses Buchs kann von http://www.sonatype.com/book/mvn-examples-1.0.zip oder http://books.sonatype.com/maven-book/mvn-examples-1.0.tar.gz heruntergeladen werden. Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und gehen Sie 112
  • 132. Multi-module Enterprise Project dann zum Verzeichnis /ch07. Darin finden Sie ein Verzeichnis mit dem Namen /simple-parent welches das multi modulare Projekt dieses Kapitels enthält. Innerhalb des Verzeichnisses /simple-parent werden Sie auf eine pom.xml-Datei sowie zwei Unterverzeichnisse /simple weather und /simple web app stossen welche den Quellcode für dieses Kapitel enthalten. 7.1.2. Multi-Modul Enterprise Projekt Die Komplexität einer extensiven Enterprise Applikation aufzuzeigen würde den Rahmen dieses Buches bei weitem sprengen. Solche Projekte sind gekennzeichnet durch den Einsatz mehrerer Datenbanken, der Integration externer Systeme und Teilprojekte welche weiter z.B. nach Abteilungen aufgeteilt werden. Solcherlei Projekte beinhalten in der Regel tausende von Zeilen Quellcode, und reflektieren die Arbeit von Dutzenden oder Hunderten von Software-Entwicklern. Da ein solches Beispiel den Rahmen des Buches sprengen würde, können wir Ihnen hier lediglich anhand eines Beispielprojekts die Komplexitäten größerer Enterprise-Anwendungen aufzeigen. Im Abschluss geben wir einige Ratschläge der modularen Strukturierung welche über dieses Kapitel hinausgehen. In diesem Kapitel wenden wir uns einem Multi Modul Projekt zu, welches zwei Benutzerschnittstellen definiert: ein Befehlszeilen-Abfrage-Tool für die Yahoo! Wetter-Dienste, sowie eine Web-Anwendung welche auf diese Dienste aufbaut. Beide Anwendungen speichern die Resultate der Abfragen in einer eingebetteten Datenbank. Beide Anwendungen erlauben es dem Benutzer auf historisierte Abfragewerte der eingebetteten Datenbank zuzugreifen. Beide Anwendungen teilen sich die Anwendungslogik und auch eine Persistenz Bibliothek. Dieses Kapitel baut auf den Code zur Auswertung des Yahoo! Wetter Dienstes auf, welcher in Chapter 4, Anpassen eines Maven Projektes (Kapitel 4 Anpassen eines Maven Projekts) eingeführt wurde. Dieses Projekt gliedert sich in fünf Untermodule wie in Figure 7.1, “Beziehungen der Module einer komplexen Enterprise Anwendung” (Abbildung 7.1: "Beziehungen der Module einer komplexen Enterprise Anwendung") veranschaulicht wird. 113
  • 133. Multi-module Enterprise Project Figure 7.1. Beziehungen der Module einer komplexen Enterprise Anwendung Aus Figure 7.1, “Beziehungen der Module einer komplexen Enterprise Anwendung” (Abbildung 7.1: "Beziehungen der Module einer komplexen Enterprise Anwendung") sehen Sie, dass es fünf Untermodule des "Eltern"-Projekt gibt. Diese sind: simple-model Dieses Modul definiert ein einfaches Objektmodell, welches die vom Yahoo! Wetter-Dienst zurückgegebenen Daten abbildet. Dieses Objektmodell beinhaltet die Objekte Weather, Condition, Atmosphere, Location und Wind (Wetterlage, Zustand, Atmosphäre, Ort sowie Windrichtung). Beim parsen der vom Yahoo! Wetter Dienst zurückgegebenen XML-Antwort werden weather Objekte erstellt, welche an die Anwendung weitergegeben werden. Dieses Projekt beinhaltet Objekte welche mit Hibernate 3 Annotations versehen sind um die Zuordnung eines jeden Objektes zu einem Datensatz in der Datenbank sicherzustellen. 114
  • 134. Multi-module Enterprise Project simple-weather Dieses Modul enthält alle Logik welche erforderlich ist, um Daten des Yahoo! Wetter-Dienstes anzuziehen und das zurückgegebene XML auszuwerten. Aus dem zurückgegebenen XML werden anschliessend Objekte wie diese in "Simple Model" definiert wurden, erstellt. Das "Simple Weather"-Modul ist abhängig vom Modul "Simple Model". "Simple Weather" stellt einen Dienst bereit ("WeatherService"), welcher sowohl von "Simple Command" wie auch von "Simple Web" konsumiert wird. simple-persist Dieses Modul enthält einige Data Access Objekte (DAO). Konfiguriert, um Weather Objekte in einer eingebetteten Datenbank abzuspeichern. Beide Benutzerschnittstellen dieser multi-modularen Anwendung (Web/Befehlszeile) bauen auf einfache DAOs (Data Access Objekts=Datenzugriffsobjekte) auf, welche zur Speicherung (speichern=to persist) herangezogen werden. Die Modellobjekte welche in "Simple Model" bereitgestellt werden, können von den DAOs sowohl verstanden wie auch zurückgegeben werden. "Simple Persist" ist direkt von "Simple Model" abhängig und definiert weitere Abhängigkeiten zu den Modellobjekten, gekennzeichnet durch Hibernate Annotations. simple-webapp Das Web-Anwendungs Projekt definiert zwei Spring MVC-Controller-Implementierungen welche unter Verwendung des WeatherService definiert werden, sowie die DAOs aus "Simple Persist". "Simple Web" verfügt über eine direkte Abhängigkeit zu "Simple Weather" sowie eine transitive Abhängigkeit zu "Simple Model". simple-command Dieses Modul enthält eine einfache Befehlszeilen-Anwendung welche dazu eingesetzt werden kann, die Yahoo! Wetter-Dienste abzufragen. Das Modul enthält eine statisch definierte Klasse main() welche mit WeatherService aus "Simple Weather" sowie der DAOs welche in "Simple Persist" definiert wurden arbeitet. "Simple Command" hat eine direkte Abhängigkeit zu "Simple 115
  • 135. Multi-module Enterprise Project Weather" sowie "Simple Persist"; es besteht eine transitive Abhängigkeit zu "Simple Model". Hiermit ist das Beispiel in diesem Kapitel vorgestellt; es ist einfach genug um es in einem Lehrbuch einzuführen, und doch komplex genug um eine Aufspaltung in fünf Untermodule zu rechtfertigen. Während unser künstliches Beispiel ein Modell-Projekt von fünf Klassen, eine Persistenz-Bibliothek mit zwei Service-Klassen und eine Parsing-Bibliothek mit fünf oder sechs Klassen hat, kann ein tatsächliches "Reale-Welt-System" leicht ein Modell-Projekt mit hunderten von Klassen, mehrere Persistenz- und Service-Bibliotheken welche über mehrere Bereiche reichen, umfassen. Während wir versucht haben sicherzustellen, dass der Quellcode des Beispiels einfach genug ist, um diesen in einer Sitzung zu verstehen, haben wir uns ebenso bemüht ein modulares Beispiel zu erstellen. Es wäre nachvollziehbar, würden Sie nach der Betrachtung des Quellcodes, sich von Maven abwenden und den Eindruck mitnehmen, Maven ermuntere dazu Komplexität einzuführen, da das Beispiel lediglich fünf Klassen umfasst. Jedoch sollten Sie immer bedenken, dass wir keine Mühen scheuten, das einfache Beispiel anzureichern, um die Möglichkeiten zur Bearbeitung komplexer Fragestellungen beispielhaft aufzuzeigen und darzustellen, insbesondere die von Maven gegebenen Multi-Modul-Funktionalitäten. 7.1.3. Technologie des Beispiels Die in diesem Kapitel eingesetzten Technologien umfassen solche, welche zwar populär sind, aber nicht in direktem Zusammenhang mit dem Einsatz von Maven stehen. Die angedeuteten Technologien sind: das Spring Framework™ und Hibernate™. Das Spring Framework stellt einen 'Inversion of Control (IoC) Container' bereit, sowie eine Reihe von Frameworks welche auf die Vereinfachung der Interaktion mit verschiedenen J2EE-Bibliotheken abzielen. Mit dem Spring Framework als Fundament der Anwendungsentwicklung erhalten Sie Zugriff auf eine Reihe von hilfreichen Abstraktionen, welche Ihnen einen Großteil der mühseligen Fummelei im Umgang mit z.B. Persistenz Frameworks wie Hibernate oder iBatis™ oder auch Enterprise-APIs wie JDBC, JNDI und JMS nehmen. Das Spring Framework hat in den vergangenen Jahren stark an Popularität gewonnen, insbesondere als Alternative zu den schwergewichtigen Enterprise Standards aus 116
  • 136. Multi-module Enterprise Project dem Hause Sun Microsystems. Hibernate ist ein weit verbreitetes Object-Relational-Mapping- (ORM-) Framework, das eine Interaktion mit relationalen Datenbanken bereitstellt, als ob es sich um eine Sammlung von Java-Objekten handele. Dieses Beispiel konzentriert sich auf den Aufbau einer einfachen Web-Anwendung und eines Befehlszeilen-Programms, welche, aufbauend auf das Spring Framework und Hibernate, eine Reihe von wiederverwendbaren Komponenten bereitstellt, welche es erlauben die bestehenden Wetter-Daten in einer eingebettete Datenbank abzulegen (persistieren). Wir haben uns entschlossen diese Frameworks einzubinden, um Ihnen am Beispiel aufzuzeigen, wie man beim Einsatz von Maven diese Technologien einbinden kann. Obschon es in das Kapitel eingestreut kurze Einführungen zu diesen Technologien gibt, so ist es nicht das erklärte Ziel dieses Kapitels diese Technologien im Detail darzustellen. Bezüglich weiterführender Dokumentation dieser Frameworks verweisen wir: bezüglich Spring auf die Projekt Website http://www.springframework.org, bezüglich Hibernate auf den Projekt Website http://www.hibernate.org. Dieses Kapitel setzt als eingebettete Datenbank auf HSQLDB, weitere Informationen zu dieser Datenbank finden Sie auf der Projekt Website http://hsqldb.org. 7.2. Das "Simple Parent" Projekt - Top Level Das "Simple Parent" Hauptmodul hat ein POM welches auf fünf Untermodule verweist: (simple-command), (simple-model), (simple-weather), (simple-persist) und (simple-webapp). Das Top-Level POM (pom.xml-Datei) ist im Example 7.1, “Simple Parent POM Projekt” (Beispiel 7.1, "Simple Parent POM Projekt") dargestellt. Example 7.1. Simple Parent POM Projekt <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> 117
  • 137. Multi-module Enterprise Project <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 7 Simple Parent Project</name> <modules> <module>simple-command</module> <module>simple-model</module> <module>simple-weather</module> <module>simple-persist</module> <module>simple-webapp</module> </modules> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> Note Sollten Sie bereits mit Maven POMs vertraut sein, so werden Sie jetzt bemerken, dass dieses top level POM kein Element dependencyManagement definiert. Das Element dependencyManagement erlaubt es Ihnen, Abhängigkeitsversionen in einem einzigen top-level POM zu definieren und wird in Chapter 8, Optimirung und 118
  • 138. Multi-module Enterprise Project Überarbeitung der POMs (Kapitel 8: Optimieren und Refaktorieren von POMs) eingeführt. Bemerken Sie bitte die Ähnlichkeiten dieses Hauptmodul POMs zu jenem Hauptmodul POM des Projekts in Example 6.1, “simple-parent Projekt POM” (Kapitel 6.1: "Simple Parent Projekt POM"). Der einzige, wirkliche Unterschied zwischen diesen beiden POMs ist die Liste der Untermodule. Wo das vorangegangene Beispiel nur zwei Untermodule aufführte, hat dieses Hauptmodul deren fünf. In den nächsten Abschnitten wird jedes dieser fünf Untermodule in einigen Details dargestellt. Da unsere Beispiel Java-Annotationen einsetzt, haben wir es so konfiguriert, dass der Compiler auf die Java 5 Virtual Machine (JVM) aufsetzt. 7.3. Das Simple Model" Modul - Das Objektmodell Das erste, was die meisten Enterprise Projekte benötigen, ist ein Objekt-Modell. Ein Objekt-Modell erfasst die Grundmenge aller Domain-Objekte eines Systems. Ein Banken-System könnten ein Objekt-Modell haben, welches aus einem Konto, Kunden- und Transaktions-Objekt besteht, ein System zum Erfassen und Verbreiten von Sportergebnisse besässe ein Mannschafts- und ein Spiel-Objekt. Was auch immer Ihr Projekt umfasst, die Chancen stehen gut, dass Sie die entsprechenden Konzepte Ihres Systems in einem Objekt-Modell erfasst haben. Eine gängige Praxis im Umgang mit Maven ist es, dieses Modul separate auszulagern um es fortan als häufig referenziertes Modul weiterzuführen. In unserem System erfassen wir jede Abfrage des Yahoo! Wetter-Dienstes mit einem Objekt, das Objekt weather, welches weitere vier Objekte referenziert: Windrichtung, Chill und Geschwindigkeit werden in einem Objekt Wind erfasst. Standortdaten einschließlich der Postleitzahl, Stadt, Region und Land sind in einer Klasse Location zusammengefasst. Die atmosphärischen Bedingungen wie die Feuchtigkeit, maximale Sichtbarkeit, Luftdruck, und ob der Druck steigt oder fällt, wird in einer Klasse Atmosphere gehalten. Eine textuelle Beschreibung der 119
  • 139. Multi-module Enterprise Project Zustände, der Temperatur, und die Daten der Beobachtung werden einer Klasse Condition zusammengefasst. Figure 7.2. Einfaches Objektmodell für Wetter-Daten Die pom.xml-Datei für dieses schlichte Modell hat nur eine Abhängigkeitsdefinition welche der Erläuterung bedarf: Unser Objekt-Modell wird mit Hibernate Annotations realisiert. Wir verwenden Annotations um die Modell-Objekte auf Tabellen einer relationalen Datenbank abzubilden. Die Abhängigkeit besteht zu org.hibernate: hibernate-annotaions: 3.3.0.ga. Werfen Sie einen Blick in diese pom.xml sowie einige der kommenden Beispiele worin dies verdeutlicht wird: Example 7.2. pom.xml des Projektmoduls: simple-model <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> 120
  • 140. Multi-module Enterprise Project <version>1.0</version> </parent> <artifactId>simple-model</artifactId> <packaging>jar</packaging> <name>Simple Object Model</name> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> </dependencies> </project> Unter /src/main/java/de/sonatype/Maven/weather/model liegt Weather.java welche das annotierte Wetter-Objekt-Modell beinhaltet. Das Weather-Objekt ist ein einfaches Java Bean. Das bedeutet, es werden private Member Variablen gesetzt (id, location, condition, wind, amosphere und date) welche durch public Getter und Setter-Methoden offengelegt werden. Dabei wird folgende Regel umgesetzt: gibt es ein String-Property mit dem Namen "name" so wird eine public Getter-Methode ohne Argumente namens getName() gesetzt, sowie eine public Setter Methode mit einem Argument setName(String name); analog für andere Typen. Während wir hier die Getter und Setter-Methode für das id-Property zeigen, haben wir die Mehrheit der anderen Properties ausgelassen um an dieser Stelle einer Anzahl Bäume das Leben zu wahren. Example 7.3. Annotiertes Wetter Modell Objekt package org.sonatype.mavenbook.weather.model; import javax.persistence.*; import java.util.Date; @Entity @NamedQueries({ @NamedQuery(name="Weather.byLocation", 121
  • 141. Multi-module Enterprise Project query="from Weather w where w.location = :location") }) public class Weather { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; @ManyToOne(cascade=CascadeType.ALL) private Location location; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Condition condition; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Wind wind; @OneToOne(mappedBy="weather",cascade=CascadeType.ALL) private Atmosphere atmosphere; private Date date; public Weather() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // Alle weiteren getter/setter Methoden ausgelassen... } In der Klasse Weather, setzen wir auf Hibernate Annotationen, um dem "simple persist" Projekt Anleitung zu geben. Diese Annotationen werden von Hibernate benutzt, um ein Objekt auf eine Tabelle einer relationalen Datenbank abzubilden. Während eine ausführliche Einführung in die Hibernate Annotationen weit über den Rahmen dieses Kapitels hinausgeht, hier eine kurze Erklärung für Neugierige: Die Annotation @Entity bezeichnet diese Klasse als zu persistierende Einheit, die @Table Annotation haben wir ausgelassen, Hibernate wird daher den Klassennamen als Tabellennamen einsetzen und so das Objekt Weather auf eine Tabelle Weather abbilden. Die @NamedQueries Annotation definiert eine Abfrage, welche vom WeatherDAO aus dem "Simple Persist" Projekt verwendet wird. Die Abfragesprache, welche von der @NamedQuery Annotation benutzt wird ist Hibernate Query Language (HQL). Jede Variable wird mit Annotationen versehen welche den Typ, die Spalte sowie jegliche Beziehungen der Spalte zu anderen 122
  • 142. Multi-module Enterprise Project Spalten definiert: Id Das Property id wird mit @Id gekennzeichnet. Dies zeichnet das Property id als das Property aus, welches den Primärschlüssel bezüglich der Tabelle in der Datenbank trägt. Die Kennzeichnung @GeneratedValue bestimmt wie neue Primärschlüssel generiert werden. Im Falle von id, bestimmen wir den GenerationType IDENTITY, welcher auf die Verwendung des Datenbank eigenen Generierungsalgoryhtmus aufsetzt. Location Jede Instanz eines Wetter-Objekts (Weather) ist an eine Instanz eines Standort Objekts (Location) gebunden. Ein Standort Objekt repräsentiert eine Postleitzahl und die Kennzeichnung @ManyToOne stellt sicher, dass Wetter-Objekte, welche an den selben Standort gebunden sind auch auf dieselbe Instanz zeigen. Das Attribut cascade der Annotation @ManyToOne stellt sicher, dass wir bei jedem persistieren des Wetter Objekts auch das zugehörige Standort Objekt persistieren. Condition, Wind, Atmosphere Jedes dieser Objekte wird mit einer Kennzeichnung von @OneToOne und dem CascadeType ALL abgebildet. Dies bedeutet, dass jedes Mal, wenn wir ein Wetter-Objekt speichern, eine Zeile in der Tabelle Weather, aber auch in den Tabellen Condition, Wind und Atmosphere erzeugt wird. Date Das Property date ist nicht annotiert, das bedeutet, dass Hibernate in Bezug auf die Spalten alle Standardwerte benutzt um diese Zuordung zu definieren. Der Spaltenname wird als date gesetzt, und der Datentyp der Spalte auf den entsprechenden Typ um dem date-Objekt zu entsprechen. Note Sollten Sie ein Property in der Abbildung nicht berücksichtingen, so würden Sie dieses Property mit @Transient annotieren. 123
  • 143. Multi-module Enterprise Project Next, take a look at one of the secondary model objects, Condition, shown in Example 7.4, “'Condition' Modell Objekt des Projektes "simple-model"”. This class also resides in src/main/java/org/sonatype/mavenbook/weather/model. Als nächstes, schauen Sie sich das weiterführende Objekt Modell Condition an. Diese Klasse ist ebenfalls unter /src/main/java/de/sonatype/maven/weather/model abgelegt. Example 7.4. 'Condition' Modell Objekt des Projektes "simple-model" package org.sonatype.mavenbook.weather.model; import javax.persistence.*; @Entity public class Condition { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String text; private String code; private String temp; private String date; @OneToOne(cascade=CascadeType.ALL) @JoinColumn(name="weather_id", nullable=false) private Weather weather; public Condition() {} public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } // Alle weiteren getter/setter Methoden ausgelassen... } Die Klasse Condition entspricht der Klasse weather. Sie wird mit @Entity annotiert, und hat eine gleichartige Kennzeichnung des Properties id. Die Properties text, code, temp sowie date werden alle auf Standardwerten belassen, das Property weather ist mit der Kennzeichnung @OneToOne versehen sowie einer weiteren Annotation welche das entsprechende Objekt Weather mit dem Fremdschlüssel des Namens weather_id in Verbindung bringt. 124
  • 144. Multi-module Enterprise Project 7.4. Das "Simple Weather" Modul - Die Dienste Das nächste Modul welches wir betrachten werden, ist so etwas wie ein "Dienst". Das "Simple Weather" Modul ist das Modul, das alle für den Abruf und die Analyse der Daten vom Yahoo! Wetter Dienst notwendige Logik enthält. Obwohl das "Simple Weather" Modul aus drei Java-Klassen und einem JUnit-Test besteht, stellt es eine einzige Komponente WeatherService, dar. Dieser wird sowohl durch "Simple Web" der Webapplikation, und auch "Simple Command" konsumiert. Häufig enthalten Enterprise Projekte ein oder mehrere API-Module welche kritische Business-Logik oder Logik zur Interaktion mit externen Systemen beinhalten. Ein Banken System könnten ein Modul beinhalten, welches Daten eines externen Providers abruft und analysiert; ein System zur Anzeige von Sportergebnisse könnte einen XML-Datenstrom verarbeiten um in Echtzeit die Ergebnisse von Basketball oder Fußball darzustellen. In unserem Beispiel kapselt dieses Modul alle notwendigen Netzwerk-Aktivitäten sowie das XML-Parsing zur Interaktion mit dem Yahoo! Wetter Dienst. Andere Module können auf dieses Modul aufbauen, Sie rufen einfach die Methode retrieveForecast() der Klasse WeatherService auf. Diese nimmt als Argument eine Postleitzahl und liefert ein Weather-Objekt zurück. Example 7.5. POM des simple-weather Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Simple Weather API</name> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-model</artifactId> 125
  • 145. Multi-module Enterprise Project <version>1.0</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Das "Simple Weather" POM erweitert das darüberliegende (elterliche) POM, setzt das Packaging auf jar und fügt die folgenden Abhängigkeiten ein: org.sonatype.mavenbook.ch07:simple-model:1.0 "Simple-Weather" analysiert den Yahoo! Wetter Dienst (RSS) und verpackt dieses in ein Objekt Weather. Es verfügt über eine direkte Abhängigkeit zum "Simple Model" Modul. log4j:log4j:1.2.14 "Simple Weather" setzt auf die Bibliothek Log4J™ zum Ausgeben von Log-Nachrichten auf. dom4j:dom4j:1.6.1 and jaxen:jaxen:1.1.1 Diese Beide Abhängigkeiten sind notwendig um das vom Yahoo! Wetter Dienst zurückgegebene XML zu verarbeiten. 126
  • 146. Multi-module Enterprise Project org.apache.commons:commons-io:1.3.2 (scope=test) Diese Abhängigkeit mit Gültigkeitsbereich test wird von der Klasse YahooParserTest benötigt. Next is the WeatherService class, shown in Example 7.6, “Die Klasse WeatherService”. This class is going to look very similar to the WeatherService class from Example 6.3, “Die Klasse WeatherService”. Although the WeatherService is the same, there are some subtle differences in this chapter’s example. This version’s retrieveForecast() method returns a Weather object, and the formatting is going to be left to the applications that call WeatherService. The other major change is that the YahooRetriever and YahooParser are both bean properties of the WeatherService bean. Als nächstes betrachten wir die Klasse WeatherService (Example 7.6, “Die Klasse WeatherService”), diese Klasse ist sehr ähnlich zur Klasse WeatherService aus Example 6.3, “Die Klasse WeatherService” (Beispiel 6.3: Die WeatherService Klasse). Während die Klasse WeatherService die Gleiche ist, gibt es in diesem Kapitel einige subtile Unterschiede des Beispiels zu beachten. Diese Version der Methode retrieveForecast() liefert ein Objekt Weather zurück und überlässt die Formatierung der aufrufenden Anwendungen. Die andere große Änderung ist, dass die Klassen YahooRetriever und YahooParser beide Bean Properties des Beans WeatherService sind. Example 7.6. Die Klasse WeatherService package org.sonatype.mavenbook.weather; import java.io.InputStream; import org.sonatype.mavenbook.weather.model.Weather; public class WeatherService { private YahooRetriever yahooRetriever; private YahooParser yahooParser; public WeatherService() {} public Weather retrieveForecast(String zip) throws Exception { // Daten holen InputStream dataIn = yahooRetriever.retrieve(zip); 127
  • 147. Multi-module Enterprise Project // Daten auswerten Weather weather = yahooParser.parse(zip, dataIn); return weather; } public YahooRetriever getYahooRetriever() { return yahooRetriever; } public void setYahooRetriever(YahooRetriever yahooRetriever) { this.yahooRetriever = yahooRetriever; } public YahooParser getYahooParser() { return yahooParser; } public void setYahooParser(YahooParser yahooParser) { this.yahooParser = yahooParser; } } Schlussendlich haben wir in diesem Projekt eine XML-Datei. Diese wird vom Spring Framework benutzt, um den so genannten ApplicationContext zu erstellen. Zunächst einige Erläuterungen: unsere beiden Anwendungen, die Web-Anwendung und das Befehlszeilen-Dienstprogramm, müssen mit der Klasse WeatherService interagieren. Sie tun dies indem sie aus dem Spring ApplicationContext eine Instanz der Klasse mit dem Namen WeatherService holen. Unsere Web-Applikation, verwendet einen Spring MVC-Controller welcher einer Instanz des WeatherService zugeordnet ist, und unsere Dienstprogramm lädt die Klasse WeatherService von einem Spring ApplicationContext in einer statischen Methode main(). Um die Wiederverwendbarkeit zu fördern, haben wir unter /src/main/resources eine Datei applicationContext-weather.xml beigefügt, welche im Klassenpfad zur Verfügung steht. Module, welche eine Abhängigkeit zum "Simple Weather" Modul haben, können diesen Context mit dem ClasspathXmlApplicationContext des Spring Framework laden. Hierauf können Sie auf eine benannte Instanz der Klasse WeatherService namens weatherService zugreifen. 128
  • 148. Multi-module Enterprise Project Example 7.7. Spring Application Context fdes simple-weather Moduls <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd default-lazy-init="true"> <bean id="weatherService" class="org.sonatype.mavenbook.weather.WeatherService"> <property name="yahooRetriever" ref="yahooRetriever"/> <property name="yahooParser" ref="yahooParser"/> </bean> <bean id="yahooRetriever" class="org.sonatype.mavenbook.weather.YahooRetriever"/> <bean id="yahooParser" class="org.sonatype.mavenbook.weather.YahooParser"/> </beans> Diese Konfiguration definiert drei Beans: yahooParser, yahooRetriever und weatherService. Das Bean weatherService ist eine Instanz von WeatherService, und das XML-Dokument belegt die Properties yahooParser und yahooRetriever mit Verweisen auf benannten Instanzen der entsprechenden Klassen. Sehen Sie in der Datei applicationContext-weather.xml die Definition der Architektur eines Teilsystems in diesem Multi-Modul-Projekt. Projekte wie "Simple Web" oder "Simple Command" können diesen Context referenzieren, und somit eine Instanz des WeatherService abrufen welcher bereits in Bezug zu Instanzen von YahooRetriever und YahooParser steht. 7.5. Das "Simple Persist" Modul - Die Datenabstraktion Dieses Modul definiert zwei sehr einfache Daten Zugriffsobjekte (Data Access Objects: DAO). Ein DAO ist ein Objekt, das eine Schnittstelle für die Persistenz bereitstellt. 129
  • 149. Multi-module Enterprise Project Note Warum Persistenz und nicht Speicherung? Persistenz ist der allgemeine Begriff, während Speicherung sich hinlänglich auf ein lokales Medium bezieht, was in keinem Fall gegeben sein muss. In einer Anwendung welche auf Object-Relational-Mapping (ORM) Werkzeuge wie Hibernate zur Abbildung aufbaut, werden in der Regel DAOs um die zu persistierenden Objekte erstellt. In diesem Projekt definieren wir zwei DAO-Objekte: WeatherDAO und LocationDAO. Die Klasse WeatherDAO erlaubt es uns, Wetter-Objekte in einer Datenbank abzuspeichern bzw. Wetter-Objekte anhand eines Schlüssels (weatherId) oder auch zu einem bestimmten Standort abzurufen. Das LocationDAO besitzt eine Methode, welche es uns erlaubt zu einer gegebenen Postleitzahl das entspreche Standort-Objekt zu holen. Lassen Sie uns einen Blick auf die "Simple Persist" POM werfen: Example 7.8. POM des "simple-persist" Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-persist</artifactId> <packaging>jar</packaging> <name>Simple Persistence API</name> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-model</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> 130
  • 150. Multi-module Enterprise Project <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> </dependencies> </project> Diese POM-Datei referenziert "Simple Parent" als darüberliegendes, elterliches POM, sowie eine Anzahl Abhängigkeiten. Im Rahmen des "Simple Persist" Moduls sind diese: org.sonatype.mavenbook.ch07:simple-model:1.0 Genau wie bereits im "Simple Weather" Modul, wird hier eine Abhängigkeit zum grundlegenden Objekt Modell wie es in "Simple Model" definiert ist, beschrieben. org.hibernate:hibernate:3.2.5.ga Wir definieren eine Abhängigkeit zu Hibernate™ Version 3.2.5.ga, aber wie Sie richtig bemerkt haben, wird eine (transitive) Abhängigkeit von Hibernate ausgeschlossen. Wir tun dies, da die Abhängigkeit javax.transaction:javax 131
  • 151. Multi-module Enterprise Project nicht im öffentlichen Maven-Repository verfügbar ist. Diese Abhängigkeit ist eine dieser Abhängigkeiten zu Sun Microsystems Quellcode, welche es noch nicht in die freien zentralen Maven-Repositories geschafft hat. Um zu verhindern, dass ein immer eine ärgerlicher Meldung uns auf den notwendigen Download dieser (unfreien) Abhängigkeit hinweist, schliessen wir diese einfach aus, und fügen eine neue Abhängigkeit hinzu ... javax.servlet:servlet-api:2.4 Da das Projekt ein Servlet beinhaltet, müssen wir das Servlet API version 2.4 einschliessen. org.springframework:spring:2.0.7 Dies umfasst das gesamte Spring Framework als Abhängigkeit . Note Es ist generell besser, eine Abhängigkeit zu den Komponenten von Spring zu definieren, zu denen diese tatsächlich besteht. Das Spring Framework-Projekt kommt Ihnen hier sehr entgegen, indem es genau definierende Artefakte wie z.B. spring-hibernate3 erschaffen hat.) Warum sich von Spring abhängig machen? Bei der Integration von Hibernate, erlaubt uns der Einsatz von Spring Helfer-Klassen wie die Klasse HibernateDaoSupport auszunützen. Um Ihnen ein Beispiel dafür zu geben, was unter Einsatz des HibernateDaoSupport möglich ist, werfen Sie einen Blick auf den Quellcode für das WeatherDAO: Example 7.9. WeatherDAO Klasse des "simple persist" Moduls package org.sonatype.mavenbook.weather.persist; import java.util.ArrayList; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.sonatype.mavenbook.weather.model.Location; 132
  • 152. Multi-module Enterprise Project import org.sonatype.mavenbook.weather.model.Weather; public class WeatherDAO extends HibernateDaoSupport# { public WeatherDAO() {} public void save(Weather weather) {# getHibernateTemplate().save( weather ); } public Weather load(Integer id) {# return (Weather) getHibernateTemplate().load( Weather.class, id); } @SuppressWarnings("unchecked") public List<Weather> recentForLocation( final Location location ) { return (List<Weather>) getHibernateTemplate().execute( new HibernateCallback() {# public Object doInHibernate(Session session) { Query query = getSession().getNamedQuery("Weather.byLocation"); query.setParameter("location", location); return new ArrayList<Weather>( query.list() ); } }); } } Das ist alles! Nein wirklich, das ist die gesamte Klasse mit welcher Sie neue Zeilen einfügen, anhand von Primärschlüsseln Werte suchen, oder auch alle Zeilen in der Weather-Tabelle finden welche eine Referenz auf einen Ort setzen. Sie werden verstehen, dass wir an dieser Stelle nicht einfach das Buch schliessen können um die notwendigen fünfhundert Seiten Erklärung der Feinheiten des Hibernate Frameworks einzuschieben, aber was wir tun können ist, Ihnen ein paar kurze Erklärungen geben: ‚ Die Klasse leitet sich von HibernateDaoSupport ab. Das bedeutet, dass die Klasse mit einer Hibernate SessionFactory verbunden ist, welche zum Einsatz kommt Hibernate Session Objekte zu erstellen. In Hibernate wird jeder Aufruf über ein Session-Objekt ausgeführt. Eine Session vermittelt Zugang zu der zugrunde liegenden Datenbank und kümmert sich um die Verwaltung der Verbindung zur JDBC-Datenquelle. Die Erweiterung von HibernateDaoSupport bedeutet auch, dass wir Zugriff auf 133
  • 153. Multi-module Enterprise Project HibernateTemplates mittels getHibernateTemplate() haben. Um Ihnen ein Beispiel davon zu geben, was mit HibernateTemplates möglich ist ... ƒ ...die Methode save() nimmt sich einer Instanz eines Wetter Objekts an, und ruft die Methode save() eines HibernateTemplate auf. Das HibernateTemplate verwandelt den Aufruf in einfache Hibernate Operationen um, und wandelt jegliche Datenbank spezifische Exeptions in entsprechende Runtime-Exceptions um. In diesem Fall rufen wir save() auf, um einen Datensatz in der Datenbank, in der Tabelle Weather abzulegen. Alternativen zu diesem Aufruf wären update() sollten wir einen Eintrag aktualisieren, oder saveOrUpdate(), welches den Datensatz entweder anlegt oder aktualisiert, abhängig von der Anwesenheit eines Properties id des Objekts Weather, das nicht Null sein darf. „ Die Methode load() ist ein weiterer Einzeiler, welcher lediglich eine Methode einer Instanz des HibernateTemplates aufruft. Die Methode load() des HibernateTemplates hat zwei Parameter eine Klasse sowie eine serialisierbares Objekt. In diesem Fall entspricht das serialisierbare Objekt dem Wert der id des Objekts Weather welches geladen werden soll. … Diese letzte Methode recentForLocation() beruft sich auf ein NamedQuery das im Wetter-Objekt-Modell festgelegt wurde. Sollten Sie sich erinnern, wir haben im Wetter-Objekt-Modell eine Abfrage "Weather.byLocation" in der Form "from Weather w where w.location = :location" definiert. Wir Laden dieses NamedQuery mit einem Verweis auf ein Hibernate Session-Objekt in einem HibernateCallback der ausgeführt wird, indem die Methode execute() eines HibernateTemplate aufgerufen wird. In dieser Methode sehen Sie, wie wir die benannten Parameter mit dem Parameter des Aufrufs der Methode recentForLocation() bevölkern. Now is a good time for some clarification. HibernateDaoSupport and HibernateTemplate are classes from the Spring Framework. They were created by the Spring Framework to make writing Hibernate DAO objects painless. To support this DAO, we’ll need to do some configuration in the simple-persist Spring ApplicationContext definition. The XML document shown in Example 7.10, “Spring Application Context Definition für das "simple-persist" Modul” is stored in src/main/resources in a file named applicationContext-persist.xml. 134
  • 154. Multi-module Enterprise Project Es ist an der Zeit für einige Klarstellungen: HibernateDaoSupport und HibernateTemplate sind Klassen des Spring Framework™. Sie wurden von den Entwicklern des Spring Framework bereitgestellt, um das Bearbeiten von Hibernate DAO-Objekte schmerzloser zu gestalten. Um dieses DAO zu unterstützen, müssen wir noch einige wenige Einstellungen in der Definition des Spring ApplicationContext von "Simple Persist" vornehmen. Das folgende XML-Dokument ist unter /src/main/resources in einer Datei mit dem Namen applicationContext-persist.xml abgelegt. Example 7.10. Spring Application Context Definition für das "simple-persist" Modul <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" default-lazy-init="true"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="annotatedClasses"> <list> <value>org.sonatype.mavenbook.weather.model.Atmosphere</value> <value>org.sonatype.mavenbook.weather.model.Condition</value> <value>org.sonatype.mavenbook.weather.model.Location</value> <value>org.sonatype.mavenbook.weather.model.Weather</value> <value>org.sonatype.mavenbook.weather.model.Wind</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </prop> <prop key="hibernate.dialect"> org.hibernate.dialect.HSQLDialect </prop> <prop key="hibernate.connection.pool_size">0</prop> <prop key="hibernate.connection.driver_class"> org.hsqldb.jdbcDriver </prop> <prop key="hibernate.connection.url"> jdbc:hsqldb:data/weather;shutdown=true </prop> <prop key="hibernate.connection.username">sa</prop> 135
  • 155. Multi-module Enterprise Project <prop key="hibernate.connection.password"></prop> <prop key="hibernate.connection.autocommit">true</prop> <prop key="hibernate.jdbc.batch_size">0</prop> </props> </property> </bean> <bean id="locationDAO" class="org.sonatype.mavenbook.weather.persist.LocationDAO"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="weatherDAO" class="org.sonatype.mavenbook.weather.persist.WeatherDAO"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </beans> Dieser ApplicationContext erreicht eine Anzahl verschiedener Dinge: zunächst wird ein SessionFactory Bean bereitgestellt, ein Bean von welchem die DAOs die Hibernate Session Objekte beziehen. Dieses Bean ist eine Instanz des AnnotatedServiceFactoryBean und wird mit einer Anzahl annotatedClasses bereitgestellt. Hier sei bemerkt, dass die Liste der bereitgestellten annotierten Klassen die Liste der Klassen darstellt, welche in unserem Modul "Simple Model" definiert wurden. Als nächstes wird die sessionFactory entsprechend einer Reihe von Hibernate Konfigurationseigenschaften (hibernateProperties) konfiguriert. In diesem Beispiel definieren die HibernateProperties eine Reihe von Einstellungen: hibernate.dialect Diese Einstellung definiert, welcher SQL-Dialekt für unsere Datenbank generiert wird. Da wir als Datenbank HSQLDB einsetzen, ist der gewählte Datenbank Dialekt org.hibernate.dialect.HSQLDialect. Hibernate unterstützt die SQL-Dialekte aller wichtigen Datenbank Systeme wie Oracle, MySQL, PostgreSQL und SQL Server. hibernate.connection.* In diesem Beispiel werden die JDBC-Verbindungsparameter aus der Spring Konfiguration heraus gesetzt. Unsere Anwendungen sind so konfiguriert, dass sie gegen eine HSQLDB unter dem Verzeichnis ./data/weather arbeiten. In 136
  • 156. Multi-module Enterprise Project einer echten Enterprise Applikation ist es wahrscheinlicher, dass Sie die Datenbankkonfiguration mittles JNDI externalisieren und somit von Ihrem Quellcode trennen. Schließlich werden in der Definitionsdatei der Bean die beiden DAO-Objekte des "Simple Persist" Moduls erstellt und mit einer Referenz auf die eben erstellte sessionFactory Bean versehen. Genau wie im Spring Applikations- Context von "Simple Weather" definiert diese Datei applicationContext-persit.xml die Architektur eines Submoduls einer grösseren Anwendung. Sollten Sie mit einer grösseren Anzahl von Persistenzklassen arbeiten, bietet es sich unter Umständen an, diese in einem eigenen applicationContext ausserhalb Ihrer Anwendung zusammenzufassen. There’s one last piece of the puzzle in simple-persist. Later in this chapter, we’re going to see how we can use the Maven Hibernate3 plugin to generate our database schema from the annotated model objects. For this to work properly, the Maven Hibernate3 plugin needs to read the JDBC connection configuration parameters, the list of annotated classes, and other Hibernate configuration from a file named hibernate.cfg.xml in src/main/resources. The purpose of this file (which duplicates some of the configuration in applicationContext-persist.xml) is to allow us to leverage the Maven Hibernate3 plugin to generate Data Definition Language (DDL) from nothing more than our annotations. See Example 7.11, “hibernate.cfg.xml des "simple-persist" Moduls”. Als letztes Puzzleteil des "Simple Persist" Beispiels werden wir später im Kapitel erfahren, wie wir mit der Hilfe des Hibernate3 Maven Plugins aus den annotierten Modellobjekten unser Datenbankschema erzeugen können. Damit dies funktioniert, muss das Hibernate3 Maven Plugin die Parameter der JDBC-Verbindungskonfiguration, die Liste der kommentierten Klassen und andere Hibernate-Konfigurations Parameter aus einer Datei mit dem Namen hibernate.cfg.xml unter /src/main/resources auslesen können. Der Zweck dieser Datei (welche einige Konfigurationen der applicationContext-persist.xml dupliziert) ist es, das Hibernate3 Maven Plugin so zu nutzen, dass wir aus nichts weiter als unseren annotierten Klassen die gesamte DDL erstellen können. 137
  • 157. Multi-module Enterprise Project Example 7.11. hibernate.cfg.xml des "simple-persist" Moduls <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- SQL Dialekt --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Datenbank Verbindungsparamenter --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:data/weather</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <property name="connection.shutdown">true</property> <!-- JDBC connection pool (den gestellten pool benutzen) --> <property name="connection.pool_size">1</property> <!-- Aktivierung des automatischen Hibernate Session Context Management --> <property name="current_session_context_class">thread</property> <!-- Deaktivierung des second Level Cache --> <property name="cache.provider_class"> org.hibernate.cache.NoCacheProvider </property> <!-- Ausgabe allen SQL auf stdout --> <property name="show_sql">true</property> <!-- deaktivierung der Batch funktionalität von HSQLDB um Fehler korrekt weiterzugeben - <property name="jdbc.batch_size">0</property> <!-- Auflisten aller bestehender Property Dateien --> <mapping class="org.sonatype.mavenbook.weather.model.Atmosphere"/> <mapping class="org.sonatype.mavenbook.weather.model.Condition"/> <mapping class="org.sonatype.mavenbook.weather.model.Location"/> <mapping class="org.sonatype.mavenbook.weather.model.Weather"/> <mapping class="org.sonatype.mavenbook.weather.model.Wind"/> </session-factory> </hibernate-configuration> Die Inhalte von Example 7.10, “Spring Application Context Definition für das "simple-persist" Modul” (Beispiel 7.10: "'Simple Persist' Spring Application 138
  • 158. Multi-module Enterprise Project Context") und Example 7.1, “Simple Parent POM Projekt” (Beispiel 7.1: "Simple Parent POM Project" ) sind redundant. Während die Datei SpringApplicationContext.xml von der Web- sowie der Befehlszeilen-Anwendung benutzt wird, findet die Datei hibernate.cfg.xml nur Einsatz zur Unterstützung des Hibernate3 Maven Plugins. Später in diesem Kapitel werden wir sehen, wie man mittels der Datei hibernate.cfg.xml und dem Hibernate3 Maven Plugin ein Datenbank-Schema, basierend auf dem kommentierten Objektmodell wie dieses in "Simple Model" besteht, generieren kann. Es ist die Datei hibernate.cfg.xml welche die Eigenschaften der JDBC-Verbindung konfiguriert und die annotierten Modelklassen in form einer Aufzählung dem Hibernate3 Maven Plugin bereitstellt. 7.6. Das "Simple Web" Modul - Das Web-Interface Die Web-Anwendung wird in einem einfachen Web Applikations Projekt definiert. Dieses einfache Web Applikations Projekt definiert zwei Spring MVC Controller Objekte: WeatherController und HistoryController. Beide Controller werden auf Komponenten der "Simple Weather" und "Simple Persist" Module abgebildet. Der Spring Container wird in der zu dieser Applikation zugehörigen Datei web.xml konfiguriert. Diese referenziert die Datei applicationContext-weather.xml unter "Simple Weather" sowie die Datei applicationContext-persist.xml in "Simple Persist". Die Komponenten-Architektur von dieser einfachen Web-Anwendung ist in Figure 7.3, “Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist'” (Abbildung 7.3: "Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist' ") dargestellt. 139
  • 159. Multi-module Enterprise Project Figure 7.3. Abhängigkeiten des Spring MVC Controllers zu den Komponenten aus 'Simple Weather' sowie 'Simple Persist' Das POM des Moduls "Simple Webapp" wird unten aufgeführt: Example 7.12. POM fder simple-webapp <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>Simple Web Application</name> <dependencies> 140
  • 160. Multi-module Enterprise Project <dependency> # <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-persist</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> # <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <dependencies># <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> # <artifactId>hibernate3-maven-plugin</artifactId> <version>2.0</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> # </component> </components> </configuration> 141
  • 161. Multi-module Enterprise Project <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Mit dem fortschreiten des Buches werden Sie festellen, dass die Datei pom.xml an Länge gewinnt. In diesem POM werden wir vier Abhängigkeiten und zwei Plugins konfigurieren. Lassen Sie uns Schritt für Schritt das POM durchgehen und die für die Konfiguration wichtigen Punkte genauer erläutern: ‚ Das "Simple Web" Applikationsmodul definiert vier Abhängigkeiten: die Servlet-Spezifikation 2.4, die "Simple Weather" Dienste, die "Simple Persist" Applikations-Bibliothek sowie das gesamte Spring Framework 2.0.7. ƒ Das Jetty Maven Plugin könnte nicht einfacher in dieses Projekt einzufügen sein: wir fügen einfach ein Plugin-Element, welches die entsprechenden Koordinaten groupId und artifactId setzt, ein. Die Tatsache, dass dieses Plugin so trivial zu konfigurieren ist bedeutet, dass die Plugin-Entwickler einen guten Dienst getan haben. Es wurden sinnvolle Standardwerte gesetzt, welche wir in den meisten Fällen nicht übersteuern müssen. Sollten Sie dennoch die Standardeinstellungen übersteuern wollen/müssen, so tun Sie dies indem Sie ein Konfigurationselement mit den entsprechenden Werten einfügen. „ In unserer Build Konfiguration werden wir das Hibernate3 Maven Plugin so konfigurieren, dass dieses gegen eine eingebettete Instanz einer HSSQL Datenbank läuft. Damit das Hibernate3 Maven Plugin auf die Datenbank zugreifen kann, muss diese einen Verweis auf die JDBC-Treiber der HSSQL-Datenbank im Klassenpfad bekommen. Um diese Abhängigkeit eines Plugins bereitzustellen, erklären wir diese direkt im Plugin-Element. Hier referenzieren wir hsqldb:hsqldb:1.8.0.7™. Das Hibernate Plugin 142
  • 162. Multi-module Enterprise Project benötigt die JDBC-Treiber ebenfalls, um die Datenbank zu erstellen. Aus diesem Grund haben wir diese dort ebenfalls referenziert. … Es ist hier im Hibernate Maven Plugin ist, wo dieses POM anfängt interessant zu werden: Im nächsten Abschnitt werden wir das Goal hbm2ddl ausführen, um die HSQLDB Struktur zu erstellen. In dieser Datei pom.xml schliessen wir ebenfalls eine Referenz auf die Version 2.0 des Hibernate3 Maven Plugin von Codehouse MoJo ein. † Das Hibernate3 Maven Plugin kann auf verschiedenerlei Wegen an die Hibernate Mapping Informationen gelangen; abhängig vom jeweiligen Einsatzszenario des Hibernate Plugins. Sollten sie Hibernate Mapping XML(.hbm.xml)-Dateien einsetzen und hätten vor, Modellklassen mit Hilfe des Goals hbmjava zu generieren, so würden Sie Ihre Implementierung auf configuration stellen. Würden Sie jedoch Hiberante3 einsetzen um eine bestehende Datenbank zu re-engineeren, um daraus die entsprechenden .hbm.xml Dateien zu generieren und die Klassen dann daraus abzuleiten, so würden Sie die Implementierung jdbcconfiguration wählen. In unserem Fall benutzen wir einfach ein annotiertes Objekt Modell um eine Datenbank zu generieren. In anderen Worten, wir haben bereits unser Hibernate Mapping, jedoch keine bestehende Datenbank. In diesem Fall ist wählen wir annotationconfiguration. Das Hiberante3 Maven Plugin wird unter Section 7.7, “Aufrufen der Web-Anwendung” (Abschnitt 7.7: "Aufrufen der Web-Anwendung") näher beleuchtet. Note Ein häufiger auftretender Fehler ist die Verwendung der extensions-Konfiguration um Abhängigkeiten von Plugins zu definieren. Wir raten von dieser Praxis dringend ab, denn dies kann dazu führen, dass Ihr Klassenpfad -neben anderen unschönen Nebeneffekten- projektweit "verschmutzt" wird. Außerdem wird in der Version 2.1 genau diese Funktionalität stark überarbeitet werden, was bedeutet, dass Sie hier in jedem Fall noch einmal Hand anlegen müssten. Die einzige "normale" Nutzung für Extensions ist die, eine neue Paketdefinition zu erstellen. 143
  • 163. Multi-module Enterprise Project Nun werden wir uns den beiden Spring MVC Controllern und deren Konfiguration zuwenden. Beide dieser Controller beziehen sich auf Beans welche in "Simple Weather" bzw "Simple Persist" definiert wurden. Example 7.13. simple-webapp WeatherController package org.sonatype.mavenbook.web; import org.sonatype.mavenbook.weather.model.Weather; import org.sonatype.mavenbook.weather.persist.WeatherDAO; import org.sonatype.mavenbook.weather.WeatherService; import javax.servlet.http.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class WeatherController implements Controller { private WeatherService weatherService; private WeatherDAO weatherDAO; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String zip = request.getParameter("zip"); Weather weather = weatherService.retrieveForecast(zip); weatherDAO.save(weather); return new ModelAndView("weather", "weather", weather); } public WeatherService getWeatherService() { return weatherService; } public void setWeatherService(WeatherService weatherService) { this.weatherService = weatherService; } public WeatherDAO getWeatherDAO() { return weatherDAO; } public void setWeatherDAO(WeatherDAO weatherDAO) { this.weatherDAO = weatherDAO; } } Die Klasse WeatherController implementiert das Spring MVC Controller Interface, welches die Existenz einer Methode handleRequest() in der oben angegebenen 144
  • 164. Multi-module Enterprise Project Form vorschreibt. Wenn Sie das Innenleben dieser Methode betrachten, so werden Sie feststellen, dass diese die Methode retrieveForecast() der Instanzvariablen weatherService aufruft. Anders als im vorgehenden Beispiel in welchem die Klasse WeatherService durch ein Servlet instanziert wurde, ist weatherController ein Bean, mit einem weatherService Property. Es liegt in der Verantwortung des Spring IoC Containers die Verdrahtung der Komponennte weatherService bereitzustellen. Darüber hinaus werden Sie bemerkt haben, dass wir in dieser Implementierung auf den weatherFormatter verzichten, an dessen Statt geben wir das Objekt Weather, welches von retrieveForecast() zurückgegeben wird an den Konstruktor von ModelAndView. Die Klasse ModelAndView werden wir benutzen um Velocity Templates darzustellen, welche eine Referenz zu einer Variablen ${weather} halten. Das zugehörige Template weather.vm liegt unter /src/main/webapp/WEB-INF/vm abgelegt und wird in ??? (Beispiel 7.14) ausgeführt. In the WeatherController, before we render the output of the forecast, we pass the Weather object returned by the WeatherService to the save() method on WeatherDAO. Here we are saving this Weather object—using Hibernate—to an HSQLDB database. Later, in HistoryController, we will see how we can retrieve a history of weather forecasts that were saved by the WeatherController. Innerhalb des WeatherController, geben wir das vom WeatherService zurückgegebene Objekt Weather an die Methode save() des WeatherDAO. Dort wird das Objekt Weather mit Hilfe von Hibernate in der HSQLDB abgespeichert. Später im HistoryController werden wir sehen wie man die Vergangenheit bezüglich einer Wettervorhersage welche von HSQLDB gespeichert wurde, zurückgeholen kann. Example 7.14. Die von WeatherController wiedergegebene Vorlage weather.vm <b>Derzeitiges Wetter von: ${weather.location.city}, ${weather.location.region}, ${weather.location.country}</b><br/> <ul> <li>Temperatur: ${weather.condition.temp}</li> <li>Wetterlage: ${weather.condition.text}</li> 145
  • 165. Multi-module Enterprise Project <li>Feuchtigkeit: ${weather.atmosphere.humidity}</li> <li>Wind Chill Faktor: ${weather.wind.chill}</li> <li>Datum: ${weather.date}</li> </ul> Die Syntax der Velocity Vorlagen ist einfach, Variablen werden durch vorangestelltes Dollarzeichen in geschwungenen Klammern markiert ${Variable}. Der Ausdruck zwischen den geschweiften Klammern verweist auf ein Property, oder ein Property eines Properties der Variable weather, welche durch den WeatherController an das Template weitergegeben wurde. Der HistoryController wird eingesetzt, um auf Prognosen aus der Vergangenheit zuzugreifen welche zuvor vom WeatherController angefragt wurden. Immer, wenn wir wieder eine Prognose mittels dem WeatherController abrufen, speichert der Controller das Objekt Weather mittels dem WeatherDAO in der Datenbank. Das WeatherDAO nutzt Hibernate um das Objekt Weather in eine Reihe von Zeilen zu zerlegen und in eine Anzahl in Relation stehende Datenbanktabellen abzuspeichern. Der HistoryController ist im Example 7.15, “simple-web HistoryController” (Beispiel 7.15: "Simple Web" HistoryController) ausgeführt. Example 7.15. simple-web HistoryController package org.sonatype.mavenbook.web; import java.util.*; import javax.servlet.http.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import org.sonatype.mavenbook.weather.model.*; import org.sonatype.mavenbook.weather.persist.*; public class HistoryController implements Controller { private LocationDAO locationDAO; private WeatherDAO weatherDAO; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { String zip = request.getParameter("zip"); Location location = locationDAO.findByZip(zip); List<Weather> weathers = weatherDAO.recentForLocation( location ); Map<String,Object> model = new HashMap<String,Object>(); 146
  • 166. Multi-module Enterprise Project model.put( "location", location ); model.put( "weathers", weathers ); return new ModelAndView("history", model); } public WeatherDAO getWeatherDAO() { return weatherDAO; } public void setWeatherDAO(WeatherDAO weatherDAO) { this.weatherDAO = weatherDAO; } public LocationDAO getLocationDAO() { return locationDAO; } public void setLocationDAO(LocationDAO locationDAO) { this.locationDAO = locationDAO; } } Der HistoryController ist mit zwei in "Simple Persist" definierten DAO-Objekten verdrahtet. Dabei sind die DAOs Bean-Properties des HistoryControllers: WeatherDAO und LocationDAO. Die Aufgabe des HistoryController ist, eine Liste von Weather Objekten entsprechend einer bestimmten Postleitzahl zurückzugeben. Wenn das WeatherDAO ein Objekt Weather in die Datenbank speichert, so legt es nicht eine Postleitzahl ab, sonder es speichert zusätzlich eine Referenz auf ein Objekt Location welches an das Objekt Weather gebunden ist, wie in "Simple Model" definiert. Um eine Liste von Weather-Objekte abzurufen, ruft der HistoryController zunächst das Objekt Lokation auf, welches der Postleitzahl entspricht. Dies wird durch den Aufruf der Methode findByZip() des LocationDAO bewerkstelligt. Nachdem ein Objekt Lokation abgerufen wurde, versucht der HistoryController die vorgängigen Weather Objekte mit Referenz auf das Objekt Lokation abzurufen. Das Ergebnis, welches in in der Form List<Weather> besteht, wird dann in eine HashMap von zwei Variablen der Velocity Vorlage history.vm wie im ??? (Beispiel 7.16: "history.vm dargestellt vom HistoryController") dargestellt, umgewandelt. 147
  • 167. Multi-module Enterprise Project Example 7.16. history.vm dargestellt vom HistoryController <b> Derzeitige Wetterlage von: ${location.city}, ${location.region}, ${location.country} </b> <br/> #foreach( $weather in $weathers ) <ul> <li>Temperatur: $weather.condition.temp</li> <li>Zustand: $weather.condition.text</li> <li>Feuchtigkeit: $weather.atmosphere.humidity</li> <li>Wind Chill Faktor: $weather.wind.chill</li> <li>Datum: $weather.date</li> </ul> #end Die Vorlage history.vm is unter /src/main/webapp/WEB-INF/vm abgelegt und verweist auf die Variable location um Angaben zum Standort/Lokation der Prognose, welche vom WeatherDAO zurückgegeben wurde auszugeben. Diese Vorlage unterliegt einer Velocity Kontrollstruktur, #foreach, welche sicherstellt, dass durch alle Elemente der Variablen weathers geschlauft wird. Jedes Element in weathers wird einer Variable weather zugeordnet und mit der Vorlage welche sich zwischen #foreach und #end befindet, verknüpft und ausgegeben. You've seen these Controller implementations, and you've seen that they reference other beans defined in simple-weather and simple-persist, they respond to HTTP requests, and they yield control to some mysterious templating system that knows how to render Velocity templates. All of this magic is configured in a Spring application context in src/main/webapp/WEB-INF/weather-servlet.xml. This XML configures the controllers and references other Spring-managed beans, it is loaded by a ServletContextListener which is also configured to load the applicationContext-weather.xml and applicationContext-persist.xml from the classpath. Let's take a closer look at the weather-servlet.xml shown in ???. Sie haben nun die Controller-Implementierung gesehen, und Sie haben gesehen, dass sich deren Implementierung auf weitere Beans welche in "Simple Weather" sowie "Simple Persist" definiert sind, stützt. Sie reagieren auf HTTP-Anfragen, 148
  • 168. Multi-module Enterprise Project und übernehmen die Kontrolle eines misteriösen Templating-Systems welches weiß, wie man Velocity-Vorlagen verarbeitet. Diese ganze Magie wurde im Spring applicationContext definiert, welcher sich unter /src/main/webapp/WEB-INF/weather-servlet.xml befindet. Diese XML-Konfiguration konfiguriert die Controller und definiert weitere Referenzen zu Beans welche von Spring verwaltet werden. die Datei weather-servlet.xml wird von einem ServletContextListener geladen, welcher zugleich so konfiguriert ist, dass er die Datei applicationContext-weather.xml sowie applicationContext-persist.xml vom Klassenpfad lädt. Lassen Sie uns die datei weather-servlet.xml einmal genauer anschauen. Example 7.17. Spring Controller Konfiguration weather-servlet.xml <beans> <bean id="weatherController" # class="org.sonatype.mavenbook.web.WeatherController"> <property name="weatherService" ref="weatherService"/> <property name="weatherDAO" ref="weatherDAO"/> </bean> <bean id="historyController" class="org.sonatype.mavenbook.web.HistoryController"> <property name="weatherDAO" ref="weatherDAO"/> <property name="locationDAO" ref="locationDAO"/> </bean> <!-- Sie können mehr als einen Controller definieren --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/weather.x"> # <ref bean="weatherController" /> </entry> <entry key="/history.x"> <ref bean="historyController" /> </entry> </map> </property> </bean> <bean id="velocityConfig" # class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> <property name="resourceLoaderPath" value="/WEB-INF/vm/"/> </bean> 149
  • 169. Multi-module Enterprise Project <bean id="viewResolver" # class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"> <property name="cache" value="true"/> <property name="prefix" value=""/> <property name="suffix" value=".vm"/> <property name="exposeSpringMacroHelpers" value="true"/> </bean> </beans> ‚ Die Datei weather-servlet.xml definiert die beiden Controller als Spring-verwaltete Beans. weatherController besitzt zwei Properties, welche Verweise auf weatherService und weatherDAO darstellen; historyController verweist auf die Beans weatherDAO und locationDAO. Wenn dieser ApplicationContext erstellt wird, so geschieht dies in einem Umfeld, welches den Zugang zu den ApplicationContexten definiert, sowohl zum "Simple Persist" wie auch zum "Simple Weather" Application Context. Im ??? Beispiel werden Sie sehen, wie man Spring konfigurieren kann, um mehrere Komponenten aus verschiedenen Spring Konfigurations-Dateien zusammenzuführen. ƒ Das Bean urlMapping definiert URL-Muster, welche den WeatherController und den HistoryController aufrufen. In diesem Beispiel benutzen wir SimpleUrlHandlerMapping und bilden /weather.x auf den WeatherController sowie /history.x auf den HistoryController ab. „ Da wir mit der Velocity-Templating-Engine arbeiten, sind wir gezwungen etliche Konfigurations-Optionen weiterzugeben. Hierzu weisen wir in der Datei velocityConfig.xml Velocity an, Vorlagen immer unter /WEB-INF/vm zu suchen. … Zuletzt wird der viewResolver mit der Klasse VelocityViewResolver konfiguriert. Es gibt bereits eine Reihe von Implementierungen des ViewResolvers in Spring; diese reichen von einem Standard-ViewResolver um JSTL/JSP-Seiten zu generieren, und reicht bis zu solchen welcher Freemaker Templates erstellen können. In unserem Beispiel werden wir die Velocity-Templating-Engine konfigurieren, sowie die Standard-Präfix und Suffixe setzen, welche automatisch an die Namen der Vorlagen angefügt werden und an ModelAndView weitergegeben werden. 150
  • 170. Multi-module Enterprise Project Finally, the simple-webapp project was a web.xml which provides the basic configuration for the web application. The web.xml file is shown in ???. Schließlich ist das "Simple Web" Projekt eine Webanwendung, und benötigt somit eine Datei web.xml als Basiskonfiguration der Anwendung. Die zugehörige Datei web.xml ist ??? (7.18: "web.xml von 'Simple Web' ") wiedergegeben. Example 7.18. web.xml von 'Simple Web' <web-app id="simple-webapp" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>Simple Web Application</display-name> <context-param> # <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext-weather.xml classpath:applicationContext-persist.xml </param-value> </context-param> <context-param> # <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <listener> # <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <listener> <listener-class> # org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> # <servlet-name>weather</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> # 151
  • 171. Multi-module Enterprise Project <servlet-name>weather</servlet-name> <url-pattern>*.x</url-pattern> </servlet-mapping> </web-app> ‚ Hier ein kleines Kusntstück das uns die Wiederverwendung des applicationContext-weather.xml sowie applicationContest.persist.xml innerhalb dieses Projekts erlaubt. Die contextConfigLocation wird vom ContextLoaderListener zur Erstellung eines ApplicationContext benutzt. Beim Erstellen des Weather Servlets wird die Datei weather-servlet.xml -wie in ??? (Beispiel 7.17: Spring Controller Konfiguration weather-servlet.xml) dargestellt - im Zusammenhang mit dem applicationContext welcher aus der contextConfigLocation geladen wird, ausgewertet. Auf diese Weise können Sie in einem anderen Projekt eine Anzahl Beans definieren und auf diese über den Klassenpfad zugreifen. Da sowohl "Simple Persist" als auch "Simple Weather" als JAR-Archiv unter /WEB-INF/lib verfügbar sein werden, müssen wir nur noch das Präfix classpath: benutzen, um diese Dateien zu referenzieren. (Eine andere Möglichkeit wäre gewesen, diese Dateien nach /WEB-INF zu kopieren, um sie dort mit einem Konstrukt wie etwa /WEB-INF/applicationContext-persist.xml zu referenzieren). ƒ Die log4jConfigLocation wird verwendet, um dem Log4JConfigListener mitzuteilen, wo dieser die Log4J Logging-Konfiguration finden kann. In diesem Beispiel weisen wir log4J an, unter /WEB-INF/log4j.properties zu suchen. „ Hiermit stellen wir sicher, dass das Log4J Subsystem konfiguriert ist, sobald die Web-Applikation gestartet wird. Es ist wichtig, den Log4JConfigListener vor dem ContextLoaderListener zu platzieren, da Sie anderenfalls unter Umständen wichtige Logging-Nachrichten verpassen, welche auf Probleme hinweisen können, die den ordungsgemässen Start der Anwendung verhindern. Insbesondere wenn Sie eine besonders große Anzahl Beans unter der Verwaltung von Spring haben, und eines der Beans beim Applikationsstart umfällt, wird Ihre Anwendung scheitern. Haben Sie dann bereits zuvor das Logging initialisiert, so haben Sei vielleicht die Möglichkeit 152
  • 172. Multi-module Enterprise Project eine Warnung oder eine Fehlermeldung aufzufangen; andernfalls werden Sie wohl keinerlei Ahnung haben warum Ihnen der Anwendungsstart misglückt ist. … Der ContextLoaderListener ist im Wesentlichen der Spring Container. Beim Start einer Anwendung erstellt dieser Listner einen ApplicationContext aus dem Wert des contextConfigLocation Parameters. † Wir definieren ein Spring MVC DispatcherServlet mit dem Namen weather. Hierdurch wird Spring unter /WEB-INF/weather-servlet.xml nach einer Spring Konfigurationsdatei suchen. Sie können so viele DispatcherServlets erstellen, wie Sie benötigen, ein DispatcherServlet kann eine oder mehrere Spring MVC-Controller-Implementierungen enthalten ‡ Alle in der Endung ".x" endenden Anfragen werden zum Weather Servlet geleitet. Wir möchten hier darauf hinweisen, dass die Endung ".x" keine tiefere Bedeutung hat, es ist eine willkürliche Auswahl welche Sie mit jedem beliebigen URL-Muster ersetzen können. 7.7. Aufrufen der Web-Anwendung Um die Web-Anwendung aufrufen zu können müssen Sei zunächst mittels dem Hibernate3 Maven Plugin die Datenbank erstellen. Um dies zu tun, geben Sie bitte das folgende Kommando auf der Befehlszeile im Verzeichnis des "Simple Web" Projekts ein : $ mvn hibernate3:hbm2ddl [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'hibernate3'. [INFO] org.codehaus.mojo: checking for updates from central [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Web Application [INFO] task-segment: [hibernate3:hbm2ddl] [INFO] ------------------------------------------------------------------------ [INFO] Preparing hibernate3:hbm2ddl ... 10:24:56,151 INFO org.hibernate.tool.hbm2ddl.SchemaExport - export complete [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ 153
  • 173. Multi-module Enterprise Project Nach Abschluss der Ausführung sollte ein Verzeichnis ${basedir}/data erstellt sein welches die HSQLDB Datenbank enthält. Um die Web Applikation nun zu starten geben Sie folgenden Befehl ein: $ mvn jetty:run [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'jetty'. [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Web Application [INFO] task-segment: [jetty:run] [INFO] ------------------------------------------------------------------------ [INFO] Preparing jetty:run ... [INFO] [jetty:run] [INFO] Configuring Jetty for project: Chapter 7 Simple Web Application ... [INFO] Context path = /simple-webapp [INFO] Tmp directory = determined at runtime [INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml [INFO] Web overrides = none [INFO] Starting jetty 6.1.7 ... 2008-03-25 10:28:03.639::INFO: jetty-6.1.7 ... 2147 INFO DispatcherServlet - FrameworkServlet 'weather': initialization completed in 1654 ms 2008-03-25 10:28:06.341::INFO: Started SelectChannelConnector@0.0.0.0:8080 [INFO] Started Jetty Server Nach dem Start von Jetty, können Sie mit dem Aufruf http://localhost:8080/simple-webapp/weather.x?zip=60202 das Wetter von Evanston, IL aufrufen. Mit der Änderung der Postleitzahl sollte es Ihnen möglich sein Ihren eigenen Wetterbericht abzurufen. Derzeitige Wetterlage von: Evanston, IL, US * Temperatur: 42 * Zustand: Partly Cloudy * Feuchtigkeit: 55 * Wind Chill Faktor: 34 * Datum: Tue Mar 25 10:29:45 CDT 2008 7.8. Das "Simple Command" Modul - Das Kommandozeilen Modul 154
  • 174. Multi-module Enterprise Project Das "Simple Command" Modul ist die Befehlszeilen-Version der "Simple Web" Anwendung. Es ist ein Dienstprogramm, das auf den selben Abhängigkeiten aufbaut: "Simple Weather" und "Simple Persist". Statt der Interaktion über einen Web-Browser, rufen Sie für diese Anwendung ein Dienstprogramm 'simple-command' von der Befehlszeile aus auf. Figure 7.4. Dienstprogramm aufbauend auf die Modue Simple-Weather und Simple-Persist Example 7.19. POM des Simple-Command Moduls <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> 155
  • 175. Multi-module Enterprise Project </parent> <artifactId>simple-command</artifactId> <packaging>jar</packaging> <name>Simple Command Line Tool</name> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </plugin> </plugins> 156
  • 176. Multi-module Enterprise Project </build> <dependencies> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-weather</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.sonatype.mavenbook.ch07</groupId> <artifactId>simple-persist</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </project> Dieses POM wird die Erstellung eines JAR files veranlassen, welches die Klasse org.sonatype.mavenbook.weather.Main beinhaltet. Diese Klasse wird in Example 7.20, “Klasse Main des Simple-Command Moduls” (Beispiel 7.20:"Klasse Main des Simple-Command Moduls" dargestellt. In dieser pom.xml-Datei werden wir das Maven Plugin Assembly um einen bereits mitgelieferten "Assembly Beschrieb" mit dem Namen "jar-with-dependencies" konfigurieren. Dieses Plugin wird sodann ein JAR- Archive erstellen, welches allen Bytecode beinhaltet welcher gebraucht wird, um das Projekt auszuführen, wie auch alle weitergehenden Abhängigkeiten. Example 7.20. Klasse Main des Simple-Command Moduls package org.sonatype.mavenbook.weather; import java.util.List; import org.apache.log4j.PropertyConfigurator; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; 157
  • 177. Multi-module Enterprise Project import org.sonatype.mavenbook.weather.model.Location; import org.sonatype.mavenbook.weather.model.Weather; import org.sonatype.mavenbook.weather.persist.LocationDAO; import org.sonatype.mavenbook.weather.persist.WeatherDAO; public class Main { private WeatherService weatherService; private WeatherDAO weatherDAO; private LocationDAO locationDAO; public static void main(String[] args) throws Exception { // Configure Log4J PropertyConfigurator.configure(Main.class.getClassLoader().getResource( "log4j.properties")); // Read the Zip Code from the Command-line (if none supplied, use 60202) String zipcode = "60202"; try { zipcode = args[0]; } catch (Exception e) { } // Read the Operation from the Command-line (if none supplied use weather) String operation = "weather"; try { operation = args[1]; } catch (Exception e) { } // Start the program Main main = new Main(zipcode); ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "classpath:applicationContext-weather.xml", "classpath:applicationContext-persist.xml" }); main.weatherService = (WeatherService) context.getBean("weatherService"); main.locationDAO = (LocationDAO) context.getBean("locationDAO"); main.weatherDAO = (WeatherDAO) context.getBean("weatherDAO"); if( operation.equals("weather")) { main.getWeather(); } else { main.getHistory(); } } private String zip; public Main(String zip) { this.zip = zip; } 158
  • 178. Multi-module Enterprise Project public void getWeather() throws Exception { Weather weather = weatherService.retrieveForecast(zip); weatherDAO.save( weather ); System.out.print(new WeatherFormatter().formatWeather(weather)); } public void getHistory() throws Exception { Location location = locationDAO.findByZip(zip); List<Weather> weathers = weatherDAO.recentForLocation(location); System.out.print(new WeatherFormatter().formatHistory(location, weathers)); } } Die Klasse Main hält eine Referenz auf die Klassen WeatherDAO, LocationDAO und WeatherService. Folgende Aufgaben werden durch deren statische Methode main() wahrgenommen: • Die Postleitzahl aus dem ersten Argument ([0]) der Befehlszeile auslesen • Das Kommando aus dem zweiten Argument ([1]) der Befehlszeile auslesen. Ist das Kommando "weather", so werden die aktuellen Wetterdaten vom Webservice angefragt. Ist das Kommando "history" so werden die Wetterdaten aus der lokalen Datenbank abgerufen. • Laden des Spring ApplicationContext unter Bezug auf zwei XML Dateien, welche von den Modulen "Simple Persist" und "Simple Weather" geladen wurden. • Instanzieren der Klasse Main • Befruchten von WeatherService, WeatherDAO sowie LocationDAO mit Beans aus dem Spring ApplicationContext • Aufrufen der entsprechenden Methode getWeather() oder getHistory() entsprechend dem übergebenen Kommando Als Teil der Web Applikation benutzen wir Spring VelocityViewResolver um eine Velocity Vorlage darzustellen. Für die befehlszeilenorientierte Applikation ist es notwendig eine einfache Klasse zu erstellen, welche unsere Wetterdaten mit 159
  • 179. Multi-module Enterprise Project Hilfe einer Velocity Vorlage darstellt. Example 7.21, “WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar” (Beispiel 7.21: "WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar") ist der Auszug der Klasse WeatherFormatter, einer Klasse mit zwei Methoden welche den Wetterbericht und die Wettergeschichte darstellen. Example 7.21. WeatherFormatter stellt Wetterdaten unter Einbezug einer Velocity Vorlage dar package org.sonatype.mavenbook.weather; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.util.List; import org.apache.log4j.Logger; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.sonatype.mavenbook.weather.model.Location; import org.sonatype.mavenbook.weather.model.Weather; public class WeatherFormatter { private static Logger log = Logger.getLogger(WeatherFormatter.class); public String formatWeather( Weather weather ) throws Exception { log.info( "Formatting Weather Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader(). getResourceAsStream("weather.vm")); VelocityContext context = new VelocityContext(); context.put("weather", weather ); StringWriter writer = new StringWriter(); Velocity.evaluate(context, writer, "", reader); return writer.toString(); } public String formatHistory( Location location, List<Weather> weathers ) throws Exception { log.info( "Formatting History Data" ); Reader reader = new InputStreamReader( getClass().getClassLoader(). getResourceAsStream("history.vm")); VelocityContext context = new VelocityContext(); context.put("location", location ); context.put("weathers", weathers ); StringWriter writer = new StringWriter(); 160
  • 180. Multi-module Enterprise Project Velocity.evaluate(context, writer, "", reader); return writer.toString(); } } Die Vorlage weather.vm gibt die zugehörige Stadt, den Staat, die Region sowie die aktuelle Temperatur aus. Die Vorlage history.vm gibt die Örtlichkeit aus, und iteriert dann über alle gespeicherten Wetterwerte der lokalen Datenbank. Diese beiden Vorlagen befinden sich unter ${basedir}/src/main/resource. Example 7.22. Die velocity Vorlage weather.vm **************************************** Current Weather Conditions for: ${weather.location.city}, ${weather.location.region}, ${weather.location.country} **************************************** * Temperature: ${weather.condition.temp} * Condition: ${weather.condition.text} * Humidity: ${weather.atmosphere.humidity} * Wind Chill: ${weather.wind.chill} * Date: ${weather.date} Example 7.23. Die Velocity Vorlage history.vm Weather History for: ${location.city}, ${location.region}, ${location.country} #foreach( $weather in $weathers ) **************************************** * Temperature: $weather.condition.temp * Condition: $weather.condition.text * Humidity: $weather.atmosphere.humidity * Wind Chill: $weather.wind.chill * Date: $weather.date #end 161
  • 181. Multi-module Enterprise Project 7.9. Aufrufen der Kommandozeilen-Anwendung Das Projekt Modul simple-command ist konfiguriert, ein einziges JAR Archiv, welches den Bytecode des Projektes sowie aller Bytecode der Abhängigkeiten enthält, zu erstellen. Um diese Baueinheit (Assembly) zu erzeugen, rufen Sie das Goal assembly des Maven Assembly Plugin von innerhalb des Verzeichnisses des Projektes "Simple Command" auf: $ mvn assembly:assembly [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Command Line Tool [INFO] task-segment: [assembly:assembly] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [surefire:test] ... [INFO] [jar:jar] [INFO] Building jar: .../simple-parent/simple-command/target/simple-command.jar [INFO] [assembly:assembly] [INFO] Processing DependencySet (output=) [INFO] Expanding: .../.m2/repository/.../simple-weather-1-SNAPSHOT.jar into /tmp/archived-file-set.93251505.tmp [INFO] Expanding: .../.m2/repository/.../simple-model-1-SNAPSHOT.jar into /tmp/archived-file-set.2012480870.tm [INFO] Expanding: .../.m2/repository/../hibernate-3.2.5.ga.jar into /tmp/archived-file-set.1296516202.tm ... skipping 25 lines of dependency unpacking ... [INFO] Expanding: .../.m2/repository/.../velocity-1.5.jar into /tmp/archived-file-set.379482 [INFO] Expanding: .../.m2/repository/.../commons-lang-2.1.jar into /tmp/archived-file-set.1329200163.tm [INFO] Expanding: .../.m2/repository/.../oro-2.0.8.jar into /tmp/archived-file-set.199315532 [INFO] Building jar: .../simple-parent/simple-command/target/simple-command-jar-with-depende Der Build schreitet durch die Lebenszyklusphasen Kompilieren des Bytecodes, Aufrufen der Tests und schliesslich Erstellen des JAR-Archives. Zuletzt erstellt das Goal assembly:assembly ein einziges JAR Archiv inklusive aller Abhängigkeiten, indem es allen abhängigen Bytecode in temporäre Verzeichnisse entpackt um 162
  • 182. Multi-module Enterprise Project diesen schliesslich in einem einzigen JAR Archiv zusammenzufassen und im Zielverzeichnis /target unter dem Namen simple-command-jar-with-dependencies.jar abzulegen. Dieses "über" JAR Archiv kommt auf stolze 15 MB. Vor Sie nun das kommandozeilenorientierte Programm aufrufen, müssen Sie noch das Goal hbm2ddl des Maven Hibernate3 Plugin aufrufen, um die HSQL Datenbank zu erstellen. Sie tun dies, indem Sie den folgenden Befehl innerhalb des Verzeichnisses von "Simple Command" absetzen: $ mvn hibernate3:hbm2ddl [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'hibernate3'. [INFO] org.codehaus.mojo: checking for updates from central [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 7 Simple Command Line Tool [INFO] task-segment: [hibernate3:hbm2ddl] [INFO] ------------------------------------------------------------------------ [INFO] Preparing hibernate3:hbm2ddl ... 10:24:56,151 INFO org.hibernate.tool.hbm2ddl.SchemaExport - export complete [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ Nach dem Aufruf sollten Sie innerhalb des "Simple Command" Verzeichnisses ein Verzeichnis /data vorfinden. Dieses Verzeichnis beinhaltet die HSQL Datenbank. Um nun das befehlszeilenorientierte Programm aufzurufen, setzen Sie den folgenden Befehl von innerhalb des "Simple Command" Verzeichnisses ab: $ java -cp target/simple-command-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 60202 2321 INFO YahooRetriever - Retrieving Weather Data 2489 INFO YahooParser - Creating XML Reader 2581 INFO YahooParser - Parsing XML Response 2875 INFO WeatherFormatter - Formatting Weather Data **************************************** Current Weather Conditions for: Evanston, IL, US **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 163
  • 183. Multi-module Enterprise Project * Date: Wed Aug 06 09:35:30 CDT 2008 Um eine Abfrage der vergangenen Werte abzusetzen, geben Sie den folgenden Befehl ein: $ java -cp target/simple-command-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 60202 history 2470 INFO WeatherFormatter - Formatting History Data Weather History for: Evanston, IL, US **************************************** * Temperature: 39 * Condition: Heavy Rain * Humidity: 93 * Wind Chill: 36 * Date: 2007-12-02 13:45:27.187 **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 * Date: 2008-08-06 09:24:11.725 **************************************** * Temperature: 75 * Condition: Partly Cloudy * Humidity: 64 * Wind Chill: 75 * Date: 2008-08-06 09:27:28.475 7.10. Fazit Nun, wir haben viel Zeit damit verbracht, uns Themen anzunehmen, welche nicht im Zusammenhang mit Maven stehen. Wir haben dies getan, um ein vollständiges und aussagekräftiges Beispielprojekt mit Bezug zur realen Welt zu implementieren. Wir haben uns keine schnellen Abkürzungen geleistet, um Ihnen ein hochglanzpoliertes fix fertiges Produkt zu liefern. Auch war unser Ziel nicht, Sie mit einer Ruby on Rails artigen Zauberei zur Annahme zu führen, dass Sie nun in wenigen Minuten eine fix-fertige Java-Enterprise-Anwendung hinstellen könnten. - Hiervon gibt es bereits zu viele Beispiel im Markt, zu viele Menschen welche Ihnen das einfachste, umfassenste Framework zu verkaufen versuchen, welches Ihnen erlaubt mit Null Investition von Zeit und Aufmerksamkeit 164
  • 184. Multi-module Enterprise Project weiterzukommen. Unser Ziel diese Kapitels war es, Ihnen ein umfassendes Bild zu vermitteln: das gesamte Ökosystem eines Multi-Modul Projekt Builds. Was wir bieten ist Maven im Rahmen einer Anwendung einer Art ähnlich deren, welche Sie in freier Wildbahn antreffen, darzustellen. Es ist nicht unsere Art Ihnen eine Art Fast-Food anzubieten: 10 Minuten Bildschirm Show, etwas Schlamm in die Richtung Apache Ant geworfen und Sie davon überzeugt haben Maven einzusetzen! Sollten Sie sich nach dem Lesen dieses Kapitels fragen, was es mit Maven zu tun hat, so waren wir erfolgreich. Wir haben eine Reihe von komplex vernetzten Projekten, unter Einsatz von populären Frameworks erzeugt, und wir haben diese unter Verwendung von deklarativen Methoden verbunden. Die Tatsache, dass mehr als 60% dieses Kapitel der Erklärung von Spring und Hibernate gewidment wurde, sollte Ihnen aufzeigen, dass Maven grösstenteils zurücksteht. Es funktionierte einfach. Es erlaubt uns, uns auf die Anwendung selbst zu konzentrieren, und nicht so sehr auf der Build-Prozess. Sie werden bemerken, dass haben wir ein Projekt erstellt haben, welches Daten in einer Datenbank speichert, ohne ein einziges SQL-Statement abzusetzen. Daraufhin haben Sie eine Web-Anwendung erstellt, ohne sich mit den Erstellung eines Build Skriptes inklusive 20 plus Abhängigkeiten herumzuschlagen um am Ende ein WAR-Archiv zu erstellen. Anstelle Zeit damit zu verbringen Maven zu diskutieren, verbrachten wir die Zeit damit Spring und Hiberante in den Grundzügen einzuführen. Sie können das eingeführte Skelett Projekt dieses Kapitels als Grundlage für Ihre eigene Projekte verwenden, und die Chancen stehen gut, dass, sollten Sie das tun, Sie sich in der Lage befinden über die Zeit hinweg mehr und mehr Module nach Bedarf anzulegen. So umfasst das ursprüngliche Projekt, auf welchem dieses Beispiel beruht zwei verschiedene Modell-Projekte, mit zwei Persistenzprojekten welche den Anschuss an völlig unterschiedliche Datenbanksysteme übernehmen, mehrere Web-Applikationen, sowie eine Java-Mobil Anwendung. Insgesamt stützt sich das reale Welt-System auf mindestens 15 miteinander verknüpften Module. Was wir hiermit sagen wollen ist, Sie haben soeben das komplexeste Beispiel welches in diesem Buch dargestellt wird gesehen, aber Sie sollten auch wissen, dass dieses Beispiel nur Kratzer an der Oberfläche dessen was mit Maven möglich ist, darstellt. 165
  • 185. Multi-module Enterprise Project 7.10.1. Programmierung gegen Interface-Projekte Dieses Kapitel stieg in die Tiefe der Multi-Modul-Projekte hinab und war komplexer als die einfachen Beispiele in Chapter 6, Ein multi-modulares Projekt (Kapitel 6: Ein Multi-Projekt-Modul), und dennoch war es eine starke Vereinfachung eines Reale- Welt-Projektes. In einem größeren Projekt finden Sie vielleicht sogar selbst ein System ähnlich Figure 7.5, “Programmierung gegen Interface-Projekte” (Abbildung 7.5) wieder. Figure 7.5. Programmierung gegen Interface-Projekte Wenn wir den Begriff Interface Projekt benutzen, so beziehen wir uns auf ein Maven Projekt, welches einzig aus Interfaces und Konstanten besteht. In Figure 7.5, “Programmierung gegen Interface-Projekte” (Abbildung 7.5 Programmierung gegen ein Interface Projekt), stellen die Einheiten persist-api 166
  • 186. Multi-module Enterprise Project sowie parse-api Interfaceprojekte dar. Sollten big-command sowie big-webapp gegen diese Interface Projekte programmiert werden, ist es ein leichtes zu einem späteren Zeitpunkt die Implementierung der Persistenz gegen eine andere auszutauschen. Auf dieser Abbildung werden zwei Implementierungen dargestellt: eine welche die Persistenz aufbauend auf einen XML-Store implementiert, sowie einer weiteren auf der Basis einer relationalen Datenbank. Bei der Anwendung der in diesem Kapitel vorgestellten Konzepte ist es einfach zu sehen, wie Sie, getrieben durch einen an das Programm übergebenen Wert, eine andere Spring ApplicationContext XML Datei übergeben, um damit die unterliegende Persistenzimplementierung auszutauschen. Wie bereits im OO-Design der Applikation, ist oftmals sinnvoll das Interface einer API von der Implementierung einer API in seperate Maven Projekte aufzutrennen. 167
  • 187. Chapter 8. Optimirung und Überarbeitung der POMs 8.1. Einführung In Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt"), zeigten wir auf, wie die verschiedenen Teile eines Maven Builds zusammenkommen, um eine voll funktionsfähige multi modularre Anwendung zu erstellen. Während das Beispiel des Kapitels eine wirklichkeitsnahe Anwendung wiederspiegelt - eine Anwendung mit Datenbankintegration, einem Web-Service, und sogar zwei Benutzer Schnittstellen: eine in Form einer Web-Anwendung, und eine als Dienstprogramm; so bleibt das Beispiel-Projekt des Kapitels letztendlich ein konstruiertes. Um die Komplexität eines realen Projektes abzubilden bräuchte es ein Buch von weit grösserem Umfang als das, das Sie gerade lesen. Anwendungen des täglichen Lebens entwickeln sich im Laufe der Jahre und werden oftmals von großen, unterschiedlichen Gruppen von Entwicklern gewartet, welche jeweils ganz verschiedene Schwerpunkte setzen. In einem solchen Projekt, müssen Sie häufig Entscheidungen und Designs gegeneinander abwägen, welche jemand anderes getätigt hat. In diesem Kapitel treten wir nun einen Schritt zurück von den Beispielen, aus Part I, “Maven by Example” (Teil I: Maven by Example), und stellen uns die Frage, ob es irgendwelche Optimierungen gibt, welche jetzt, wo wir mehr über Maven wissen, Sinn machen. Maven ist ein vielfältiges Werkzeug, welches so einfach oder auch komplex sein kann, wie Sie es gerade benötigen. Aus diesem Grund gibt es oft viele verschiedene Möglichkeiten, um die gleiche Aufgabe zu bewerkstelligen, und oft gibt es keinen einzigen "richtigen" Weg bei der Konfiguration Ihres Maven-Projektes. Bitte verstehen Sie den letzten Satz nicht falsch. Versuchen Sie nun nicht mit Maven etwas zu lösen, für das Maven nicht konzipiert wurde! Auch wenn Maven eine Vielfalt verschiedener Ansätze unterstützt, gibt es natürlich den "Maven Way", und Sie werden mit Maven produktiver sein wenn Sie diesen einschlagen. Das Ziel diesen Kapitels ist es, einige der Optimierungen weiterzugeben, welche Sie auch 168
  • 188. Optimirung und Überarbeitung der POMs auf ein bestehendes Maven-Projekt anwenden können. Warum haben wir nicht gleich ein optimiertes POM eingeführt, warum der Umweg? POMs pädagogisch sinnvoll zu gestalten, und POMs in der Praxis effizient zu gestalten, sind zwei sehr unterschiedliche Anforderungen. Zwar ist es sicherlich sehr viel einfacher, eine bestimmte Einstellung in Ihrem Profil in der ~/.m2/settings.xml zu definieren, als deren Einbindung in der pom.xml Datei, aber wenn man ein Buch schreibt damit dieses gelesen wird, geht es auch meist darum das Tempo richtig zu wählen und dafür zu sorgen, dass nicht Konzepte eingeführt werden, bevor Sie dazu bereit sind. In Part I, “Maven by Example” (Teil I: Maven by Example), haben wir uns grosse Mühe gegeben, den Leser nicht mit zu viel Information zu überlasten, und haben darum einige grundlegende Konzepte wie das des Elements dependencyManagement bewusst übersprungen. Dieses Element wird nun in diesem Kapitel eingeführt. Im Part I, “Maven by Example” (Teil I: "Maven by Example") dieses Buches gibt es Beispiele, in welchen die Autoren eine Abkürzung gewählt haben, oder über ein wichtiges Detail grosszügig hinweggeschritten sind, einfach um Sie zügig zu den Kernpunkten des Kapitels weiterzuleiten. Sie konnten lernen wie man ein Maven Projekt aufsetzt, kompiliert und installiert, ohne dass Sie durch hunderte Seiten Detailspezifikation waten mussten, welche jeden letzten Schalter erklärte, der Ihnen zur Verfügung gestanden hätte. Wir haben diesen Weg bewusst gewählt, da es uns wichtig schien, den neuen Maven Benutzer schnell zu einem Ergebnis zu führen, statt Ihn auf einem langen mäandernden Weg zu begleiten, welcher uns durch eine schier endlos erscheinende Weite führt. Sobald Sie damit begonnen haben, selbst regelmässig Maven einzusetzen, sollten Sie wissen, wie man eigene Projekte und POMs analysiert. In diesem Kapitel treten wir nun einen Schritt zurück, und werfen einen Blick auf das was wir zum Abschlus von Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt") tatsächlich bekommen haben. 8.2. POM Bereinigung Die Optimierung eines multi-modularen POMs wird am besten in mehreren Phasen durchgeführt, denn es gilt sich auf unterschiedliche Bereiche zu konzentrieren. Im 169
  • 189. Optimirung und Überarbeitung der POMs Normalfall suchen wir nach Wiederholungen innerhalb eines POM und über dessen Geschwister-POMs hinweg. Wenn Sie am Anfang stehen, oder wenn sich ein Projekt noch immer sehr schnell entwickelt, es ist akzeptabel, einige Abhängigkeiten und Plugin-Konfigurationen hier und da zu duplizieren, aber so wie das Projekt reift und die Anzahl der Module sich erhöht, sollten Sie sich Zeit nehmen um die gemeinsamen Abhängigkeiten sowie Plugin Konfigurationen zu refaktorieren/überarbeiten. Die Erstellung effizienter POMs trägt Sie ein grosses Stück des Weges, die Komplexität Ihres Projekts zu verwalten während dieses noch immer wächst. Wo immer Verdopplungen von Informationen auftauchen können Sie davon ausgehen, dass es in der Regel einen besseren Weg gibt. 8.3. Optimirung der Abhängigkeiten Wenn Sie einen Blick in die verschiedenen POMs in Chapter 7, Multi-module Enterprise Project (Kapitel 7: "Multi-Modul Enterprise Projekt") werfen, beachten Sie mehrere bestehende Muster der Replikation/Wiederholung. Das erste Muster, das wir sehen können ist, dass einige Abhängigkeiten wie Spring und Hibernate-Annotations in mehreren Modulen definiert werden. Die Hibernate Abhängigkeit hat in jeder Definition die Ausgrenzung von javax.transaction repliziert. Das zweite Muster von Wiederholung ist, dass manchmal mehrere Abhängigkeiten in einem Zusammenhang stehen und sich die gleiche Version teilen. Dies ist häufig der Fall, wenn ein Projekt Release aus mehreren eng gekoppelten Komponenten besteht. Sehen Sie sich zum Beispiel die Abhängigkeiten der Hibernate-Annotations sowie der Hibernate-Commons-Annotations an: beide tragen als Version 3.3.0.ga, und wir können davon ausgehen, dass die Version der beiden Abhängigkeiten sich auch in Zukunft im Gleichschritt bewegen wird. Sowohl die Hibernate-Annotations wie auch Hibernate-Commons-Annotations sind Komponenten des gleichen Projekts, freigegeben von JBoss. Wann immer es einen neuen Projekt Release gibt, werden diese beiden Abhängigkeiten sich ändern. Das letzte Muster der Replikation ist die Wiederholung der Geschwister-Modul Abhängigkeiten und Geschwister-Modul-Versionen. Maven bietet einfache Mechanismen, mit denen Sie alle Fälle von Duplikation in das Top Level POM (Parent POM) ziehen 170
  • 190. Optimirung und Überarbeitung der POMs können. Ein kleine Diskrepanz der Version einer Abhängigkeit eines Projektes bezüglich eines Bytecode-Manipulations Bibliothek mit dem Namen ASM, drei Ebenen tiefer in der Projekt-Hierarchie, führt leicht zur vollständigen Blockade einer Web-Applikation, welche von einer ganz anderen Gruppe von Entwicklern bearbeitet wird, und von diesem speziellen Modul abhängig ist. Unit-Tests bestehen, da diese gegen eine Version der Abhängigkeit testen, und scheitern fürchterlich in der Produktion, in welcher - in einem WAR-Archive gebündelt - eine ganz andere Version dieser Abhängigkeit vorliegt. Sollten Sie Dutzende von Projekten am Laufen haben, welche alle mit etwas wie Hibernate Annotations arbeiten und deren Deklaration sich ständig wiederholt und dupliziert, mit allen Abhängigkeiten und Ausschlüssen, so wird die durchschnittliche Zeit zwischen grossen Build Fehlern eher klein sein. So wie Ihre Maven Projekte immer weiter wachsen und komplexer werden, werden Ihre Listen von Abhängigkeiten wachsen; und Sie werden nicht umhinkommen, Versionen und Deklarationen Ihrer Abhängigkeiten in Ihrem Top Level POM zu konsolidieren. Die Wiederholung von Geschwister-Modul-Versionen bereiten das Feld für einen besonders fieses Problem, welches zwar nicht direkt durch Maven verschuldet wird, aber dessen man sich erst bewusst wird, nachdem Sie ein paar Mal von diesem Fehler gebissen wurden. So Sie das Maven Release Plugin benutzen, ist die Verwaltung der Geschwister Versionen und deren Abhängigkeiten kein Thema, Versionen werden jeweils automatisch aktualisiert. Wenn Simple Web Version 1.3-Snapshot von Simple Persist Version 1.3-Snapshot abhängig ist, und Sie geben beide Module in der Version 1.3 frei, so ist das Maven Release-Plugin schlau genug, um die Versionen innerhalb Ihrer gesamten multi-modularen Projekt POMs automatisch anzupassen. Wenn Sie die Freigabe mittels dem Release-Plugin vornehmen werden automatisch alle Versionen in Ihrem Build auf 1.4-Snapshot hochgezählt und das Release-Plugin wird darüber hinaus allen Code in das Repository einchecken. Ein großes multi-modulares Projekt freizugeben könnte also nicht einfacher sein, bis ... Probleme treten auf, wenn Entwickler Änderungen am POM einfliessen lassen, und sich in eine laufende Freigabe einmischen. Oftmals fügt ein Entwickler im Konfliktfall zwei Abhängigkeiten zusammen, oder agiert falsch im Fall eines 171
  • 191. Optimirung und Überarbeitung der POMs Modul Konflikts, so dass er unwissentlich auf eine frühere Version zurückrollt. Da die aufeinander folgenden Versionen der Abhängigkeit gewöhnlich kompatibel sind, zeigt sich dies nicht, wenn der Entwickler seinen Build erstellt, und es zeigt sich ebenso wenig in einem Continuous Integration Build-System. Stellen Sie sich einen sehr komplexen Build vor, der Trunk besteht aus Komponenten des 1.4-Snapshot, und jetzt müssen Sie sich vorstellen, dass Entwickler A eine Komponente tief in der Hierarchie des Projekts aktualisiert, welche von der Version 1.3-Snapshot der Komponente B abhängt. Obwohl die meisten Entwickler bereits auf Version 1.4-Snapshot bauen, wird es beim Build keinen Fehler geben, solange wie 1.3-SNAPSHOT und 1.4-SNAPSHOT der Komponente B kompatibel sind. Maven wird auch weiterhin den Build auf der Basis der 1.3-SNAPSHOT-Version von Komponente B ab dem lokalen Repository des Entwicklers durchführen. Alles scheint ganz reibungslos zu funktionieren, der Build des Projektes läuft durch, der Continuous Integration Build funktioniert ebenfalls, ein paar esoterische Fehler im Zusammenhang mit der Komponente B, aber diese werden auf das Werk hinterlistiger Kobolde abgetan. Mittlerweile pumpt eine Pumpe im Reaktor Raum und baut stetig Druck auf, bis schliesslich etwas bricht ... Jemand, nennen wir ihn Herr Unachtsam, hatte beim Zusammenführen einen Konflikt in Komponente A, um diesen zu lösen, wurde irrtümlicherweise die Abhängigkeit von Komponente A auf Komponente B Version 1.3-SNAPSHOT gekoppelt, während der Rest des Projekts stets weiter marschiert. Ein Gruppe Entwickler haben die ganze Zeit versucht, den Fehler in Komponente B zu beheben, und diese stehen vor einem Rätsel, warum Ihnen dies offenbar in der Produktion nicht gelingt. Schließlich befasst sich jemand mit Komponente A und realisiert, dass die Abhängigkeit auf die falsche Version zeigt. Es bleibt zu hoffen, dass der Fehler nicht groß genug war, um Geld oder Leben zu kosten, aber Herr Unachtsam ist beschämt, und seine Kollegen vertrauen ihm ein bisschen weniger als vor der ganzen Abhängigkeits-Sache. (Ich hoffe zwar, dass Herr Unachtsam realisiert, dass es sich hier um einen Benutzerfehler handelt, und das Maven nicht Schuld war. Leider ist es mehr als wahrscheinlich Herr Unachtsam startet eine furchtbare Blog-Tirade und beschwert sich endlos über Maven, allein schon um sich besser zu fühlen.) 172
  • 192. Optimirung und Überarbeitung der POMs Glücklicherweise ist die Weiderholung von Abhängigkeiten und die Unstimmigkeit von Geschwisterversionen einfach vermeidbar. Es braucht nur einige kleine Veränderungen. Der erste Schritt ist, alle Abhängigkeiten welche in mehr als einem Projekt vorkommen zu finden und diese in das darüber stehende POM im Abschnitt dependencyManagement aufzunehmen. Die Geschwister Abhängigkeiten blenden wir für den Moment aus. Das Simple Parent POM beinhaltet nun die folgenden Einträge: <project> ... <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> ... </project> Sobald die Abhängigkeiten im übergeordneten POM eingetragen sind, müssen wir 173
  • 193. Optimirung und Überarbeitung der POMs diese Einträge aus den 'Kind'-POM entfernen. Andernfalls werden die bestehenden Einträge die des übergeordneten Abschnitts dependencyManagement übersteuern. Der Einfachheit halber werden wir hier nur das Simple Model Modul POM zeigen: <project> ... <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> </dependencies> ... </project> Das nächste, das wir angehen sollten, ist die Replikation der Hibernate-Annotations sowie der Hibernate-Commons-Annotations, da diese Versionen immer übereinstimmen sollten. Wir tun dies, indem wir ein Property mit dem Namen hibernate-annotations-version erstellen. Der daraus resultierende Abschnitt des übergeordneten POM sieht wie folgt aus: <project> ... <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> </properties> <dependencyManagement> ... <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> ... </dependencyManagement> ... </project 174
  • 194. Optimirung und Überarbeitung der POMs Zum Schluss nehmen wir uns noch der Geschwister-Abhängigkeit an. Einen Weg welchen wir beschreiten könnten wäre, wie zuvor auch, diese Abhängigkeiten nach oben in das übergeordnete POM in den Abschnitt dependencyManagement zu verschieben, genau wie die Abhängigkeiten zuvor, mit einer definierten Versionen der Geschwister-Projekte im Top-Level Projekt POM. Dies ist sicherlich ein gültiges Konzept. Allerdings können wir dieses Problem mittles dem Einsatz zweier eingebauter Variablen lösen: ${project.groupId} und ${project.version}. Da es sich um Geschwister Abhängigkeiten handelt, macht es nicht viel Sinn, einen numerischen Wert im übergeordneten POM zu definieren, daher stützen wir uns auf die eingebaute Eigenschaft ${project.version}. Da alle Geschwister derselben Gruppe angehören, können wir weitere Zukunftssicherheit einbauen, indem wir auf das bestehende POM mittels der Eigenschaft ${project.groupId} verweisen. Der Abschnitt der Abhängigkeiten des Simple Command Moduls sieht nun wie folgt aus: <project> ... <dependencies> ... <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> ... </dependencies> ... </project> Zusammenfassend hier die beiden Optimierungen welche wir angewandt haben, um Wiederholungen in der Deklaration der Abhängigkeiten zu reduzieren: Ziehen Sie gemeinsame Abhängigkeiten in das übergeordnete POM in den Abschnitt dependencyManagement 175
  • 195. Optimirung und Überarbeitung der POMs Wenn mehr als ein Projekt von einer Abhängigkeit Gebrauch macht, können Sie die Abhängigkeit im Element dependencyManagement des übergeordneten POM erfassen. Im übergeordneten POM werden Version und Ausschlüsse gesetzt, in den untergeordneten POM müssen sie lediglich die Koordinaten groupId und artifactId angeben. Untergeordnete Projekte können die Version sowie die Ausschlüsse weglassen, sollte die Abhängigkeit im Element dependencyManagement deklariert sein. Benutzen Sie auf die eingebauten Variablen 'version' und 'groupId' Um sich auf ein Geschwisterprojekt zu beziehen, verwenden Sie ${project.version} und ${project.groupId}. Geschwister-Projekte umfassen fast immer die gleiche groupId und Release-Version. Mittels ${project.version} entschärfen Sie das oben aufgeführte Problem der Geschwister-Version Missverhältnisse. 8.4. Optimirung der Plugins Betrachten wir die verschiedenen Plugin-Konfigurationen, können wir sehen, dass die Abhängigkeiten zu HSQL DB an mehreren Orten wiederholt wird. Leider ist das Element dependencyManagement nicht auf Plugin-Abhängigkeiten anwendbar, dennoch können wir ein Property benutzen, um die verschiedenen Versionen zu konsolidieren. Tendeziell definieren komplexe multi-modulare Maven-Projekte alle Versionen im Top-Level-POM. Dieses Top-Level-POM wird dann zu einem Dreh-und Angelpunkt bezüglich allen Veränderungen, welche Auswirkungen auf das gesamte Projekt haben. Stellen Sie sich die Version als Java String in einer Java Klasse vor; bei einer ständigen Wiederholung werden Sie wahrscheinlich eine Variable einsetzen wollen, damit, wenn es den String zu ändern gilt, Sie dies nur an einem Ort durchführen müssen. Das 'Hochziehen' der Version von HSQLDB in ein Property des Top-Level-POM liefert uns fogenden Eintrag: <project> ... <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> <hsqldb.version>1.8.0.7</hsqldb.version> </properties> ... 176
  • 196. Optimirung und Überarbeitung der POMs </project> Das nächste, was wir bemerken ist, dass die Hibernate3-Maven-Plugin-Konfiguration doppelt, sowohl im Simple Web- sowie im Simple Command Modul vorkommt. Entsprechend der Abhängigkeiten können wir die Plugin-Konfiguration in der Top-Level-POM Datei genau wie Abhängigkeiten im Element dependencyManagement definieren. Hierfür benutzen wir das Element pluginManagement in der Top Level POM Datei. <project> ... <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </pluginManagement> </build> ... </project> 177
  • 197. Optimirung und Überarbeitung der POMs 8.5. Optimierung unter Zuhilfenahmen des Maven Dependency Plugin Grössere Projekte, haben eine Tendenz, dass mit wachsender Anzahl Abhängigkeiten auch zusätzliche Abhängigkeiten in ein POM einfliessen. Über die Zeit hinweg werden sich die Abhängigkeiten verändern, und so werden Sie über kurz oder lang auch verwaiste Abhängigkeiten beherbergen, genauso wie Sie Bibliotheken anziehen werden, welche nicht deklariert sind. Da Maven 2.x transitive Abhängigkeiten im Geltungsbereich des Compilers einschliesst, kann es passieren, dass Ihr Projekt zwar fehlerfrei kompiliert, jedoch in der Produktion nicht lauffähig ist. Nehmen Sie zum Beispiel ein Projekt welches auf eine weit verbreitete Bibliothek wie Jakarta Commons BeanUtils aufsetzt. Statt diese explizit zu referenzieren, verlassen Sie sich auf die transitive Abhängigkeit, da Sie ebenfalls Hibernate einsetzen, welches diese Library transitiv deklariert. Ihr Projekt kompiliert und läuft wie erwartet, bis Sie eines Tages Hibernate updaten und hierbei auf eine Version wechseln, welche nicht mehr auf BeanUtils aufbaut. Plötzlich bekommen Sie viele Fehler, deren Grund nicht gerade offensichtlich ist. Darüber hinaus kann Maven auftretende Konflikte nicht lösen, da die Abhängigkeit nicht explizit deklariert wurde. Eine bewährte Faustregel ist es, alle ausdrücklich im Code deklarierten Abhängigkeiten explizit zu deklarieren. Wenn Sie Jakarta Commons Beanutils importieren, so sollten Sie diese auch explizit in der pom.xml-Datei festhalten. Glücklicherweise kann Ihnen hier Maven helfen, denn durch Bytecode Analyse ist Maven in der Lage, solche direkten Abhängigkeiten aufzudecken. Lassen Sie uns das zuvor optimierte POM auf Fehler durchleuchten: $ mvn dependency:analyze [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Chapter 8 Simple Parent Project [INFO] Chapter 8 Simple Object Model [INFO] Chapter 8 Simple Weather API [INFO] Chapter 8 Simple Persistence API [INFO] Chapter 8 Simple Command Line Tool [INFO] Chapter 8 Simple Web Application [INFO] Chapter 8 Parent Project [INFO] Searching repository for plugin with prefix: 'dependency'. 178
  • 198. Optimirung und Überarbeitung der POMs ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO] task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING] javax.persistence:persistence-api:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING] org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [WARNING] org.hibernate:hibernate:jar:3.2.5.ga:compile [WARNING] junit:junit:jar:3.8.1:test ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Web Application [INFO] task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] No sources to compile [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING] org.sonatype.mavenbook.ch08:simple-model:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING] org.apache.velocity:velocity:jar:1.5:compile [WARNING] javax.servlet:jstl:jar:1.1.2:compile [WARNING] taglibs:standard:jar:1.1.2:compile [WARNING] junit:junit:jar:3.8.1:test Die vorangegangene, gekürzte Darstellung zeigt die Ausgabe des Goals dependency:analyse. Dieses Goal analysisert, ob es irgendwelche indirekten Abhängigkeiten gibt, oder Abhängigkeiten, welche zwar referenziert, aber nicht 179
  • 199. Optimirung und Überarbeitung der POMs deklariert werden. Im Projekt Simple Model zeigt das Plugin beispielhaft auf, wie die Abhängigkeit javax.persistence:persistence-api eine "nicht deklarierte" Abhängigkeit darstellt. Um dies genauer zu untersuchen, welchseln Sie in das Verzeichnis des Simple Model Projekt und rufen dort das Goal dependency:tree auf. Dies wird Ihnen eine Auflistung aller direkten und transitiven Abhängigkeiten ausgeben. $ mvn dependency:tree [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'dependency'. [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO] task-segment: [dependency:tree] [INFO] ------------------------------------------------------------------------ [INFO] [dependency:tree] [INFO] org.sonatype.mavenbook.ch08:simple-model:jar:1.0 [INFO] +- org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [INFO] | - javax.persistence:persistence-api:jar:1.0:compile [INFO] +- org.hibernate:hibernate:jar:3.2.5.ga:compile [INFO] | +- net.sf.ehcache:ehcache:jar:1.2.3:compile [INFO] | +- commons-logging:commons-logging:jar:1.0.4:compile [INFO] | +- asm:asm-attrs:jar:1.5.3:compile [INFO] | +- dom4j:dom4j:jar:1.6.1:compile [INFO] | +- antlr:antlr:jar:2.7.6:compile [INFO] | +- cglib:cglib:jar:2.1_3:compile [INFO] | +- asm:asm:jar:1.5.3:compile [INFO] | - commons-collections:commons-collections:jar:2.1.1:compile [INFO] - junit:junit:jar:3.8.1:test [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ Aus dieser Ausgabe, können wir ablesen, dass das bemängelte persistence-api durch Hibernate eingeflossen ist. Ein flüchtiger Scan des Quellcodes aus diesem Modul wird uns zeigen dass hier tatsächlich viele direkte Referenzen auf die Abhängigkleit javax.persistence vorliegen. Die einfache Lösung besteht darin einen Verweis auf die Abhängigkeit in das POM aufzunehmen. In diesem Beispiel setzen wir die Abhängigkeit zur Version in den Abschnitt dependencyManagement des POM von Simple Parent, da die Abhängikeit im Zusammenhang mit Hibernate steht, und Hibernate dort deklariert wird. Irgendwann wird der Zeipunkt gekommen sein, und Sie werden die Version von Hibernate upgraden. Die Nähe der Auflistung des persistence-api wird es Ihnen dann klarer aufzeigen, dass hier eine Abhängigkeit besteht. 180
  • 200. Optimirung und Überarbeitung der POMs Beim betrachten der Ausgabe von dependency:analyse des Simple Web Moduls werden Sie feststellen, dass Sie eine Referenz zum Simple Model erstellen sollten. Ihr Quellcode in Simple Web referenziert direkt Objekte aus Simple Model welches jedoch nur transitiv über Simple Persist eingebunden ist. Da es sich hier um eine Abhängigkeit unter Geschwistern handelt welche beide die gleiche groupId sowie version haben, können Sie die Abhängigkeit im POM von Simple Web in der Form ${project.goupId} und ${project.version} vornehmen. Wie konnte Maven diese Abhängigkeiten überhaupt aufdecken? Woher weiss dependency:analyse überhaupt welche Klassen und andere Abhängigkeiten von Ihrem Bytecode referenziert werden? Das Dependency Plugin benutzt den ObjectWeb ASM Toolkit um den Bytecode zu analysieren. Mittels ASM durchläuft das Dependency Plugin den gesamten Code und erstellt einen Baum aller Klassen welche im aktuellen Projekt referenziert werden. Anschliessend durchläuft das Werkzeug alle deklarierten und transitiven Abhängigkeiten und steicht alle Klassen welche den direkten Abhängigkeiten zugeordnet werden können. Klassen welche nicht Teil der direkten Abhängigkeitsliste sind, müssen somit der transitiven Abhängigkeitsliste entspringen. Diese können als eingebundene, jedoch nicht deklarierte Abhängigkeit (used, undeclared dependencies) ausgegeben werden. Im Gegensatz dazu ist die Liste der deklarierten, aber nicht eingesetzten Abhängigkeiten ungleich schwieriger zu validieren, und weit weniger nützlich als die der gebrauchten, nicht deklarierternAbhängigkeiten. Zum einen werden manche der Abhängigkeiten nur während der Laufzeit oder in Tests angezogen, diese sind im Bytecode nicht ersichtlich. Beim Betrachten der Liste wird dies offensichtlich: so ist zum Beispiel JUnit Teil der Liste, was zu erwarten ist, da es nur zum Test eingesetzt wird. Sie werden auch feststellen, dass Velocity sowie das Servlet API auf der Liste der Abhängigkeiten des Simpel Web Moduls erscheint. Das ist nicht weiter verwunderlich, da während das Projekt keine direkten Verweise auf die Klassen dieser Artefakte hat, diese nach wie vor im laufenden Betrieb unverzichtbar sind. Seien Sie vorsichtig mit dem Entfernen unbenutzer, deklarierter Abhängigkeiten (unused, declared dependencies), denn ausser Sie haben eine sehr gute Testabdeckung fügen Sie hierbei leicht Laufzeitfehler ein. Eine noch bösere 181
  • 201. Optimirung und Überarbeitung der POMs Überraschung rührt von Bytecode Optimierern her: So ist es zum Beispiel legal den Wert einer Konstanten zu ersetzen um damit die bestehende Referenz wegzuoptimieren. Die Entfernung dieser Abhängigkeit führt jedoch dazu, dass die Kompilierung scheitert, obschon das Tool zeigt, dass diese ungenutzt ist. Es ist abzusehen, dass zukünftige Versionen des Dependency Maven-Plugin wird verbesserte Techniken zur Erkennung solcherlei Problemstellungen bereitstellen werden. Es ist zu empfehlen, dass Sie ab und zu dependency:analyse ausführen, um solcherlei Fehler des POM aufzudecken. Das Goal kann derart konfigueriert werden, dass beim Auftreten bestimmter Konditionen der Build abbricht, alternativ wird ein Report bereitgestellt. 8.6. Abschliessende POMs Abschliessend sollen hier nun die endgültigen, optimierten pom.xml-Dateien als Referenz für dieses Kapitel widergegeben werden. Hier ist das Top-Level POM für Simple Parent: Example 8.1. Abschliessendes POM des Moduls Simple-Parent <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <name>Chapter 8 Simple Parent Project</name> <modules> <module>simple-command</module> <module>simple-model</module> <module>simple-weather</module> <module>simple-persist</module> <module>simple-webapp</module> </modules> <build> 182
  • 202. Optimirung und Überarbeitung der POMs <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>hibernate3-maven-plugin</artifactId> <version>2.1</version> <configuration> <components> <component> <name>hbm2ddl</name> <implementation>annotationconfiguration</implementation> </component> </components> </configuration> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </pluginManagement> </build> <properties> <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version> <hsqldb.version>1.8.0.7</hsqldb.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> 183
  • 203. Optimirung und Überarbeitung der POMs <version>1.0</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> </project> The POM shown in Example 8.2, “Abschliessendes POM des Mouls Simple-Command” captures the POM for simple-command, the command-line version of the tool. Die folgenden POM erfasst das Simple Command Tool, die Kommando-Zeilen Version des Tools (Dienstprogramm). Example 8.2. Abschliessendes POM des Mouls Simple-Command <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 184
  • 204. Optimirung und Überarbeitung der POMs http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-command</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Command Line Tool</name> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>org.sonatype.mavenbook.weather.Main</mainClass> <addClasspath>true</addClasspath> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </pluginManagement> </build> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> 185
  • 205. Optimirung und Überarbeitung der POMs <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> </dependency> </dependencies> </project> Darauf folgend das POM des Simple Model Projekt. Simple Model beinhaltet alle Objekt Modelle der gesamten Anwendung. Example 8.3. Abschlissendes POM des Moduls Simple-Model <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-model</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Object Model</name> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> </dependency> </dependencies> 186
  • 206. Optimirung und Überarbeitung der POMs </project> Im folgenden das POM des Simple Persist Moduls. Hierin wird die gesamte Persistenzlogik durch Hibernate gekapselt. Example 8.4. Abschliessendes POM fdes Moduls Simple-Persist <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-persist</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Persistence API</name> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> 187
  • 207. Optimirung und Überarbeitung der POMs </dependency> </dependencies> </project> Dann das Simple Weather Projekt, das Projekt, welches die gesamte Arbeit mit dem Yahoo! Wetter RSS-Feed und dessen Analyse zu tun hat. Dieses Projekt ist von Simple Model abhängig. Example 8.5. Abschlissendes POM des Moduls Simple-Weather <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-weather</artifactId> <packaging>jar</packaging> <name>Chapter 8 Simple Weather API</name> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> 188
  • 208. Optimirung und Überarbeitung der POMs <version>1.3.2</version> <scope>test</scope> </dependency> </dependencies> </project> Schließlich ist das Simple Web Projekt, eine Web-Applikation welche die abgerufenen Wettervorhersagen in einer HSQLDB Datenbank speichert, sowie mit Simple Weather und den daraus bereitgestellten Bibliotheken kommuniziert. Example 8.6. Abschliessendes POM des Moduls Simple-Webapp <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simple-parent</artifactId> <version>1.0</version> </parent> <artifactId>simple-webapp</artifactId> <packaging>war</packaging> <name>Chapter 8 Simple Web Application</name> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> <dependency> 189
  • 209. Optimirung und Überarbeitung der POMs <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> </dependency> </dependencies> <build> <finalName>simple-webapp</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.9</version> <dependencies> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> 8.7. Fazit Dieses Kapitel hat aufgezeigt, welche Techniken Ihnen zur Verbesserung der Kontrolle Ihrer Abhängigkeiten und Plugins zur Verfügung stehen, um Ihre zukünftigen Builds wartungsfreundlich zu gestalten. Wir empfehlen in regelmäßigen Abständen der Überprüfung Ihres Builds wie aufgezeigt, um sicherzustellen, dass Wiederholungen und damit verbundene potenzielle Krisenherde minimiert werden. Wie Ihr Projekt reift, werden Sie zwangsläufig 190
  • 210. Optimirung und Überarbeitung der POMs neue Abhängigkeiten einführen, oder Sie werden feststellen, dass eine Einzelabhängigkeit nun an 10 oder mehr Orten zum Einsatz kommt und somit besser höher aufgehängt wird. Die Liste der zum Einsatz kommenden bzw. nicht kommenden Abhängigkeiten verändert sich im Laufe der Zeit und lässt sich mit dem Dependency Maven Plugin leicht bearbeiten. 191
  • 211. Part II. Maven Reference 192
  • 212. Chapter 9. The Project Object Model 9.1. Introduction This chapter covers the central concept of Maven—the Project Object Model. The POM is where a project’s identity and structure are declared, builds are configured, and projects are related to one another. The presence of a pom.xml file defines a Maven project. 9.2. The POM Maven projects, dependencies, builds, artifacts: all of these are objects to be modeled and described. These objects are described by an XML file called a Project Object Model. The POM tells Maven what sort of project it is dealing with and how to modify default behavior to generate output from source. In the same way a Java web application has a web.xml that describes, configures, and customizes the application, a Maven project is defined by the presence of a pom.xml. It is a descriptive declaration of a project for Maven; it is the figurative “map” that Maven needs to understand what it is looking at when it builds your project. You could also think of the pom.xml as analogous to a Makefile or an Ant build.xml. When you are using GNU make to build something like MySQL, you’ll usually have a file named Makefile that contains explicit instructions for building a binary from source. When you are using Apache Ant, you likely have a file named build.xml that contains explicit instructions for cleaning, compiling, packaging, and deploying an application. make, Ant, and Maven are similar in that they rely on the presence of a commonly named file such as Makefile, build.xml, or pom.xml, but that is where the similarities end. If you look at a Maven pom.xml, the majority of the POM is going to deal with descriptions: Where is the source code? Where are the resources? What is the packaging? If you look at an Ant build.xml file, you’ll see something entirely different. You’ll see explicit 193
  • 213. The Project Object Model instructions for tasks such as compiling a set of Java classes. The Maven POM is declarative, and although you can certainly choose to include some procedural customizations via the Maven Ant plugin, for the most part you will not need to get into the gritty procedural details of your project’s build. The POM is also not specific to building Java projects. While most of the examples in this book are geared towards Java applications, there is nothing Java-specific in the definition of a Maven Project Object Model. While Maven's default plugins are targeted at building JAR artifacts from a set of source, tests, and resources, there is nothing preventing you from defining a POM for a project that contains C# sources and produces some proprietary Microsoft binary using Microsoft tools. Similarly, there is nothing stopping you from defining a POM for a technical book. In fact, the source for this book and this book's examples is captured in a multi-module Maven project which uses one of the many Maven Docbook plugins to apply the standard Docbook XSL to a series of chapter XML files. Others have created Maven plugins to build Adobe Flex code into SWCs and SWFs, and yet others have used Maven to build projects written in C. We've established that the POM describes and declares, it is unlike Ant or Make in that it doesn't provide explicit instructions, and we've noted that POM concepts are not specific to Java. Diving into more specifics, take a look at Figure 9.1, “The Project Object Model” for a survey of the contents of a POM. 194
  • 214. The Project Object Model Figure 9.1. The Project Object Model The POM contains four categories of description and configuration: General project information This includes a project’s name, the URL for a project, the sponsoring organization, and a list of developers and contributors along with the license for a project. Build settings In this section, we customize the behavior of the default Maven build. We can change the location of source and tests, we can add new plugins, we can attach plugin goals to the lifecycle, and we can customize the site generation parameters. Build environment The build environment consists of profiles that can be activated for use in different environments. For example, during development you may want to deploy to a development server, whereas in production you want to deploy to a 195
  • 215. The Project Object Model production server. The build environment customizes the build settings for specific environments and is often supplemented by a custom settings.xml in ~/.m2. This settings file is discussed in Chapter 11, Build Profiles and in the section Section A.2, “Die Details der settings.xml Datei”. POM relationships A project rarely stands alone; it depends on other projects, inherits POM settings from parent projects, defines its own coordinates, and may include submodules. 9.2.1. The Super POM Before we dive into some examples of POMs, let's take a quick look at the Super POM. All Maven project POMs extend the Super POM which defines a set of defaults shared by all projects. This Super POM is a part of the Maven installation, and can be found in the maven-2.0.9-uber.jar file in ${M2_HOME}/lib. If you look in this JAR file, you will find a file named pom-4.0.0.xml under the org.apache.maven.project package. The Super POM for Maven is shown in Example 9.1, “The Super POM”. Example 9.1. The Super POM <project> <modelVersion>4.0.0</modelVersion> <name>Maven Default Project</name> <repositories> <repository> <id>central</id> # <name>Maven Repository Switchboard</name> <layout>default</layout> <url>http://repo1.maven.org/maven2</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>central</id> # <name>Maven Plugin Repository</name> 196
  • 216. The Project Object Model <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <build> # <directory>target</directory> <outputDirectory>target/classes</outputDirectory> <finalName>${pom.artifactId}-${pom.version}</finalName> <testOutputDirectory>target/test-classes</testOutputDirectory> <sourceDirectory>src/main/java</sourceDirectory> <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory> <testSourceDirectory>src/test/java</testSourceDirectory> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> <testResources> <testResource> <directory>src/test/resources</directory> </testResource> </testResources> </build> <pluginManagement># <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.1</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.2-beta-1</version> </plugin> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> </plugin> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.0</version> </plugin> 197
  • 217. The Project Object Model <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-ear-plugin</artifactId> <version>2.3.1</version> </plugin> <plugin> <artifactId>maven-ejb-plugin</artifactId> <version>2.1</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-javadoc-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <artifactId>maven-plugin-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-rar-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-release-plugin</artifactId> <version>2.0-beta-7</version> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>2.0-beta-6</version> </plugin> <plugin> <artifactId>maven-source-plugin</artifactId> <version>2.0.4</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.4.2</version> </plugin> <plugin> 198
  • 218. The Project Object Model <artifactId>maven-war-plugin</artifactId> <version>2.1-alpha-1</version> </plugin> </plugins> </pluginManagement> <reporting> <outputDirectory>target/site</outputDirectory> </reporting> </project> The Super POM defines some standard configuration variables that are inherited by all projects. Those values are captured in the annotated sections: ‚ The default Super POM defines a single remote Maven repository with an ID of central. This is the central Maven repository that all Maven clients are configured to read from by default. This setting can be overridden by a custom settings.xml file. Note that the default Super POM has disabled snapshot artifacts on the central Maven repository. If you need to use a snapshot repository, you will need to customize repository settings in your pom.xml or in your settings.xml. Settings and profiles are covered in Chapter 11, Build Profiles and in Section A.2, “Die Details der settings.xml Datei”. ƒ The central Maven repository also contains Maven plugins. The default plugin repository is the central Maven repository. Snapshots are disabled, and the update policy is set to “never,” which means that Maven will never automatically update a plugin if a new version is released. „ The build element sets the default values for directories in the Maven Standard Directory layout. … Starting in Maven 2.0.9, default versions of core plugins have been provided in the Super POM. This was done to provide some stability for users that are not specifying versions in their POMs. 199
  • 219. The Project Object Model Figure 9.2. The Super POM is always the base Parent 9.2.2. The Simplest POM All Maven POMs inherit defaults from the Super POM (introduced earlier in the section Section 9.2.1, “The Super POM””). If you are just writing a simple project that produces a JAR from some source in src/main/java, want to run your JUnit tests in src/test/java, and want to build a project site using mvn site, you don’t have to customize anything. All you would need, in this case, is the simplest possible POM shown in Example 9.2, “The Simplest POM”. This POM defines a groupId, artifactId, and version: the three required coordinates for every project. Example 9.2. The Simplest POM <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook.ch08</groupId> <artifactId>simplest-project</artifactId> <version>1</version> </project> 200
  • 220. The Project Object Model Such a simple POM would be more than adequate for a simple project—e.g., a Java library that produces a JAR file. It isn’t related to any other projects, it has no dependencies, and it lacks basic information such as a name and a URL. If you were to create this file and then create the subdirectory src/main/java with some source code, running mvn package would produce a JAR in target/simple-project-1.jar. 9.2.3. The Effective POM This simplest POM brings us to the concept of the “effective POM.” Since POMs can inherit configuration from other POMs, you must always think of a Maven POM in terms of the combination of the Super POM, plus any parent POMs, and finally the current project’s POM. Maven starts with the Super POM and then overrides default configuration with one or more parent POMs. Then it overrides the resulting configuration with the current project’s POM. You end up with an effective POM that is a mixture of various POMs. If you want to see a project’s effective POM, you’ll need to run the effective-pom goal in the Maven Help plugin, which was introduced earlier in the section Section 2.8, “Das Maven Hilfe Plugin”.” To run the effective-pom goal, execute the following in a directory with a pom.xml file: $ mvn help:effective-pom Executing the effective-pom goal should print out an XML document capturing the merge between the Super POM and the POM from Example 9.2, “The Simplest POM”. 9.2.4. Real POMs Instead of typing up a contrived set of POMs to walk you through step-by-step, you should take a look at the examples in Part I, “Maven by Example”. Maven is something of a chameleon; you can pick and choose the features you want to take advantage of. Some open source projects may value the ability to list developers and contributors, generate clean project documentation, and manage releases automatically using the Maven Release plugin. On the other hand, someone 201
  • 221. The Project Object Model working in a corporate environment on a small team might not be interested in the distribution management capabilities of Maven nor the ability to list developers. The remainder of this chapter is going to discuss features of the POM in isolation. Instead of bombarding you with a 10-page listing of a set of related POMs, we’re going to focus on creating a good reference for specific sections of the POM. In this chapter, we discuss relationships between POMs, but we don’t illustrate such a project here. If you are looking for such an illustration, refer to Chapter 7, Multi-module Enterprise Project. 9.3. POM Syntax The POM is always in a file named pom.xml in the base directory of a Maven project. This XML document can start with the XML declaration, or you can choose to omit it. All values in a POM are captured as XML elements. 9.3.1. Project Versions A Maven project’s version encodes a release version number that is used to group and order releases. Maven versions contain the following parts: major version, minor version, incremental version, and qualifier. In a version, these parts correspond to the following format: <major version>.<minor version>.<incremental version>-<qualifier> For example, the version "1.3.5" has a major version of 1, a minor version of 3, and an incremental version of 5. The version "5" has a major version of 5 and no minor or incremental version. The qualifier exists to capture milestone builds: alpha and beta releases, and the qualifier is separated from the major, minor, and incremental versions by a hyphen. For example, the version "1.3-beta-01" has a major version of 1, a minor version of 3, and a qualifier of "beta-01". Keeping your version numbers aligned with this standard will become very important when you want to start using version ranges in your POMs. Version ranges, introduced in Section 9.4.3, “Dependency Version Ranges”, allow you to specify a dependency on a range of versions, and they are only supported because 202
  • 222. The Project Object Model Maven has the ability to sort versions based on the version release number format introduced in this section. If your version release number matches the format <major>.<minor>.<incremental>-<qualifier> then your versions will be compared properly; "1.2.3" will be evaluated as a more recent build than "1.0.2", and the comparison will be made using the numeric values of the major, minor, and incremental versions. If your version release number does not fit the standard introduced in this section, then your versions will be compared as strings; "1.0.1b" will be compared to "1.2.0b" using a String comparison. 9.3.1.1. Version Build Numbers One gotcha for release version numbers is the ordering of the qualifiers. Take the version release numbers “1.2.3-alpha-2” and “1.2.3-alpha-10,” where the “alpha-2” build corresponds to the 2nd alpha build, and the “alpha-10” build corresponds to the 10th alpha build. Even though “alpha-10” should be considered more recent than “alpha-2,” Maven is going to sort “alpha-10” before “alpha-2” due to a known issue in the way Maven handles version numbers. Maven is supposed to treat the number after the qualifier as a build number. In other words, the qualifier should be "alpha", and the build number should be 2. Even though Maven has been designed to separate the build number from the qualifier, this parsing is currently broken. As a result, "alpha-2" and "alpha-10" are compared using a String comparison, and "alpha-10" comes before "alpha-2" alphabetically. To get around this limitation, you will need to left-pad your qualified build numbers. If you use "alpha-02" and "alpha-10" this problem will go away, and it will continue to work once Maven properly parses the version build number. 9.3.1.2. SNAPSHOT Versions Maven versions can contain a string literal to signify that a project is currently under active development. If a version contains the string “SNAPSHOT,” then Maven will expand this token to a date and time value converted to UTC (Coordinated Universal Time) when you install or release this component. For 203
  • 223. The Project Object Model example, if your project has a version of “1.0-SNAPSHOT” and you deploy this project’s artifacts to a Maven repository, Maven would expand this version to “1.0-20080207-230803-1” if you were to deploy a release at 11:08 PM on February 7th, 2008 UTC. In other words, when you deploy a snapshot, you are not making a release of a software component; you are releasing a snapshot of a component at a specific time. Why would you use this? SNAPSHOT versions are used for projects under active development. If your project depends on a software component that is under active development, you can depend on a SNAPSHOT release, and Maven will periodically attempt to download the latest snapshot from a repository when you run a build. Similarly, if the next release of your system is going to have a version "1.4", your project would have a version "1.4-SNAPSHOT" version until it was formally released. As a default setting, Maven will not check for SNAPSHOT releases on remote repositories, to depend on SNAPSHOT releases, users must explicitly enable the ability to download snapshots using a repository or pluginRepository element in the POM. When releasing a project you should resolve all dependencies on SNAPSHOT versions to dependencies on released versions. If a project depends on a SNAPSHOT, it is not stable as the dependencies may change over time. Artifacts published to non-snapshot Maven repositories such as http://repo1.maven.org/maven2 cannot depend on SNAPSHOT versions, as Maven's Super POM has snapshot's disabled from the Central repository. SNAPSHOT versions are for development only. 9.3.1.3. LATEST and RELEASE Versions When you depend on a plugin or a dependency, you can use the a version value of LATEST or RELEASE. LATEST refers to the latest released or snapshot version of a particular artifact, the most recently deployed artifact in a particular repository. RELEASE refers to the last non-snapshot release in the repository. In general, it is not a best practice to design software which depends on a non-specific version of an artifact. If you are developing software, you might want to use 204
  • 224. The Project Object Model RELEASE or LATEST as a convenience so that you don't have to update version numbers when a new release of a third-party library is released. When you release software, you should always make sure that your project depends on specific versions to reduce the chances of your build or your project being affected by a software release not under your control. Use LATEST and RELEASE with caution, if at all. Starting with Maven 2.0.9, Maven locks down the version numbers of common and core Maven plugins in the super POM to standardize a core set of Maven plugins for a particular version of Maven. This change was introduced to Maven 2.0.9 to bring stability and reproducibility to Maven builds. Before Maven 2.0.9, Maven would automatically update core Maven plugins using the LATEST version. This behavior led to a number of surprises when bugs was introduced into core plugins or functionality changed in a core plugin which subsequently broke a build. When Maven automatically updated core plugins, it was noted that there was little guarantee that a build would be reproducible as plugins could be updated whenever a new version was pushed to the central repository. Starting with Maven 2.0.9, Maven, essentially, ships with a core set of locked down plugin versions. Non-core plugins, or plugins without versions assigned in the Super POM will still use the LATEST version to retrieve a plugin artifact from the repository. It is for this reason that you should assign explicit version numbers to any custom or non-core plugins used in your build. 9.3.2. Property References A POM can include references to properties preceded by a dollar sign and surrounded by two curly braces. For example, consider the following POM: <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-a</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <build> <finalName>${project.groupId}-${project.artifactId}</finalName> </build> </project> 205
  • 225. The Project Object Model If you put this XML in a pom.xml and run mvn help:effective-pom, you will see that the output contains the line: ... <finalName>org.sonatype.mavenbook-project-a</finalName> ... When Maven reads a POM, it replaces references to properties when it loads the POM XML. Maven properties occur frequently in advanced Maven usage, and are similar to properties in other systems such as Ant or Velocity. They are simply variables delimited by ${...}. Maven provides three implicit variables which can be used to access environment variables, POM information, and Maven Settings: env The env variable exposes environment variables exposed by your operating system or shell. For example, a reference to ${env.PATH} in a Maven POM would be replaced by the ${PATH} environment variable (or %PATH% in Windows). project The project variable exposes the POM. You can use a dot-notated (.) path to reference the value of a POM element. For example, in this section we used the groupId and artifactId to set the finalName element in the build configuration. The syntax for this property reference was: ${project.groupId}-${project.artifactId}. settings The settings variable exposes Maven settings information. You can use a dot-notated (.) path to reference the value of an element in a settings.xml file. For example, ${settings.offline} would reference the value of the offline element in ~/.m2/settings.xml. Note You may see older builds that use ${pom.xxx} or just ${xxx} to reference POM properties. These methods have been deprecated and only ${project.xxx} should be used. 206
  • 226. The Project Object Model In addition to the three implicit variables, you can reference system properties and any custom properties set in the Maven POM or in a build profile: Java System Properties All properties accessible via getProperties() on java.lang.System are exposed as POM properties. Some examples of system properties are: ${user.name}, ${user.home}, ${java.home}, and ${os.name}. A full list of system properties can be found in the Javadoc for the java.lang.System class. x Arbitrary properties can be set with a properties element in a pom.xml or settings.xml, or properties can be loaded from external files. If you set a property named fooBar in your pom.xml, that same property is referenced with ${fooBar}. Custom properties come in handy when you are building a system that filters resources and targets different deployment platforms. Here is the syntax for setting ${foo}=bar in a POM: <properties> <foo>bar</foo> </properties> For a more comprehensive list of available properties, see Chapter 13, Properties and Ressource Filterung. 9.4. Project Dependencies Maven can manage both internal and external dependencies. An external dependency for a Java project might be a library such as Plexus, the Spring Framework, or Log4J. An internal dependency is illustrated by a web application project depending on another project that contains service classes, model objects, or persistence logic. Example 9.3, “Project Dependencies” shows some examples of project dependencies. Example 9.3. Project Dependencies <project> 207
  • 227. The Project Object Model ... <dependencies> <dependency> <groupId>org.codehaus.xfire</groupId> <artifactId>xfire-java5</artifactId> <version>1.2.5</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> The first dependency is a compile dependency on the XFire SOAP library from Codehaus. You would use this type of dependency if your project depended on this library for compilation, testing, and during execution. The second dependency is a test-scoped dependency on JUnit. You would use a test-scoped dependency when you need to reference this library only during testing. The last dependency in Example 9.3, “Project Dependencies” is a dependency on the Servlet 2.4 API. The last dependency is scoped as a provided dependency. You would use a provided scope when the application you are developing needs a library for compilation and testing, but this library is supplied by a container at runtime. 9.4.1. Dependency Scope Example 9.3, “Project Dependencies” briefly introduced three of the five dependency scopes: compile, test, and provided. Scope controls which dependencies are available in which classpath, and which dependencies are included with an application. Let’s explore each scope in detail: 208
  • 228. The Project Object Model compile compile is the default scope; all dependencies are compile-scoped if a scope is not supplied. compile dependencies are available in all classpaths, and they are packaged. provided provided dependencies are used when you expect the JDK or a container to provide them. For example, if you were developing a web application, you would need the Servlet API available on the compile classpath to compile a servlet, but you wouldn’t want to include the Servlet API in the packaged WAR; the Servlet API JAR is supplied by your application server or servlet container. provided dependencies are available on the compilation classpath (not runtime). They are not transitive, nor are they packaged. runtime runtime dependencies are required to execute and test the system, but they are not required for compilation. For example, you may need a JDBC API JAR at compile time and the JDBC driver implementation only at runtime. test test-scoped dependencies are not required during the normal operation of an application, and they are available only during test compilation and execution phases. The test scope was previously introduced in Section 4.10, “Hinzufügen von Gebietsbezogenen Unit Tests”.” system The system scope is similar to provided except that you have to provide an explicit path to the JAR on the local file system. This is intended to allow compilation against native objects that may be part of the system libraries. The artifact is assumed to always be available and is not looked up in a repository. If you declare the scope to be system, you must also provide the systemPath element. Note that this scope is not recommended (you should always try to reference dependencies in a public or custom Maven repository). 209
  • 229. The Project Object Model 9.4.2. Optional Dependencies Assume that you are working on a library that provides caching behavior. Instead of writing a caching system from scratch, you want to use some of the existing libraries that provide caching on the file system and distributed caches. Also assume that you want to give the end user an option to cache on the file system or to use an in-memory distributed cache. To cache on the file system, you’ll want to use a freely available library called EHCache (http://ehcache.sourceforge.net/), and to cache in a distributed in-memory cache, you want to use another freely available caching library named SwarmCache (http://swarmcache.sourceforge.net/). You’ll code an interface and create a library that can be configured to use either EHCache or SwarmCache, but you want to avoid adding a dependency on both caching libraries to any project that depends on your library. In other words, you need both libraries to compile this library project, but you don't want both libraries to show up as transitive runtime dependencies for the project that uses your library. You can accomplish this by using optional dependencies as shown in Example 9.4, “Declaring Optional Dependencies”. Example 9.4. Declaring Optional Dependencies <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>1.4.1</version> <optional>true</optional> </dependency> <dependency> <groupId>swarmcache</groupId> <artifactId>swarmcache</artifactId> <version>1.0RC2</version> <optional>true</optional> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> 210
  • 230. The Project Object Model </dependency> </dependencies> </project> Once you've declared these dependencies as optional, you are required to include them explicitly in the project that depends on my-project. For example, if you were writing an application which depended on my-project and wanted to use the EHCache implementation, you would need to add the following dependency element to your project. <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-application</artifactId> <version>1.0.0</version> <dependencies> <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>my-project</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>swarmcache</artifactId> <version>1.4.1</version> </dependency> </dependencies> </project> In an ideal world, you wouldn’t have to use optional dependencies. Instead of having one large project with a series of optional dependencies, you would separate the EHCache-specific code to a my-project-ehcache submodule and the SwarmCache-specific code to a my-project-swarmcache submodule. This way, instead of requiring projects that reference my-project to specifically add a dependency, projects can just reference a particular implementation project and benefit from the transitive dependency. 9.4.3. Dependency Version Ranges You don’t just have to depend on a specific version of a dependency; you can specify a range of versions that would satisfy a given dependency. For example, 211
  • 231. The Project Object Model you can specify that your project depends on version 3.8 or greater of JUnit, or anything between versions 1.2.10 and 1.2.14 of JUnit. You do this by surrounding one or more version numbers with the following characters: (, ) Exclusive quantifiers [, ] Inclusive quantifiers For example, if you wished to access any JUnit version greater than or equal to 3.8 but less than 4.0, your dependency would be as shown in Example 9.5, “Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0”. Example 9.5. Specifying a Dependency Range: JUnit 3.8 - JUnit 4.0 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[3.8,4.0)</version> <scope>test</scope> </dependency> If you want to depend on any version of JUnit no higher than 3.8.1, you would specify only an upper inclusive boundary, as shown in Example 9.6, “Specifying a Dependency Range: JUnit <= 3.8.1”. Example 9.6. Specifying a Dependency Range: JUnit <= 3.8.1 <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[,3.8.1]</version>ex-de <scope>test</scope> </dependency> A version before or after the comma is not required, and means +/- infinity. For example, "[4.0,)" means any version greater than or equal to 4.0. "(,2.0)" is any 212
  • 232. The Project Object Model version less than 2.0. "[1.2]" means only version 1.2, and nothing else. Note When declaring a "normal" version such as 3.8.2 for Junit, internally this is represented as "allow anything, but prefer 3.8.2." This means that when a conflict is detected, Maven is allowed to use the conflict algorithms to choose the best version. If you specify [3.8.2], it means that only 3.8.2 will be used and nothing else. If somewhere else there is a dependency that specifies [3.8.1], you would get a build failure telling you of the conflict. We point this out to make you aware of the option, but use it sparingly and only when really needed. The preferred way to resolve this is via dependencyManagement. 9.4.4. Transitive Dependencies A transitive dependency is a dependency of a dependency. If project-a depends on project-b, which in turn depends on project-c, then project-c is considered a transitive dependency of project-a. If project-c depended on project-d, then project-d would also be considered a transitive dependency of project-a. Part of Maven’s appeal is that it can manage transitive dependencies and shield the developer from having to keep track of all of the dependencies required to compile and run an application. You can just depend on something like the Spring Framework and not have to worry about tracking down every last dependency of the Spring Framework. Maven accomplishes this by building a graph of dependencies and dealing with any conflicts and overlaps that might occur. For example, if Maven sees that two projects depend on the same groupId and artifactId, it will sort out which dependency to use automatically, always favoring the more recent version of a dependency. Although this sounds convenient, there are some edge cases where transitive dependencies can cause some configuration issues. For these scenarios, you can use a dependency exclusion. 213
  • 233. The Project Object Model 9.4.4.1. Transitive Dependencies and Scope Each of the scopes outlined earlier in the section Section 9.4.1, “Dependency Scope”” affects not just the scope of the dependency in the declaring project, but also how it acts as a transitive dependency. The easiest way to convey this information is through a table, as in Table 9.1, “How Scope Affects Transitive Dependencies”. Scopes in the top row represent the scope of a transitive dependency. Scopes in the leftmost column represent the scope of a direct dependency. The intersection of the row and column is the scope that is assigned to a transitive dependency. A blank cell in this table means that the transitive dependency will be omitted. Table 9.1. How Scope Affects Transitive Dependencies Direct Scope Transitive Scope compile provided runtime test compile compile - runtime - provided provided provided provided - runtime runtime - runtime - test test - test - To illustrate the relationship of transitive dependency scope to direct dependency scope, consider the following example. If project-a contains a test scoped dependency on project-b which contains a compile scoped dependency on project-c. project-c would be a test-scoped transitive dependency of project-a. You can think of this as a transitive boundary which acts as a filter on dependency scope. Transitive dependencies which are provided and test scope usually do not affect a project. The exception to this rule is that a provided scoped transitive dependency to a provided scope direct dependency is still a provided dependency of a project. Transitive dependencies which are compile and runtime scoped 214
  • 234. The Project Object Model usually affect a project regardless of the scope of a direct dependency. Transitive dependencies which are compile scoped will have the same scope irregardless of the scope of the direct dependency. Transitive dependencies which are runtime scoped will generally have the same scope of the direct dependency except when the direct dependency has a scope of compile. When a transitive dependency is runtime scoped and a direct is compile scoped the direct dependency the transitive dependency will have an effective scope of runtime. 9.4.5. Conflict Resolution There will be times when you need to exclude a transitive dependency, such as when you are depending on a project that depends on another project, but you would like to either exclude the dependency altogether or replace the transitive dependency with another dependency that provides the same functionality. Example 9.7, “Excluding a Transitive Dependency” shows an example of a dependency element that adds a dependency on project-a, but excludes the transitive dependency project-b. Example 9.7. Excluding a Transitive Dependency <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-b</artifactId> </exclusion> </exclusions> </dependency> Often, you will want to replace a transitive dependency with another implementation. For example, if you are depending on a library that depends on the Sun JTA API, you may want to replace the declared transitive dependency. Hibernate is one example. Hibernate depends on the Sun JTA API JAR, which is not available in the central Maven repository because it cannot be freely 215
  • 235. The Project Object Model redistributed. Fortunately, the Apache Geronimo project has created an independent implementation of this library that can be freely redistributed. To replace a transitive dependency with another dependency, you would exclude the transitive dependency and declare a dependency on the project you wanted instead. Example 9.8, “Excluding and Replacing a Transitive Dependency” shows an example of a such replacement. Example 9.8. Excluding and Replacing a Transitive Dependency <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jta_1.1_spec</artifactId> <version>1.1</version> </dependency> </dependencies> In Example 9.8, “Excluding and Replacing a Transitive Dependency”, there is nothing marking the dependency on geronimo-jta_1.1_spec as a replacement, it just happens to be a library which provides the same API as the original JTA dependency. Here are some other reasons you might want to exclude or replace transitive dependencies: 1. The groupId or artifactId of the artifact has changed, where the current project requires an alternately named version from a dependency's version - resulting in 2 copies of the same project in the classpath. Normally Maven would capture this conflict and use a single version of the project, but when groupId or artifactId are different, Maven will consider this to be two different libraries. 216
  • 236. The Project Object Model 2. An artifact is not used in your project and the transitive dependency has not been marked as an optional dependency. In this case, you might want to exclude a dependency because it isn't something your system needs and you are trying to cut down on the number of libraries distributed with an application. 3. An artifact which is provided by your runtime container thus should not be included with your build. An example of this is if a dependency depends on something like the Servlet API and you want to make sure that the dependency is not included in a web application's WEB-INF/lib directory. 4. To exclude a dependency which might be an API with multiple implementations. This is the situation illustrated by Example 9.8, “Excluding and Replacing a Transitive Dependency”; there is a Sun API which requires click-wrap licensing and a time-consuming manual install into a custom repository (Sun's JTA JAR) versus a freely distributed version of the same API available in the central Maven repository (Geronimo's JTA implementation). 9.4.6. Dependency Management Once you've adopted Maven at your super complex enterprise and you have two hundred and twenty inter-related Maven projects, you are going to start wondering if there is a better way to get a handle on dependency versions. If every single project that uses a dependency like the MySQL Java connector needs to independently list the version number of the dependency, you are going to run into problems when you need to upgrade to a new version. Because the version numbers are distributed throughout your project tree, you are going to have to manually edit each of the pom.xml files that reference a dependency to make sure that you are changing the version number everywhere. Even with find, xargs, and awk, you are still running the risk of missing a single POM. Luckily, Maven provides a way for you to consolidate dependency version numbers in the dependencyManagement element. You'll usually see the 217
  • 237. The Project Object Model dependencyManagement element in a top-level parent POM for an organization or project. Using the dependencyManagement element in a pom.xml allows you to reference a dependency in a child project without having to explicitly list the version. Maven will walk up the parent-child hierarchy until it finds a project with a dependencyManagement element, it will then use the version specified in this dependencyManagement element. For example, if you have a large set of projects which make use of the MySQL Java connector version 5.1.2, you could define the following dependencyManagement element in your multi-module project's top-level POM. Example 9.9. Defining Dependency Versions in a Top-level POM <project> <modelVersion>4.0.0</modelVersion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0.0</version> ... <dependencyManagement> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.2</version> </dependency> ... <dependencies> </dependencyManagement> Then, in a child project, you can add a dependency to the MySQL Java Connector using the following dependency XML: <project> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0.0</version> </parent> <artifactId>project-a</artifactId> ... <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> 218
  • 238. The Project Object Model </dependency> </dependencies> </project> You should notice that the child project did not have to explicitly list the version of the mysql-connector-java dependency. Because this dependency was defined in the top-level POM's dependencyManagement element, the version number is going to propagate to the child project's dependency on mysql-connector-java. Note that if this child project did define a version, it would override the version listed in the top-level POM's dependencyManagement section. That is, the dependencyManagement version is only used when the child does not declare a version directly. Dependency management in a top-level POM is different from just defining a dependency on a widely shared parent POM. For starters, all dependencies are inherited. If mysql-connector-java were listed as a dependency of the top-level parent project, every single project in the hierarchy would have a reference to this dependency. Instead of adding in unnecessary dependencies, using dependencyManagement allows you to consolidate and centralize the management of dependency versions without adding dependencies which are inherited by all children. In other words, the dependencyManagement element is equivalent to an environment variable which allows you to declare a dependency anywhere below a project without specifying a version number. 9.5. Project Relationships One of the compelling reasons to use Maven is that it makes the process of tracking down dependencies (and dependencies of dependencies) very easy. When a project depends on an artifact produced by another project we say that this artifact is a dependency. In the case of a Java project, this can be as simple as a project depending on an external dependency like Log4J or JUnit. While dependencies can model external dependencies, they can also manage the dependencies between a set of related projects, if project-a depends on project-b, Maven is smart enough to know that project-b must be built before 219
  • 239. The Project Object Model project-a. Relationships are not only about dependencies and figuring out what one project needs to be able to build an artifact. Maven can model the relationship of a project to a parent, and the relationship of a project to submodules. This section gives an overview of the various relationships between projects and how such relationships are configured. 9.5.1. More on Coordinates Coordinates define a unique location for a project, they were first introduced in Chapter 3, Ein einfaches Maven Projekt. Projects are related to one another using Maven Coordinates. project-a doesn't just depend on project-b; a project with a groupId, artifactId, and version depends on another project with a groupId, artifactId, and version. To review, a Maven Coordinate is made up of three components: groupId A groupId groups a set of related artifacts. Group identifiers generally resemble a Java package name. For example, the groupId org.apache.maven is the base groupId for all artifacts produced by the Apache Maven project. Group identifiers are translated into paths in the Maven Repository; for example, the org.apache.maven groupId can be found in /maven2/org/apache/maven on repo1.maven.org. artifactId The artifactId is the project's main identifier. When you generate an artifact, this artifact is going to be named with the artifactId. When you refer to a project, you are going to refer to it using the artifactId. The artifactId, groupId combination must be unique. In other words, you can't have two separate projects with the same artifactId and groupId; artifactIds are unique within a particular groupId. Note While '.'s are commonly used in groupIds, you should try to avoid 220
  • 240. The Project Object Model using them in artifactIds. This can cause issues when trying to parse a fully qualified name down into the subcomponents. version When an artifact is released, it is released with a version number. This version number is a numeric identifier such as "1.0", "1.1.1", or "1.1.2-alpha-01". You can also use what is known as a snapshot version. A snapshot version is a version for a component which is under development, snapshot version numbers always end in SNAPSHOT; for example, "1.0-SNAPSHOT", "1.1.1-SNAPSHOT", and "1-SNAPSHOT". Section 9.3.1.1, “Version Build Numbers” introduces versions and version ranges. There is a fourth, less-used qualifier: classifier You would use a classifier if you were releasing the same code but needed to produce two separate artifacts for technical reasons. For example, if you wanted to build two separate artifacts of a JAR, one compiled with the Java 1.4 compiler and another compiled with the Java 6 compiler, you might use the classifier to produce two separate JAR artifacts under the same groupId:artifactId:version combination. If your project uses native extensions, you might use the classifier to produce an artifact for each target platform. Classifiers are commonly used to package up an artifact's sources, JavaDocs or binary assemblies. When we talk of dependencies in this book, we often use the following shorthand notation to describe a dependency: groupId:artifactId:version. To refer to the 2.5 release of the Spring Framework, we would refer to it as org.springframework:spring:2.5. When you ask Maven to print out a list of dependencies with the Maven Dependency plugin, you will also see that Maven tends to print out log messages with this shorthand dependency notation. 9.5.2. Multi-module Projects 221
  • 241. The Project Object Model Multi-module projects are projects which contain a list of modules to build. A multi-module project always has a packaging of pom, and rarely produces an artifact. A multi-module project exists only to group projects together in a build. Figure 9.3, “Multi-module Project Relationships” shows a project hierarchy which includes two parent projects with packaging of pom, and three projects with packaging of jar. Figure 9.3. Multi-module Project Relationships The directory structure on the file system would also mirror the module relationships. A set of projects illustrated by Figure 9.3, “Multi-module Project Relationships” would have the following directory structure: top-group/pom.xml top-group/sub-group/pom.xml top-group/sub-group/project-a/pom.xml top-group/sub-group/project-b/pom.xml top-group/project-c/pom.xml The projects are related to one another because top-group and sub-group are referencing sub-modules in a POM. For example, the org.sonatype.mavenbook:top-group project is a multi-module project with 222
  • 242. The Project Object Model packaging of type pom. top-group's pom.xml would include the following modules element: Example 9.10. top-group modules element <project> <groupId>org.sonatype.mavenbook</groupId> <artifactId>top-group</artifactId> ... <modules> <module>sub-group</module> <module>project-c</module> </modules> ... </project> When Maven is reading top-group POM it will look at the modules element and see that top-group references the projects sub-group and project-c. Maven will then look for a pom.xml in each of these subdirectories. Maven repeats this process for each of the submodules: it will read the sub-group/pom.xml and see that the sub-group project references two projects with the following modules element: Example 9.11. sub-group modules element <project> ... <modules> <module>project-a</module> <module>project-b</module> </modules> ... </project> Note that we call the projects under the multi-module projects "modules" and not "children" or "child projects". This is purposeful, so as not to confuse projects grouped by multi-module projects with projects that inherit POM information from each other. 9.5.3. Project Inheritance 223
  • 243. The Project Object Model There are going to be times when you want a project to inherit values from a parent POM. You might be building a large system, and you don't want to have to repeat the same dependency elements over and over again. You can avoid repeating yourself if your projects make use of inheritance via the parent element. When a project specifies a parent, it inherits the information in the parent project's POM. It can then override and add to the values specified in this parent POM. All Maven POMs inherit values from a parent POM. If a POM does not specify a direct parent using the parent element, that POM will inherit values from the Super POM. Example 9.12, “Project Inheritance” shows the parent element of project-a which inherits the POM defined by the a-parent project. Example 9.12. Project Inheritance <project> <parent> <groupId>com.training.killerapp</groupId> <artifactId>a-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>project-a</artifactId> ... </project> Running mvn help:effective-pom in project-a would show a POM that is the result of merging the Super POM with the POM defined by a-parent and the POM defined in project-a. The implicit and explicit inheritance relationships for project-a are shown in Figure 9.4, “Project Inheritance for a-parent and project-a”. 224
  • 244. The Project Object Model Figure 9.4. Project Inheritance for a-parent and project-a When a project specifies a parent project, Maven uses that parent POM as a starting point before it reads the current project's POM. It inherits everything, including the groupId and version number. You'll notice that project-a does not specify either, both groupId and version are inherited from a-parent. With a parent element, all a POM really needs to define is an artifactId. This isn't mandatory, project-a could have a different groupId and version, but by not providing values, Maven will use the values specified in the parent POM. If you start using Maven to manage and build large multi-module projects, you will often be creating many projects which share a common groupId and version. When you inherit a POM, you can choose to live with the inherited POM information or to selectively override it. The following is a list of items a Maven POM inherits from its parent POM: • identifiers (at least one of groupId or artifactId must be overridden.) • dependencies 225
  • 245. The Project Object Model • developers and contributors • plugin lists • reports lists • plugin executions (executions with matching ids are merged) • plugin configuration When Maven inherits dependencies, it will add dependencies of child projects to the dependencies defined in parent projects. You can use this feature of Maven to specify widely used dependencies across all projects which inherit from a top-level POM. For example, if your system makes universal use of the Log4J logging framework, you can list this dependency in your top-level POM. Any projects which inherit POM information from this project will automatically have Log4J as a dependency. Similarly, if you need to make sure that every project is using the same version of a Maven plugin, you can list this Maven plugin version explicitly in a top-level parent POM's pluginManagement section. Maven assumes that the parent POM is available from the local repository, or available in the parent directory (../pom.xml) of the current project. If neither location is valid this default behavior may be overridden via the relativePath element. For example, some organizations prefer a flat project structure where a parent project's pom.xml isn't in the parent directory of a child project. It might be in a sibling directory to the project. If your child project were in a directory ./project-a and the parent project were in a directory named ./a-parent, you could specify the relative location of parent-a's POM with the following configuration: <project> <parent> <groupId>org.sonatype.mavenbook</groupId> <artifactId>a-parent</artifactId> <version>1.0-SNAPSHOT</version> <relativePath>../a-parent/pom.xml</relativePath> </parent> <artifactId>project-a</artifactId> </project> 226
  • 246. The Project Object Model 9.6. POM Best Practices Maven can be used to manage everything from simple, single-project systems to builds that involve hundreds of inter-related submodules. Part of the learning process with Maven isn't just figuring out the syntax for configuring Maven, it is learning the "Maven Way"—the current set of best practices for organizing and building projects using Maven. This section attempts to distill some of this knowledge to help you adopt best practices from the start without having to wade through years of discussions on the Maven mailing lists. 9.6.1. Grouping Dependencies If you have a set of dependencies which are logically grouped together. You can create a project with pom packaging that groups dependencies together. For example, let's assume that your application uses Hibernate, a popular Object-Relational mapping framework. Every project which uses Hibernate might also have a dependency on the Spring Framework and a MySQL JDBC driver. Instead of having to include these dependencies in every project that uses Hibernate, Spring, and MySQL you could create a special POM that does nothing more than declare a set of common dependencies. You could create a project called persistence-deps (short for Persistence Dependencies), and have every project that needs to do persistence depend on this convenience project: Example 9.13. Consolidating Dependencies in a Single POM Project <project> <groupId>org.sonatype.mavenbook</groupId> <artifactId>persistence-deps</artifactId> <version>1.0</version> <packaging>pom</packaging> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>${hibernateVersion}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> 227
  • 247. The Project Object Model <version>${hibernateAnnotationsVersion}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-hibernate3</artifactId> <version>${springVersion}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysqlVersion}</version> </dependency> </dependencies> <properties> <mysqlVersion>(5.1,)</mysqlVersion> <springVersion>(2.0.6,)</springVersion> <hibernateVersion>3.2.5.ga</hibernateVersion> <hibernateAnnotationsVersion>3.3.0.ga</hibernateAnnotationsVersion> </properties> </project> If you create this project in a directory named persistence-deps, all you need to do is create this pom.xml and run mvn install. Since the packaging type is pom, this POM is installed in your local repository. You can now add this project as a dependency and all of its dependencies will be added to your project. When you declare a dependency on this persistence-deps project, don't forget to specify the dependency type as pom. Example 9.14. Declaring a Dependency on a POM <project> <description>This is a project requiring JDBC</description> ... <dependencies> ... <dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>persistence-deps</artifactId> <version>1.0</version> <type>pom</type> </dependency> </dependencies> </project> 228
  • 248. The Project Object Model If you later decide to switch to a different JDBC driver (for example, JTDS), just replace the dependencies in the persistence-deps project to use net.sourceforge.jtds:jtds instead of mysql:mysql-java-connector and update the version number. All projects depending on persistence-deps will use JTDS if they decide to update to the newer version. Consolidating related dependencies is a good way to cut down on the length of pom.xml files that start having to depend on a large number of dependencies. If you need to share a large number of dependencies between projects, you could also just establish parent-child relationships between projects and refactor all common dependencies to the parent project, but the disadvantage of the parent-child approach is that a project can have only one parent. Sometimes it makes more sense to group similar dependencies together and reference a pom dependency. This way, your project can reference as many of these consolidated dependency POMs as it needs. Note Maven uses the depth of a dependency in the tree when resolving conflicts using a nearest-wins approach. Using the dependency grouping technique above pushes those dependencies one level down in the tree. Keep this in mind when choosing between grouping in a pom or using dependenctManagement in a parent POM 9.6.2. Multi-module vs. Inheritance There is a difference between inheriting from a parent project and being managed by a multimodule project. A parent project is one that passes its values to its children. A multimodule project simply manages a group of other subprojects or modules. The multimodule relationship is defined from the topmost level downwards. When setting up a multimodule project, you are simply telling a project that its build should include the specified modules. Multimodule builds are to be used to group modules together in a single build. The parent-child relationship is defined from the leaf node upwards. The parent-child relationship deals more with the definition of a particular project. When you associate a child with its parent, you are telling Maven that a project’s POM is derived from 229
  • 249. The Project Object Model another. To illustrate the decision process that goes into choosing a design that uses inheritance vs. multi-module or both approaches consider the following two examples: the Maven project used to generate this book and a hypothetical project that contains a number of logically grouped modules. 9.6.2.1. Simple Project First, let's take a look at the maven-book project. The inheritance and multi-module relationships are shown in Figure 9.5, “maven-book Multi-module vs. Inheritance”. Figure 9.5. maven-book Multi-module vs. Inheritance When we build this Maven book you are reading, we run mvn package in a multi-module project named maven-book. This multi-module project includes two submodules: book-examples and book-chapters. Neither of these projects share the same parent, they are related only in that they are modules in the maven-book 230
  • 250. The Project Object Model project. book-examples builds the ZIP and TGZ archives you downloaded to get this book's example. When we run the book-examples build from book-examples/ directory with mvn package, it has no knowledge that it is a part of the larger maven-book project. book-examples doesn't really care about maven-book, all it knows in life is that its parent is the top-most sonatype POM and that it creates an archive of examples. In this case, the maven-book project exists only as a convenience and as a aggregator of modules. The book projects do all define a parent. Each of the three projects: maven-book, book-examples, and book-chapters all list a shared "corporate" parent — sonatype. This is a common practice in organizations which have adopted Maven, instead of having every project extend the Super POM by default, some organizations define a top-level corporate POM that serves as the default parent when a project doesn't have any good reason to depend on another. In this book example, there is no compelling reason to have book-examples and book-chapters share the same parent POM, they are entirely different projects which have a different set of dependencies, a different build configuration, and use drastically different plugins to create the content you are now reading. The sonatype POM gives the organization a change to customize the default behavior of Maven and supply some organization-specific information to configure deployment settings and build profiles. 9.6.2.2. Multi-module Enterprise Project Let's take a look at an example that provides a more accurate picture of a real-world project where inheritance and multi-module relationships exist side by side. Figure 9.6, “Enterprise Multi-module vs. Inheritance” shows a collection of projects that resemble a typical set of projects in an enterprise application. There is a top-level POM for the corporation with an artifactId of sonatype. There is a multi-module project named big-system which references sub-modules server-side and client-side. 231
  • 251. The Project Object Model Figure 9.6. Enterprise Multi-module vs. Inheritance What's going on here? Let's try to deconstruct this confusing set of arrows. First, let's take a look at big-system. The big-system might be the project that you would run mvn package on to build and test the entire system. big-system references submodules client-side and server-side. Each of these projects effectively rolls up all of the code that runs on either the server or on the client. Let's focus on the server-side project. Under the server-side project we have a project called server-lib and a multi-module project named web-apps. Under web-apps we have two Java web applications: client-web and admin-web. Let's start with the parent/child relationships from client-web and admin-web to web-apps. Since both of the web applications are implemented in the same web 232
  • 252. The Project Object Model application framework (let's say Wicket), both projects would share the same set of core dependencies. The dependencies on the Servlet API, the JSP API, and Wicket would all be captured in the web-apps project. Both client-web and admin-web also need to depend on server-lib, this dependency would be defined as a dependency between web-apps and server-lib. Because client-web and admin-web share so much configuration by inheriting from web-apps, both client-web and admin-web will have very small POMs containing little more than identifiers, a parent declaration, and a final build name. Next we focus on the parent/child relationship from web-apps and server-lib to server-side. In this case, let's just assume that there is a separate working group of developers which work on the server-side code and another group of developers that work on the client-side code. The list of developers would be configured in the server-side POM and inherited by all of the child projects underneath it: web-apps, server-lib, client-web, and admin-web. We could also imagine that the server-side project might have different build and deployment settings which are unique to the development for the server side. The server-side project might define a build profile that only makes sense for all of the server-side projects. This build profile might contain the database host and credentials, or the server-side project's POM might configure a specific version of the Maven Jetty plugin which should be universal across all projects that inherit the server-side POM. In this example, the main reason to use parent/child relationships is shared dependencies and common configuration for a group of projects which are logically related. All of the projects below big-system are related to one another as submodules, but not all submodules are configured to point back to parent project that included it as a submodule. Everything is a submodule for reasons of convenience, to build the entire system just go to the big-system project directory and run mvn package. Look more closely at the figure and you'll see that there is no parent/child relationship between server-side and big-system. Why is this? POM inheritance is very powerful, but it can be overused. When it makes sense to share dependencies and build configuration, a parent/child relationship should be used. When it doesn't make sense is when there are distinct differences between two projects. Take, for example, the server-side and client-side projects. It is 233
  • 253. The Project Object Model possible to create a system where client-side and server-side inherited a common POM from big-system, but as soon as a significant divergence between the two child projects develops, you then have to figure out creative ways to factor out common build configuration to big-system without affecting all of the children. Even though client-side and server-side might both depend on Log4J, they also might have distinct plugin configurations. There's a certain point defined more by style and experience where you decide that minimal duplication of configuration is a small price to pay for allowing projects like client-side and server-side to remain completely independent. Designing a huge set of thirty plus projects which all inherit five levels of POM configuration isn't always the best idea. In such a setup, you might not have to duplicate your Log4J dependency more than once, but you'll also end up having to wade through five levels of POM just figure out how Maven calculated your effective POM. All of this complexity to avoid duplicating five lines of dependency declaration. In Maven, there is a "Maven Way", but there are also many ways to accomplish the same thing. It all boils down to preference and style. For the most part, you won't go wrong if all of your submodules turn out to define back-references to the same project as a parent, but your use of Maven may evolve over time. 9.6.2.3. Prototype Parent Projects Take the following example shown in Figure 9.7, “Using parent projects as "prototypes" for specialized projects” as another hypothetical and creative way to use inheritance and multi-modules builds to reuse dependencies. 234
  • 254. The Project Object Model Figure 9.7. Using parent projects as "prototypes" for specialized projects Figure 9.7, “Using parent projects as "prototypes" for specialized projects” is yet another way to think about inheritance and multi-module projects. In this example, you have two distinct systems. system-a and system-b each define independent applications. system-a defines two modules a-lib and a-swing. system-a and a-lib both define the top-level sonatype POM as a parent project, but the a-swing project defines swing-proto as a parent project. In this system, swing-proto supplies a foundational POM for Swing applications and the struts-proto project provides a foundational POM for Struts 2 web applications. While the sonatype POM provides high level information such as the groupId, organization information, and build profiles, struts-proto defines all of the dependencies that you need to create a struts application. This approach would work well if your 235
  • 255. The Project Object Model development is characterized by many independent applications which each have to follow the same set of rules. If you are creating a lot of struts applications but they are not really related to one another, you might just define everything you need in struts-proto. The downside to this approach is that you won't be able to use parent/child relationships within the system-a and system-b project hierarchies to share information like developers and other build configuration. A project can only have one parent. The other downside of this approach is that as soon as you have one project that "breaks the mold" you'll either have to override the prototype parent POM or find a way to factor customizations into the shared parent without those customizations affecting all the children. In general, using POMs as prototypes for specialized project "types" isn't a recommended practice. 236
  • 256. Chapter 10. Der Build Lebenszyklus 10.1. Einführung Maven modelliert Projekte als Nomen welche von einem Projekt Objekt Modell (POM) beschrieben werden. Das POM fängt die Identität des Projektes ein: Was beinhaltet das Projekt? Welche Art der Packetierung benötigt das Projekt? Ist das Projekt ein abgeleitetes Projekt? Was sind die Abhängigkeiten? Im vorangegangenen Kapitel haben wir eingeführt wie ein Projekt beschrieben werden kann, was wir nicht eingeführt haben sind die Mechanismen mittels welchen Maven auf diesen Objekten agieren kann. Im Maven Universum werden Verben durch Goals, welche in Maven Plugins verpackt und an Lebenszyklusphasen gebunden sind, dargestellt. Ein Maven Lebenszyklus besteht aus einer Reihe namentlich benannter Lebenszyklusphasen: prepare-resources, compile, package und install neben anderen. Es gibt also eine Phase welche die Kompilierung beschreibt und eine Phase welche die Packetierung umfasst. Darüber hinaus gibt es vorbereitende und nachbereitende Phasen, welche benutzt werden, um Goals welche vor oder gerade nach einer bestimmten Phase abgearbeitet werden müssen zu registrieren. Sobald Sie Maven aufrufen um einen Build zu erstellen, rufen Sie tatsächlich Maven dazu auf eine bestimmte Kette von Phasen zu durchlaufen und alle an eine Phase gebndene Goals abzuarbeiten. Ein Build Lebenszyklus ist eine Abfolge von Phasen welche existiert, um einer Anzahl Goals zu ordnen. Diese Goals sind gewählt und an Phasen gebunden abhängig vom entsprechenden Packetierungstyp des Projektes in Verarbeitung. In Maven gibt es drei Standard Lebenszyklen: clean, default (auch build genannt) und site. Dieses Kapitel wird erläutern, wie Maven Goals an Lebenszyklusphasen bindet und wie Lebenszyklen angepasst werden können. Die Standard Lebenszyklusphasen werden ebenfalls eingeführt. 10.1.1. Lebenszyklus: clean Der erste Lebenszyklus für welchen Sie sich interessieren werden ist zugleich der 237
  • 257. Der Build Lebenszyklus einfachste Lebenszyklus von Maven. Der Aufruf von mvn clean started den Lebenszyklus clean welcher aus drei Phasen besteht: • pre-clean • clean • post-clean Die interessante Phase des Lebenszyklus clean ist die Phase clean. Das Goal clean (clean:clean) des Plugins clean wird an die Phase clean des Lebenszyklus clean gebunden. (Noch ganz sauber oder was!?!) Das Goal clean:clean löscht die Ergebnisse des vorgehenden Builds indem es das Zielverzeichnis löscht. Sollten Sie das Zielverzeichnis nicht weiter angepasst haben, so ist dieses Zielverzeichnis das Verzeichnis ${basedir}/target wie dies im Super POM definiert ist. Die Ausführung des Goals clean:clean wird nicht direkt mit dem Aufruf mvn clean:clean gestartet, statt dessen wird die Phase clean des Lebenszyklus zur Ausführung gebracht. Die Ausführung der Phase clean ermöglicht Maven auch Goals welche an die Phase pre-clean gebunden sind auszuführen. Ein Beispiel: nehmen wir an Sie möchten das Goal antrun:run während der Phase pre-clean anstossen, um eine Nachricht auszugeben, oder um ein Archiv des Project Build Verzeichnis anzulegen bevor dieses gelöscht wird. Der Aufruf von clean:clean wird den Lebenszyklus gar nicht ausführen, jedoch wird die Angabe der Phase clean auslösen, dass die drei Lebenszyklusphasen bis zur Phase clean ausgeführt werden. Example 10.1, “Auslösen eines Goals in der Phase pre-clean” (Beispiel 10.1: Auslösen eines Goals in der Phase pre-clean) zeigt beispielhaft eine Build Konfiguration welche das Goal antrun:run and die Phase pre-clean bindet um eine Warnung auszugeben, welche mitteilt, dass der Projektartefakt gelöscht werden soll. In diesem Beispiel wird das Goal antrun:run benutzt, um ein beliebiges Ant Kommando auszuführen um zu prüfen ob es den Artefakten gibt. Besteht der Artefakt und soll gelöscht werden, so wird dies auf der Konsole ausgegeben. Example 10.1. Auslösen eines Goals in der Phase pre-clean 238
  • 258. Der Build Lebenszyklus <project> ... <build> <plugins>... <plugin> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>file-exists</id> <phase>pre-clean</phase> <goals> <goal>run</goal> </goals> <configuration> <tasks> <!-- adds the ant-contrib tasks (if/then/else used below) --> <taskdef resource="net/sf/antcontrib/antcontrib.properties" /> <available file="${project.build.directory}/${project.build.finalName}.${project.packagin property="file.exists" value="true" /> <if> <not> <isset property="file.exists" /> </not> <then> <echo>No ${project.build.finalName}.${project.packaging} to delete</echo> </then> <else> <echo>Deleting ${project.build.finalName}.${project.packaging}</echo> </else> </if> </tasks> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>ant-contrib</groupId> <artifactId>ant-contrib</artifactId> <version>1.0b2</version> </dependency> </dependencies> </plugin> </plugins> </build> </project> Das Ausführen von mvn clean auf einem Projekt mit der oben gegebenen Build 239
  • 259. Der Build Lebenszyklus Konfiguration wird eine Ausgabe ähnlich der unten wiedergegebenen erzeugen. [INFO] Scanning for projects... [INFO] ---------------------------------------------------------------------- [INFO] Building Your Project [INFO] task-segment: [clean] [INFO] ---------------------------------------------------------------------- [INFO] [antrun:run {execution: file-exists}] [INFO] Executing tasks [echo] Deleting your-project-1.0-SNAPSHOT.jar [INFO] Executed tasks [INFO] [clean:clean] [INFO] Deleting directory ~/corp/your-project/target [INFO] Deleting directory ~/corp/your-project/target/classes [INFO] Deleting directory ~/corp/your-project/target/test-classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 second [INFO] Finished at: Wed Nov 08 11:46:26 CST 2006 [INFO] Final Memory: 2M/5M [INFO] ------------------------------------------------------------------------ Zusätzlich zur Möglichkeit Maven so zu konfigurieren, dass es ein Goal während der Phase pre-clean ausführt wird, kann man das Clean Plugin so anzupassen, dass es Dateien über denen des Zielverzeichnisses hinaus löscht. Sie können das Plugin so konfigurieren, dass es bestimmte in einer Aufzählung angegebene Dateien löscht. Das unten aufgeführte Beispiel konfiguriert clean so, dass es unter Einsatz der standard Ant Wildcards (* und **) alle .class Dateien in einem Verzeichnis mit dem Namen target-other/ löscht Example 10.2. Anpassen des Verhaltens des Clean Plugin <project> <modelVersion>4.0.0</modelVersion> ... <build> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <configuration> <filesets> <fileset> <directory>target-other</directory> <includes> <include>*.class</include> 240
  • 260. Der Build Lebenszyklus </includes> </fileset> </filesets> </configuration> </plugin> </plugins> </build> </project> 10.1.2. Standard Lebenszyklus: default Die meisten Maven Benutzer sind mit dem Standardlebenszyklus vertraut. Dieser bildet das algemeine Modell des Build Prozesses einer Software Applikation ab. Die erste Phase ist validate und die letzte Phase deploy. Die Phasen des Maven Standard Lebenszyklus sind in Table 10.1, “Maven Lebenszyklusphasen” (Tabelle 10.1: Maven Lebenszyklusphasen) aufgeführt. Table 10.1. Maven Lebenszyklusphasen Lebenszyklus Phase Beschreibung validate Validieren, dass ein Projekt komplett ist, und dass alle notwendigen Informationen um einen Build zu erstellen verfügbar sind. generate-sources Generieren jeglicher Quellen welche benötigt werden um in ein Kompilat eingefügt zu werden. process-sources Verarbeiten der Quellen, zum Beispiel um Werte zu Filtern generate-resources Generieren der Ressourcen welche einem Paket beigefügt werden process-resources Kopieren und Verarbeiten der Ressourcen, Ablage in das Zielverzeichnis 241
  • 261. Der Build Lebenszyklus Lebenszyklus Phase Beschreibung bereit um zu packetieren. compile Kompilieren der Quellen des Projektes. process-classes Nachverarbeitung der generierten Dateien der Kompilierung, zum Beispiel um Bytecode Modifikationen einzufügen. generate-test-sources Generieren von Test Quellcode welcher in das Kompilat eingefügt werden muss. process-test-sources Verarbeiten des Test Quellcodes, z.B. um Werte zu filtern generate-test-resources Erstellen der Test Ressourcen process-test-resources Verarbeiten und Kopieren der Ressourcen in das Test-Zielverzeichnis test-compile Kompilieren der Test Quelldateien in das Test Zielverzeichnis test Abarbeiten der Tests unter Einsatz eines geeigneten Unit Test Frameworks. Diese Tests sollten nicht voraussetzen, dass der Code Packetiert und Deployed wird. prepare-package Ausführen jeglicher notwendiger Schritte, um die Packetierung vorzubereiten, dies führt oftmals zu einer unpacketierten verarbeiteten Version des Pakets (Teil von Maven 2.1+) package Paketieren des Kompilates in ein verteilbares Format wie z.B. JAR, WAR oder EAR pre-integration-test Ausführen vorbereitender Schritte für Integrationstests. Dies kann die Erstellung 242
  • 262. Der Build Lebenszyklus Lebenszyklus Phase Beschreibung des notwendigen Testumfeldes umfassen. integration-test Verarbeiten und deployen des Packages in eine Umgebung in welcher Integrationstests ausgeführt werden können. post-integration-test Ausführen von abschliessenden Schritten nachdem die Integrationstests ausgeführt wurden, dies kann auch die Bereinigung der Umgebung beinhalten. verify Jegliche Prüfungen ausführen um sicherzustellen dass das Package die erforderlichen Kriterien erfüllt. install Installieren des Packages in das lokale Repository um dort lokal als Abhängigkeit für andere Projekte bereitzustehen. deploy Kopiert das endgültige Paket in das ferne Repository um dieses anderen Entwicklern und Projekten bereitzustellen. Dieser Schritt ist gewöhnlich nur bei offiziellen Versionen von Bedeutung. 10.1.3. Lebenszyklus: site Maven kann mehr als nur Software Artefakte von Projekten erstellen. Es kann auch Projektdokumentation und Reports bezüglich dem Projekt oder eine Reihe von Projekten, erstellen. Projektdokumentation und Site Generierung haben einen eigenen Lebenszyklus zugeordnet, welcher vier Phasen kennt: 243
  • 263. Der Build Lebenszyklus 1. pre-site 2. site 3. post-site 4. site-deploy Die an den Lebenszyklus site gebundenen Goals sind: 1. site - site:site 2. site-deploy -site:deploy Die Art der Packetierung ändert im allgemeinen diesen Lebenszyklus nicht, da Packetierungstypen sich primär mit der Artefakt Erzeugung befassen, nicht mit der Art des Sites der Generiert wird. Das Site Plugin started die Ausführung der Doxia Dokument Generierung und anderer Report generierender Plugins. Sie können einen Site eines Maven Projekts erstellen indem Sie das folgende Kommando absetzen: $ mvn site Die Generierung von Sites mittels Maven wird in Chapter 15, Site Generation (Kapitel 15: Site Generierung) weiter vertieft. 10.2. Package-spezifische Lebenszyklen Die genauen Goals welche an eine Phase gebunden werden sind bestimmt durch die Menge der Goals welche durch die projektspezifische Packetierung festgelegt wird. Ein Projekt welches vom Packetierungs Typ jar ist hat einen anderen Standardsatz Goals als das ein Projekt vom Typ war hat. Das Element packaging beeinflusst den Satz der Goals welche beim Build abgearbeitet werden müssen. Um Ihnen beispielhaft vorzuführen wie sehr packaging den Build beeinflusst, hier zwei Projekte: Eines mit packaging-Element pom und eines vom Typ jar. Das Projekt vom Typ pom wird das Goal site:attach-descriptor in der Phase 244
  • 264. Der Build Lebenszyklus packaging abarbeiten, das Projekt vom Typ jar wird satt dessen das Goal jar:jar abarbeiten. The following sections describe the lifecycle for all built-in packaging types in Maven. Use these sections to find out what default goals are mapped to default lifecycle phases. Der folgende Abschnitt beschreibt den Lebenszyklus aller standardmässig verfügbaren Package Typen in Maven. Benutzen Sie diesen Abschnitt als Referenz um herauszufinden welche Goals standardmässig an welche Phase gebunden werden. 10.2.1. jar JAR ist der Standard Paketierungstyp, es ist der am häufigsten benutzte und die am meisten in Lebenszyklen angetroffene Konfiguration. Die Standard Goals des JAR Lebenszyklus sind in Table 10.2, “Standard Goals des JAR Packaging” (Tabelle 10.2: Standard Goals des JAR Packaging) aufgeführt. Table 10.2. Standard Goals des JAR Packaging Lebesnzyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package jar:jar install install:install deploy deploy:deploy 245
  • 265. Der Build Lebenszyklus 10.2.2. pom POM ist der einfachste Paketierungstyp. Der generierte Artefakt ist sich selbst, kein JAR, WAR oder EAR. Es gibt keinen Code zu Testen oder Kompilieren, und es gibt auch keine Ressourcen zu verarbeiten. Die Standard Goals des POM Lebenszyklus sind in Table 10.3, “Standard Goals des POM Packaging” (Tabelle 10.3: Standard Goals des POM Packaging) aufgeführt: Table 10.3. Standard Goals des POM Packaging Lebenszyklus Phase Goal package site:attach-descriptor install install:install deploy deploy:deploy 10.2.3. plugin Dieser Paketierungstyp ist ähnlich dem der JAR Packetierung mit drei Unterschieden: plugin:descriptor, plugin:addPluginArtifactMetadata, und plugin:updateRegistry. Diese Goals generieren eine Descriptor Datei und führen gewisse Änderungen an den Repository Daten aus. Die Standard Goals des plugin Lebenszyklus sind in Table 10.4, “Standard Goals des plugin Packaging” (Tabelle 10.4: Standard Goals des plugin Packaging) aufgeführt: Table 10.4. Standard Goals des plugin Packaging Lebenszyklus Phase Goal generate-resources plugin:descriptor process-resources resources:resources compile compiler:compile process-test-resources resources:testResources 246
  • 266. Der Build Lebenszyklus Lebenszyklus Phase Goal test-compile compiler:testCompile test surefire:test package jar:jar, plugin:addPluginArtifactMetadata install install:install, plugin:updateRegistry deploy deploy:deploy 10.2.4. ejb Enterprise Java Beans (EJBs) sind eine häufing angewandte Technologie für die Modell getriebene Entwicklung von Enterprise Java Applikationen. Maven unterstützt EJB 2 und EJB 3, obschon man das Plugin speziell konfigurieren muss um EJB 3 zu unterstützen. Standardmässig wird EJB 2.1 umgesetzt, wobei das Plugin nach bestimmten EJB Konfigurationsdateien sucht. Die Standard Goals des ejb Lebenszyklus sind in Table 10.5, “Standard Goals des ejb Packaging” (Tabelle 10.5: Standard Goals des ejb Packaging) aufgeführt: Table 10.5. Standard Goals des ejb Packaging Lebenszyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package ejb:ejb install install:install deploy deploy:deploy 247
  • 267. Der Build Lebenszyklus 10.2.5. war Der Paketeierungstyp war ist ähnlich dem des Types jar oder ejb. Die Ausnahme hierbei ist das Goal war:war. Bitte beachten Sie, dass das war Plugin eine web.xml Konfigurationsdatei im Verzeichnis src/main/webapp/WEB-INF voraussetzt. Die Standard Goals des war Lebenszyklus sind in Table 10.6, “Standard Goals des war Packaging” (Tabelle 10.6: Standard Goals des war Packaging) aufgeführt: Table 10.6. Standard Goals des war Packaging Lebenszyklus Phase Goal process-resources resources:resources compile compiler:compile process-test-resources resources:testResources test-compile compiler:testCompile test surefire:test package war:war install install:install deploy deploy:deploy 10.2.6. ear EARs sind wohl das einfachste Java EE Konstrukt, bestehen Sie ja lediglich aus der Deployment Beschreibungsdatei (Descriptor) application.xml, Ressourcen und einigen Modulen. Das ear Plugin hat besitzt ein Goal mit dem Namen generate-application.xml welche die applicaiton.xml Datei auf der Basis des POM generiert. Die Standard Goals des ear Lebenszyklus sind in Table 10.7, “Standard Goals des POM Packaging” (Tabelle 10.7: Standard Goals des ear Packaging) aufgeführt: 248
  • 268. Der Build Lebenszyklus Table 10.7. Standard Goals des POM Packaging Lebenszyklus Phase Goal generate-resources ear:generate-application-xml process-resources resources:resources package ear:ear install install:install deploy deploy:deploy 10.2.7. Andere Packetierungs Typen Dies ist keine abschliessende Liste aller für Maven verfügbarer Paketierungstypen. Es bestehen eine ganze Anzahl Paketierungs Formate verfügbar mittels externer Projekte und Plugins: der Typ native archive (NAR), die Typen SWF und SWC für Projekte welche Adobe Flash und Flex Inhalte erzeugen und viele andere. Sie können ebenfalls eigene Benutzer spezifische Paketierungs Typen definieren und den standardmässigen Lebenszyklus Ihren spezifischen Bedürfnissen anpassen. Um einen derart angepassten Paketierungstypen einzusetzen braucht es zwei Dinge: ein Plugin welches den angepassten Lebenszyklus definiert und ein Repository welches dieses Plugin beinhaltet. Manche angepassten Paketierungs Typen sind in Plugins definiert und über das zentrale Repository verfügbar. Hier ein Beispiel eines Projekts welches sich auf das Israfil Flex Plugin stützt und einen benutzerdefinierten Paketierungstyp SWF benutzt um die Ausgabe aus Adobe Flex Quellen zu erstellen. Example 10.3. Benutzerdefinierter Packetierungs -Type für Adobe Flex (SWF) <project> ... <packaging>swf</packaging> ... 249
  • 269. Der Build Lebenszyklus <build> <plugins> <plugin> <groupId>net.israfil.mojo</groupId> <artifactId>maven-flex2-plugin</artifactId> <version>1.4-SNAPSHOT</version> <extensions>true</extensions> <configuration> <debug>true</debug> <flexHome>${flex.home}</flexHome> <useNetwork>true</useNetwork> <main>org/sonatype/mavenbook/Main.mxml</main> </configuration> </plugin> </plugins> </build> ... </project> In Section 17.6, “Plugins and the Maven Lifecycle” (Abschnitt 17.6 Plugins und der Maven Lebenszyklus) führen wir aus, wie Sie Ihren eigenen Paketierungstypen mit angepasstem Lebenszyklus erstellen. Das Beispiel sollte Ihnen aufzeigen was notwendig ist, um einen angepassten Paketierungstypen einzusetzen. Das einzige was Sie tatsächlich dazu beitragen müssen ist, das Plugin welches den angepassten Paketierungstyp mitbringt zu referenzieren. Das Israfil FLex Plugin ist ein Drittpartei Maven Plugin welches von Google Code bereitgehalten wird. Für weitere Informationen bezüglich des Plugins, oder den Schritten die notwendig sind um Adobe Flex zu Kompilieren, wenden Sie sich bitte an http://code.google.com/code.google.com/p/israfil-mojo. Das Plugin liefert die folgenden Lebenszyklen des SWF Packetierungs Typ. Table 10.8. Standard Lebenszyklus für SWF Packetierung Lebenszyklus Phase Goal compile flex2:compile-swc install install deploy deploy 250
  • 270. Der Build Lebenszyklus 10.3. Gebräuchliche Lebenszyklus Goals Viele der Paketierungs-Lebenszyklen haben ähnliche Goals. Wenn Sie die Goals des war und des jar Lebenszyklus vergleichen, so werden Sie feststellen, dass diese sich lediglich in der Phase package unterscheiden. Die Phase package des Lebenszyklus war ruft war:war auf, die des Lebenszyklus jar ruft jar:jar auf. Die meisten der Lebenszyklen mit welchen Sie in Kontakt kommen werden teilen sich einige gemeinsame Lebenszyklus Goals um Ressourcen zu Managen, Tests Abzuarbeiten sowie Quellen zu Kompilieren. In diesem Abschnitt wollen wir nun einige dieser gemeinsamen Lebenszyklus Goals genauer betrachten. 10.3.1. Ressourcen Verarbeiten Die meisten Lebenszyklen binden das Goal resources:resources an die Phase process-resources. Diese Phase „verarbeitet“ Ressourcen und kopiert diese in das Zielverzeichnis. So Sie dieses nicht angepasst haben, gelten die im Super POM festegelegten Standardverzeichnisse, das heisst, Maven kopiert die Dateien von ${basedir}/src/main/resources nach ${project.build.outputDirectory}. Zusätzlich zu der reinen Kopieraktion kann Maven auch die Ressourcen filtern, d.h. Sie können gewisse Token in den Dateien ersetzen. Genau wie Variablen innerhalb der POM Datei mittels ${ … } markiert sind, so können Sie die gleiche Syntax einsetzen um Variablen innerhalb von Ressourcen zu referenzieren. Im Zusammenspiel mit Build-Profilen kann diese Eigenschaft dazu benutzt werden, um Builds für unterschiedliche Zielplattformen zu erstellen. Dies trifft man häufig in Umgebungen an, welche für das gleiche Projekt unterschiedliche Builds für das Development, Test, Integrationstest und Produktion erstellen müssen. Bezüglich weiter Informationen zu Build-Profilen verweisen wir auf Chapter 11, Build Profiles (Kapitel 11: Build Profile). Um Ressource Filterung zu veranschaulichen, stellen Sie sich vor, Sie habe ein Projekt mit einer XML-Datei unter /src/main/resources/META-INF/services.xml. Hierbei möchten Sie eine Konfigurationseinstellung in eine Property-Datei auslagern. In anderen Worten, Sie möchten zum Beispiel die Werte der JDBC URL, Benutzername und Passwort 251
  • 271. Der Build Lebenszyklus der Datenbank nicht direkt in der service.xml Datei ablegen. Statt dessen möchten Sie eine Property Datei mit den variablen Werten Ihres Programms anlegen. Das ermöglicht Ihnen, alle Konfigurationsparameter in einer Datei zusammenzufassen, um diese an einem Ort zu ändern sobald Sie von einer Umgebung in eine andere wechseln. Sehen Sie sich also zuallererts den Inhalt der entsprechenden Datei services.xml unter /src/main/resources/META-INF an: Example 10.4. Einsatz von Properties in den Projektressourcen <service> <!-- This URL was set by project version ${project.version} --> <url>${jdbc.url}</url> <user>${jdbc.username}</user> <password>${jdbc.password}</password> </service> Die in der XML Datei zum Einsatz kommende Syntax ist die selbe welche bereits in der POM Datei zum Einsatz kommt. Die erste zum Einsatz kommende Variable ist sogar eine Maven-implizite Variable welche vom POM bereitgestellt wird. Die Variable projekt erlaubt den Zugriff auf die Werte des POM. Die folgenden drei Referenzen sind jdbc.url, jdbc.username und jdbc.passwort. Diese benutzerdefinierten Variablen wurden in der Property Datei unter src/main/filters/default.properties definiert. Example 10.5. Datei default.properties unter src/main/filters jdbc.url=jdbc:hsqldb:mem:mydb jdbc.username=sa jdbc.password= Um Ressourcenfilterung der Datei default.properties einzustellen, müssen innerhalb des POM zwei Dinge gesetzt werden: eine Liste der properties-Dateien innerhalb des Elements filters der Buildkonfiguration, sowie eine Maven Einstellung dass das Ressourcenverzeichnis der Filterung unterliegt. Standardmässig ist dieses Verhalten abgestellt und Ressourcen werden direkt in das Ausgabeverzeichnis kopiert, oder Maven übergeht diesen Schritt ganz. Diese 252
  • 272. Der Build Lebenszyklus Einstellung ist standardmässig so gewählt, damit es nicht vorkommt, dass ohne ersichtlichen Grund alle bestehenden ${...}-Referenzen ersetzt werden, welche Sie gar nicht ersetzen wollten. Example 10.6. Ressourcen Filtern (Ersetzen von Properties) <build> <filters> <filter>src/main/filters/default.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> Wie mit allen Verzeichnissen unter Maven, ist es nicht Bedingung, das das Ressourcenverzeichnis unbedingt unter src/main/ressources liegt. Das ist lediglich der entsprechende Standardpfad wie er im Super POM festgelegt ist. Ebenfalls ist es gut zu wissen, dass Sie nicht gezwungen sind, alle Ihre Ressourcen in ein einzigen Verzeichniss zu konsolidieren. Sie können Ressourcen auch in getrennten Verzeichnissen unter src/main ablegen. Nehmen Sie einmal an, Sie arbeiten an einem Projekt mit hunderten von XML-Dokumenten und hunderten von Bildern. Anstatt nun alle diese Dateien unter dem Verzeichnis src/main/ressources gemischt abzulegen, ist es möglich, zwei Verzeichnisse src/main/xml und src/main/images anzulegen um die Inhalte dort abzulegen. Um weitere Verzeichnisse Ihrer Liste von Ressourceverzeichnissen zuzufügen, müssen Sie folgendes Element resource Ihrer Buildkonfiguration zufügen: Example 10.7. Festlegen zusätzlicher Ressource Verzeichnisse <build> ... <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> 253
  • 273. Der Build Lebenszyklus <directory>src/main/xml</directory> </resource> <resource> <directory>src/main/images</directory> </resource> </resources> ... </build> Sollten Sie ein Projekt erstellen, welches eine Befehlszeilenapplikation erzeugt, so finden Sie sich oftmals in einer Situation wieder, in welcher Sie einfache Shell Skripte erstellen, welche ein Jar-Dateien des Builds referenzieren. Sollten Sie dann das Assembly Plugin benutzen um ein Distributionspaket in Form eines ZIP- oder TAR-Archivs zu erstellen, so macht es Sinn, alle ihre Skripte in einem Verzeichniss wie etwa src/main/command zusammenzufassen. Im folgenden Ausschnitt einer POM Ressourcenkonfiguration können Sie sehen, wie wir Ressourcenfilterung und eine Referenz auf eine Projektvariable benutzen können, um den endgültigen Ausgabenamen des jar-Archives zu definieren. Weitere Informationen bezüglich des Maven Assembly Plugins finden Sie in Chapter 12, Maven Assemblies (Kapitel 12: Maven Assemblies). Example 10.8. Filterung von Skript Ressourcen <build> <groupId>org.sonatype.mavenbook</groupId> <artifactId>simple-cmd</artifactId> <version>2.3.1</version> ... <resources> <resource> <filtering>true</filtering> <directory>${basedir}/src/main/command</directory> <includes> <include>run.bat</include> <include>run.sh</include> </includes> <targetPath>${basedir}</targetPath> </resource> <resource> <directory>${basedir}/src/main/resources</directory> </resource> </resources> ... </build> 254
  • 274. Der Build Lebenszyklus Der Aufruf von mvn process-resources dieses Projektes erzeugt zwei Dateien, run.sh sowie run.bat, beide unter ${basedir}. Wir haben innerhalb eines Elements resource diese zwei Dateien herausgepickt, Filterung eingestellt, und den Zielpfad (targetPath) auf ${basedir} gesetzt. In einem zweiten Elememt Resource haben wir definiert, dass die Ressourcen des Standard Ressourcenpfades in das Standardausgabeverzeichnis kopiert werden ohne diese zu filtern. Example 10.8, “Filterung von Skript Ressourcen” (Beispiel 10.8: Filterung von Skript Ressourcen) zeigt beispielhaft auf, wie Sie zwei Resourceverzeichnisse bestimmen und diese mit unterschiedlichen Filterkriterien sowie Zielverzeichnissen versehen. Das Projekt des Example 10.8, “Filterung von Skript Ressourcen” (Beispiels 10.8: Filterung von Skript Ressourcen) würde eine Datei run.bat unter src/main/command mit folgendem Inhalt enthalten: @echo off java -jar ${project.build.finalName}.jar %* Nach der Ausführung von mvn process-resources, erscheint eine Datei run.bat im Verzeichnis ${basedir} mit folgendem Inahlt: @echo off java -jar simple-cmd-2.3.1.jar %* Die Möglichkeit unterschiedliche Filter für speziefische Untergruppen von Ressourcen zu setzen, ist ein weiterere Grund weshalb Projekte mit vielen verschiedenen Arten von Artefakten es oftmals als hilfreich erachten, diese in verschiedenen Verziechnissen abzulegen. Die Alternative zum Ablegen verschiedener Ressourcetypen in unterschiedlichen Verzichnissen ist der Einsatz von komplexeren Sätzen von Ein- und Ausschlusskriterien, um die entsprechenden Ressource Dateien welche einem Muster genügen, zu spezifizieren. 10.3.2. compile Die allermeisten Lebenszyklen binden das Goal compile des Compiler Plugin an die Phase compile. Diese Phase ruft compile:compile auf, welches konfiguriert ist, alle Quellen zu verarbeiten und den resultierenden Bytecode im Build Ausgabeverzeichniss abzulegen. Sollten Sie die Standardeinstellungen des 255
  • 275. Der Build Lebenszyklus SuperPOM nicht angepasst haben, so wird compile:compile alle Quellen unter src/main/java verarbeiten und das Ergebnis unter /target/classes ablegen. Das Compile Plugin ruft javac™ auf, und hat die Standardeinstellungen 1.3 und 1.1, in anderen Worten, das Compiler Plugin geht davon aus, dass Sie Java 1.3 konformen Code erzeugen und diesen an eine Java Virtual Machine 1.1 addressieren. Sollten Sie diese Einstellungen verändern wollen, so müssen sie die Ziel und Quellkonfiguration ihres Projektes dem Compiler Plugin mit auf den Weg geben. Sie tun dies mittels einem Eintrag in der POM Datei wie dies in Example 10.9, “Einstellen der Quell- und Zielkonfiguration des Compiler Plugins” (Beispiel 10.9: Einstellen der Quell- und Zielkonfiguration des Kompiler Plugins). Example 10.9. Einstellen der Quell- und Zielkonfiguration des Compiler Plugins <project> ... <build> ... <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> ... </build> ... </project> Beachten Sie, dass wir das Compiler Plugin konfigurieren, und nicht das spezifische Goal compile:compile. Um nur das spezifische Goal compile:compile zu konfigurieren, müssten wir das Element configuration unterhalb eines Elements execution des Goals compile:compile spezifizieren. Wir haben aber das Quell-und Zielverzeichnis des Compile Plugins konfiguriert, denn wir wollten nicht nur dieses eine Goal konfigurieren. Das Compiler Plugin kommt ebenfalls zum Einsatz, wenn Maven die Testquellen mittels dem Goal 256
  • 276. Der Build Lebenszyklus compile:testCompile verarbeitet. Die Definition des Plugin erlaubt uns die Konfiguration einmal auf der Pluginebene für alle Goals vorzunehmen. Sollten Sie den Ort Ihrer Quelldateien umdefinieren müssen, so können Sie dies durch die Veränderung der Build Konfiguration erreichen. Sollten Sie zum Beispiel ihre Quellen unter src/java statt src/main/java ablegen, und sollten das Ergebnis unter classes anstatt target/classes ablegen wollen, so können Sie jederzeit die Standarwerte sourceDirectory (Quellverzeichnis) des SuperPOM übersteuern. Example 10.10. Übersteuern des Standard Quellverzeichnisses <build> ... <sourceDirectory>src/java</sourceDirectory> <outputDirectory>classes</outputDirectory> ... </build> Warning Obschon es Gründe geben mag die dafür sprechen, Maven ihren Wünschen bezüglich Verzeichnisstruktur anzupassen, so können wir hier nicht stark genug betonen, dass es besser ist Ihre eingene Struktur zugunsten der Maven Standardverzeichnisstruktur aufzugeben. Dies ist keinesfalls darin begründet, dass wir Sie von dieser Struktur bekehren wollen, sondern es ist einfacher für andere Entwickler, ihre Projektstruktur zu verstehen, sollte diese den grundlegenden Konventionen folgen. Vergessen Sie schon den Ansatz! - Tun Sie sich diesen Gefallen. 10.3.3. Verarbeiten von Test Ressourcen Die Phase process-test-resources lässt sich kaum von der Phase process-ressources unterscheiden. Es gibt einige triviale Unterschiede des POM, alles andere ist gleich. Sie können Test Ressourcen filtern wie die anderen 257
  • 277. Der Build Lebenszyklus Ressourcen auch. Der standardmässige Ort der Testressourcen ist im Super POM mit src/test/resources gesetzt und das standardmässige Ausgabeverzeichnis von Testressourcen auf target/test-classes wie dies unter ${project.build.testOutputDirectory} definiert ist. 10.3.4. Kompilieren der Test Klassen (testCompile) Die Phase test-compile ist beinahe identisch mit der Phase compile. Der einzige Unterschied ist, dass test-compile das Goal compile:testCompile anzieht, um Testquellen vom Quellverzeichnis anzuziehen und die Ausgabe im Test-Ausgabeverzeichnis abzulegen. Sollten Sie die Standardverzeichnisse des SuperPOM nicht übersteuert haben, so wird compile:Test die Sourcen aus dem Verzeichnis src/test/java in das Verzeichnis target/test-classes kompilieren. Wie mit den Quellverzeichnissen gilt, sollten Sie den Ort der Test Verzeichnisse ändern wollen, so können Sie dies tun indem Sie testSourceDirectory und testOutputDirectory anpassen. Sollten Sie Testquellen unter src-test/ anstatt src/test/java ablegen und den Bytecode unter classes-test/ anstatt target/test-classes ablegen wollen, so wüden Sie auf die folgende Konfiguration vornehmen. Example 10.11. Übersteuern des Ortes der Testquellen und -ausgabe <build> ... <testSourceDirectory>src-test</testSourceDirectory> <testOutputDirectory>classes-test</testOutputDirectory> ... </build> 10.3.5. test Die meisten Lebenszyklen binden das Goal test des Surefire Plugin an die Phase test. Das Surefire Plugin ist das UnitTest Plugin von Maven. Das Standardverhalten des Surefire Plugin ist, nach allen Klassen welche im Test 258
  • 278. Der Build Lebenszyklus Quellenverzeichnis auf *Test enden zu suchen, um diese als JUnit Tests auszuführen. Das Surefire Plugin kann auch konfiguriert werden die Tests als TestNG Unit Test abzuarbeiten. Nach der Ausführung von mvn test sollten Sie feststellen können, das das Surefire Plugin eine Anzahl Reports unter target/surefire-reports ablegt. Dieses Reportverzeichnis beinhaltet zwei Dateien für jeden ausgeführten Test: eine XML Datei welche Informationen bezüglich des Tests enthält und eine Textdatei welche die Ausgabe des UnitTest enthält. Sollte ein Problem während der Phase test auftreten und ein Unit Test nicht bestehen, so können Sie die Ausgabe von Maven zusammen mit dieser Datei benutzen, um den Grund des Abbruchs zu finden. Dieses Verzeichnis surefire-reports/ kommt ebenfalls zum Einsatz während der Site Generierung wobei eine einfach zu lesende Zusammenfassung der Unit Tests des Projektes erstellt wird. Sollten Sie an einem Projekt arbeiten, bei welchem einige abbrechende Unit Test bestehen, Sie möchten aber dennoch eine Ausgabe erzeugen, so müssen Sie das Surefire Plugin so konfigurieren, dass dieses die Ausführung beim Auftreten eines Fehlers nicht abbricht. Das Standardverhalten ist, dass der Build beim ersten auftreten eines Unit Test Fehlers abbricht. Um dieses Verhalten zu ändern, müssen Sie das Property testFailureIgnore des SureFire Plugin auf true setzen. Example 10.12. Konfiguratio