Maven系列第6篇:生命周期和插件詳解,此篇看過之後在maven的理解上可以超越同級別90%的人!

maven系列目標:從入門開始開始掌握一個高級開發所需要的maven技能。

這是maven系列第6篇。

整個maven系列的內容前後是有依賴的,如果之前沒有接觸過maven,建議從第一篇看起,本文尾部有maven完整系列的連接。

前面我們使用maven過程中,用到了一些命令,如下:

mvn clean
mvn compile
mvn test
mvn package
mvn install
mvn install -Dmaven.test.skip=true
mvn deploy
mvn help:system

上面這些命令,如果你玩過maven,估計大家還是比較眼熟的,只是大家有沒有想過這些命令為什麼這麼寫,為什麼-Dmaven.test.skip=true可以跳過測試,大家需要知道mvn命令背後的一些原理,這就是本文的主要內容,廢話不多說,上乾貨。

本文主要內容

  1. 用戶自定義屬性的使用
  2. maven生命周期詳解
  3. maven插件詳解

用戶屬性Properties的使用

項目pom.xml中,有下面這樣一段依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.2.1.RELEASE</version>
    </dependency>
</dependencies>

大家看一下上面的配置,有沒有什麼問題?

他們的groupId和version都是一樣的,程序員面對與重複的代碼,需要提取,如果是java代碼中,我們可以將同樣的代碼或者變量值,提取成方法或者變量,做到重用,方便維護。

那麼maven的pom.xml中也支持這麼做:

<properties>
    <spring.group>org.springframework</spring.group>
    <spring.version>5.2.1.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>${spring.group}</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

大家看一下上面的代碼,properties位於pom.xml中的,是project元素的子元素,用戶可以在properties中自定義一些用戶屬性,然後可以在其他地方使用${屬性名稱}這種方式進行引用。

生命周期

我們開發一個項目的時候,通常有這些環節:創建項目、編寫代碼、清理已編譯的代碼、編譯代碼、執行單元測試、打包、集成測試、驗證、部署、生成站點等,這些環節組成了項目的生命周期,這些過程也叫做項目的構建過程,幾乎所有的項目都由這些環節中的其中幾個,創建項目和編寫代碼是我們程序員需要多參与的,其他的都可以做成自動化的方式。

用過ant的朋友回憶一下,在maven出現以前,開發人員每天都在對項目進行清理、編譯、執行單元測試、打包、部署等操作,雖然大家都在做這些工作,但是沒有一個統一的標準,項目和項目之間,公司和公司之間,大多數都是各寫各的,寫法是千奇百怪,能滿足自身需求就可以了,但是換個項目就得從頭再來,這些操作又需要重新編寫腳本。

而maven出來之後,項目生命周期中的這些環節都被簡化了,被規範化了,maven出現之前,項目的結構沒有一個統一的標準,所以生命周期中各個環節對應的自動化腳本也是各種各樣,而maven約定好了項目的結構,源碼的位置、資源文件的位置、測試代碼的位置、測試用到的資源文件的位置、靜態資源的位置、打包之後文件的位置等,這些都是maven約定好的,所以清理代碼用一個命令mvn clean就可以完成,不需要我們去配置清理的目標目錄;用mvn compile命令就可以完成編譯的操作;用mvn test就可以自動運行測試用例;用mvn package就可以將項目打包為jar、war格式的包,能夠如此簡單,主要還是maven中約定大於配置的結果。

maven中生命周期詳解

maven將項目的生命周期抽象成了3套生命周期,每套生命周期又包含多個階段,每套中具體包含哪些階段是maven已經約定好的,但是每個階段具體需要做什麼,是用戶可以自己指定的。

maven中定義的3套生命周期:

  1. clean生命周期
  2. default生命周期
  3. site生命周期

上面這3套生命周期是相互獨立的,沒有依賴關係的,而每套生命周期中有多個階段,每套中的多個階段是有先後順序的,並且後面的階段依賴於前面的階段,而用戶可以直接使用mvn命令來調用這些階段去完成項目生命周期中具體的操作,命令是:

mvn 生命周期階段

通俗點解釋:

maven中的3套生命周期相當於maven定義了3個類來解決項目生命周期中需要的各種操作,每個類中有多個方法,這些方法就是指具體的階段,方法名稱就是階段的名稱,每個類的方法是有順序的,當執行某個方法的時候,這個方法前面的方法也會執行。具體每個方法中需要執行什麼,這個是通過插件的方式讓用戶去配置的,所以非常靈活。

用戶執行mvn 階段名稱就相當於調用了具體的某個方法。

下面我們來看看每個生命周期中有哪些階段(也就是我們說的每個類中有哪些方法,順序是什麼樣的)。

clean生命周期

clean生命周期的目的是清理項目,它包含三個階段:

生命周期階段 描述
pre-clean 執行一些需要在clean之前完成的工作
clean 移除所有上一次構建生成的文件
post-clean 執行一些需要在clean之後立刻完成的工作

用戶可以通過mvn pre-clean來調用clean生命周期中的pre-clean階段需要執行的操作。

調用mvn post-clean會執行上面3個階段所有的操作,上文中有說過,每個生命周期中的後面的階段會依賴於前面的階段,當執行某個階段的時候,會先執行其前面的階段。

default生命周期

這個是maven主要的生命周期,主要被用於構建應用,包含了23個階段。

生命周期階段 描述
validate 校驗:校驗項目是否正確並且所有必要的信息可以完成項目的構建過程。
initialize 初始化:初始化構建狀態,比如設置屬性值。
generate-sources 生成源代碼:生成包含在編譯階段中的任何源代碼。
process-sources 處理源代碼:處理源代碼,比如說,過濾任意值。
generate-resources 生成資源文件:生成將會包含在項目包中的資源文件。
process-resources 編譯:複製和處理資源到目標目錄,為打包階段最好準備。
compile 處理類文件:編譯項目的源代碼。
process-classes 處理類文件:處理編譯生成的文件,比如說對Java class文件做字節碼改善優化。
generate-test-sources 生成測試源代碼:生成包含在編譯階段中的任何測試源代碼。
process-test-sources 處理測試源代碼:處理測試源代碼,比如說,過濾任意值。
generate-test-resources 生成測試源文件:為測試創建資源文件。
process-test-resources 處理測試源文件:複製和處理測試資源到目標目錄。
test-compile 編譯測試源碼:編譯測試源代碼到測試目標目錄.
process-test-classes 處理測試類文件:處理測試源碼編譯生成的文件。
test 測試:使用合適的單元測試框架運行測試(Juint是其中之一)。
prepare-package 準備打包:在實際打包之前,執行任何的必要的操作為打包做準備。
package 打包:將編譯后的代碼打包成可分發格式的文件,比如JAR、WAR或者EAR文件。
pre-integration-test 集成測試前:在執行集成測試前進行必要的動作。比如說,搭建需要的環境。
integration-test 集成測試:處理和部署項目到可以運行集成測試環境中。
post-integration-test 集成測試后:在執行集成測試完成後進行必要的動作。比如說,清理集成測試環境。
verify 驗證:運行任意的檢查來驗證項目包有效且達到質量標準。
install 安裝:安裝項目包到本地倉庫,這樣項目包可以用作其他本地項目的依賴。
deploy 部署:將最終的項目包複製到遠程倉庫中與其他開發者和項目共享。

site生命周期

site生命周期的目的是建立和發布項目站點,Maven能夠基於pom.xml所包含的信息,自動生成一個友好的站點,方便團隊交流和發布項目信息。主要包含以下4個階段:

階段 描述
pre-site 執行一些需要在生成站點文檔之前完成的工作
site 生成項目的站點文檔
post-site 執行一些需要在生成站點文檔之後完成的工作,並且為部署做準備
site-deploy 將生成的站點文檔部署到特定的服務器上

mvn命令和生命周期

從命令行執行maven任務的最主要方式就是調用maven生命周期的階段,需要注意的是,每套生命周期是相互獨立的,但是每套生命周期中階段是有前後依賴關係的,執行某個的時候,會按序先執行其前面所有的。

mvn執行階段的命令格式是:

mvn 階段1 [階段2] [階段n]

多個階段的名稱之間用空格隔開。

下面我們舉一些常見的例子來說明一下:

mvn clean

該命令是調用clean生命周期的clean階段,實際執行的階段為clean生命周期中的pre-clean和clean階段。

mvn test

該命令調用default生命周期的test階段,實際上會從default生命周期的第一個階段(validate)開始執行一直到test階段結束。這裏面包含了代碼的編譯,運行測試用例。

mvn clean install

這個命令中執行了兩個階段:cleaninstall,從上面3個生命周期的階段列表中找一下,可以看出clean位於clean生命周期的表格中,install位於default生命周期的表格中,所以這個命令會先從clean生命周期中的pre-clean階段開始執行一直到clean生命周期的clean階段;然後會繼續從default生命周期的validate階段開始執行一直到default生命周期的install階段。

這裏面包含了清理上次構建的結果,編譯代碼,測試,打包,將打好的包安裝到本地倉庫。

mvn clean deploy

這個命令也比較常用,會先按順序執行clean生命周期的[pre-clean,clean]這個閉區間內所有的階段,然後按序執行default生命周期的[validate,deploy]這個閉區間內的所有階段(也就是default生命周期中的所有階段)。這個命令內部包含了清理上次構建的結果、編譯代碼、運行單元測試、打包、將打好的包安裝到本地倉庫、將打好的包發布到私服倉庫。

上面說了這麼多理論,我們來看一下效果。

案例

創建一個maven項目

打開idea,點擊File->New->Project,選擇Maven,如下:

點擊Next,輸入項目坐標信息,如下:

點擊Next,輸入Project name 為maven-chat06,如下:

點擊Finish,創建成功,如下:

配置一下idea的maven環境,點擊File->Settings,如下圖:

點擊上面的OK完成配置。

還原~/.m2/settings.xml的配置到初始狀態,操作如下:

將M2_HOME/conf/settings.xml複製到~/.m2/settings.xml目錄,如果存在先備份一個,然後進行覆蓋。

maven項目是約定大於配置的,項目結構是按照maven的約定生成好的,關於maven約定項目結構,我們再來回顧一下。

Maven 提倡使用一個共同的標準目錄結構,Maven 使用約定優於配置的原則,大家盡可能的遵守這樣的目錄結構,如下所示:

目錄 目的
${basedir} 存放pom.xml和所有的子目錄
${basedir}/src/main/java 項目的java源代碼
${basedir}/src/main/resources 項目的資源,比如說property文件,springmvc.xml
${basedir}/src/test/java 項目的測試類,比如說Junit代碼
${basedir}/src/test/resources 測試用的資源
${basedir}/src/main/webapp/WEB-INF web應用文件目錄,web項目的信息,比如存放web.xml、本地圖片、jsp視圖頁面
${basedir}/target 打包輸出目錄
${basedir}/target/classes 編譯輸出目錄
${basedir}/target/test-classes 測試編譯輸出目錄
Test.java Maven只會自動運行符合該命名規則的測試類
~/.m2/repository Maven默認的本地倉庫目錄位置

