第五章 座標和依賴

###5.1 何爲Maven座標###   Maven的世界中擁有數量龐大的構件,也就是日常用的jar、war包,就像在三維座標系中一個座標值(x,y,z)能夠惟一的肯定一個點的位置信息,Maven中的構件也須要一個座標來標識它們。在咱們開發Maven項目的時候,須要爲其定義適當的座標,這是Maven強制要求的。在這個基礎上,其餘Maven項目才能應用該項目生成的構件。 ###5.2 座標詳解###   Maven座標爲各類構件引入了秩序,任何一個構件都必須明肯定義本身的座標,而一組Maven座標是經過一些元素定義的,它們是groupId,artifactId,version,packaging,class-sifer。下面是一組座標定義:html

<groupId>com.mycompany.app</groupId>  
  <artifactId>my-app</artifactId>  
  <packaging>jar</packaging>  
<version>0.0.1-SNAPSHOT</version>

  下面講解一下各個座標元素:java

  • groupId:定義當前Maven項目隸屬的實際項目。首先,Maven項目和實際項目不必定是一對一的關係。好比SpringFrameWork這一實際項目,其對應的Maven項目會有不少,如spring-core,spring-context等。這是因爲Maven中模塊的概念,所以,一個實際項目每每會被劃分紅不少模塊。其次,groupId不該該對應項目隸屬的組織或公司。緣由很簡單,一個組織下會有不少實際項目,若是groupId只定義到組織級別,然後面咱們會看到,artifactId只能定義Maven項目(模塊),那麼實際項目這個層次將難以定義。最後,groupId的表示方式與Java包名的表達方式相似,一般與域名反向一一對應。
  • artifactId: 該元素定義當前實際項目中的一個Maven項目(模塊),推薦的作法是使用實際項目名稱做爲artifactId的前綴。好比上例中的my-app。
  • version: 該元素定義Maven項目當前的版本
  • packaging: 定義Maven項目打包的方式,首先,打包方式一般與所生成構件的文件擴展名對應,如上例中的packaging爲jar,最終的文件名爲my-app-0.0.1-SNAPSHOT.jar。也能夠打包成war, ear等。當不定義packaging的時候,Maven 會使用默認值jar。 web項目通常都用war
  • classifier: 該元素用來幫助定義構建輸出的一些附件。附屬構件與主構件對應,如上例中的主構件爲my-app-0.0.1-SNAPSHOT.jar,該項目可能還會經過一些插件生成如my-app-0.0.1-SNAPSHOT-javadoc.jar,my-app-0.0.1-SNAPSHOT-sources.jar, 這樣附屬構件也就擁有了本身惟一的座標
      不能直接定義項目的 classifer,由於附屬構件不是項目直接默認生成的,而是由附加的插件幫助生成的,Maven 的classifier的做用例子:
      classifier的用途在於:
      1. maven download javadoc / sources 架包的時候
      2. 引入依賴的時候,一般引入依賴 咱們只須要:
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.2.RELEASE</version> 
</dependency>

  可是有些架包仍是比較特殊的:
  好比 JSON-lib ,我利用 sonatype的 jar 搜素引擎搜索發現,JSON-lib的jar提供了兩個版本的SDK,如圖:
  輸入圖片說明
  這個時候就必須使用classifier屬性指定了:mysql

<dependency>
   <groupId>net.sf.json-lib</groupId>
   <artifactId>json-lib</artifactId>
   <version>2.4</version>
   <classifier>jdk15</classifier> 
</dependency>

  若是不定義classifier的話就maven就會報錯說找不到 jar 文件。 ###5.3 account-email###   工程總佈局如圖:
輸入圖片說明 ####5.3.1 account-email的POM####web

<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.my.account</groupId>
  <artifactId>account-email</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  
  <properties>
    <springversion>4.2.1.RELEASE</springversion>
   </properties>
  
  <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${springversion}</version>
            <!--classifier>RELEASE</classifier-->
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${springversion}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${springversion}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${springversion}</version>
        </dependency>
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.icegreen</groupId>
            <artifactId>greenmail</artifactId>
            <version>1.4.1</version>
            <scope>test</scope>
        </dependency>
  </dependencies>
    
