Thứ Hai, 21 tháng 6, 2021

Xây dựng ứng dụng Java Web Multi Module sử dụng Springboot framework với Gradle

Xây dựng ứng dụng Java Web Multi Module sử dụng Springboot framework với Gradle

Lê Hoàng Hiệp

Mục đích:  Bài viết này chúng tôi đưa ra cách build một project java web, được chia thành 2 modules là Web application và Library common. Mục tiêu của vấn đề này nhằm tách nhỏ project để dễ quản lý và chuyển đổi các tính năng phổ dụng mà có thể tái sử dụng lại trong các project khác thành thư viện. Gradle được sử dụng để làm build tool trong bài viết này.

Yêu cầu kiến thức cơ bản:

·                Nắm vững và sử dụng các kỹ năng để về việc xây dựng hoàn tất ứng dụng với mô hình MVC2. (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/search/label/Servlet%26JSP )

·                Nắm vững các khái niệm về ngôn ngữ lập trình Java, lập trình thao tác hướng đối tượng.

·                Hiểu cơ bản và cách sử dụng Springboot framework. https://spring.io/projects/spring-boot

·                Hiểu và biết cách thiết lập biến môi trường cho các công cụ lập trình trên hệ điều hành Windows (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/2016/11/video-cau-hinh-trien-khai-tomcat-server.html )

·                Hiểu được cấu trúc web và cấu hình server tomcat rời trên hệ điều hành cùng với quá trình deploy và undeploy ứng dụng vào trong tomcat chạy độc lập (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/2016/11/video-cau-hinh-trien-khai-tomcat-server.html )

Tool sử dụng:

·        Gradle 7.0.2 (download tại địa chỉ Gradle | Releases)

·        spring-tool-suite (sts) 4.10.0 (download tại địa chỉ Spring | Tools)

·        JDK 8

 

I.                  Giới thiệu

-         Hiện nay, khi phát triển phần mềm chúng ta ít nhiều sẽ thấy đa số project đều sử dụng lại vài chức năng, như ghi file, đọc file, xử lý chuỗi… Thay vì chúng ta clone lại function như đã đề cập từ các project cũ và tích hợp lại vào project mới, chúng ta sẽ build những function đấy thành một thư viện để có thể tiện sử dụng lại trong các project khác. Ở đây, chúng tôi gọi các chức năng tái sử dụng lại với tên gọi chung là library common.

II.              Giới thiệu Gradle

1.     Gradle là gì?

-         Gradle là một hệ thống tự động build mã nguồn mở, dựa trên các khái niệm về Apache Ant và Apache Maven.

-         Gradle được phát hành năm 2007, kết hợp các ưu điểm của Ant và Maven và được phát triển bằng ngôn ngữ Groovy thay vì XML

-         Gradle được xem là một hệ thống tự động build, nhưng về cơ bản, Gradle không biên dịch bất kì 1 đoạn mã nào cả. Tất cả tính năng đấy đến từ một hệ thống sinh thái xung quanh của Gradle, được gọi là Plugin.

-         Chính vì điều đã đề cập ở trên, Gradle đã hỗ trợ build cho hơn 60 ngôn ngữ lập trình khác nhau, bao gồm Java, Scala, Python, C/C++…

2.     Cài đặt Gradle

-         Biến môi trường Gradle được cài đặt trong windows là GRADLE_USER_HOME

-         Ở đây, chúng tôi sử dụng gradle latest version ở thời điểm 29/5/2021 là 7.0.2

-         Chúng ta tiếp tục thiết lập reference đến công cụ Gradle sẵn sàng để dùng trong hệ điều hành Windows bằng cách thiết lập PATH reference tới thư mục bin của gradle theo đường dẫn: %GRADLE_USER_HOME%\bin

-         Sau khi thiết lập biến môi trường, chúng ta check lại gradle đã được thiết lập thành công hay chưa bằng cách cmd – command prompt và gõ lệnh gradle -v hoặc gradle –version (nhấn enter)

-         Nếu cmd hiển thị kết quả như hình bên trên, điều này có nghĩa là chúng ta đã thiết lập gradle thành công

3.     Cách hoạt động của Gradle

-         Gradle hoạt động xoay quanh 2 file là build.gradle và settings.gradle

o   build.gradle là nơi chúng ta cấu hình các plugins, version, repositories, dependencies…

o   settings.gradle là nơi chúng ta thiết lập tên của project hoặc các sub-modules của project nếu có

-         Về cơ bản, cách thức hoạt động của gradle gần giống với maven, chúng chỉ khác nhau ở syntax.