結合剛才項目的結構和這個表格領會一下,下面我們來感受一下執行生命周期中的階段產生的效果。

修改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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 配置maven編譯的時候採用的編譯器版本 -->
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <!-- 指定源代碼是什麼版本的,如果源碼和這個版本不符將報錯,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-source參數 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <!-- 該命令用於指定生成的class文件將保證和哪個版本的虛擬機進行兼容,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-target參數 -->
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

上面properties這個元素中的配置,可能大家看不懂,先略過,後面會詳解。

創建一個Demo類,源碼是放在src/main/java目錄中,如下:

package com.javacode2018.maven;

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
    public static void main(String[] args) {
        System.out.println("歡迎和【路人甲java】一起學習maven,帶你成為maven高手!");
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(i);
        }
        list.forEach(System.out::print);
    }
}
mvn clean命令效果

在當前項目pom.xml所在目錄中執行下面命令:

mvn clean

效果如下:

D:\code\IdeaProjects\maven-chat06>mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.477 s
[INFO] Finished at: 2019-11-15T18:46:13+08:00
[INFO] ------------------------------------------------------------------------

上面有提到編譯、打包的內容都放在target目錄,看上面輸出中有個Deleting target目錄,說明mvn clean是對這個目錄進行清理,這個目錄中目前是空的。

mvn compile命令效果

先看一下項目的目錄中是沒有target目錄的,如下圖:

cmd中執行:

mvn compile

輸出:

D:\code\IdeaProjects\maven-chat06>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.897 s
[INFO] Finished at: 2019-11-15T18:53:12+08:00
[INFO] ------------------------------------------------------------------------

可以看到上面有Compiling 1 source ....,這個是編譯Demo.java,然後輸出到了target中的classes目錄,再來看一下項目的結構,如下圖:

上圖中匡紅的是新生成的。

mvn clean package效果
D:\code\IdeaProjects\maven-chat06>mvn clean package
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO] Building jar: D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.177 s
[INFO] Finished at: 2019-11-15T18:56:59+08:00
[INFO] ------------------------------------------------------------------------

從輸出中看一下,有個Building jar ...,生成了一個jar包,這個項目的pom.xml中的packaging元素沒有指定值,那就取默認值jar,表示這個構件是一個jar包,mvn clean package先清理編譯的代碼,然後執行了default生命周期的compile階段,將項目打成了jar放在了target目錄,如下圖:

大家看到上面還有很多其他的輸出,這個大家可以先忽略,本文看完了,都會明白的。

mvn clean install效果
D:\code\IdeaProjects\maven-chat06>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO] Building jar: D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ maven-chat06 ---
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.jar
[INFO] Installing D:\code\IdeaProjects\maven-chat06\pom.xml to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.977 s
[INFO] Finished at: 2019-11-15T19:05:21+08:00
[INFO] ------------------------------------------------------------------------

mvn clean package的輸出對比一下,多了幾行輸出,主要是多了2個Installing...,將項目打成jar包以及項目的pom文件放到本地倉庫去了,也就是將構件打包安裝到本地倉庫了。

上面幾個mvn命令的案例,都是通過mvn命令去執行了mvn中定義的生命周期中的階段,然後完成了很多看似內部很複雜的操作。比如打包,內部包含很多複雜的操作,maven都幫我們屏蔽了,通過一個簡單的mvn package就完成了。

上面也有說過,每個階段具體做的事情是由maven插件來完成的。

我們在回頭看一下上面一個輸出中,有很多類似於maven-xxxx-plugin:版本:xxx這樣的內容,這個就是表示當前在運行這個插件來完成對應階段的操作,mvn 階段明明執行的是階段,但是實際輸出中確實插件在幹活,那麼階段是如何和插件關聯起來的呢?插件又是什麼呢?

注意以下所有命令都在cmd窗口執行,執行位置位於上面這個項目的pom.xml所在目錄。

Maven插件

maven插件主要是為maven中生命周期中的階段服務的,maven中只是定義了3套生命周期,以及每套生命周期中有哪些階段,具體每個階段中執行什麼操作,完全是交給插件去乾的。

maven中的插件就相當於一些工具,比如編譯代碼的工具,運行測試用例的工具,打包代碼的工具,將代碼上傳到本地倉庫的工具,將代碼部署到遠程倉庫的工具等等,這些都是maven中的插件。

插件可以通過mvn命令的方式調用直接運行,或者將插件和maven生命周期的階段進行綁定,然後通過mvn 階段的方式執行階段的時候,會自動執行和這些階段綁定的插件。

插件目標

maven中的插件以jar的方式存在於倉庫中,和其他構件是一樣的,也是通過坐標進行訪問,每個插件中可能為了代碼可以重用,一個插件可能包含了多個功能,比如編譯代碼的插件,可以編譯源代碼、也可以編譯測試代碼;插件中的每個功能就叫做插件的目標(Plugin Goal),每個插件中可能包含一個或者多個插件目標(Plugin Goal)

目標參數

插件目標是用來執行任務的,那麼執行任務肯定是有參數配的,這些就是目標的參數,每個插件目標對應於java中的一個類,參數就對應於這個類中的屬性。

列出插件所有目標
mvn 插件goupId:插件artifactId[:插件version]:help
mvn 插件前綴:help

上面插件前綴的先略過,我們先看第一種效果。

如:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

This plugin has 2 goals:

clean:clean
  Goal which cleans the build.
  This attempts to clean a project's working directory of the files that were
  generated at build-time. By default, it discovers and deletes the directories
  configured in project.build.directory, project.build.outputDirectory,
  project.build.testOutputDirectory, and project.reporting.outputDirectory.

  Files outside the default may also be included in the deletion by configuring
  the filesets tag.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.

上面列出了maven-clean-plugin這個插件所有的目標,有2個,分別是clean:clean、clean:help,分號後面的部分是目標名稱,分號前面的部分是插件的前綴,每個目標的後面包含對這個目標的詳細解釋說明,關於前綴的後面會有詳細介紹。

查看插件目標參數列表
mvn 插件goupId:插件artifactId[:插件version]:help -Dgoal=目標名稱 -Ddetail
mvn 插件前綴:help -Dgoal=目標名稱 -Ddetail

上面命令中的-Ddetail用戶輸出目標詳細的參數列表信息,如果沒有這個,目標的參數列表不會輸出出來,看效果。

如:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.

  Available parameters:

    detail (Default: false)
      If true, display all settable properties for each goal.
      Expression: ${detail}

    goal
      The name of the goal for which to show help. If unspecified, all goals
      will be displayed.
      Expression: ${goal}

    indentSize (Default: 2)
      The number of spaces per indentation level, should be positive.
      Expression: ${indentSize}

    lineLength (Default: 80)
      The maximum length of a display line, should be positive.
      Expression: ${lineLength}

上面列出了clean插件的help目標的詳細參數信息。

注意上面參數詳細參數說明中有Expression: ${xxx}這樣的部分,這種表示給這個運行的目標傳參,可以通過mvn -Dxxx這種方式傳參,xxx${xxx}中的xxx部分,這個xxx有時候和目標參數的名稱不一致,所以這點需要注意,運行帶參數的目標,看一下效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail=false
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-clean-plugin:2.5

Maven Clean Plugin
  The Maven Clean Plugin is a plugin that removes files generated at build-time
  in a project's directory.

clean:help
  Display help information on maven-clean-plugin.
  Call
    mvn clean:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.332 s
[INFO] Finished at: 2019-11-18T15:14:56+08:00
[INFO] ------------------------------------------------------------------------

上面傳了一個detail=false,上面未輸出目標的詳細參數信息。

命令行運行插件
mvn 插件goupId:插件artifactId[:插件version]:插件目標 [-D目標參數1] [-D目標參數2] [-D目標參數n]
mvn 插件前綴:插件目標  [-D目標參數1] [-D目標參數2] [-D目標參數n]

案例:

maven中運行測試用例使用到的插件坐標是:

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12.4</version>
</dependency>

我們看一下這個插件有哪些目標:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

This plugin has 2 goals:

surefire:help
  Display help information on maven-surefire-plugin.
  Call mvn surefire:help -Ddetail=true -Dgoal=<goal-name> to display parameter
  details.

surefire:test
  Run tests using Surefire.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.662 s
[INFO] Finished at: 2019-11-18T15:26:26+08:00
[INFO] ------------------------------------------------------------------------

maven-surefire-plugin插件有2個目標helptest,描述中可以看出test目標是用來運行測試用例的。

我們看一下test目標對應的參數列表:

test目標對應的參數太多,我們只列出了部分參數,如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help -Dgoal=test -Ddetail=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

surefire:test
  Run tests using Surefire.

  Available parameters:

    skip (Default: false)
      Set this to 'true' to bypass unit tests entirely. Its use is NOT
      RECOMMENDED, especially if you enable it using the 'maven.test.skip'
      property, because maven.test.skip disables both running the tests and
      compiling the tests. Consider using the skipTests parameter instead.

大家認真看一下skip這個參數說明,這個參數默認是false,如果設置為true的時候,項目將跳過測試代碼的編譯和測試用例的執行,可以maven.test.skip這個屬性來進行命令行傳參,將其傳遞給test目標的skip屬性,這個通過-D傳遞的參數名稱就和目標參數名稱不一樣了,所以需要注意-D後面並不一定是參數名稱。

我們來運行一下test目標看看效果。

先看一下不加參數的效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.640 s
[INFO] Finished at: 2019-11-18T15:33:48+08:00
[INFO] ------------------------------------------------------------------------

maven.skip.test=true的效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test -Dmaven.test.skip=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.652 s
[INFO] Finished at: 2019-11-18T15:34:45+08:00
[INFO] ------------------------------------------------------------------------

對比一下上面2個輸出,下面的多了一行如下:

[INFO] Tests are skipped.

說明跳過了測試的執行。

插件傳參的2種方式

剛才上面講了一種通過-D後面跟用戶屬性的方式給用戶傳參,還有一種方式,在pom.xml中properties的用戶自定義屬性中進行配置,如下:

修改項目maven-chat06的pom.xml,properties中加入:

<maven.test.skip>true</maven.test.skip>

cmd中運行:

mvn org.apache.maven.plugins:maven-surefire-plugin:test

效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.638 s
[INFO] Finished at: 2019-11-18T15:46:04+08:00
[INFO] ------------------------------------------------------------------------