</project>

####5.3.2 account-email的主代碼####spring

package com.learn.mvn.account.email;

public class AccountEmailException  extends Exception
{
    private static final long serialVersionUID = -4817386460334501672L;

    public AccountEmailException( String message )
    {
        super( message );
    }

    public AccountEmailException( String message, Throwable throwable )
    {
        super( message, throwable );
    }
}
package com.learn.mvn.account.email;

public interface AccountEmailService 
{
	void sendMail(String to, String subject, String htmlText )
	throws AccountEmailException;
}
package com.learn.mvn.account.email;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

public class AccountEmailServiceImpl implements AccountEmailService
{
	private JavaMailSender javaMailSender;
	private String systemEmail;

	public JavaMailSender getJavaMailSender() {
		return javaMailSender;
	}

	public void setJavaMailSender(JavaMailSender javaMailSender) {
		this.javaMailSender = javaMailSender;
	}

	public String getSystemEmail() {
		return systemEmail;
	}

	public void setSystemEmail(String systemEmail) {
		this.systemEmail = systemEmail;
	}

	public void sendMail(String to, String subject, String htmlText) 
			throws AccountEmailException
	{
		try 
		{
			MimeMessage msg = javaMailSender.createMimeMessage();
			MimeMessageHelper msgHelper = new MimeMessageHelper(msg);
			
			msgHelper.setFrom(systemEmail);
			msgHelper.setTo(to);
			msgHelper.setSubject(subject);
			msgHelper.setText(htmlText, true);
			
			javaMailSender.send(msg);
		} 
		catch (MessagingException e) 
		{
			throw new AccountEmailException("Failed to send email.", e);
		}
	}
}

  Spring的配置文件account-email.xml:sql

<?xml version="1.0" encoding="UTF-8"?>
<beans
 xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-2.5.xsd">
 
 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location" value="classpath:email.properties" />
 </bean>
 
 <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">  
       <property name="host" >  
        <value>${email.host}</value>  
       </property>  
       <property name="port" >  
        <value>${email.port}</value>  
       </property>  
       <property name="protocol">  
        <value>${email.protocol}</value>  
       </property>  
       <property name="username">  
        <value>${email.username}</value>  
       </property>  
       <property name="password">  
        <value>${email.password}</value>  
       </property>  
       <!-- SMTP服務器驗證 -->  
       <property name="javaMailProperties">  
           <props>  
               <!-- 驗證身份 -->  
               <prop key="mail.${email.protocol}.auth">${email.auth}</prop>  
          </props>  
       </property>  
   </bean>  
     
   <bean id="accountEmailService" class="com.learn.mvn.account.email.AccountEmailServiceImpl">  
    <property name="javaMailSender" ref="javaMailSender" />
    <property name="systemEmail" value="${email.systemEmail}" />
   </bean>  
 
</beans>

####5.3.3 account-email的測試代碼####數據庫

package com.learn.mvn.account.email;

import static org.junit.Assert.*;

import javax.mail.Message;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetup;

public class AccountEmailServiceTest 
{
	private GreenMail greenMail;

    private ApplicationContext applicationContext;

    /**
     * 啓動郵件服務器
     * 
     * @throws Exception
     * @return void
     */
    @Before
    public void setUp() throws Exception {
        greenMail = new GreenMail(ServerSetup.SMTP);
        greenMail.setUser("test1234@163.com", "test1234");
        greenMail.start();
    }

