顯示具有 Maven 標籤的文章。 顯示所有文章
顯示具有 Maven 標籤的文章。 顯示所有文章

2025年2月19日 星期三

Spark 新手筆記(一):在本機安裝 Spark 並執行一個簡單的 Scala 程式

最近開始參與 Spark 的專案,所以有空的時候會開始做一些 Spark 新手筆記 😆。我的環境是 Windows 11,不過實際上在不同環境的作法應該沒有差太多的感覺,但或許會有一些小地方有微妙的不同就是了。因為我自己的電腦是 Windows 11,但平常比較習慣在 Linux-based 的環境開發,所以實際上文章中會紀錄的東西,大體上會是 Windows 11 + WSL2 的狀況,指令原則上我都是在 WSL2 的 Ubuntu 裡執行的。

Spark 環境準備

首先,根據 Spark 官方文件來看,Spark 本身不需要安裝,只需要下載並執行相應的指令即可。因此主要需要準備的環境是 Java、Maven,並且下載 Spark。其中 Java 跟 Maven 怎麼安裝,我就直接略過了,這裡簡單說明我目前的環境:

  • Java: 21.0.6+7-Ubuntu-124.04.1
  • Maven: 3.8.8

在 Spark 官方網站下載 Spark 時,因為目前的最新版是 3.5.4,因此我是選擇以下的選項下載的:

  • Spark: 3.5.4
  • Package Type: Pre-built for Apache Hadoop 3.3 and later (Scala 2.13)

其中 Package Type 其實我目前也不太確定效果是什麼,但因為我的範例專案會用 Scala,因此就選了名字有 Scala 的 🙃。

準備範例專案

新手的第一件事就是先弄個 Hello World,不過 Spark 印 Hello World 好像有點弱,所以我弄了一個比 Hello World 複雜一滴滴的範例專案,順便也簡單試了一下 Spark 與 Scala 的寫法。

git clone git@github.com:jimwayneyeh/example-spark-scala.git

接著因為要把打包的 JAR 丟去給 Spark 執行,所以我們需要把這個專案打包成 JAR。

cd example-spark-scala
mvn clean package

打包出來的檔案會在 ./target/example-spark-scala-1.0-SNAPSHOT.jar,這個路徑先記住,等等會用到。在那之前先來看一下這個範例專案裡寫了什麼。首先可以先看 tw.jimwayneyeh.example.spark_scala.App 這個類別,它的內容如下:

object App {

  def main(args: Array[String]): Unit = {
    println("Hello World!")

    val spark = SparkSession.builder().appName("exmaple app").getOrCreate()
    val customerDf =
      spark.read.options(Map("header" -> "true")).csv("customers-100.csv")
    println(s"Count: ${customerDf.count()}")

    spark.stop()
  }

}

因為程式碼很短,就不一一贅述了,簡單來說就是先建立一個 SparkSession,讀取專案根目錄的 customers-100.csv 檔,這個 CSV 檔是從 sample-csv-files 下載的範例 CSV 檔。CSV 讀取進來變成 data frame,指定它是有標頭的,然後算出這個 data frame 的個數並印出來。

讀取檔案的部份要特別注意的是,這裡給的路徑是相對於專案目錄的路徑,但檔案並非放在 src/main/resources 資料夾內。我詢問 ChatGPT 時,ChatGPT 回答說 Spark 正常運作時,資料都是從外部路徑帶進來的,所以一般來說 Spark 程式並不會讀取 JAR 裡面的資源。這個理由聽起來是感覺蠻合理的,畢竟會特地用 Spark 執行的運算,資料量應該都不會小,也不會有人想把資料放進 JAR 裡吧 😆。

另外也看一下一個最簡單的 Spark 專案,如果用 Maven 打包的話,需要什麼東西。POM 裡面只需要以下的 dependencies 即可。

<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>${scala.version}</version>
</dependency>
<dependency> <!-- Spark dependency -->
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_${scala.binary.version}</artifactId>
    <version>3.5.4</version>
    <scope>provided</scope>
</dependency>

其中可以特別注意到,Spark 的 dependency 的 scope 會是 provided,這應該是因為實際執行時,JAR 會跑在 Spark 上,所以 Spark dependencies 應該在執行環境上都有。

在 Spark 執行

範例專案的 JAR 準備好以後,回到前面下載好的 Spark 壓縮檔,把它解壓縮後進入 /bin 資料夾,並且執行以下的 spark-submit 指令,就可以啟動一個本機的 Spark 環境執行剛剛的範例專案了。