輸出中也有Tests are skipped.,說明也跳過了測試,和-Dmaven.test.skip=true效果一樣。

上面說的都是插件目標的東西,那麼插件目標是如何和生命周期關聯起來的呢?繼續向下看。

獲取插件目標詳細描述信息的另外一種方式
mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

上面這個命令調用的是help插件的describe這個目標,這個目標可以列出其他指定插件目標的詳細信息,看效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin -Dgoal=test -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] Mojo: 'surefire:test'
surefire:test
  Description: Run tests using Surefire.
  Implementation: org.apache.maven.plugin.surefire.SurefirePlugin
  Language: java
  Bound to phase: test

  Available parameters:

    additionalClasspathElements
      Additional elements to be appended to the classpath.

    argLine
      User property: argLine
      Arbitrary JVM options to set on the command line.

    skip (Default: false)
      User property: maven.test.skip
      Set this to 'true' to bypass unit tests entirely. Its use is NOT
      RECOMMENDED, especially if you enable it using the 'maven.test.skip'
      property, because maven.test.skip disables both running the tests and
      compiling the tests. Consider using the skipTests parameter instead.

可以拿這種和上面獲取插件目標參數詳情列表對比一下,上面這個更詳細一些,參數說明中多了一行User property: 屬性名稱,這個屬性名稱可以通過兩種方式傳遞:

  1. mvn命令-D屬性名稱的方式傳遞
  2. pom.xml中properties中定義的方式指定。

現在可以大家估計可以知道我們一直用的-Dmaven.test.skip為什麼可以跳過測試代碼的編譯和單元測試的執行了吧。

插件前綴

運行插件的時候,可以通過指定插件坐標的方式運行,但是插件的坐標信息過於複雜,也不方便寫和記憶,所以maven中給插件定義了一些簡捷的插件前綴,可以通過插件前綴來運行指定的插件。

可以通過下面命令查看到插件的前綴:

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version]

示例效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] org.apache.maven.plugins:maven-surefire-plugin:2.12.4

Name: Maven Surefire Plugin
Description: Surefire is a test framework project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-surefire-plugin
Version: 2.12.4
Goal Prefix: surefire

輸出中的Goal Prefix:部分對應的就是插件的前綴,上面這個插件的前綴是surefire

我們使用前綴來運行一下插件感受一下效果:

D:\code\IdeaProjects\maven-chat06>mvn surefire:test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.934 s
[INFO] Finished at: 2019-11-18T16:18:42+08:00
[INFO] ------------------------------------------------------------------------

上面通過別名來運行插件maven-surefire-plugintest目標,是不是簡潔了很多。

上面用了很多mvn help:這個命令,這個調用的是maven-help-plugin插件的功能,help是插件的前綴,它的坐標是:

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-help-plugin</artifactId>
    <version>3.2.0</version>
</dependency>

插件和生命周期階段綁定

maven只是定義了生命周期中的階段,而沒有定義每個階段中具體的實現,這些實現是由插件的目標來完成的,所以需要將階段和插件目標進行綁定,來讓插件目標幫助生命周期的階段做具體的工作,生命周期中的每個階段支持綁定多個插件的多個目標。

當我們將生命周期中的階段和插件的目標進行綁定的時候,執行mvn 階段就可以執行和這些階段綁定的插件目標

maven內置插件以及綁定

maven為了讓我們不用做任何配置就可以實現一些項目的構建操作,比如運行mvn clean就可以幫我們清理代碼,運行mvn install就可以將構件安裝到本地倉庫,所以maven幫我們做了一些事情,maven內部已經提供了很多默認的插件,而將一些階段默認和這些插件階段綁定好了,所以我們不用做任何配置就可以執行清理代碼、編譯代碼、測試、打包、安裝到本地倉庫、上傳到遠程倉庫等階段的操作,是因為maven已經默認給這些階段綁定好了插件目標,所以不需要我們再去配置,就直接可以運行,這些都是maven內置綁定幫我們做的事情,我們來看看maven有哪些內置綁定。

maven內置綁定

clean生命周期階段與插件綁定關係
生命周期階段 插件:目標
pre-clean
clean maven-clean-plugin:clean
post-clean

clean周期中只有clean階段默認綁定了maven-clean-plugin插件的clean目標。maven-clean-plugin插件的clean目標作用就是刪除項目的輸出目錄。

default生命周期階段與插件綁定關係

default生命周期中有23個階段,我只列出有默認綁定的,其他的沒有列出的沒有綁定任何插件,因此沒有任何實際的行為。

生命周期階段 插件:目標 執行任務
process-resources maven-resources-plugin:resources 複製主資源文件至主輸出目錄
compile maven-compiler-plugin:compile 編譯主代碼至主輸出目錄
process-test-resources maven-resources-plugin:testResources 複製測試資源文件至測試輸出目錄
test-compile maven-compiler-plugin:testCompile 編譯測試代碼至測試輸出目錄
test maven-surefile-plugin:test 執行測試用例
package maven-jar-plugin:jar 創建項目jar包
install maven-install-plugin:install 將輸出構件安裝到本地倉庫
deploy maven-deploy-plugin:deploy 將輸出的構件部署到遠程倉庫
site生命周期階段與插件綁定關係
生命周期階段 插件:目標
pre-site
site maven-site-plugin:site
post-site
site-deploy maven-site-plugin:deploy

來幾個案例解說一下。

mvn clean

該命令是調用clean生命周期的clean階段,實際執行的階段為clean生命周期中的pre-clean和clean階段,從上面內置綁定表格中找一下,可以看到只有clean階段綁定了maven-clean-plugin插件的clean目標,所以運行mvn clean的時候,實際上會調用maven-clean-plugin插件的clean目標來清理代碼。

運行一下看一下效果:

D:\code\IdeaProjects\maven-chat06>mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.408 s
[INFO] Finished at: 2019-11-18T16:34:14+08:00
[INFO] ------------------------------------------------------------------------

上面有一行輸出如下:

[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---

這個表示調用的插件是:maven-clean-plugin,版本是:2.5,插件的目標是:clean

mvn test

該命令調用default生命周期的test階段,實際上會從default生命周期的第一個階段(validate)開始執行一直到test階段結束。這裏面包含了代碼的編譯,運行測試用例。還是和上面的分析過程一樣,對照上面表格中的綁定關係,可以得到mvn test會調用下面一些插件的目標:

maven-resources-plugin:resources
maven-compiler-plugin:compile
maven-resources-plugin:testResources
maven-compiler-plugin:testCompile
maven-surefile-plugin:test

我們來驗證一下,看看是不是和我們分析的一樣:

D:\code\IdeaProjects\maven-chat06>mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.880 s
[INFO] Finished at: 2019-11-18T16:36:55+08:00
[INFO] ------------------------------------------------------------------------

從上面輸出中可以看到調用了5個插件的目標,和分析的一樣。

再來看一個跳過測試的例子,如下:

D:\code\IdeaProjects\maven-chat06>mvn test -Dmaven.skip.test=true
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.384 s
[INFO] Finished at: 2019-11-18T16:38:52+08:00
[INFO] ------------------------------------------------------------------------

上面這個是不是很熟悉,經常用到的跳過測試,為什麼這麼寫,我想大家都知道了吧。

其他幾個mvn compilemvn installmvn deploy建議大家也自己去玩玩,加深理解。

自定義綁定

除了默認綁定的一些操作,我們自己也可以將一些階段綁定到指定的插件目標上來完成一些操作,這種自定義綁定讓maven項目在構件的過程中可以執行更多更豐富的操作。

常見的一個案例是:創建項目的源碼jar包,將其安裝到倉庫中,內置插件綁定關係中沒有涉及到這一步的任務,所以需要用戶自己配置。

插件maven-source-pluginjar-no-fork可以幫助我們完成該任務,我們將這個目標綁定在default生命周期的verify階段上面,這個階段沒有任何默認綁定,verify是在測試完成之後並將構件安裝到本地倉庫之前執行的階段,在這個階段我們生成源碼,配置如下:

maven-chat06中的pom.xml加入如下配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <!-- 使用插件需要執行的任務 -->
                <execution>
                    <!-- 任務id -->
                    <id>attach-source</id>
                    <!-- 任務中插件的目標,可以指定多個 -->
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                    <!-- 綁定的階段 -->
                    <phase>verify</phase>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意上面配置的attach-source,後面輸出中會有。

id:任務的id,需唯一,如果不指定,默認為default

每個插件的配置在pom.xml的plugins元素中只能寫一次,否則會有警告。

最終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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 配置maven編譯的時候採用的編譯器版本 -->
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <!-- 指定源代碼是什麼版本的,如果源碼和這個版本不符將報錯,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-source參數 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <!-- 該命令用於指定生成的class文件將保證和哪個版本的虛擬機進行兼容,maven中執行編譯的時候會用到這個配置,默認是1.5,這個相當於javac命令後面的-target參數 -->
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <!-- 使用插件需要執行的任務 -->
                    <execution>
                        <!-- 任務id -->
                        <id>attach-source</id>
                        <!-- 任務中插件的目標,可以指定多個 -->
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                        <!-- 綁定的階段 -->
                        <phase>verify</phase>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

運行下面命令:

mvn install

效果:

D:\code\IdeaProjects\maven-chat06>mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ maven-chat06 ---
[INFO]
[INFO] --- maven-source-plugin:3.2.0:jar-no-fork (attach-source) @ maven-chat06 ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ maven-chat06 ---
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.jar
[INFO] Installing D:\code\IdeaProjects\maven-chat06\pom.xml to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT.pom
[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT-sources.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT-sources.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.268 s
[INFO] Finished at: 2019-11-18T16:59:12+08:00
[INFO] ------------------------------------------------------------------------

上面有個輸出如下:

maven-source-plugin:3.2.0:jar-no-fork (attach-source) @ maven-chat06 ---

可以看出調用了我們配置的插件生成源碼jar,上面的括號中的attach-source就是pom.xml中配置的任務id。

最後有個輸出:

[INFO] Installing D:\code\IdeaProjects\maven-chat06\target\maven-chat06-1.0-SNAPSHOT-sources.jar to C:\Users\Think\.m2\repository\com\javacode2018\maven-chat06\1.0-SNAPSHOT\maven-chat06-1.0-SNAPSHOT-sources.jar

可以看到將源碼安裝到本地倉庫了。

有些插件的目標默認會綁定到一些生命周期的階段中,那麼如果剛好插件默認綁定的階段和上面配置的一致,那麼上面phase元素可以不寫了,那麼怎麼查看插件的默認綁定呢?

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

我們看一下插件sourcejar-no-fork目標默認的綁定:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=source -Dgoal=jar-no-fork -Ddetail
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 ---
[INFO] Mojo: 'source:jar-no-fork'
source:jar-no-fork
  Description: This goal bundles all the sources into a jar archive. This
    goal functions the same as the jar goal but does not fork the build and is
    suitable for attaching to the build lifecycle.
  Implementation: org.apache.maven.plugins.source.SourceJarNoForkMojo
  Language: java
  Bound to phase: package

上面輸出中有個Bound to phase: package,表示默認綁定在了package階段上。

我們知道3套生命周期的運行時沒有依賴的,但是每套中的階段是有先後順序的,運行某個階段的時候,會先執行他前面所有的階段。清理代碼使用的是clean周期中的clean階段,編譯代碼用的是default周期中的compile階段,當直接運行mvn compile編譯代碼的時候並不會去清理代碼,編譯代碼的時候若發現文件沒有變動,會跳過沒有變化的文件進行編譯。如果我們想每次編譯之前強制先清理代碼,我們經常這麼寫:

mvn clean compile

上面的寫法是不是很熟悉,運行一下看看效果:

D:\code\IdeaProjects\maven-chat06>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.666 s
[INFO] Finished at: 2019-11-18T17:16:53+08:00
[INFO] ------------------------------------------------------------------------

還有其他方式么?

我們剛才學了自定義綁定,我們可以在default生命周期的第一個階段validate綁定清理代碼的插件,那我們來通過自定義綁定來實現一下,project->build->plugins元素中加入下面配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-clean-plugin</artifactId>
    <version>2.5</version>
    <executions>
        <!-- 使用插件需要執行的任務 -->
        <execution>
            <!-- 任務中插件的目標,可以指定多個 -->
            <id>clean-target</id>
            <goals>
                <goal>clean</goal>
            </goals>
            <!-- 綁定的階段 -->
            <phase>validate</phase>
        </execution>
    </executions>
</plugin>

運行下面命令看效果:

D:\code\IdeaProjects\maven-chat06>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (clean-target) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.630 s
[INFO] Finished at: 2019-11-18T17:22:37+08:00
[INFO] ------------------------------------------------------------------------

輸出中有:

[INFO] --- maven-clean-plugin:2.5:clean (clean-target) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target

這個表示運行了代碼清理的功能,進行了代碼清理,是不是感覺很爽,不用每次都寫clean了。

POM.xml插件配置詳解

插件目標共享參數配置

build->plugins->plugin中配置:

<!-- 插件參數配置,對插件中所有的目標起效 -->
<configuration>
    <目標參數名>參數值</目標參數名>
</configuration>

configuration節點下配置目標參數的值,節點名稱為目標的參數名稱,上面這種配置對當前插件的所有目標起效,也就是說這個插件中所有的目標共享此參數配置。

案例:

將案例中的pom.xml中的build元素修改成下面這樣。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.4</version>
            <!-- 插件參數配置,對插件中所有的目標起效 -->
            <configuration>
                <skip>true</skip>
            </configuration>
        </plugin>
    </plugins>
</build>

運行下面命令,看效果:

D:\code\IdeaProjects\maven-chat06>mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ maven-chat06 ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.328 s
[INFO] Finished at: 2019-11-18T17:30:58+08:00
[INFO] ------------------------------------------------------------------------

可以看到Test are skipped,說明跳過了測試,到此為止,跳過測試已經講了3種了:

1. mvn -Dmaven.test.skip=tue
2. properties中配置<maven.test.skip>true</maven.test.skip>
3. build中配置插件參數的方式

上面這個配置參數方式對當前插件的所有目標有效,如果想對指定的目標進行配置呢,用下面的方式。

插件目標參數配置

project->build->plugins->plugin->executions->execution元素中進行配置,如下:

<!-- 這個地方配置只對當前任務有效 -->
<configuration>
    <目標參數名>參數值</目標參數名>
</configuration>

上面這種配置常用於自定義插件綁定,只對當前任務有效。

感受一下效果,將pom.xml中的build元素改為下面內容:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>test</goal>
                        <goal>help</goal>
                    </goals>
                    <phase>pre-clean</phase>
                    <!-- 這個地方配置只對當前任務有效 -->
                    <configuration>
                        <skip>true</skip>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

上面自定義了一個綁定,在clean周期的pre-clean階段綁定了插件maven-surefire-plugin的兩個目標test和helpexecution元素沒有指定id,所以默認id是default

運行下面命令,見效果:

D:\code\IdeaProjects\maven-chat06>mvn pre-clean
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default) @ maven-chat06 ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:help (default) @ maven-chat06 ---
[INFO] Maven Surefire Plugin 2.12.4
  Surefire is a test framework project.

This plugin has 2 goals:

surefire:help
  Display help information on maven-surefire-plugin.
  Call mvn surefire:help -Ddetail=true -Dgoal=<goal-name> to display parameter
  details.

surefire:test
  Run tests using Surefire.


[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.738 s
[INFO] Finished at: 2019-11-18T17:41:08+08:00
[INFO] ------------------------------------------------------------------------

可以看到上面輸出中運行了插件的兩個目標,和預期結果一致。

獲取maven插件信息

上面我們介紹了,可以通過下面命令獲取插件詳細介紹信息

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目標名稱 -Ddetail
mvn help:describe -Dplugin=插件前綴 -Dgoal=目標名稱 -Ddetail

更多maven插件的幫助文檔可以參考maven的官方網站,上面有詳細的介紹,建議大家去看看,地址:

http://maven.apache.org/plugins/

插件解析機制

為了方便用戶使用和配置插件,maven不需要用戶提供完整的插件坐標信息,就可以解析到正確的插件,不過我建議使用插件配置的時候最好還是配置完整的坐標信息,不然不利於新人的理解和問題的排查。

插件倉庫

與其他maven構件一樣,插件構件也是基於坐標存儲在maven倉庫中,有需要的時候,maven會從本地查找插件,如果不存在,則到遠程倉庫查找,找到了以後下載到本地倉庫,然後使用。

大家回憶一下,上一章講過的,pom.xml中可以配置依賴的構件的倉庫地址,如下:

<repositories>
    <repository>
        <id>maven-nexus</id>
        <url>http://localhost:8081/repository/maven-public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

但是插件倉庫的配置和這個有一點不一樣,插件的是在pluginRepositories->pluginRepository元素中配置的,如下:

<pluginRepositories>
    <pluginRepository>
        <id>myplugin-repository</id>
        <url>http://repo1.maven.org/maven2/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
    </pluginRepository>
</pluginRepositories>

看一下上面2段配置,repository中的配置和pluginRepository中的子元素是一樣的,這個主意下就可以了。

插件的默認groupId

在pom.xml中配置插件的時候,如果是官方的插件,可以省略groupId

案例:

修改本篇示例中的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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <compilerVersion>1.8</compilerVersion>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

上面用到了maven-compiler-plugin,這個插件是編譯代碼的,是maven官方提供的插件,我們省略了groupId

上面這個插件用於編譯代碼的,編譯代碼的時候需要指定編譯器的版本,源碼的版本,目標代碼的版本,都是用的是1.8。

大家回頭去看一下,文章最開始的時候,在properties中有幾個屬性值是1.8的配置,這幾個值默認會被maven-compiler-plugin這個插件的上面3個參數獲取,具體可以去看一下這個插件compile目標的參數說明。

運行下面命令:

D:\code\IdeaProjects\maven-chat06>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.javacode2018:maven-chat06 >--------------------
[INFO] Building maven-chat06 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 ---
[INFO] Deleting D:\code\IdeaProjects\maven-chat06\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.692 s
[INFO] Finished at: 2019-11-18T18:11:34+08:00
[INFO] ------------------------------------------------------------------------

可以看到可以正常運行。

上面pom.xml省略了插件的groupId配置,如下:

<groupId>org.apache.maven.plugins</groupId>

maven在解析該插件的時候,會自動給這個插件補上默認的官方的groupId,所以可以正常運行,但是不建議大家這麼使用,容易讓新手比較懵逼。

插件前綴的解析

前面說過了使用mvn命令調用插件的時候,可以使用插件的前綴來代替繁瑣的插件坐標的方式,那麼maven是如何根據插件的前綴找到對應的插件的呢?

插件前綴與插件groupId:artifactId是一一對應的關係,這個關係的配置存儲在倉庫的元數據中,元數據位於下面2個xml中:

~/.m2/repository/org/apache/maven/plugins/maven-metadata-central.xml
~/.m2/repository/org/codehaus/mojo/maven-metadata-central.xml

接幾個圖,大家感受一下:

也可以通過在settings.xml中配置,讓maven檢查其他grouId上的插件元數據中前綴和插件關係的配置,如下:

<settings>
  <pluginGroups>
    <pluginGroup>com.your.plugins</pluginGroup>
  </pluginGroups>
</settings>

pluginGroups中有多個pluginGroup,可以配置你自己插件的元數據所在的groupId,然後可以通過前綴來訪問你自己的插件元數據目錄,此處先不細說,這個後面文章中講自定義插件的時候會再次說明。

查看項目最終pom.xml文件

我們的pom.xml默認會繼承maven頂級的一個父類pom.xml,頂級的pom.xml中指定了很多默認的配置,如生命周期中的階段和很多插件的綁定,這些如果我們想看到,到哪裡看呢?

mvn命令在項目中執行的時候,我們的pom.xml和父類的pom.xml最終會進行合併,當我們的pom.xml寫的比較複雜的時候,最終合併之後是什麼效果呢,我們可以通過下面這個命令查看:

mvn help:effective-pom

效果:

D:\code\IdeaProjects\maven-chat06>mvn help:effective-pom > 1.xml

上面我們將命令產生的結果輸出到項目的1.xml文件中了,我們看一下項目的1.xml的內容:

<?xml version="1.0" encoding="GBK"?>
<!-- ====================================================================== -->
<!--                                                                        -->
<!-- Generated by Maven Help Plugin on 2019-11-18T18:41:40+08:00            -->
<!-- See: http://maven.apache.org/plugins/maven-help-plugin/                -->
<!--                                                                        -->
<!-- ====================================================================== -->
<!-- ====================================================================== -->
<!--                                                                        -->
<!-- Effective POM for project                                              -->
<!-- 'com.javacode2018:maven-chat06:jar:1.0-SNAPSHOT'                       -->
<!--                                                                        -->
<!-- ====================================================================== -->
<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.javacode2018</groupId>
    <artifactId>maven-chat06</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <repositories>
        <repository>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <releases>
                <updatePolicy>never</updatePolicy>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
        </pluginRepository>
    </pluginRepositories>
    <build>
        <sourceDirectory>D:\code\IdeaProjects\maven-chat06\src\main\java</sourceDirectory>
        <scriptSourceDirectory>D:\code\IdeaProjects\maven-chat06\src\main\scripts</scriptSourceDirectory>
        <testSourceDirectory>D:\code\IdeaProjects\maven-chat06\src\test\java</testSourceDirectory>
        <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\classes</outputDirectory>
        <testOutputDirectory>D:\code\IdeaProjects\maven-chat06\target\test-classes</testOutputDirectory>
        <resources>
            <resource>
                <directory>D:\code\IdeaProjects\maven-chat06\src\main\resources</directory>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>D:\code\IdeaProjects\maven-chat06\src\test\resources</directory>
            </testResource>
        </testResources>
        <directory>D:\code\IdeaProjects\maven-chat06\target</directory>
        <finalName>maven-chat06-1.0-SNAPSHOT</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.3</version>
                </plugin>
                <plugin>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.2-beta-5</version>
                </plugin>
                <plugin>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.8</version>
                </plugin>
                <plugin>
                    <artifactId>maven-release-plugin</artifactId>
                    <version>2.5.3</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <executions>
                    <execution>
                        <id>default-compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <compilerVersion>1.8</compilerVersion>
                            <source>1.8</source>
                            <target>1.8</target>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-testCompile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <compilerVersion>1.8</compilerVersion>
                            <source>1.8</source>
                            <target>1.8</target>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <compilerVersion>1.8</compilerVersion>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>2.5</version>
                <executions>
                    <execution>
                        <id>default-clean</id>
                        <phase>clean</phase>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <id>default-testResources</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testResources</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>default-resources</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>resources</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>default-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <executions>
                    <execution>
                        <id>default-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-install-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>default-install</id>
                        <phase>install</phase>
                        <goals>
                            <goal>install</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.7</version>
                <executions>
                    <execution>
                        <id>default-deploy</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.3</version>
                <executions>
                    <execution>
                        <id>default-site</id>
                        <phase>site</phase>
                        <goals>
                            <goal>site</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                            <reportPlugins>
                                <reportPlugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-project-info-reports-plugin</artifactId>
                                </reportPlugin>
                            </reportPlugins>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-deploy</id>
                        <phase>site-deploy</phase>
                        <goals>
                            <goal>deploy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                            <reportPlugins>
                                <reportPlugin>
                                    <groupId>org.apache.maven.plugins</groupId>
                                    <artifactId>maven-project-info-reports-plugin</artifactId>
                                </reportPlugin>
                            </reportPlugins>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
                    <reportPlugins>
                        <reportPlugin>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-project-info-reports-plugin</artifactId>
                        </reportPlugin>
                    </reportPlugins>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <outputDirectory>D:\code\IdeaProjects\maven-chat06\target\site</outputDirectory>
    </reporting>
</project>

上面這個文件,大家一定要認真多看幾遍,理解一下,裡面包含太多東西,再重複一下,上面的文件多看幾遍!多看幾遍!多看幾遍!要理解!

課後題

留給大家2個問題

  1. 下面這個配置是干什麼的?給哪個插件使用的?

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  2. mvn test運行測試用例的時候,測試用例類名的寫法默認是有規則的,這些規則有人知道么?從哪裡可以看到這些規則?如何自定義?

大家可以留言或者在技術群討論。

總結

本文內容比較多,希望大家多讀幾遍,要理解。如果有收穫的幫忙分享一下,你們的支持也是我不斷輸出的一個動力,希望大家都能夠學到東西!

Maven系列目錄

更多好文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

斯柯達混動/純電動汽車未來幾年將相繼上市

近日,據海外媒體報導,斯柯達在未來幾年計畫推出一系列新能源車,其中包括或將於2019年上市的插電式混動車型以及最早2020年才能推出的純電動版車型。

斯柯達未來的純電動車型就將基於大眾MEB電動車模組化平臺進行打造,目前在考慮推出晶銳和明銳的純電動版車型,不過即使推出的話,最早也要等到2020年,不知道那時候某些城市購買純電動車搖號的話會不會很費勁了。

在推出純電動車之前,斯柯達計畫先推出插電式混動車型,首先考慮的是旗艦車型速派,最早能在2019年正式推出。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

花了幾個小時總結了一些容易出錯的 Java 知識點!

本文已經收錄自 (61k+Star!【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。歡迎 Star!歡迎騷擾!)
原文地址:https://javaguide.cn/2019/08/20/java/java%E5%9F%BA%E7%A1%80/Java%E7%96%91%E9%9A%BE%E7%82%B9/

1. 基礎

1.1. 正確使用 equals 方法

Object的equals方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。

舉個例子:

// 不能使用一個值為null的引用類型變量來調用非靜態方法,否則會拋出異常
String str = null;
if (str.equals("SnailClimb")) {
  ...
} else {
  ..
}

運行上面的程序會拋出空指針異常,但是我們把第二行的條件判斷語句改為下面這樣的話,就不會拋出空指針異常,else 語句塊得到執行。:

"SnailClimb".equals(str);// false 

不過更推薦使用 java.util.Objects#equals(JDK7 引入的工具類)。

Objects.equals(null,"SnailClimb");// false

我們看一下java.util.Objects#equals的源碼就知道原因了。

public static boolean equals(Object a, Object b) {
        // 可以避免空指針異常。如果a==null的話此時a.equals(b)就不會得到執行,避免出現空指針異常。
        return (a == b) || (a != null && a.equals(b));
    }

注意:

Reference:

  • 每種原始類型都有默認值一樣,如int默認值為 0,boolean 的默認值為 false,null 是任何引用類型的默認值,不嚴格的說是所有 Object 類型的默認值。
  • 可以使用 == 或者 != 操作來比較null值,但是不能使用其他算法或者邏輯操作。在Java中null == null將返回true。
  • 不能使用一個值為null的引用類型變量來調用非靜態方法,否則會拋出異常

1.2. 整型包裝類值的比較

所有整型包裝類對象值的比較必須使用equals方法。

先看下面這個例子:

Integer x = 3;
Integer y = 3;
System.out.println(x == y);// true
Integer a = new Integer(3);
Integer b = new Integer(3);
System.out.println(a == b);//false
System.out.println(a.equals(b));//true

當使用自動裝箱方式創建一個Integer對象時,當數值在-128 ~127時,會將創建的 Integer 對象緩存起來,當下次再出現該數值時,直接從緩存中取出對應的Integer對象。所以上述代碼中,x和y引用的是相同的Integer對象。

注意:如果你的IDE(IDEA/Eclipse)上安裝了阿里巴巴的p3c插件,這個插件如果檢測到你用 ==的話會報錯提示,推薦安裝一個這個插件,很不錯。

1.3. BigDecimal

1.3.1. BigDecimal 的用處

《阿里巴巴Java開發手冊》中提到:浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals 來判斷。 具體原理和浮點數的編碼方式有關,這裏就不多提了,我們下面直接上實例:

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999964
System.out.println(a == b);// false

具有基本數學知識的我們很清楚的知道輸出並不是我們想要的結果(精度丟失),我們如何解決這個問題呢?一種很常用的方法是:使用使用 BigDecimal 來定義浮點數的值,再進行浮點數的運算操作。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true 

1.3.2. BigDecimal 的大小比較

a.compareTo(b) : 返回 -1 表示小於,0 表示 等於, 1表示 大於。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

1.3.3. BigDecimal 保留幾位小數

通過 setScale方法設置保留幾位小數以及保留規則。保留規則有挺多種,不需要記,IDEA會提示。

BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255

1.3.4. BigDecimal 的使用注意事項

注意:我們在使用BigDecimal時,為了防止精度丟失,推薦使用它的 BigDecimal(String) 構造方法來創建對象。《阿里巴巴Java開發手冊》對這部分內容也有提到如下圖所示。

1.3.5. 總結

BigDecimal 主要用來操作(大)浮點數,BigInteger 主要用來操作大整數(超過 long 類型)。

BigDecimal 的實現利用到了 BigInteger, 所不同的是 BigDecimal 加入了小數位的概念

1.4. 基本數據類型與包裝數據類型的使用標準

Reference:《阿里巴巴Java開發手冊》

  • 【強制】所有的 POJO 類屬性必須使用包裝數據類型。
  • 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
  • 【推薦】所有的局部變量使用基本數據類型。

比如我們如果自定義了一個Student類,其中有一個屬性是成績score,如果用Integer而不用int定義,一次考試,學生可能沒考,值是null,也可能考了,但考了0分,值是0,這兩個表達的狀態明顯不一樣.

說明 :POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。

正例 : 數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。

反例 : 比如显示成交總額漲跌情況,即正負 x%,x 為基本數據類型,調用的 RPC 服務,調用不成功時,返回的是默認值,頁面显示為 0%,這是不合理的,應該显示成中劃線。所以包裝數據類型的 null 值,能夠表示額外的信息,如:遠程調用失敗,異常退出。

2. 集合

2.1. Arrays.asList()使用指南

最近使用Arrays.asList()遇到了一些坑,然後在網上看到這篇文章: 感覺挺不錯的,但是還不是特別全面。所以,自己對於這塊小知識點進行了簡單的總結。

2.1.1. 簡介

Arrays.asList()在平時開發中還是比較常見的,我們可以使用它將一個數組轉換為一個List集合。

String[] myArray = { "Apple", "Banana", "Orange" }; 
List<String> myList = Arrays.asList(myArray);
//上面兩個語句等價於下面一條語句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");

JDK 源碼對於這個方法的說明:

/**
 *返回由指定數組支持的固定大小的列表。此方法作為基於數組和基於集合的API之間的橋樑,與           Collection.toArray()結合使用。返回的List是可序列化並實現RandomAccess接口。
 */ 
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

2.1.2. 《阿里巴巴Java 開發手冊》對其的描述

Arrays.asList()將數組轉換為集合后,底層其實還是數組,《阿里巴巴Java 開發手冊》對於這個方法有如下描述:

2.1.3. 使用時的注意事項總結

傳遞的數組必須是對象數組,而不是基本類型。

Arrays.asList()是泛型方法,傳入的對象必須是對象數組。

int[] myArray = { 1, 2, 3 };
List myList = Arrays.asList(myArray);
System.out.println(myList.size());//1
System.out.println(myList.get(0));//數組地址值
System.out.println(myList.get(1));//報錯:ArrayIndexOutOfBoundsException
int [] array=(int[]) myList.get(0);
System.out.println(array[0]);//1

當傳入一個原生數據類型數組時,Arrays.asList() 的真正得到的參數就不是數組中的元素,而是數組對象本身!此時List 的唯一元素就是這個數組,這也就解釋了上面的代碼。

我們使用包裝類型數組就可以解決這個問題。

Integer[] myArray = { 1, 2, 3 };

使用集合的修改方法:add()remove()clear()會拋出異常。

List myList = Arrays.asList(1, 2, 3);
myList.add(4);//運行時報錯:UnsupportedOperationException
myList.remove(1);//運行時報錯:UnsupportedOperationException
myList.clear();//運行時報錯:UnsupportedOperationException

Arrays.asList() 方法返回的並不是 java.util.ArrayList ,而是 java.util.Arrays 的一個內部類,這個內部類並沒有實現集合的修改方法或者說並沒有重寫這些方法。

List myList = Arrays.asList(1, 2, 3);
System.out.println(myList.getClass());//class java.util.Arrays$ArrayList

下圖是java.util.Arrays$ArrayList的簡易源碼,我們可以看到這個類重寫的方法有哪些。

  private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        ...

        @Override
        public E get(int index) {
          ...
        }

        @Override
        public E set(int index, E element) {
          ...
        }

        @Override
        public int indexOf(Object o) {
          ...
        }

        @Override
        public boolean contains(Object o) {
           ...
        }

        @Override
        public void forEach(Consumer<? super E> action) {
          ...
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
          ...
        }

        @Override
        public void sort(Comparator<? super E> c) {
          ...
        }
    }

我們再看一下java.util.AbstractListremove()方法,這樣我們就明白為啥會拋出UnsupportedOperationException

public E remove(int index) {
    throw new UnsupportedOperationException();
}

2.1.4. 如何正確的將數組轉換為ArrayList?

stackoverflow:https://dwz.cn/vcBkTiTW

1. 自己動手實現(教育目的)

//JDK1.5+
static <T> List<T> arrayToList(final T[] array) {
  final List<T> l = new ArrayList<T>(array.length);

  for (final T s : array) {
    l.add(s);
  }
  return (l);
}
Integer [] myArray = { 1, 2, 3 };
System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList

2. 最簡便的方法(推薦)

List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

3. 使用 Java8 的Stream(推薦)

Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//基本類型也可以實現轉換(依賴boxed的裝箱操作)
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());