    /**
     * Test method for
     */
    @Test
    public void testSendEmail() throws Exception {
        applicationContext = new ClassPathXmlApplicationContext("account-email.xml");
        AccountEmailService accountEmailService = (AccountEmailService) applicationContext.getBean("accountEmailService");
        String subject = "Test Subject";
        String htmlText = "<h3> Test </h3>";

        accountEmailService.sendMail("test1234@163.com", subject, htmlText);

        greenMail.waitForIncomingEmail(2000, 1);
        Message[] msgs = greenMail.getReceivedMessages();
        assertEquals(1, msgs.length);
        assertEquals(subject, msgs[0].getSubject());
        assertEquals(htmlText, GreenMailUtil.getBody(msgs[0]).trim());
    }

    /**
     * 關閉郵件服務器
     * 
     * @throws Exception
     * @return void
     */
    @After
    public void tearDown() throws Exception {
        greenMail.stop();
    }
}

  email.properties文件:apache

email.protocol=smtp
email.host=localhost
email.port=25
email.username=test1234@163.com
email.password=test1234
email.auth=true
email.systemEmail=test1234@163.com

  運行mvn clean test執行測試:json

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.learn.mvn.account.email.AccountEmailServiceTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further detail
s.
九月 18, 2015 12:20:11 上午 org.springframework.context.support.ClassPathXmlAppl
icationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationCont
ext@220711: startup date [Fri Sep 18 00:20:11 CST 2015]; root of context hierarc
hy
九月 18, 2015 12:20:11 上午 org.springframework.beans.factory.xml.XmlBeanDefinit
ionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [account-email.xml]
九月 18, 2015 12:20:11 上午 org.springframework.beans.factory.config.PropertyPla
ceholderConfigurer loadProperties
信息: Loading properties file from class path resource [email.properties]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.991 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.984 s
[INFO] Finished at: 2015-09-18T00:20:11+08:00
[INFO] Final Memory: 12M/29M
[INFO] ------------------------------------------------------------------------

###5.4 依賴的配置###   依賴能夠聲明以下:api

<project>  
  ...  
  <dependencies>  
    <dependency>  
      <groupId>group-a</groupId>  
      <artifactId>artifact-a</artifactId>  
      <version>1.0</version>  
      <exclusions>  
        <exclusion>  
          <groupId>group-c</groupId>  
          <artifactId>excluded-artifact</artifactId>  
        </exclusion>  
      </exclusions>  
    </dependency>  
    <dependency>  
      <groupId>group-a</groupId>  
      <artifactId>artifact-b</artifactId>  
      <version>1.0</version>  
      <type>bar</type>  
      <scope>runtime</scope>  
    </dependency>  
  </dependencies>  
</project>

  依賴會包含基本的groupId, artifactId,version等元素,根元素project下的dependencies能夠包含一個或者多個dependency元素,以聲明一個或者多個依賴。
  下面詳細講解每一個依賴能夠包含的元素:
  groupId,artifactId和version:依賴的基本座標,對於任何一個依賴來講,基本座標是最重要的,Maven根據座標才能找到須要的依賴。
  type: 依賴的類型,對應於項目座標定義的packaging。大部分狀況下,該元素沒必要聲明,其默認值是jar。
  scope: 依賴的範圍,下面會進行詳解。
  optional: 標記依賴是否可選。
  exclusions: 用來排除傳遞性依賴,下面會進行詳解。
  大部分依賴聲明只包含基本座標。 ###5.5 依賴範圍###   Maven在編譯主代碼的時候須要使用一套classpath,在編譯和執行測試的時候會使用另外一套classpath,實際運行項目的時候,又會使用一套classpath。
  依賴範圍就是用來控制依賴與這三種classpath(編譯classpath、測試classpath、運行classpath)的關係,Maven有如下幾種依賴範圍:

  • compile: 編譯依賴範圍。若是沒有指定,就會默認使用該依賴範圍。使用此依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效。
  • test: 測試依賴範圍。使用此依賴範圍的Maven依賴,只對於測試classpath有效,在編譯主代碼或者運行項目的使用時將沒法使用此類依賴。典型的例子就是JUnit,它只有在編譯測試代碼及運行測試的時候才須要。
  • provided: 已提供依賴範圍。使用此依賴範圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效。典型的例子是servlet-api,編譯和測試項目的時候須要該依賴,但在運行項目的時候,因爲容器已經提供,就不須要Maven重複地引入一遍。
  • runtime: 運行時依賴範圍。使用此依賴範圍的Maven依賴,對於測試和運行classpath有效,但在編譯主代碼時無效。典型的例子是JDBC驅動實現,項目主代碼的編譯只須要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才須要實現上述接口的具體JDBC驅動。
  • system: 系統依賴範圍。該依賴與三種classpath的關係,和provided依賴範圍徹底一致。可是,使用system範圍依賴時必須經過systemPath元素顯式地指定依賴文件的路徑。因爲此類依賴不是經過Maven倉庫解析的,並且每每與本機系統綁定,可能形成構建的不可移植,所以應該謹慎使用。systemPath元素能夠引用環境變量,如:
<dependency>  
    <groupId>javax.sql</groupId>  
    <artifactId>jdbc-stdext</artifactId>  
    <version>2.0</version>  
    <scope></scope>  
    <systemPath>${java.home}/lib/rt.jar</systemPath>  
</dependency>
  • import(Maven 2.0.9及以上): 導入依賴範圍。該依賴範圍不會對三種classpath產生實際的影響。 ###5.6 傳遞性依賴### ####5.6.1 何爲傳遞性依賴####   account-mail有一個compile範圍的spring-core依賴,spring-core有一個compile範圍的commons-logging依賴,那麼commons-logging就會成爲account-mail的compile範圍依賴。即A-->C B-->A ==>B-->C(這種依賴是基於compile這個範圍進行傳遞)。 ####5.6.2 傳遞性依賴和依賴範圍####   接依賴範圍爲test,第二直接依賴範圍是compile的時候,依賴範圍就是test。
      當第二直接依賴範圍是compile,傳遞性依賴的範圍與第一直接依賴相同。當第二直接依賴爲test,依賴不會得以傳遞。
      當第二直接依賴範圍是provided,只傳遞第一直接依賴也爲provided.且傳遞性依賴範圍一樣爲provided。
      當第二直接以來範圍是runtime,傳遞性依賴的範圍與第一直接依賴相同,當compile例外,此時傳遞性依賴的範圍爲runtime。 ###5.7 依賴調節###   maven引入的傳遞性依賴機制,一方面大大簡化和方便了依賴聲明,另外一方面大部分狀況下咱們只須要關心項目直接依賴的是什麼,而不用考慮這些依賴會引入什麼傳遞性依賴。但有時候,當傳遞依賴形成問題的時候,咱們須要清楚地知道該傳遞性依賴是從哪條依賴路徑引入的。
      maven依賴調解的兩個原則。(1)第一原則是:路徑最近者優先。(2)第二原則是:第一聲明者優先。在依賴路徑長度相等的前提下,在pom依賴聲明的順序決定了誰會解析使用,順序最靠前的那個依賴優勝。
      A->B->C->X(1.0)、A->D->X(2.0),X(1.0)的路徑爲3,而X(2.0)的路徑爲2,所以X(2.0)會被解析。
      A->B->Y(1.0)、A->C->Y(2.0),對應路徑長度相等的,在POM中依賴聲明的順序決定誰會被解析。 ###5.8 可選依賴###   假設有這樣一個依賴關係,項目A依賴於項目B,項目B依賴於項目X和Y,B對於X和Y的依賴都是可選依賴:A->B,B->X(可選),B->Y(可選)。根據傳遞性依賴的定義,若是全部這三個依賴的範圍都是compile,那麼X,Y 就是A的compile範圍傳遞性依賴。然而,因爲這裏X,Y是可選依賴,依賴不會得以傳遞。換句話說,X,Y將不會對A有任何影響。項目B的依賴聲明見代碼清單。關於可選依賴須要說明的一點就是,在理想狀況下,是不該該使用可選依賴的。
