Bộ sưu tập

Đóng gói ứng dụng Java với Maven plugin


https://codersontrang.com/2017/10/02/dong-goi-ung-dung-java-voi-maven-plugin
Trong bài viết trước, chúng ta đã đi qua một ví dụ về cách tạo một ứng dụng web bằng Spring Boot chạy trên nền của web server Tomcat dạng nhúng. Nhắc lại là đối với ứng dụng web kiểu này, ta không cần triển khai ứng dụng lên một web server riêng rẽ, mà tất cả mọi thứ sẽ nằm trong chính bản thân ứng dụng luôn và tất cả những gì chúng ta cần làm để kích hoạt ứng dụng là gọi đến hàm main() nằm trong một lớp giống như bao ứng dụng Java bình thường khác. Điều này cũng có nghĩa là khi đóng gói ứng dụng trên, ta không phải tạo ra một file *.war (Web Archive) mà thay vào đó sẽ là một file *.jar (Java application Archive). Việc tạo ra file *.war hay file *.jar có nhiều các công cụ khác nhau, tuy nhiên các ví dụ của mình ở đây đều tuân theo chuẩn cấu trúc của Maven, vì thế khi đóng gói cũng sẽ trở nên dễ dàng hơn với sự giúp sức của các Maven Plugins. Bài viết này sẽ hướng dẫn các bạn cách đóng gói ứng dụng thành file *.jar nhờ hai Maven plugin có tên là maven-dependency-pluginmaven-jar-plugin.

(Nếu bạn nào muốn tìm hiểu cách tạo ứng dụng web bằng Maven và đóng gói thành file *.war thì có thể tham khảo ở bài viết này, và nếu bạn nào chưa biết đến Maven cùng với đó cảm thấy lạ lẫm với một số thuật ngữ như Maven plugin chẳng hạn thì các bạn có thể ghé qua bài viết này để tìm hiểu trước nhé).

Quay trở lại với ví dụ trong bài viết trước, như các bạn đã biết là cấu trúc của ví dụ tuân theo chuẩn Maven, vì thế ta hãy thử dùng câu lệnh của Maven để đóng gói ứng dụng lại. Qua cửa sổ command line (ở đây mình dùng Window Power Shell), truy cập đến thư mục của dự án và gõ mvn clean package, ta sẽ thấy thông báo là ứng dụng được build thành công như hình dưới đây.

Theo quy tắc của Maven, thì ứng dụng sau khi build ra sẽ nằm ở trong thư mục \target như hình dưới đây.

Ta thấy trong thư mục /target, ứng dụng sẽ được đóng gói thành file có tên là spring-mvc-with-boot-1.0-SNAPSHOT.jar. Ta sẽ thử chạy ứng dụng bằng cách gọi đến file jar này qua câu lệnh java -jar spring-mvc-with-boot-1.0-SNAPSHOT.jar. Ta sẽ có kết quả như hình dưới đây:

No main manifest attribute” là lời nhắn mà ta nhận lại được từ kết quả thực hiện câu lệnh. Điều này cho thấy file ứng dụng spring-mvc-with-boot-1.0-SNAPSHOT.jar chưa phải là một file chương trình có thể chạy được, hay nói cách khác nó chưa phải là một Executable Jar file. Tiếp đến là một loạt các bộ thư viện mà ta sử dụng trong ứng dụng như spring, tomcat, thymeleaf … hoàn toàn không có ở trong class path của ứng dụng vì vậy dù cho úng dụng có thể bật lên được thì nó cũng sẽ bị lỗi ngay sau đó.

Đây chính là lúc mà hai plugin là maven-dependency-pluginmaven-jar-plugin vào cuộc. Việc dùng hai Maven plugin trên như mình đã nói không phải là giải pháp duy nhất tuy nhiên nó cũng là một lựa chọn tốt. Vai trò và cách hoạt động của hai plugin như thế nào được mô tả trong hình minh họa ở dưới đây

I. maven-dependency-plugin

Khi các bạn nghe đến cái tên của plugin này, chắc hẳn các bạn cũng hình dung ra phần nào về vai trò của nó. maven-dependency-plugin tham gia vào quá trình đóng gói ứng dụng và quản lý các dependency mà ứng dụng tham chiếu đến. Hay nói cách khác nó đảm bảo trong quá trình đóng gói ứng dụng thì các thư viện đi kèm với ứng dụng sẽ được tập hợp lại để tránh gây ra các lỗi trong quá trình chạy ứng dụng. Việc tập hợp các dependency lại nhờ vào việc phân tích các khai báo dependency trong file pom.xml. Không chỉ các dependency được khai báo trực tiếp trong file pom.xml mà các dependency gián tiếp (transitive dependency) dẫn xuất từ các dependency trực tiếp cũng sẽ được tập hợp lại. Để sử dụng maven-dependency-plugin trong ví dụ của chúng ta, ta sẽ khai báo phần cấu hình cho plugin này trong file pom.xml như đoạn mã dưới đây

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codersontrang</groupId>
    <artifactId>spring-mvc-with-boot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        ...
    </repositories>

    <dependencies>
        ...
    </dependencies>

    <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-dependency-plugin</artifactId>
               <version>3.0.2</version>
               <executions>
                   <execution>
                       <id>copy-dependencies</id>
                       <phase>package</phase>
                       <goals>
                           <goal>copy-dependencies</goal>
                       </goals>
                       <configuration>
                           <outputDirectory>${project.build.directory}/lib</outputDirectory>
                       </configuration>
                   </execution>
               </executions>
           </plugin>
       </plugins>
    </build>
