Pobierz wersję artefaktu Maven w czasie wykonywania

177

Zauważyłem, że w pliku JAR artefaktu Mavena atrybut project.version jest zawarty w dwóch plikach:

META-INF/maven/${groupId}/${artifactId}/pom.properties
META-INF/maven/${groupId}/${artifactId}/pom.xml

Czy istnieje zalecany sposób czytania tej wersji w czasie wykonywania?

Armand
źródło
Zobacz stackoverflow.com/a/26589696/52176
Leif Gruenwoldt

Odpowiedzi:

265

Nie powinieneś mieć dostępu do plików specyficznych dla Mavena, aby uzyskać informacje o wersji dowolnej biblioteki / klasy.

Możesz po prostu użyć, getClass().getPackage().getImplementationVersion()aby uzyskać informacje o wersji, które są przechowywane w plikach .jar MANIFEST.MF. Na szczęście Maven jest wystarczająco inteligentny. Niestety, Maven domyślnie nie zapisuje również prawidłowych informacji w manifeście!

Zamiast tego należy zmodyfikować <archive>element konfiguracyjny maven-jar-pluginto set addDefaultImplementationEntriesi addDefaultSpecificationEntriesto true, na przykład:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

Idealnie taką konfigurację należy wprowadzić do firmy pomlub innej bazy-pom.

Szczegółową dokumentację <archive>elementu można znaleźć w dokumentacji Maven Archive .

Joachim Sauer
źródło
6
niestety nie każdy program ładujący klasy wydaje się ładować te właściwości z pliku manifestu (pamiętam, że miałem problemy z Tomcat w tym przypadku).
dwegener
@avithan: naprawdę? Nigdy nie miałem problemu z Tomcat z takim podejściem. Myślę też, że program ładujący klasy, który ignoruje manifest, prawdopodobnie nie jest zgodny.
Joachim Sauer
@JoachimSauer ok, myliłem się. Obecnie wygląda na to, że działa świetnie na HotSpot, ale nie działa niezawodnie na OpenJDK.
Odniosę
@avithan to jest dla mnie istotne (i nie widziałem, co zgłaszasz) - czy otrzymałeś już szczegółowe informacje?
Thorbjørn Ravn Andersen
4
Niestety to nie działa, jeśli projekt jest uruchamiany z Eclipse lub przy użyciu "mvn exec: java".
Jaan
77

Aby odpowiedzieć na powyższą odpowiedź, stwierdziłem, że w przypadku .warartefaktu muszę zastosować równoważną konfigurację maven-war-pluginzamiast maven-jar-plugin:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.1</version>
    <configuration>
        <archive>                   
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
</plugin>

Ta dodatkowa informacja do wersji MANIFEST.MFw Project .jar(wliczone w WEB-INF/libz .war)

Obrabować
źródło
3
<archiveClasses> true </archiveClasses> spowodowało błąd w moim przypadku. Ale problem został rozwiązany stackoverflow.com/questions/14934299/ ...
Paul Verest
10
Kiedy próbuję tego, mój wynik jest zawsze taki, nullże plik MANIFEST.MF w plikach wojennych zawiera poprawne informacje.
thomas.mc.work
Musiałem również dodać go do wtyczki maven-assembly-plugin
acheron55
2
<archiveClasses> prawda </archiveClasses> wydaje się niezwiązane
Karl Kildén
1
@RafaelSimonelli Usunąłem <archiveClasses>true</archiveClasses>- i od tego czasu działa niezawodnie.
thomas.mc.work
28

Oto metoda pobierania wersji z pom.properties, polegająca na cofaniu się do pobierania jej z manifestu

public synchronized String getVersion() {
    String version = null;

    // try to load from maven properties first
    try {
        Properties p = new Properties();
        InputStream is = getClass().getResourceAsStream("/META-INF/maven/com.my.group/my-artefact/pom.properties");
        if (is != null) {
            p.load(is);
            version = p.getProperty("version", "");
        }
    } catch (Exception e) {
        // ignore
    }

    // fallback to using Java API
    if (version == null) {
        Package aPackage = getClass().getPackage();
        if (aPackage != null) {
            version = aPackage.getImplementationVersion();
            if (version == null) {
                version = aPackage.getSpecificationVersion();
            }
        }
    }

    if (version == null) {
        // we could not compute the version so use a blank
        version = "";
    }

    return version;
} 
mysomic
źródło
2
Umieść to w statycznym bloku inicjalizatora.
opyate
1
Dobra rada. Chociaż, jeśli używasz tego w serwlecie (lub .jsp), pamiętaj, aby użyć metody getServletContext (). GetResourceAsStream zamiast getClass (). GetResourceAsStream
Sandman
3
Działa to tylko wtedy, gdy aplikacja jest uruchamiana z jar. Jeśli jest uruchamiany z exec-maven-plugin (np. Netbeans), zasób jest pusty.
Leif Gruenwoldt
Ten kod będzie częścią domyślnych ustawień mojej głównej klasy! Dzięki!!
Wendel,
Użyłem tego z odpowiedzią Willa, aby uzyskać prostą i łatwą w utrzymaniu opcję.
javydreamercsw
3