<project>
   <modelVerion>4.0</modelVersion>
   <groupId>com.learn.mvn</groupId>
   <artifactId>project-B</artifactId>
   <version>1.0</version>
   <dependencies>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.10</version>
           <optional>true</optional>
       </dependency>
       <dependency>
           <groupId>postgresql</groupId>
           <artifactId>postagresql</artifactId>
           <version>8.4-701.jdbc3</version>
           <optional>true</optional>
       </dependency>
   </dependencies>
</project>

  所以當項目A依賴項目B的時候,若是實際使用基於Mysql數據庫,那麼在項目A中須要顯示的聲明mysql-connection-java依賴。
  最後,關於可選依賴須要說明的一點是,在理想的狀況下,是不該該使用可選依賴的。前面咱們能夠看到,使用可選依賴的緣由是某一個項目實現了多個特性,在面向對象設計中,有個單一職責性原則,意指一個類應該只有一項職責,而不是糅合太多的功能 。這個原則在規劃maven項目的時候也一樣適用。在上面的例子中,更好的作法是爲mysql和postgresql分別建立一個maven項目,基於一樣的groupId分配不一樣的artifactId。 ###5.9 最佳實踐### ####5.9.1 排除依賴####   傳遞性依賴給項目隱式地引入了不少依賴,這極大地簡化了項目的依賴管理,可是有時候這種特性也會帶來問題。好比,當前項目有一個第三方依賴,而這個第三方依賴因爲某些緣由依賴了另一個類庫的SNAPSHOT的版本,那麼這個SNAPSHOT就會成爲當前項目的傳遞性依賴,而SNAPSHOT的不穩定性會影響到當前項目。這時須要排除該SNAPSHOT,而且在當前項目中聲明該類庫某個正式發佈版本。

<project>
   <modelVerion>4.0</modelVersion>
   <groupId>com.learn.mvn</groupId>
   <artifactId>project-a</artifactId>
   <version>1.0.0</version>
   <dependencies>
       <dependency>
           <groupId>com.juvenxu.mvnbook</groupId>
           <artifactId>project-b</artifactId>
           <version>1.0.0</version>
           <exclusions>
             <exclusion>
                 <groupId>com.juvencu.mvnbook</groupId>
                 <artifactId>project-c</artifactId>
             </exclusion>
           </exclusions>
       </dependency>
       <dependency>
          <groupId>com.juvencu.mvnbook</groupId>
          <artifactId>project-c</artifactId>          
          <version>1.1.0</version>
       </dependency>
   </dependencies>
</project>

  代碼中,項目A依賴於項目B,可是因爲一些緣由,不想引入傳遞性依賴C,而是本身顯示地聲明對於項目C1.1.0版本的依賴。代碼中使用exclusions元素聲明排除依賴,exclusions能夠包含一個或者多個exclusion子元素。
  須要注意的是,聲明exclusion的時候只須要groupId,artifactId就能惟必定義某個依賴。 ####5.9.2 歸類依賴####   經過<properties>元素來定義。經過${變量名}來引用聲明一個常量信息,全部用到的地方都用這個常量

<properties>
    <springversion>2.5.6</springversion>
    <junitversion>2.5.6</junitversion>
   </properties>

  在依賴使用的時候只須要:<version>${springversion}</version> ####5.9.3 優化依賴####   maven會自動解析全部項目的直接依賴和傳遞性依賴,而且根據規則正確判斷每一個依賴的範圍。對於一些依賴衝突,也能進行調節,以確保任何一個構件只有惟一的版本在依賴中存在。在這些工做以後,最後獲得的那些依賴被稱爲解析依賴。運行下面兩條命令分別能夠查看當前項目的已解析依賴。   mvn dependency:list   mvn dependency:tree   使用mvn dependency:list和mvn dependency:tree能夠幫助咱們詳細瞭解項目中全部依賴的具體信息。在此基礎上,還有dependency:analyze工具能夠幫助分析當前項目的依賴,可是該工具只會分析編譯主代碼和測試代碼所須要用到的依賴,一些執行測試和運行時須要的依賴它就發現不了。

相關文章
相關標籤/搜索