-         Build phases của Gradle gồm: Initialization, Configuration, Execution

-         Chúng ta có thể tham khảo về build lifecycle, Gradle vs Maven comparison ở link dưới:

o   https://docs.gradle.org/current/userguide/build_lifecycle.html

o   https://gradle.org/maven-vs-gradle/

4.     Gradle dependencies và repositories

-         Dependencies là nơi mà ta khai báo để tích hợp thư viện từ bên thứ 3 vào project

-         Repositories hiểu đơn giản là kho chứa. Dựa vào repositories mà chúng ta thiết lập, Gradle sẽ tìm các dependencies mà chúng ta cần sử dụng được chứa ở trong repository

-         Các dependencies phổ dụng đã được cộng đồng publish để chúng ta có thể sử dụng ở link: https://mvnrepository.com/.

o   Ví dụ:

o   Hoặc, chúng ta cũng có thể implement thư viện local để đưa vào project theo cú pháp:

§  implementation project(':common')

·        Chỉ định cụ thể sub-module, thư viện mà chúng ta cần.

·        common là tên sub-module (chúng ta sẽ tìm hiểu trong các nội dung tiếp theo).

·        Lưu ý khi khai báo, trước tên module, library đều phải được bắt đầu bằng dấu : (colon).

§  Hoặc khai báo toàn bộ thư viện local trong 1 folder theo cú pháp
       compile fileTree(dir: 'libs', include: ['*.jar'])

·        Trong đó, libs là tên folder chứa thư viện, *.jar là toàn bộ thư viện jar có trong folder libs.

§  Để phân cấp package, chúng ta sẽ khai báo bằng dấu : (colon). Ví dụ:

·        com.example.utils -> com:example:utils

§  Nếu chúng ta không muốn phân cấp thư mục nhưng package name lại dài, chúng ta có thể dùng dấu – (dash) để phân tách giữa các từ theo naming recommendations của gradle, kebab. Quí vị có thể tham khảo thêm tại địa chỉ:
https://docs.gradle.org/current/userguide/multi_project_builds.html

5.     Cấu trúc của một Gradle project

a.     Cấu trúc a standard gradle project

b.     Cấu trúc a multi-modules gradle project

-         subproject-one, subproject-two là nơi chứa source code, nhưng đã được tách thành 2 modules riêng biệt và có thể refer lẫn nhau

III.           Build và Implement project Web application sử dụng sts

1.     Tạo project mới

-         Chúng ta thực hiện tạo một root folder tên: MultiModule.

o   Chúng ta mở cmd lên và từ root folder chúng ta lần lượt tạo các sub-project và các file tương ứng như hình mô tả bên dưới

§  Ở đây, chúng ta có các lệnh như mkdir, cd, type nul > build.gradle, type nul > settings.gradle

·        mkdir là lệnh tạo folder rỗng. Như vậy, với mkdir webapp\src\main\java là tạo 4 folder đúng cấp độ với đường dẫn đã nêu ra.

·        cd là lệnh di chuyển từ vị trí thư mục mà chúng ta đang thao tác vào thư mục bên trong hay thư mục khác với đường dẫn cụ thể

·        type nul > build.gradle là lệnh tạo file rỗng, với tên là build và phần mở rộng (extension) là gradle. Chúng ta sẽ làm tương tự để tạo file settings.graddle với dòng lệnh type nul > settings.gradle

-         Sau khi các file và cấu trúc thư mục đã được tạo xong, chúng ta thực hiện bổ sung nội dung cấu hình vào các file đó như sau

o   Chúng ta thực hiện cấu hình nội dung file settings.graddle như sau

§  rootProject.name là tên của project.

·        Nếu chúng ta import 02 project khác nhau nhưng có cùng rootProject.name vào ide thì project thứ 2 sẽ import lỗi. Bởi vì ide hiểu rằng project này đã được import trước đó

§  include là khai báo các sub-projects. Nhờ vậy, các sub-projects mới có thể refer tới lẫn nhau

o   Đối với ide sts4, khi đặt project ngay trong workspace mà rootProject.name khác với tên folder của project thì sts4 sẽ báo lỗi

§  Project at 'D:\Project\SpringBoot\abc' can't be named 'MultiModule' because it's located directly under the workspace root. If such a project is renamed, Eclipse would move the container directory. To resolve this problem, move the project out of the workspace root or configure it to have the name 'abc'.

o   Nghĩa là chúng ta buộc phải đặt tên folder giống với rootProject.name hoặc phải đặt folder project ở vị trí khác với workspace của sts4. Chúng ta có thể lách như thế này