Spędziłem trochę czasu nad dwoma głównymi podejściami tutaj i one mi nie wyszły. Używam Netbeans do kompilacji, być może dzieje się tam więcej. Wystąpiły błędy i ostrzeżenia z Maven 3 w przypadku niektórych konstrukcji, ale myślę, że były one łatwe do skorygowania. Nic takiego.

W tym artykule na DZone znalazłem odpowiedź, która wygląda na łatwą w utrzymaniu i prostą do wdrożenia:

Mam już podfolder resources / config i nazwałem mój plik: app.properties, aby lepiej odzwierciedlić rodzaj rzeczy, które możemy tam przechowywać (np. Adres URL pomocy technicznej itp.).

Jedynym zastrzeżeniem jest to, że Netbeans ostrzega, że ​​IDE wymaga filtrowania. Nie wiem, gdzie / jak. W tym momencie nie ma to żadnego skutku. Być może jest na to jakiś sposób, jeśli będę musiał przejść przez ten most. Powodzenia.

będzie
źródło
3

Używam maven-assembly-plugindo mojego opakowania Maven. Użycie Apache Maven Archiver w odpowiedzi Joachima Sauera może również działać:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution .../>
    </executions>
</plugin>

Ponieważ archiever jest jednym ze współdzielonych komponentów mavena , może być używany przez wiele wtyczek budujących mavena, co może również powodować konflikt, jeśli zostaną wprowadzone dwie lub więcej wtyczek, w tym archivekonfiguracja wewnątrz.

千 木 郷
źródło
2

Aby uruchomić to w Eclipse, a także w kompilacji Mavena, należy dodać wpisy addDefaultImplementationEntriesi addDefaultSpecificationEntriespom, jak opisano w innych odpowiedziach, a następnie użyć następującego kodu:

public synchronized static final String getVersion() {
    // Try to get version number from pom.xml (available in Eclipse)
    try {
        String className = getClass().getName();
        String classfileName = "/" + className.replace('.', '/') + ".class";
        URL classfileResource = getClass().getResource(classfileName);
        if (classfileResource != null) {
            Path absolutePackagePath = Paths.get(classfileResource.toURI())
                    .getParent();
            int packagePathSegments = className.length()
                    - className.replace(".", "").length();
            // Remove package segments from path, plus two more levels
            // for "target/classes", which is the standard location for
            // classes in Eclipse.
            Path path = absolutePackagePath;
            for (int i = 0, segmentsToRemove = packagePathSegments + 2;
                    i < segmentsToRemove; i++) {
                path = path.getParent();
            }
            Path pom = path.resolve("pom.xml");
            try (InputStream is = Files.newInputStream(pom)) {
                Document doc = DocumentBuilderFactory.newInstance()
                        .newDocumentBuilder().parse(is);
                doc.getDocumentElement().normalize();
                String version = (String) XPathFactory.newInstance()
                        .newXPath().compile("/project/version")
                        .evaluate(doc, XPathConstants.STRING);
                if (version != null) {
                    version = version.trim();
                    if (!version.isEmpty()) {
                        return version;
                    }
                }
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Try to get version number from maven properties in jar's META-INF
    try (InputStream is = getClass()
        .getResourceAsStream("/META-INF/maven/" + MAVEN_PACKAGE + "/"
                + MAVEN_ARTIFACT + "/pom.properties")) {
        if (is != null) {
            Properties p = new Properties();
            p.load(is);
            String version = p.getProperty("version", "").trim();
            if (!version.isEmpty()) {
                return version;
            }
        }
    } catch (Exception e) {
        // Ignore
    }

    // Fallback to using Java API to get version from MANIFEST.MF
    String version = null;
    Package pkg = getClass().getPackage();
    if (pkg != null) {
        version = pkg.getImplementationVersion();
        if (version == null) {
            version = pkg.getSpecificationVersion();
        }
    }
    version = version == null ? "" : version.trim();
    return version.isEmpty() ? "unknown" : version;
}

Jeśli Twoja kompilacja Java umieszcza klasy docelowe w innym miejscu niż „docelowe / klasy”, może być konieczne dostosowanie wartości segmentówToRemove.

Luke Hutchison
źródło
Wiesz, jeśli to jest dla testów jednostkowych, możesz po prostu System.getProperty("user.dir")/pom.xml. Jestem całkiem pewien, że będzie to również dla innych rzeczy, z wyjątkiem może nie dla WTP.
Adam Gent
To zadziała tylko wtedy, gdy Twój projekt znajduje się w katalogu - jeśli prowadzisz projekt oparty na jarfiles, Twoje rozwiązanie nie zadziała. Musisz użyć .getResource()lub .getResourceAsStream().
Luke Hutchison
Tak, zakładałem, że sprawdziłeś już jar (ala getResource). To znaczy, że najpierw sprawdzasz za pomocą getResource, jeśli to się nie powiedzie, to projekt nie został jeszcze wbudowany w jar, co oznacza, że ​​uruchamiasz go z Eclipse lub Maven, co oznacza `System.getProperty (" user.dir ") / pom.xml . Jedynym problemem jest to, że ten plik pom nie jest prawdziwym efektywnym pom (to znaczy, że niektóre właściwości nie zostaną jeszcze rozwinięte), ale też nie jest to ten, który otrzymujesz dzięki Eclipse.
Adam Gent
1

W mojej aplikacji startowej wiosennej rozwiązanie z zaakceptowanej odpowiedzi działało, dopóki nie zaktualizowałem mojego jdk do wersji 12. Wypróbowałem również wszystkie inne odpowiedzi i nie mogłem ich uruchomić.

W tym momencie dodałem poniższy wiersz do pierwszej klasy mojej aplikacji do rozruchu wiosennego, zaraz po adnotacji @SpringBootApplication

@PropertySources({ 
        @PropertySource("/META-INF/maven/com.my.group/my-artefact/pom.properties")
})

Później używam poniższego, aby uzyskać wartość z pliku właściwości w dowolnej klasie, w której chcę użyć jej wartości i appVersionotrzymuję wersję projektu:

@Value("${version}")
private String appVersion;

Mam nadzieję, że to komuś pomoże.

Reema
źródło
Jak zrobić to samo z wieloma plikami pom? Chcę załadować wersję z wielu plików pom.
THM
0

Proste rozwiązanie, które jest kompatybilne z Maven i działa dla każdej klasy (a więc także innej firmy):

    private static Optional<String> getVersionFromManifest(Class<?> clazz) {
        try {
            File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
            if (file.isFile()) {
                JarFile jarFile = new JarFile(file);
                Manifest manifest = jarFile.getManifest();
                Attributes attributes = manifest.getMainAttributes();
                final String version = attributes.getValue("Bundle-Version");
                return Optional.of(version);
            }
        } catch (Exception e) {
            // ignore
        }
        return Optional.empty();
    }
rdehuyss
źródło
-1

Wariant Java 8 dla EJB w pliku wojennym z projektem maven. Przetestowano w EAP 7.0.

@Log4j // lombok annotation
@Startup
@Singleton
public class ApplicationLogic {

    public static final String DEVELOPMENT_APPLICATION_NAME = "application";

    public static final String DEVELOPMENT_GROUP_NAME = "com.group";

    private static final String POM_PROPERTIES_LOCATION = "/META-INF/maven/" + DEVELOPMENT_GROUP_NAME + "/" + DEVELOPMENT_APPLICATION_NAME + "/pom.properties";

    // In case no pom.properties file was generated or wrong location is configured, no pom.properties loading is done; otherwise VERSION will be assigned later
    public static String VERSION = "No pom.properties file present in folder " + POM_PROPERTIES_LOCATION;

    private static final String VERSION_ERROR = "Version could not be determinated";

    {    
        Optional.ofNullable(getClass().getResourceAsStream(POM_PROPERTIES_LOCATION)).ifPresent(p -> {

            Properties properties = new Properties();

            try {

                properties.load(p);

                VERSION = properties.getProperty("version", VERSION_ERROR);

            } catch (Exception e) {

                VERSION = VERSION_ERROR;

                log.fatal("Unexpected error occured during loading process of pom.properties file in META-INF folder!");
            }
        });
    }
}
onderbewustzijn
źródło