4. 使用 Guava(推薦)

對於不可變集合,你可以使用類及其與工廠方法:(參數不能為空)

List<String> il = ImmutableList.of("string", "elements");  // from varargs
List<String> il = ImmutableList.copyOf(aStringArray);      // from array

對於可變集合,你可以使用類及其工廠方法:

List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
List<String> l2 = Lists.newArrayList(aStringArray);               // from array
List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs

5. 使用 Apache Commons Collections

List<String> list = new ArrayList<String>();
CollectionUtils.addAll(list, str);

2.2. Collection.toArray()方法使用的坑&如何反轉數組

該方法是一個泛型方法:<T> T[] toArray(T[] a); 如果toArray方法中沒有傳遞任何參數的話返回的是Object類型數組。

String [] s= new String[]{
    "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
};
List<String> list = Arrays.asList(s);
Collections.reverse(list);
s=list.toArray(new String[0]);//沒有指定類型的話會報錯

由於JVM優化,new String[0]作為Collection.toArray()方法的參數現在使用更好,new String[0]就是起一個模板的作用,指定了返回數組的類型,0是為了節省空間,因為它只是為了說明返回的類型。詳見:

2.3. 不要在 foreach 循環里進行元素的 remove/add 操作

如果要進行remove操作,可以調用迭代器的 remove方法而不是集合類的 remove 方法。因為如果列表在任何時間從結構上修改創建迭代器之後,以任何方式除非通過迭代器自身remove/add方法,迭代器都將拋出一個ConcurrentModificationException,這就是單線程狀態下產生的 fail-fast 機制