./spark-submit --class "tw.jimwayneyeh.example.spark_scala.App" --master local /PATH/TO/PROJECT/target/example-spark-scala-1.0-SNAPSHOT.jar

等了一下之後,就可以看到 console 輸出的 Spark logs 了。這裡我貼了很長,是因為順便想要紀錄它的執行過程,之後想要分析它的內容。不過現階段重要的是 log 當中格式沒有日期的那兩行,因為程式碼是用 println() 來輸出的,所以會忽略 logging 格式直接在 console 印出只有 message 的 log。可以看出它算出了 data frame 裡面有 100 筆資料。

Hello World!
Count: 100

以下則是紀錄一下 log 當中印出跟 App.scala 有關的部份,這些 log 就是實際在排程並執行範例程式時的過程了。

25/02/19 00:38:51 INFO SparkContext: Created broadcast 0 from csv at App.scala:14
25/02/19 00:38:51 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:52 INFO SparkContext: Starting job: csv at App.scala:14
25/02/19 00:38:52 INFO DAGScheduler: Got job 0 (csv at App.scala:14) with 1 output partitions
25/02/19 00:38:52 INFO DAGScheduler: Final stage: ResultStage 0 (csv at App.scala:14)
25/02/19 00:38:52 INFO DAGScheduler: Parents of final stage: List()
25/02/19 00:38:52 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:52 INFO DAGScheduler: Submitting ResultStage 0 (MapPartitionsRDD[3] at csv at App.scala:14), which has no missing parents
25/02/19 00:38:52 INFO MemoryStore: Block broadcast_1 stored as values in memory (estimated size 13.8 KiB, free 434.2 MiB)
25/02/19 00:38:52 INFO MemoryStore: Block broadcast_1_piece0 stored as bytes in memory (estimated size 6.5 KiB, free 434.2 MiB)
25/02/19 00:38:52 INFO BlockManagerInfo: Added broadcast_1_piece0 in memory on 10.255.255.254:43927 (size: 6.5 KiB, free: 434.4 MiB)
25/02/19 00:38:52 INFO SparkContext: Created broadcast 1 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:52 INFO DAGScheduler: Submitting 1 missing tasks from ResultStage 0 (MapPartitionsRDD[3] at csv at App.scala:14) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:52 INFO TaskSchedulerImpl: Adding task set 0.0 with 1 tasks resource profile 0
25/02/19 00:38:52 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0) (10.255.255.254, executor driver, partition 0, PROCESS_LOCAL, 9896 bytes) 
25/02/19 00:38:52 INFO Executor: Running task 0.0 in stage 0.0 (TID 0)
25/02/19 00:38:52 INFO CodeGenerator: Code generated in 12.528525 ms
25/02/19 00:38:52 INFO FileScanRDD: Reading File path: file:///mnt/g/scala/example-spark-scala/customers-100.csv, range: 0-17255, partition values: [empty row]
25/02/19 00:38:52 INFO CodeGenerator: Code generated in 40.709858 ms
25/02/19 00:38:53 INFO Executor: Finished task 0.0 in stage 0.0 (TID 0). 1800 bytes result sent to driver
25/02/19 00:38:53 INFO TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 697 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:53 INFO TaskSchedulerImpl: Removed TaskSet 0.0, whose tasks have all completed, from pool
25/02/19 00:38:53 INFO DAGScheduler: ResultStage 0 (csv at App.scala:14) finished in 0.890 s
25/02/19 00:38:53 INFO DAGScheduler: Job 0 is finished. Cancelling potential speculative or zombie tasks for this job
25/02/19 00:38:53 INFO TaskSchedulerImpl: Killing all running tasks in stage 0: Stage finished
25/02/19 00:38:53 INFO DAGScheduler: Job 0 finished: csv at App.scala:14, took 0.965247 s
25/02/19 00:38:53 INFO CodeGenerator: Code generated in 17.506485 ms
25/02/19 00:38:53 INFO FileSourceStrategy: Pushed Filters: 
25/02/19 00:38:53 INFO FileSourceStrategy: Post-Scan Filters:
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_2 stored as values in memory (estimated size 199.5 KiB, free 434.0 MiB)
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_2_piece0 stored as bytes in memory (estimated size 34.4 KiB, free 433.9 MiB)
25/02/19 00:38:53 INFO BlockManagerInfo: Added broadcast_2_piece0 in memory on 10.255.255.254:43927 (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:53 INFO SparkContext: Created broadcast 2 from csv at App.scala:14
25/02/19 00:38:53 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:53 INFO FileSourceStrategy: Pushed Filters: 
25/02/19 00:38:53 INFO FileSourceStrategy: Post-Scan Filters:
25/02/19 00:38:53 INFO CodeGenerator: Code generated in 12.86595 ms
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_3 stored as values in memory (estimated size 199.4 KiB, free 433.7 MiB)
25/02/19 00:38:53 INFO MemoryStore: Block broadcast_3_piece0 stored as bytes in memory (estimated size 34.4 KiB, free 433.7 MiB)
25/02/19 00:38:53 INFO BlockManagerInfo: Added broadcast_3_piece0 in memory on 10.255.255.254:43927 (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:53 INFO SparkContext: Created broadcast 3 from count at App.scala:15
25/02/19 00:38:53 INFO FileSourceScanExec: Planning scan with bin packing, max size: 4211559 bytes, open cost is considered as scanning 4194304 bytes.
25/02/19 00:38:54 INFO DAGScheduler: Registering RDD 13 (count at App.scala:15) as input to shuffle 0
25/02/19 00:38:54 INFO DAGScheduler: Got map stage job 1 (count at App.scala:15) with 1 output partitions
25/02/19 00:38:54 INFO DAGScheduler: Final stage: ShuffleMapStage 1 (count at App.scala:15)
25/02/19 00:38:54 INFO DAGScheduler: Parents of final stage: List()
25/02/19 00:38:54 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:54 INFO DAGScheduler: Submitting ShuffleMapStage 1 (MapPartitionsRDD[13] at count at App.scala:15), which has no missing parents
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_4 stored as values in memory (estimated size 18.8 KiB, free 433.7 MiB)
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_4_piece0 stored as bytes in memory (estimated size 9.1 KiB, free 433.7 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Added broadcast_4_piece0 in memory on 10.255.255.254:43927 (size: 9.1 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO SparkContext: Created broadcast 4 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:54 INFO DAGScheduler: Submitting 1 missing tasks from ShuffleMapStage 1 (MapPartitionsRDD[13] at count at App.scala:15) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:54 INFO TaskSchedulerImpl: Adding task set 1.0 with 1 tasks resource profile 0
25/02/19 00:38:54 INFO TaskSetManager: Starting task 0.0 in stage 1.0 (TID 1) (10.255.255.254, executor driver, partition 0, PROCESS_LOCAL, 9885 bytes)
25/02/19 00:38:54 INFO Executor: Running task 0.0 in stage 1.0 (TID 1)
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 11.085534 ms
25/02/19 00:38:54 INFO FileScanRDD: Reading File path: file:///mnt/g/scala/example-spark-scala/customers-100.csv, range: 0-17255, partition values: [empty row]
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 7.906767 ms
25/02/19 00:38:54 INFO Executor: Finished task 0.0 in stage 1.0 (TID 1). 2105 bytes result sent to driver
25/02/19 00:38:54 INFO TaskSetManager: Finished task 0.0 in stage 1.0 (TID 1) in 320 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:54 INFO TaskSchedulerImpl: Removed TaskSet 1.0, whose tasks have all completed, from pool
25/02/19 00:38:54 INFO DAGScheduler: ShuffleMapStage 1 (count at App.scala:15) finished in 0.351 s
25/02/19 00:38:54 INFO DAGScheduler: looking for newly runnable stages
25/02/19 00:38:54 INFO DAGScheduler: running: HashSet()
25/02/19 00:38:54 INFO DAGScheduler: waiting: HashSet()
25/02/19 00:38:54 INFO DAGScheduler: failed: HashSet()
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_2_piece0 on 10.255.255.254:43927 in memory (size: 34.4 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_1_piece0 on 10.255.255.254:43927 in memory (size: 6.5 KiB, free: 434.3 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Removed broadcast_0_piece0 on 10.255.255.254:43927 in memory (size: 34.4 KiB, free: 434.4 MiB)
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 12.077153 ms
25/02/19 00:38:54 INFO SparkContext: Starting job: count at App.scala:15
25/02/19 00:38:54 INFO DAGScheduler: Got job 2 (count at App.scala:15) with 1 output partitions
25/02/19 00:38:54 INFO DAGScheduler: Final stage: ResultStage 3 (count at App.scala:15)
25/02/19 00:38:54 INFO DAGScheduler: Parents of final stage: List(ShuffleMapStage 2)
25/02/19 00:38:54 INFO DAGScheduler: Missing parents: List()
25/02/19 00:38:54 INFO DAGScheduler: Submitting ResultStage 3 (MapPartitionsRDD[16] at count at App.scala:15), which has no missing parents
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_5 stored as values in memory (estimated size 13.1 KiB, free 434.1 MiB)
25/02/19 00:38:54 INFO MemoryStore: Block broadcast_5_piece0 stored as bytes in memory (estimated size 6.1 KiB, free 434.1 MiB)
25/02/19 00:38:54 INFO BlockManagerInfo: Added broadcast_5_piece0 in memory on 10.255.255.254:43927 (size: 6.1 KiB, free: 434.4 MiB)
25/02/19 00:38:54 INFO SparkContext: Created broadcast 5 from broadcast at DAGScheduler.scala:1585
25/02/19 00:38:54 INFO DAGScheduler: Submitting 1 missing tasks from ResultStage 3 (MapPartitionsRDD[16] at count at App.scala:15) (first 15 tasks are for partitions Vector(0))
25/02/19 00:38:54 INFO TaskSchedulerImpl: Adding task set 3.0 with 1 tasks resource profile 0
25/02/19 00:38:54 INFO TaskSetManager: Starting task 0.0 in stage 3.0 (TID 2) (10.255.255.254, executor driver, partition 0, NODE_LOCAL, 9276 bytes)
25/02/19 00:38:54 INFO Executor: Running task 0.0 in stage 3.0 (TID 2)
25/02/19 00:38:54 INFO ShuffleBlockFetcherIterator: Getting 1 (60.0 B) non-empty blocks including 1 (60.0 B) local and 0 (0.0 B) host-local and 0 (0.0 B) push-merged-local and 0 (0.0 B) remote blocks
25/02/19 00:38:54 INFO ShuffleBlockFetcherIterator: Started 0 remote fetches in 15 ms
25/02/19 00:38:54 INFO CodeGenerator: Code generated in 8.620798 ms
25/02/19 00:38:54 INFO Executor: Finished task 0.0 in stage 3.0 (TID 2). 4084 bytes result sent to driver
25/02/19 00:38:54 INFO TaskSetManager: Finished task 0.0 in stage 3.0 (TID 2) in 97 ms on 10.255.255.254 (executor driver) (1/1)
25/02/19 00:38:54 INFO TaskSchedulerImpl: Removed TaskSet 3.0, whose tasks have all completed, from pool
25/02/19 00:38:54 INFO DAGScheduler: ResultStage 3 (count at App.scala:15) finished in 0.108 s
25/02/19 00:38:54 INFO DAGScheduler: Job 2 is finished. Cancelling potential speculative or zombie tasks for this job
25/02/19 00:38:54 INFO TaskSchedulerImpl: Killing all running tasks in stage 3: Stage finished
25/02/19 00:38:54 INFO DAGScheduler: Job 2 finished: count at App.scala:15, took 0.120309 s
Count: 100
25/02/19 00:38:54 INFO SparkContext: SparkContext is stopping with exitCode 0.

2019年7月21日 星期日

dependencyManagement 和 parent

最近專案上遇到一個奇怪的狀況,概略來說就是 @NotEmpty(javax.validation.constraints.NotEmpty)這個驗證無效。後來同事研究了好一段時間,發現問題好像是出在 dependency conflict。然後就做了一些 dependencyManagement 和 parent 的小研究~不過因為沒有很仔細在研究,所以也只是粗略紀錄一下的程度。

  1. dependencyManagement 指的是事先宣告 dependency 的參考,然後當後面在 dependencies 裡面有使用到宣告的 dependency 時,就會引用事先宣告的參考版本。換句話說,如果 dependencyManagement 裡面宣告了 dependencies 裡面沒有的東西,那就不會造成什麼影響。
  2. parent 是強制引入所有宣告在 parent 裡面的 dependency。因此不管專案自己的 dependencies 裡面有沒有使用到,parent 宣告的 dependency 都會被引入。
  3. parent 裡宣告的東西好像是強制引入,就算在 depepdencies 裡面想要覆寫版本也是沒有用的。不過聽同事說,如果是間接引用的狀況,例如 parent 裡的 dependency A 引用的 dependency B,就可以透過 maven 本身的特性覆蓋或變更~這點是還沒有確實實驗過。
參考資料
  1. Maven实战(六)--- dependencies与dependencyManagement的区别
  2. maven 中parent 与 dependencyManagement 
  3. What is the difference between “pom” type dependency with scope “import” and without “import”?
  4. maven如何不继承parent里面的部分依赖
  5. How to exclude artifacts inherited from a parent POM? 
  6. Spring Boot 不使用默认的 parent,改用自己的项目的 parent

2019年7月3日 星期三

在 Maven 專案中自行指定原始碼的位置

在我們團隊的習慣,會在 Maven 專案裡面使用 Groovy 來寫測試,並且在寫測試時會用 Cucumber 來做整合測試。其中整合測試也許是為了想要跟單元測試分開,所以不會擺在一般 Maven 預設的路徑 src/test 裡面,而是會放在 src/integration-test 裡面,這樣的狀況會導致 IDE 無法正確找到原始碼,Maven 的 dependency 設定也會出現問題。

舉例來說,在我的案例裡,因為 Cucumber 只有測試階段才會使用,因此很自然我會在 Maven 上做這樣的 dependency 設定:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>4.3.1</version>
    <scope>test</scope>
</dependency>

這邊的 scope 會特地設定成 test,表示 Cucumber 的套件只有在測試期間才會被載入。接著我如果直接上把 src/integration-test/groovy 加入 build path,會因為 Cucumber 只會在測試階段被引用的關係,導致 IDE 發出編譯錯誤的訊息。根本來說,問題是在於我必須要好好地告訴 IDE 或者 Maven 說,src/integration-test/groovy 這個資料夾應該要被視為是測試的資料夾。

所幸,Maven 本身有方法可以自行指定資料夾的功能,也就是利用 build-helper-maven-plugin 這個 plugin。這個 plugin 的功能主要就是讓我們可以告訴 Maven 說哪個資料夾要看成是 source directory、哪個資料夾要看成是 test directory 等等。

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>${maven-build-helper-plugin.version}</version>
            <executions>
                <execution>
                    <id>add-test-sources</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>add-test-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>src/test/groovy</source>
                            <source>src/test/java</source>
                            <source>src/integration-test/groovy</source>
                            <source>src/integration-test/java</source>
                        </sources>
                    </configuration>
                </execution>
                <execution>
                    <id>add-test-resources</id>
                    <phase>generate-test-resources</phase>
                    <goals>
                        <goal>add-test-resource</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/integration-test/resources</directory>
                            </resource>
                        </resources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

可以注意到,在上述的 POM 當中,我設定了 src/integration-test/javasrc/integration-test/groovy 兩個資料夾都是 test-source 的 source,而 src/integration-test/resources 則是 test-resource 的 resource。如此一來,我就可以在 eclipse 裡面直接依靠 Maven 來幫我設定專案的原始碼資料夾了。

參考資料
  1. build-helper-maven-plugin
  2. Maven插件之build-helper-maven-plugin

2019年4月26日 星期五

plugin execution not covered by lifecycle configuration

公司的 Maven Plugin 在 eclipse 上會跑出標題的錯誤訊息,實際上問題好像是出在 m2e plugin。幾種不同的解法可以參考 [1]。比較完整的做法似乎應該在 dependencyManagement 上追加設定,不過因為在公司裡只有我用 eclipse,其他同事都用 Intellij....所以目前是直接從 eclipse 的 Maven 設定處把這個錯誤忽略掉 XD。

具體來說,就是從 Preferences → Maven → Errors/Warnings → 在 plugin execution not covered by lifecycle configuration 處選擇 “ignore”。

參考資料
  1. Maven —— Plugin execution not covered by lifecycle configuration 错误

2018年9月14日 星期五

在 Sonatype Nexus 2 上設定 HTTPS

之前一直用 HTTP,但覺得 HTTP 很不安全,所以趁著這次升級 Sonatype Nexus 的時候,一併來實驗啟用 HTTPS。

2017年2月8日 星期三

Maven 部署專案時,附上 javadoc 或原始碼

一般在 pom 檔沒有特別設定時,使用 mvn deploy 指令只會把函式庫打包後的 jar 送上 Maven Repository Server
但這樣會造成其他使用者在使用這個函式庫時,沒有 javadoc 可以即時查閱。
因此改成以下的指令 [1],讓 Maven 打包時順便一起把 javadoc 打包起來會比較好些。

2016年12月23日 星期五

Maven BOM (bill of materials)

在比較大型的專案中使用 Maven,有頗高的機率會遇到版本衝突的問題
例如 project A 使用了 guava 的某個 15 版才開始有的功能,而 project B 則是引用了 guava v14
接著有一個新的專案 project C,必須要同時引用 project A 和 project B,這時會發生什麼事呢?
有可能會造成最後打包出來的專案,裡面實際上被打包的是 guava v14
因而導致了在執行時,執行到 project A 的程式碼,就遇到 class/method not found 之類的錯誤訊息。

在 Maven 中,針對這個問題有一些基礎的解法 [1]
當同樣的套件被重複引用時,Maven 的基礎規則是誰先寫就看誰
但畢竟在遇到某些動態載入之類的情境時,並不是那麼容易可以決定誰先寫
更何況遇到的如果是 guava 這種被大量引用的函式庫,其實我們不見得能夠清楚地知道到底有哪些函式庫用了它。
因此,在基礎的解法之上的下個階段,就是利用 dependencyManagement 來指派版本了。

具體來說,dependencyManagement 的指派好像有兩種用法,一種是建立父子關係,另一種則是 BOM
詳細的範例其實都可以參考官方文件 [1],這裡紀錄的是 BOM。

2016年11月11日 星期五

在 Windows 上變更 Maven 預設路徑

Maven 預設會在 ${user.home}/.m2/repository 這個路徑內存放所有下載回來的函式庫
不過如果電腦是用 SSD 當系統碟時,就會覺得這樣有點討厭 XD
想要更改路徑的話,只需要在 Maven 的設定檔上加上 localRepository 標籤,指定新的路徑即可。

例如在 ${user.home}/.m2/settings.xml 裡寫上以下的內容,即可把 Maven 存放函式庫的位置移到 D:\maven 裡。

<settings>
	<localRepository>D:\maven</localRepository>
</settings>
參考資料
  1. Configuring Maven

2016年6月6日 星期一

使用 Maven 建立 Jersey 專案

當想要新建一個使用 Jersey 的 RESTful 的專案時,可以利用 Maven 直接產生範例 [1]

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.23
從上述的官方範例中,執行後會自動產生 groupId 為 com.example、artifactId 是 simple-service
並且會先建好 com.example 這個 package 的檔案(包含 src 目錄以及 pom.xml)
之後以 eclipse 來說就直接 import 到 eclipse 裡面,就可以開始增加自己需要的東西了。

另外紀錄一件小事,因為最近在 Windows 上習慣用 PowerShell,但上述的語法卻一直不能使用
研究了一段時間之後才發現,錯誤的並不是語法本身,而是在 PowerShell 上必須加上引號 [2]
例如:

mvn archetype:generate "-DarchetypeArtifactId=jersey-quickstart-grizzly2" \
"-DarchetypeGroupId=org.glassfish.jersey.archetypes" "-DinteractiveMode=false" \
"-DgroupId=com.example" "-DartifactId=simple-service" "-Dpackage=com.example" \
"-DarchetypeVersion=2.23"

參考資料:

  1. Jersey - Chapter 1. Getting Started
  2. Maven 3: Maven in 5 minutes “mvn archetype:generate…” command NOT WORKING

2016年1月11日 星期一

使用 Maven 打包 jar,並將引用的 dependencies 一併打包

在匯出 jar 時,如果是用在 Spark 或 Storm 這類平台
就沒有簡短的途徑,一定要把 jar 引用到的所有第三方函式庫,一併全打包進 jar 裡
在 Maven 中可以透過設定幾個 build 參數來達成。

2015年9月9日 星期三

[WARNING] Using platform encoding (MS950 actually) to copy filtered resources, i.e. build is platform dependent!

最近突然注意到,Maven 在執行時都會跑出這一段警告訊息
[WARNING] Using platform encoding (MS950 actually) to copy filtered resources, i.e. build is platform dependent!
雖然不影響 Maven 的運作,但就覺得編碼不用 UTF-8 就有點不太對~
很快速地 Google 後,看到別人發表的解法,只要在專案的 pom.xml 裡加入以下的設定
就可以指定 Maven 使用 UTF-8 編碼來執行了。
<project>
    ....
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

參考資料:
  1. Maven compile 出現 [WARNING] Using platform encoding 警告

2015年9月4日 星期五

使用 Maven 匯出第三方函式庫

在已經寫好 pom.xml 的情況下,如果需要匯出 pom.xml 上指定的所有第三方函式庫的話
可以使用 Maven 的 copy-dependencies 的功能:
mvn dependency:copy-dependencies
那些第三方函式庫就會被匯出到 ./target/dependency 的目錄裡。

參考資料:
  1. export dependency libs