§  Ví dụ ở đây workspace của chúng ta là D:\Project\SpringBoot. Việc tạo thêm folder Newfolder và đặt project folder thành sub-folder đã thoát ra khỏi workspace của ide

§  Hoặc chúng ta thêm eclipse.project.name = project.rootDir.name ở file build.gradle của root project

o   Tiếp theo, chúng ta cấu hình lần lượt vào file build.gradle ở các module

§  build.gradle

·        Vì chúng ta tạo 1 project lớn chứa các module bên trong nên chúng ta có thể cấu hình những gì chung cho tất cả sub-project ở đây, và những sub-project chỉ việc sử dụng các cấu hình chung mà không cần khai báo lại

·        Nếu sub-project nào muốn khai báo dependency riêng, thì có thể khai báo riêng tại các file build.gradle của chính project đó

·        Nội dung được cấu hình cụ thể như sau

 

o   Plugin là các tiện ích được thêm vào

§  Như đã đề cập ở trên, chúng ta khai báo plugin là java, nghĩa là ta đang thông báo cho gradle biết source code của chúng ta được viết bằng ngôn ngữ java và build thì build ra binary file

§  Với id có giá trị là ‘org.springframework.boot’ nghĩa là project sử dụng springboot framework

§  Với id có giá trị là io.spring.dependency-management nghĩa là chúng ta mong muốn springboot hỗ trợ quản lý các dependency cho chúng ta trong các project. Khi chúng khai báo plugin này kết hợp cùng các dependency của springboot, chúng ta chỉ cần implement tên của chúng mà không cần phải kèm theo version. Vì plugin này sẽ đảm nhiệm việc đấy và tìm kiếm version tương ứng. Quí vị có thể tham khảo thêm tại địa chỉ https://docs.spring.io/spring-boot/docs/2.4.5/gradle-plugin/reference/htmlsingle/

·        Allprojects dùng để thể hiện rằng tất cả nội dung được khai báo ở đây sẽ được apply cho tất cả các sub-projects.

o   sourceCompatibility là version java sẽ được sử dụng

·        subprojects là nơi chúng ta có thể khai báo độc lập từng sub-project sẽ có những gì, hoặc có thể khai báo riêng vào từng build.gradle file của sub-project. Mặc định không chỉ định project cụ thể nào, mọi thứ sẽ được apply cho tất cả sub-projects giống như allprojects

§  webapp\build.gradle

 

·        Vì build.gradle của main project đã khai báo plugins rồi nên những sub-project chỉ cần khai báo lại tên của plugin thôi mà không cần khai báo version của chúng

·        Khi chúng ta khi báo với gradle là sử dụng springboot framework, thì mặc định gradle sẽ hiểu là cần phải build executable archive

·        bootJar là 1 task để build ra executable jar. Trong đó, baseName là tên và version là phiên bản của jar. Sau khi build thì package jar sẽ có tên như sau: multi-module-jar-1.0.0.jar

·        dependencies là nơi mà chúng ta implement thư viện của bên thứ 3 và chỉ apply cho riêng sub-project này.

·        Trong đó implementation project(‘:common’) là khai báo import sub-project có tên là common. Để import thành công, sub-project common phải được khai báo ở ngoài settings.gradle file bằng tag include

§  common\build.gradle

 

·        Vì ở đây common chỉ là thư viện, không có hàm main() nên chúng ta sẽ tắt bootJar đi bởi vì bootJar sẽ tìm kiếm hàm main() trong project

·        jar task được bật lên ý nói với gradle sub-project này chỉ cần build plain archive, thư viện dùng để import.

o   Sau khi cấu hình xong

§  Cấu trúc project của chúng ta hiện nay như sau

§  Chúng ta mở cmd lên và chạy lệnh gradle wrapper để Gradle build cho chúng ta thư mục gradle\wrapper. Đây là package gradle sẽ download các dependencies sau này cho chúng ta.

·        Mọi thông tin thêm về gradle wrapper có thể tham khảo thêm ở địa chỉ: https://docs.gradle.org/current/userguide/gradle_wrapper.html

-         Sau khi mọi nội dung cấu hình và thực thi hoàn tất, thư mục project của chúng ta có cấu trúc như hình bên dưới

-         Nếu cấu hình bất kì file build.gradle nào không chính xác, thì khi run gradle wrapper sẽ phát sinh lỗi

2.     Implement project

-         Import project chúng ta vừa tạo vào sts4

-         Chọn thư mục project chúng ta đã phát sinh. Click Finish