fail-fast 機制 :多個線程對 fail-fast 集合進行修改的時,可能會拋出ConcurrentModificationException,單線程下也會出現這種情況,上面已經提到過。

java.util包下面的所有的集合類都是fail-fast的,而java.util.concurrent包下面的所有的類都是fail-safe的。

開源項目推薦

作者的其他開源項目推薦:

  1. :【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
  2. : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
  3. : 我覺得技術人員應該有的一些好習慣!
  4. :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

Model 3 預售量逾 27.6 萬輛,分析師預言 Tesla 無法準時交貨

電動車製造商 Tesla 宣布,新款電動車 Model 3 開放預售後,僅 36 小時內 Model 3 的預訂總量就超過了 27.6 萬輛,這是 Tesla 迄今為止推出的最廉價的電動車,Tesla CEO Elon Musk 表示,Model 3 將在 2017 年年底投產,首批新車至少需要等待 18 個月後才能發貨。

2016 年 4 月 1 日 Tesla 發表最新入門級電動車 Model 3,這款電動車起售價為 3.5 萬美元,續航里程大約 350 公里,計劃在 2017 年年底投產,Tesla 首先將交付北美的訂單,其次是歐洲、亞太等地區。   儘管這款新車需要等待非常長的時間,預售還是吸引了大量消費者,Model 3 訂單總量超過了 27.6 萬輛,訂單總額為 106 億美元。Tesla CEO Elon Musk 表示,Model 3 的基礎定價是 3.5 萬美元,加上運輸費和稅費,首批在加州生產的 Model 3 平均售價將達到了 5 萬至 6 萬美元。   Tesla 加州工廠正在為 Model 3 的生產線擴建,目標是在 2020 年將產能提升到每年 50 萬輛,以滿足市場需求,由於產能提升緩慢,Tesla 將需要很長的時間才能交付首批訂單,部分分析師認為 Tesla 無法完成首批訂單的生產,無法向消費者交貨 Model 3 不得不在 2020 年向預訂的消費者退還 1,000 美元的訂金。   分析師預期 2016 年 6 月 Model 3 的訂單將達到 25 萬輛到 30 萬輛,由於該車的預訂量超過預期,將推高 Tesla 的股價,便於該公司增發新股,募集資金投建新的工廠。自 2016 年 2 月 Tesla 股價創下新低後已累計反彈 60%。

(本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

[UWP]通過自定義XamlCompositionBrushBase實現圖片平鋪

1. 什麼是XamlCompositionBrushBase

我早就想試試自定義XamlCompositionBrushBase,但一直沒機會。上一篇文章介紹到,原理很簡單,但每次都要寫這些代碼很繁瑣,正好就用這個作為例子試試XamlCompositionBrushBase。

CompositionBrush靈活多變,它的基本用法如下:

  1. 通過Compositor創建CompositionBrush;
  2. 配置CompositionBrush;
  3. 創建SpriteVisual並將它的Brush設置為CompositionBrush;
  4. 使用 將SpriteVisual設置到某個UIElement的可視化層里。

這些步驟很繁瑣,而且不能用在XAML中。XamlCompositionBrushBase提供了將CompositionBrush用在XAML中一個橋樑,他繼承自Brush類,可以直接像普通的XAML 畫筆(如SolidColorBrush)那樣直接用在XAML中。

如上圖所示,中已經提了很不少XamlCompositionBrushBase的實現,它們的使用方式已經有很多文章介紹,這裏不一一列舉。

2. 自定義XamlCompositionBrushBase

這篇文章將介紹一個自定義的畫筆:TiledImageBrush,它的主要目標是實現ImageBrush沒有的圖片平鋪功能,並且它可以在XAML中使用,使用方式如下:

<Rectangle IsHitTestVisible="False">
    <Rectangle.Fill>
        <controls:TiledImageBrush Source="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

順便複習下普通的ImageBrush的用法:

<Rectangle >
    <Rectangle.Fill>
        <ImageBrush ImageSource="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

看起來TiledImageBrush的用法是不是和ImageBrush很像?接下來講解TiledImageBrush的實現步驟。TiledImageBrush繼承自XamlCompositionBrushBase,而實現XamlCompositionBrushBase的一般步驟如下:

protected override void OnConnected()
{
    // Delay creating composition resources until they're required.
    if (CompositionBrush == null)
    {
         CompositionBrush = CreateCompositionBrush();//Create A CompositionBrush.
    }
}

protected override void OnDisconnected()
{
    // Dispose of composition resources when no longer in use.
    if (CompositionBrush != null)
    {
        CompositionBrush.Dispose();
        CompositionBrush = null;
    }
}

首先重寫,當畫筆在屏幕上首次用於繪製元素時會調用這個函數。在這個函數里創建CompositionBrush並賦值給。

然後重寫,它在畫筆不再用於繪製任何元素時被調用。在這個函數里盡可能地釋放各種資源,例如CompositionBrush。這兩步就是實現XamlCompositionBrushBase的基本步驟。

創建CompositionBrush有很多種玩法,我之前寫過兩篇文章分別介紹 及 。這裏使用這篇文章里介紹到的代碼,首先使用LoadedImageSurface.StartLoadFromUri創建CompositionSurfaceBrush,然後加入到BorderEffect里實現圖片平鋪,然後把產生的CompositionEffectBrush賦值給XamlCompositionBrushBase.CompositionBrush

TiledImageBrush中添加了Source屬性用於設置圖片Uri(實際上是個ImageSource類型),模仿ImageBrush,這裏的Source也是一個ImageSource類型的屬性,雖然實際上使用的是它的UriSource。詳細代碼如下:

public ImageSource Source
{
    get => (ImageSource)GetValue(SourceProperty);
    set => SetValue(SourceProperty, value);
}

private void UpdateSurface()
{
    if (Source != null && _surfaceBrush != null)
    {
        var uri = (Source as BitmapImage)?.UriSource ?? new Uri("ms-appx:///");
        _surface = LoadedImageSurface.StartLoadFromUri(uri);
        _surfaceBrush.Surface = _surface;
    }
}

OnConnected的詳細代碼如下:

protected override void OnConnected()
{
    base.OnConnected();

    if (CompositionBrush == null)
    {
        _surfaceBrush = Compositor.CreateSurfaceBrush();
        _surfaceBrush.Stretch = CompositionStretch.None;

        UpdateSurface();

        _borderEffect = new BorderEffect()
        {
            Source = new CompositionEffectSourceParameter("source"),
            ExtendX = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap,
            ExtendY = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap
        };

        _borderEffectFactory = Compositor.CreateEffectFactory(_borderEffect);
        _borderEffectBrush = _borderEffectFactory.CreateBrush();
        _borderEffectBrush.SetSourceParameter("source", _surfaceBrush);
        CompositionBrush = _borderEffectBrush;
    }
}

這樣一個基本的XamlCompositionBrush就完成了,完整的代碼可以在這裏查看:

3. 參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

Vue躬行記(9)——Vuex

  Vuex是一個專為Vue.js設計的狀態管理庫,適用於多組件共享狀態的場景。Vuex能集中式的存儲和維護所有組件的狀態,並提供相關規則保證狀態的獨立性、正確性和可預測性,這不僅讓調試變得可追蹤,還讓代碼變得更結構化且易維護。本文所使用的Vuex,其版本是3.1.1。

一、基本用法

  首先需要引入Vue和Vuex兩個庫,如果像下面這樣在Vue之後引入Vuex,那麼Vuex會自動調用Vue.use()方法註冊其自身;但如果以模塊的方式引用,那麼就得顯式地調用Vue.use()。注意,因為Vuex依賴Promise,所以對於那些不支持Promise的瀏覽器,要使用Vuex的話,得引入相關的polyfill庫,例如es6-promise。

<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>

  然後創建Vuex應用的核心:Store(倉庫)。它是一個容器,保存着大量的響應式狀態(State),並且這些狀態不能直接修改,需要顯式地將修改請求提交到Mutation(變更)中才能實現更新,因為這樣便於追蹤每個狀態的變化。在下面的示例中,初始化了一個digit狀態,並在mutations選項中添加了兩個可將其修改的方法。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    add: state => state.digit++,
    minus: state => state.digit--
  }
});

  接着創建根實例,並將store實例注入,從而讓整個應用都能讀寫其中的狀態,在組件中可通過$store屬性訪問到它,如下所示,以計算屬性的方式讀取digit狀態,並通過調用commit()方法來修改該狀態。