</project>

<phase>package</phase> chỉ thị rằng maven-dependency-plugin sẽ được thực hiện khi vòng đời build của Maven (build life-cycle) bước vào giai đoạn đóng gói (package), tức là sau khi tất cả các mã nguồn đã được biên dịch (compile) thành công. Mỗi một plugin có thể thực hiện nhiều việc cho nhiều mục đích khác nhau còn gọi là các goal. Ở trong ví dụ này, maven-dependency-plugin được cấu hình để thực hiện công việc có tên là copy-dependencies, công việc này cụ thể sẽ là phân tích các dependency trong file pom.xml như đã nói ở trên sau đó tập hợp chúng lại vào cùng một thư mục ở vị trí ${project.build.directory}/lib. ${project.build.directory} là biểu thức của Maven, theo quy tắc nó chính là thư mục /target nằm trong thư mục chứa mã nguồn. Sau khi cấu hình plugin này, ta tiến hành chạy lại câu lệnh mvn clean package trong command line, ta sẽ thấy các thư viện (dependency) được tập hợp lại trong thư mục như hình dưới đây:

II. maven-jar-plugin

maven-jar-plugin giúp giải quyết vấn đề còn lại trong bài toán của chúng ta đó là đóng gói mã nguồn đã biên dịch của ứng dụng lại vào một file jar có thể chạy được (Executable Jar File) thay vì ném ra lỗi “No main manifest attribute” như miêu tả ở trên. Ta thêm phần cấu hình cho plugin này trong file pom.xml như ở dưới đây

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codersontrang</groupId>
    <artifactId>spring-mvc-with-boot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        ...
    </repositories>

    <dependencies>
        ...
    </dependencies>

    <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-dependency-plugin</artifactId>
               ...
           </plugin>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-jar-plugin</artifactId>
               <version>3.0.2</version>
               <configuration>
                   <archive>
                       <manifest>
                           <addClasspath>true</addClasspath>
                           <classpathPrefix>lib/</classpathPrefix>
                           <mainClass>springmvcdemo.Spring5MVCDemoApp</mainClass>
                       </manifest>
                   </archive>
               </configuration>
           </plugin>
       </plugins>
    </build>
</project>

Sở dĩ ở phần trên, file jar của chúng ta không thể chạy được là vì ở trong bản thân nó có file /META-INF/MANIFEST.MF còn thiếu sót các thông tin cần thiết. Hai thông tin quan trọng ở trong file MANIFEST.MF cần phải khai báo là

  • Class-Path bao gồm đường dẫn đến các thư viện được dùng ở trong ứng dụng. Trong plugin ta khai báo nội dung của thẻ <classpathPrefix>lib/, tức là nói với maven-jar-plugin rằng hãy phân tích các dependency trong file pom.xml, sau đó liệt kê tất cả các file jar cần dùng, thêm vào phần đầu ở tên mỗi file jar từ “lib\” rồi ghi vào trong Class-Path của file MANIFEST.MF
  • Main-Class chỉ đích danh đến lớp có chưa phương thức main() tức là điểm bắt đầu chương trình khi có một lời gọi đến file jar. Trong ví dụ của chúng ta lớp đó chính là springmvcdemo.Spring5MVCDemoApp. Lưu ý tên của lớp sẽ phải là tên đầy đủ tức là phải bao gồm cả phần thông tin java package của nó nữa.

Sau khi đã hoàn thiện cấu hình 2 plugins ở trên, ta tiến hành đóng gói lại ứng dụng bằng cách chạy câu lệnh mvn clean package ở cửa sổ command line, ta sẽ thấy hai plugins trên được kích hoạt, thực hiện nhiệm vụ của chúng và được ghi lại ở trong log của màn hình console như hình dưới đây:

Sau khi nhận được thông báo là quá trình build đã thành công, ta quay trở vào thư mục build là /target ta sẽ thấy file ứng dụng được đóng gói có tên là spring-mvc-with-boot-1.0-SNAPSHOT.jar và bộ các thư viện đi kèm nằm trong thư mục /lib như hình dưới đây:

Nếu bạn nào tò mò thì có thể dùng các công cụ giải nén để bung file ứng dụng spring-mvc-with-boot-1.0-SNAPSHOT.jar ra, bạn có thể để ý thấy nội dung của file /META-INF/MANIFEST.MF được cập nhật bởi maven-jar-plugin sẽ giống như dưới đây:

MANIFEST.MF


Manifest-Version: 1.0
Built-By: codersontrang
Class-Path: lib/spring-boot-starter-web-2.0.0.M3.jar lib/spring-boot-s
 tarter-2.0.0.M3.jar lib/spring-boot-2.0.0.M3.jar lib/spring-boot-auto
 configure-2.0.0.M3.jar lib/spring-boot-starter-logging-2.0.0.M3.jar l
 ib/logback-classic-1.2.3.jar lib/logback-core-1.2.3.jar lib/jul-to-sl
 f4j-1.7.25.jar lib/log4j-over-slf4j-1.7.25.jar lib/spring-core-5.0.0.
 RC3.jar lib/spring-jcl-5.0.0.RC3.jar lib/snakeyaml-1.18.jar lib/sprin
 g-boot-starter-json-2.0.0.M3.jar lib/jackson-databind-2.9.0.pr4.jar l
 ib/jackson-annotations-2.9.0.pr4.jar lib/jackson-core-2.9.0.pr4.jar l
 ib/jackson-datatype-jdk8-2.9.0.pr4.jar lib/jackson-datatype-jsr310-2.
 9.0.pr4.jar lib/jackson-module-parameter-names-2.9.0.pr4.jar lib/jack
 son-module-kotlin-2.9.0.pr4.jar lib/spring-boot-starter-tomcat-2.0.0.
 M3.jar lib/tomcat-embed-core-8.5.16.jar lib/tomcat-embed-el-8.5.16.ja
 r lib/tomcat-embed-websocket-8.5.16.jar lib/hibernate-validator-5.4.1
 .Final.jar lib/validation-api-1.1.0.Final.jar lib/jboss-logging-3.3.0
 .Final.jar lib/classmate-1.3.1.jar lib/spring-web-5.0.0.RC3.jar lib/s
 pring-beans-5.0.0.RC3.jar lib/spring-webmvc-5.0.0.RC3.jar lib/spring-
 aop-5.0.0.RC3.jar lib/spring-context-5.0.0.RC3.jar lib/spring-express
 ion-5.0.0.RC3.jar lib/spring-boot-starter-thymeleaf-2.0.0.M3.jar lib/
 thymeleaf-spring5-3.0.7.RC1.jar lib/thymeleaf-3.0.7.RELEASE.jar lib/a
 ttoparser-2.0.4.RELEASE.jar lib/unbescape-1.1.5.RELEASE.jar lib/slf4j
 -api-1.6.6.jar lib/thymeleaf-extras-java8time-3.0.0.RELEASE.jar
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_111
Main-Class: springmvcdemo.Spring5MVCDemoApp

Lần này, chạy thử file jar với câu lệnh java -jar spring-mvc-with-boot-1.0-SNAPSHOT.jar, ta sẽ không còn thấy bị lỗi như ở lần chạy trước nữa. Thay vào đó sẽ là biểu tượng của Spring Boot được log ra ở màn hình console như hình dưới đây, báo hiệu ứng dụng đã kích hoạt thành công nhờ gọi vào hàm main() của lớp Spring5MvcDemoApp

Và khi ứng dụng đã hoàn toàn được khởi tạo thành công thì lời nhắn gồm tổng thời gian ứng dụng cần để hoàn thiện việc khởi tạo của nó được xuất hiện ở màn hình console như hình dưới đây:

Khi đó truy cập vào địa chỉ http://localhost:8080 ta cũng nhận được trang web giống như kết quả chạy chương trình từ bài viết trước.

Như vậy là bài này đã hướng dẫn các bạn một cách để tạo ra Executable Jar File từ mã nguồn theo cấu trúc Maven nhờ vào hai Maven plugin là maven-dependency-plugin và maven-jar-plugin. Để tìm hiểu thêm về cách cấu hình cũng như tùy biến các thông số khác mà hai plugin này cung cấp, các bạn có thể tìm hiểu ở trang của Apache lần lượt như ở dưới đây:

https://maven.apache.org/plugins/maven-dependency-plugin/
https://maven.apache.org/plugins/maven-jar-plugin/

Good luck!

By Coder Sơn Trang Posted in Maven

5 comments on “Đóng gói ứng dụng Java với Maven plugin

  1. Pingback: Một cách nhìn khác về ứng dụng web với Spring MVC, Spring Boot, Tomcat dạng nhúng và Thymeleaf | Coder Sơn Trang

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s