-         Trong quá trình import, chúng ta quan sát sẽ thấy tool sẽ download các dependencies cho chúng ta.

-         Sau khi import xong, chúng ta sẽ thấy nội dung project trong tool như hình

-         Tiếp theo, chúng ta xây dựng một API HelloWorld đơn giản trong gói thư viện chung – common như cấu trúc hình bên dưới

-         Chúng ta xây dựng ứng dụng thực thi đơn giản để gọi API vừa tạo ra với tên class là Application.java  trong gói ứng dụng web với hình thể hiện bên dưới

o   Trong quá trình viết code chúng ta cứ gõ khoảng 03 ký tự nhấn tổ hợp phím control -space bar thì code sẽ bung kể cả dòng import cho chúng ta

 

o   Trong quá trình viết code, chúng ta có thể import class Hello là bởi vì chúng ta đã implement sub-project common ở trong webapp\build.gradle và dependency của common khi đã được add thành công sẽ được add vào project. Chúng ta có thể kiểm tra nội dung này bằng cách mở phần Project and External Dependencies trong gói ứng dụng web

3.     Build project

-         Chúng ta mở cmd lên và di chuyển vào thư mục root của project, ..\MultiModule và chạy lệnh gradle build. Chúng ta sẽ nhận được kết quả là

-         Nguyên nhân là root project chúng ta không implement bất kì source code nào. Mà source code của chúng ta được implement ở những sub-project.

-         Giải pháp để build được project đó là chúng ta cần phải đi vào các sub-project mà chúng ta đã thực hiện ở trên (Ở đây, chúng ta chỉ cần vào một project để thực hiện không cần nhất thiết vào cả 02 project để thực hiện)

o   Chúng ta cũng cần lưu ý rằng webapp project đã implement sử dụng common project, cho nên khi build webapp, gradle sẽ build cả 2 project thành jar.

o   Tuy nhiên, nếu chúng ta build ở common project, gradle sẽ chỉ build mỗi 1 package jar của common mà không build gói ứng dụng web

-         Ở đây, chúng ta sẽ build webapp và run kiểm tra thử

o   Chúng ta chuyển vào thư mục của webapp từ thư mục hiện hành của project chính bằng lệnh cd trên cmd

o   Gõ lại lệnh gradle build, chúng ta sẽ có kết quả như sau

§  Lưu ý: nếu chúng ta ép đổi tên project folder khác với rootProject.name và sử dụng eclipse.project.name = project.rootDir.name để có thể import thành công vào ide, thì sau khi build, chúng ta phải xóa dòng lệnh này đi. Nếu không quá trình build sẽ phát sinh lỗi

§  Sau khi gradle build thành công, sẽ xuất hiện 1 folder build như hình

-         Package webapp jar được build ở đường dẫn \webapp\build\libs

-         Tương tự, package common jar \common \build\libs

-         Mở cmd lên và dùng lệnh cd để di chuyển vào thư mục chứa jar mà chúng ta vừa build. Cụ thể ở đây là .\webapp\build\libs

-         Tiếp theo ta chạy tiếp lệnh java –jar <tên gói jar> để run server

o   Vì chúng ta không tạo file application.properties để cấu hình, cho nên default server sẽ chạy ở port 8080. Chúng ta có thể nhìn trên màn hình server để xác định port và version của tomcat đang được host

§  Theo hình run jar ta thấy, ở dòng info đầu tiên, Starting Application…  with PID 8792 hoặc tất cả dòng INFO theo sau đều là 8792. Điều này nghĩa là, process này hiện đang chạy trong windows với pid là 8792

o   Để tắt server, chúng ta có 3 cách:

§  Ấn tổ hợp phím Ctrl + c

§  Mở cmd khác lên và gõ lệnh taskkill /f /pid 8792

§  Để xem tất cả lệnh của taskkill, chúng ta gõ taskkill /?

2.     Mở Task Manager và tìm đến process jar cần kill

§  Để hiển thị PID trên Task Manager, chúng ta click chuột phải vào label bất kì ở Name, Status, CPU… và chọn PID

-         Toàn bộ quá trình ở trên là hướng dẫn về việc build jar, vậy nếu chúng ta muốn build ra package war để deploy lên tomcat server thì sẽ thế nào?

 

o   Chúng ta thực hiện cấu hình thêm nội dung sau vào file build.gradle trong gói ứng dụng web như hình bên dưới

§  Chúng ta sẽ apply thêm 1 plugin nữa là war.

§  Thay đổi từ bootJar sang bootWar

§  Và cuối cùng là implement thêm tomcat server dependency