var vm = new Vue({
  el: "#container",
  store: store,
  computed: {
    digit() {
      return this.$store.state.digit;
    }
  },
  methods: {
    add() {
      this.$store.commit("add");
    },
    minus() {
      this.$store.commit("minus");
    }
  }
});

  最後將根實例中的方法分別註冊到兩個按鈕的點擊事件中,如下所示,每當點擊這兩個按鈕時,狀態就會更新,並在頁面中显示。

<div id="container">
  <p>{{digit}}</p>
  <button @click="add">增加</button>
  <button @click="minus">減少</button>
</div>

二、主要組成

  Vuex的主要組成除了上一節提到的Store、State和Mutation之外,還包括Getter和Action,本節會對其中的四個做重點講解,它們之間的關係如圖2所示。

圖2  四者的關係

1)State

  State是一個可存儲狀態的對象,在應用的任何位置都能被訪問到,並且作為單一數據源(Single Source Of Truth)而存在。

  當組件需要讀取大量狀態時,一個個的聲明成計算屬性會顯得過於繁瑣而冗餘,於是Vuex提供了一個名為mapState()的輔助函數,用來將狀態自動映射成計算屬性,它的參數既可以是數組,也可以是對象。

  當計算屬性的名稱與狀態名稱相同,並且不需要做額外處理時,可將名稱組成一個字符串數組傳遞給mapState()函數,在組件中可按原名調用,如下所示。

var vm = new Vue({
  computed: Vuex.mapState([ "digit" ])
});

  當計算屬性的名稱與狀態名稱不同,或者計算屬性讀取的是需要處理的狀態時,可將一個對象傳遞給mapState()函數,其鍵就是計算屬性的名稱,而其值既可以是函數,也可以是字符串,如下代碼所示。如果是函數,那麼它的第一個參數是state,即狀態對象;如果是字符串,那麼就是從state中指定一個狀態作為計算屬性。

var vm = new Vue({
  computed: Vuex.mapState({
    digit: state => state.digit,
    alias: "digit"        //相當於state => state.digit
  })
});

  因為mapState()函數返回的是一個對象,所以當組件內已經包含計算屬性時,可以對其應用擴展運算符(…)來進行合併,如下所示,這是一種極為簡潔的寫法。

var vm = new Vue({
  computed: {
    name() {},
    ...Vuex.mapState([ "digit" ])
  }
});

2)Getter

  Getter是從State中派生出的狀態,當多個組件要對同一個狀態進行相同的處理時,就需要將狀態轉移到Getter中,以免產生重複的冗餘代碼。

  Getter相當於Store的計算屬性,它能接收兩個參數,第一個是state對象,第二個是可選的getters對象,該參數能讓不同的Getter之間相互訪問。Getter的返回值會被緩存,並且只有當依賴值發生變化時才會被重新計算。不過當返回值是函數時,其結果就不會被緩存,如下所示,其中caculate返回的是個数字,而sum返回的是個函數。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  getters: {
    caculate: state => {
      return state.digit + 2;
    },
    sum: state => right => {
      return state.digit + right;
    }
  }
});

  在組件內可通過this.$store.getters訪問到Getter中的數據,如下所示,讀取了上一個示例中的兩個Getter。

var vm = new Vue({
  methods: {
    add() {
      this.$store.getters.caculate;
      this.$store.getters.sum(1);
    }
  }
});

  Getter也有一個輔助函數,用來將Getter自動映射為組件的計算屬性,名字叫mapGetters(),其參數也是數組或對象。但與之前的mapState()不同,當參數是對象時,其值不能是函數,只能是字符串,如下所示,為了對比兩種寫法,聲明了兩個computed選項。

var vm = new Vue({
  computed: Vuex.mapGetters([ "caculate" ]),
  computed: Vuex.mapGetters({
    alias: "caculate"
  })
});

3)Mutation

  更改狀態的唯一途徑是提交Mutation,Vuex中的Mutation類似於事件,也包含一個類型和回調函數,在函數體中可進行狀態更改的邏輯,並且它能接收兩個參數,第一個是state對象,第二個是可選的附加數據,叫載荷(Payload)。下面這個Mutation的類型是“interval”,接收了兩個參數。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    interval: (state, payload) => state.digit += payload.number
  }
});

  在組件中不能直接調用Mutation的回調函數,得通過this.$store.commit()方法觸發更新,如下所示,採用了兩種提交方式,第一種是傳遞type參數,第二種是傳遞包含type屬性的對象。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.commit("interval", { number: 2 });             //第一種
      this.$store.commit({ type: "interval", number: 2 });       //第二種
    }
  }
});

  當多人協作時,Mutation的類型適合寫成常量,這樣更容易維護,也能減少衝突。

const INTERVAL = "interval";

  Mutation有一個名為mapMutations()的輔助函數,其寫法和mapState()相同,它能將Mutation自動映射為組件的方法,如下所示。

var vm = new Vue({
  methods: Vuex.mapMutations(["interval"])
  //相當於
  methods: {
    interval(payload) {
      this.$store.commit(INTERVAL, payload);
    }
  }
});

  注意,為了能追蹤狀態的變更,Mutation只支持同步的更新,如果要異步,那麼得使用Action。

4)Action

  Action類似於Mutation,但不同的是它可以包含異步操作,並且只能用來通知Mutation,不會直接更新狀態。Action的回調函數能接收兩個參數,第一個是與Store實例具有相同屬性和方法的context對象(注意,不是Store實例本身),第二個是可選的附加數據,如下所示,調用commit()方法提交了一個Mutation。

const store = new Vuex.Store({
  actions: {
    interval(context, payload) {
      context.commit("interval", payload);
    }
  }
});

  在組件中能通過this.$store.dispatch()方法分發Action,如下所示,與commit()方法一樣,它也有兩種提交方式。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.dispatch("interval", { number: 2 });            //第一種
      this.$store.dispatch({type: "interval", number: 2});        //第二種
    }
  }
});

  注意,由於dispatch()方法返回的是一個Promise對象,因此它能以一種更優雅的方式來處理異步操作,如下所示。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.dispatch("interval", { number: 2 }).then(() => {
        console.log("success");
      });
    }
  }
});

  Action有一個名為mapActions()的輔助函數,其寫法和mapState()相同,它能將Action自動映射為組件的方法,如下所示。

var vm = new Vue({
  methods: Vuex.mapActions([ "interval" ])
});

三、模塊

  當應用越來越大時,為了避免Store變得過於臃腫,有必要將其拆分到一個個的模塊(Module)中。每個模塊就是一個對象,包含屬於自己的State、Getter、Mutation和Action,甚至還能嵌套其它模塊。

1)局部狀態

  對於模塊內部的Getter和Mutation,它們接收的第一個參數是模塊的局部狀態,而Getter的第三個參數rootState和Action的context.rootState屬性可訪問根節點狀態(即全局狀態),如下所示。

const moduleA = {
  state: { digit: 0 },
  mutations: {
    add: state => state.digit++
  },
  getters: {
    caculate: (state, getter, rootState) => {
      return state.digit + 2;
    }
  },
  actions: {
    interval(context, payload) {
      context.commit("add", payload);
    }
  }
};

2)命名空間

  默認情況下,只有在訪問State時需要帶命名空間,而Getter、Mutation和Action的調用方式不變。將之前的moduleA模塊註冊到Store實例中,如下所示,modules選項的值是一個子模塊對象,其鍵是模塊名稱。

const store = new Vuex.Store({
  modules: {
    a: moduleA
  }
});

  如果要訪問模塊中的digit狀態,那麼可以像下面這樣寫。

store.state.a.digit;

  當模塊的namespaced屬性為true時,它的Getter、Mutation和Action也會帶命名空間,在使用時,需要添加命名空間前綴,如下代碼所示,此舉大大提升了模塊的封裝性和復用性。

const moduleA = {
  namespaced: true
};
var vm = new Vue({
  el: "#container",
  store: store,
  methods: {
    add() {
      this.$store.commit("a/add");
    },
    caculate() {
      this.$store.getters["a/caculate"];
    }
  }
});

  如果要在帶命名空間的模塊中提交全局的Mutation或分發全局的Action,那麼只要將{root: true}作為第三個參數傳給commit()或dispatch()就可實現,如下所示。

var vm = new Vue({
  methods: {
    add() {
      this.$store.dispatch("add", null, { root: true });
      this.$store.commit("add", null, { root: true });
    }
  }
});

  如果要在帶命名空間的模塊中註冊全局的Action,那麼需要將其修改成對象的形式,然後添加root屬性並設為true,再將Action原先的定義轉移到handler()函數中,如下所示。

const moduleA = {
  actions: {
    interval: {
      root: true,
      handler(context, payload) {}
    }
  }
};

3)輔助函數

  當使用mapState()、mapGetters()、mapMutations()和mapActions()四個輔助函數對帶命名空間的模塊做映射時,需要顯式的包含命名空間,如下所示。

var vm = new Vue({
  computed: Vuex.mapState({
    digit: state => state.a.digit
  }),
  methods: Vuex.mapMutations({
    add: "a/add"
  })
});

  這四個輔助函數的第一個參數都是可選的,用於綁定命名空間,可簡化映射過程,如下所示。

var vm = new Vue({
  computed: Vuex.mapState("a", {
    digit: state => state.digit
  }),
  methods: Vuex.mapMutations("a", {
    add: "add"
  })
});

  Vuex還提供了另一個輔助函數createNamespacedHelpers(),可創建綁定命名空間的輔助函數,如下所示。

const { mapState, mapMutations } = Vuex.createNamespacedHelpers("a");

四、動態註冊

  在創建Store實例后,可通過registerModule()方法動態註冊模塊,如下代碼所示,調用了兩次registerModule()方法,第一次註冊了模塊“a”,第二次註冊了嵌套模塊“a/b”。

const store = new Vuex.Store();
store.registerModule("a", moduleA);    
store.registerModule(["a", "b"], moduleAB);

  通過store.state.a和store.state.a.b可訪問模塊的局部狀態。如果要卸載動態註冊的模塊,那麼可以通過unregisterModule()方法實現。

  registerModule()方法的第三個參數是可選的配置對象,當preserveState屬性的值為true時(如下所示),在註冊模塊時會忽略模塊中的狀態,即無法在store中讀取模塊中的狀態。

store.registerModule("a", moduleA, { preserveState: true });

五、表單處理

  表單默認能直接修改組件的狀態,但是在Vuex中,狀態只能由Mutation觸發更新。為了能更好的追蹤狀態的變化,也為了能更符合Vuex的思維,需要讓表單控件與狀態綁定在一起,並通過input或change事件監聽狀態更新的行為,如下所示。

<div id="container">
  <input :value="digit" @input="add" />
</div>

  然後在Store實例中初始化digit狀態,並添加更新狀態的Mutation,如下所示。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    add: (state, value) => {
      state.digit = value;
    }
  }
});

  最後在創建根實例時,將digit狀態映射成它的計算屬性,在事件處理程序add()中調用commit()方法,並將控件的值作為第二個參數傳入,如下所示。

var vm = new Vue({
  el: "#container",
  store: store,
  computed: Vuex.mapState(["digit"]),
  methods: {
    add(e) {
      this.$store.commit("add", e.target.value);
    }
  }
});

  還有一個方法也能實現相同的功能,那就是在控件上使用v-model指令,但需要與帶setter的計算屬性配合,如下所示(只給出了關鍵部分的代碼)。

<div id="container">
  <input v-model="digit" />
</div>
<script>
  var vm = new Vue({
    computed: {
      digit: {
        get() {
          return this.$store.state.digit;
        },
        set(value) {
          this.$store.commit("add", value);
        }
      }
    }
  });
</script>

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

要吸管請「開金口」 加州餐廳明年起不主動提供

摘錄自2018年9月21日蘋果日報美國加州報導

美聯社報導,加州州長布朗(Jerry Brown)簽署通過法案,明年起禁止州內餐廳主動提供顧客塑膠吸管服務,首開全美先例。餐廳業者若不配合將先予以警告,兩次警告後開罰,每年最高300美元(約9,200台幣),由州內衛生人員負責不定時執行稽查。不過,此法僅適用於正式、有服務生點餐的餐廳,速食業者不再此限。儘管不是直接禁止,部分共和黨人仍批評這項新法「管太多」。

民主黨的布朗將環保議題列為優先施政考量。他指,每年大量塑膠垃圾流入海中,殺死鯨魚和魚類,污染最終進到民眾的食物和供水:「我們對一次性便利用具的迷戀,將招致災難性的後果。所有形式的塑膠製品,塑膠吸管、寶特瓶、包裝、塑膠袋等,都在使地球窒息。」

同天,布朗簽署通過另項法案,規定販售兒童餐的各級餐廳,都必須將菜單飲料預設為牛奶和開水。兩項法案皆從明年1月1日開始生效。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

Model 3 接單爆量部分是膨風?Musk:重複訂單將取消

特斯拉(Tesla Motors)平價電動車「Model 3」3 月 31 日亮相一週就狂接了 32.5 萬筆訂單,執行長 Elon Musk 之後更於 4 月 22 日指稱 Model 3 已接獲近 40 萬筆訂單。   不過,許多人對此抱持懷疑態度,認為或許有顧客重複下單導致數據「膨風」,分析師 Anton Wahlman 上週更在 Seeking Alpha 網站發文宣稱,雖然特斯拉限制每人只能預購兩台 Model 3,但他個人卻已成功下了 20 筆訂單。   Yahoo! Finance 的新聞記者隨後也宣稱,只要以不同表格遞交多筆訂單,就能成功突破每人限購 2 台的限制。   Wahlman 宣稱,這意味著特斯拉接獲的 40 萬筆訂單有一部份也許來自投機客,Model 3 龐大的需求可能只是膨風。   對此,Musk 決定重新檢視顧客的下單狀況,並把多餘的訂單全部取消。MarketWatch 2 日報導,Musk 4 月 29 日透過 Twitter 指出,重複的訂單將被刪除,每位客戶都只限購 2 台,而所有訂單中僅有 5% 預購了 2 台 Model 3,因此不太可能有投機客。   儘管如此,投機客瘋狂預購 Model 3,仍代表這款車種深獲顧客期待,畢竟 Model 3 在還未亮相前,搶著掏錢預訂 Model 3 的粉絲們就已在實體門市外排起超長人龍,這樣的熱絡景象真的是前所未見。   現在特斯拉遇到的問題,應該不是需求疲軟、而是訂單太多。有分析師認為,特斯拉當前產能不足以應付爆量需求,未來可能得籌錢擴產、甚至打造第二座組裝廠。   CNBC、Forbes 4 月中報導,巴克萊分析師 Brian Johnson 表示,他估計特斯拉需要另行籌資 30 億美元,才能滿足如雪片飛來的訂單。他說,特斯拉訂單暴增,或許會以需要打造更多汽車和電池工廠為由,尋求額外資金。   從特斯拉生產情況看來,確實有擴產需要。今年第一季,特斯拉交車數量僅有 14,820 輛,低於預期的 16,000 輛。今年全年特斯拉預計交車 80,000~90,000 輛。這讓外界憂心忡忡,擔心該公司無法從小眾車廠,蛻變為大型公司。特斯拉的加州組裝工廠,以往為通用汽車(GM)和豐田(Toyota)的合資企業,年度產能達 50 萬輛,意味特斯拉仍有大幅增產空間,能加裝設備,生產 Model 3。    (本文內容由授權使用;首圖來源: CC BY 2.0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

【Elasticsearch 7 探索之路】(三)倒排索引

上一篇,我們介紹了 ES 文檔的基本 CURE 和批量操作。我們都知道倒排索引是搜索引擎非常重要的一種數據結構,什麼是倒排索引,倒排索引的原理是什麼。

1 索引過程

在講解倒排索引前,我們先了解索引創建,下圖是 Elasticsearch 中數據索引過程的流程。

從上圖可以看到,文檔未在 ES 中進行索引,而是 由 Analyzer 組件對其執行一些操作並將其拆分為 token/term。然後將這些術語作為倒排索引存儲在磁盤中。假設我們有兩個名為 name 和 age 字段,當要將文檔索引到 ES 時,Analyzers 組件 以某些定界符(有默認定界符,例如空格,句號等)將它們分割開獲取 token,再對每個 token 應用特定的過濾器。經過分析的這些標記稱為 term。然後將這些 term 針對該字段)存儲在倒排列表中。

2 倒排索引

2.1 正排與倒排索引

一般在我們閱讀圖書,我們會根據目錄快速定位想要閱讀的章節,過了一段時間,你想要的回顧之前某一個知識點,你發現從目錄難以查找到對應的地方,這時你可能就會從索引頁從去查找對應內容索引,從而找到頁碼。

搜索引擎其實跟我們的使用圖書很相似,下面我來對圖書和搜索引擎進行一個簡單的類比,來看一下搜素引擎中正排和倒排索引。

  • 圖書
    • 正排索引-目錄頁
    • 倒排索引-索引頁
  • 搜索引擎
    • 正排索引-文檔 Id 到文檔內容和單詞的關聯
    • 倒排索引-單詞到文檔 Id 的關係

2.2 倒排索引的核心組成

舉個例子,假設我們有 3 個文檔:

Doc 1:breakthrough drug for schizophrenia

Doc 2:new schizophrenia drug 

Doc 3:new approach for treatment of schizophrenia

經過分析,文件中的術語如下

文檔 分詞結果
Doc 1 breakthrough,drug,for,schizophrenia
Doc 2 new,schizophrenia,drug
Doc 3 new,approach,for,treatment,of

倒排列表的元數據結構:

(DocID;TF;<POS>)

其中:

  • DocID:出現某單詞的文檔ID

  • TF(詞頻):單詞在該文檔中出現的次數

  • POS:單詞在文檔中的位置

則它們生成的倒排索引

單詞 逆向文檔頻率 倒排列表(DocID;TF; ))
breakthrough 1 (1;1;<1>)
drug 2 (1;1;<2>),(2;1;<3>)
for 2 (1;1;<3>),(3;1;<3>)
schizophrenia 2 (1;1;<4>),(2;1;<2>)
new 2 (2;1;<1>),(3;1;<1>)
approach 1 (3;1;<2>)
treatment 1 (3;1;<4>)
of 1 (3;1;<5>)
  • ES 倒排索引包含兩個部分

    • 單詞詞典 (Term Dictionary),索引最小單位,記錄所有文檔的單詞,記錄單詞到倒排列表的關聯關係
      • 單詞詞典一般都會非常多,通過 B+ 樹或 Hash 表方式以滿足高性能的插入與查詢
    • 倒排列表(Posting List)-由倒排索引項(Posting)組成
      • 文檔 ID
      • 詞頻 TF,該單詞在文檔中出現的次數,用於相關性評分
      • 位置(Position),單詞在文檔中分詞的位置。用於語句搜索(phrase query)
      • 偏移(Offset),記錄單詞的開始結束位置,實現高亮显示

ES 也可以指定對某些字段不做索引

  • 優點:節省存儲空間
  • 缺點:字段無法被搜索

3 總結

在之前文章說了 ES 的文檔是基於 JSON 格式,在我們創建索引的時候,對每一個文檔記錄對應索引相關的信息。在對倒排索引進行搜索時,查詢單詞是否在單詞字典,獲取單詞在倒排列表的指針,獲取有該單詞單詞的文檔 Id 列表,通過 ES 的倒排索引,我們輕易對全文進行快速搜素。

系列文章

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

豐田與Uber將聯手打造新型汽車租賃業務

據路透社報導,豐田與Uber 5月24日宣稱,或在汽車共用服務領域展開合作,前者將投資汽車租賃。

雙方在一份聯合聲明中表示,將打造新型租賃業務,購車者可從豐田金融服務公司租賃車輛,作為Uber司機獲取收入,該收入可作為租車費用。

稍早時候,大眾宣佈向汽車共用公司Gett投資3億美元。此前,通用已向Lyft投資了5億美元,開發按需自動駕駛網路。

近日,蘋果向滴滴出行投資了10億美元,這被認為是科技巨頭搶佔中國市場份額的重大舉動。與此同時,福特正在尋求合作夥伴,以發展汽車製造和銷售之外的業務。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!