§  Lưu ý

·        Gradle chỉ build duy nhất 1 package 1 lần, kể cả chúng ta có để bootWar và bootJar

·        Nếu plugin war không được apply, thì khi build với bootWar sẽ phát sinh lỗi

§  Bên cạnh đó, bởi vì mặc định spring boot đang start server và ứng dụng với gói jar và tomcat lại không thể tự nhận dạng application mà phải là servlet vì ứng dụng web application được xây dựng dựa trên Servlet-based application với cấu hình là web deployment descriptor (chúng ta từng quen thuộc với web.xml).

·        Trong khi Spring boot đã dùng Java Configuration để overwrite XML Configuration

·        Bên cạnh đó, SpringBootServletInitializer class đã implements the WebApplicationInitializer interface và override cấu hình web tại thời điểm ứng dụng được deploy. Cho nên có thể dễ dàng deploy lại ứng dụng theo định hướng Servlet-based Application dựa trên cấu hình Java Configuration

·        Kết luận, để deploy ứng dụng web (gói war) trên tomcat rời chúng ta cần bổ sung extends SpringBootServletInitializer vào file chính của ứng dụng web cụ thể như hình bên dưới

§  Chúng ta thực hiện lệnh gradle build (mỗi lần thay đổi source code chúng ta không cần phải chạy lại gradle wrapper), chúng ta sẽ thấy được gói war trong thư mục \webapp\build\libs

§  Sau đó, chúng ta start server tomcat độc lập để deploy thử.

·        Start server tomcat bằng cmd

·        Tomcat được start

·        Copy gói war vào thư mục webapp của tomcat. Tomcat sẽ deploy – phần deploy sẽ giống như chạy với jar

·        Dấu hiệu deploy thành công là gói war được giải nén ở thư mục deploy

·        Test thử ứng dụng trên browser

·        Quá trình undeploy ứng dụng chỉ cần xóa gói war và thư mục giải nén ra khỏi thư mục webapp của tomcat. Server sẽ hiển thị

·        Tắt server tomcat bằng lệnh shutdown hay tổ hợp phím Ctrl + C

IV.          Các vấn đề liên quan khi sử dụng gradle

-         Chúng ta cùng tìm hiểu thêm tại sao bootJar có thể tìm kiếm được hàm main() để có thể build executable jar khi chúng ta không khai báo main() ở đâu?

o   Theo gradle, mặc định executable archive’s main class sẽ được cấu hình một cách tự động bằng cách tìm kiếm class có public static void main(String[])

o   Vậy nếu chúng ta cố tình để thiếu tham số ở main() và build thì sẽ thế nào?

o   Chúng ta thực hiện chỉnh sửa file Application.java như hình bên dưới

§  Chúng ta thực hiện gradle build lại, chúng ta sẽ có kết quả như hình bên dưới

o   Để thực hiện ghi đè lại việc tìm main() có tham số và để build thành công, chúng ta chỉ cần thêm property mainClass và chỉ định class main ở đâu trong bootJar

o   Thực hiện build lại

o   Mọi thông tin chi tiết về build jar và war có tham khảo ở đây: Spring Boot Gradle Plugin Reference Guide

 

-         Cuối cùng việc tái sử dụng lại các project hay thư viện rất dễ dàng,

o   Chúng ta có thể tách các sub-project đưa vào các project khác và cấu hình include như các bước đã nêu ra ở trên

o   Hay có thể sử dụng các thư viện gói jar được build từ common project vào ứng dụng theo cơ chế sử dụng thư viện

 

Chúng ta vừa tìm hiểu cơ bản kiến thức về build tool Gradle và đồng thời tách module project sử dụng Springboot framework

 

Mọi quá trình khởi tạo project hoàn toàn bằng thủ công. IDE chỉ đóng vai trò là môi trường hỗ trợ chúng ta trong quá trình code. Mọi IDE apply project sử dụng gradle cũng đều dựa trên quy tắc chuẩn do gradle đề ra.

Bài viết này đưa ra một giải pháp để backup khi IDE có lỗi và áp dụng quá trình triển khai ứng dụng cần fix bug, chúng ta vẫn có thể tự sử dụng phương pháp manual với console cùng các công cụ sẵn có mà không cần thiết phải lệ thuộc quá nhiều vào IDE.

 

Rất mong quí vị góp ý về nội dung bài viết này vì bài viết được chia sẻ dựa trên ý kiến chủ quan của tác giả. Hẹn gặp lại quí vị ở các bài viết sau.

Không có nhận xét nào:

Đăng nhận xét