第5章 坐标和依赖

Maven坐标

maven为构件定义一个唯一的标识,标识由groupId、artifactId、version、packaging、classifier组成。这个标识就是maven构件的坐标。

坐标详解

maven通过坐标来管理项目构件,任务一个构件都必须明确自己的坐标,maven坐标是通过一组元素来定义的,它们是groupId、artifactId、version、packaging、classifier,下面详细解释每个坐标元素:
1、groupId:定义当前maven实际项目,一个实际项目可能包括多个maven模块,一个maven模块就是一个artifactId,一个组织或者一个公司可能存在多个实际项目,因此groupId应该到组织或公司的实际项目,不能到maven模块或者组织。

2、artifactId:定义maven实际项目ID,即maven模块ID,artifactId一般用实际项目名称为前缀,便于查询组件。

3、version:该元素定义了maven项目的版本,具体版本规范,见第十三章。

4、packaging:该元素定义maven项目打包方式,打包方式即所生成构件的文件扩展名,不定义packaging时,maven默认jar。

5、classifier:用来帮助构件生成一些附属功能的构件,比如通过插件生成带源代码或文档的附属插件,classifier附属构建不能直接生成,需要通过附属插件帮助生成。

以上五个元素中,groupId、artifactId、version是必须的,packaging是可以选择的(默认jar),classifier是不能直接配置的。

一般项目构件的名称和坐标是对应的,项目构件名称规则如下:artifactId-version[-classifier].packaging,此外一般仓库的布局和坐标也是对应的。

account-email

下面以account-email模块为例,进行maven操作说明。

1、POM

account-email的pom配置如下:

<?xml version="1.0" charset="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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-email</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>account email</name>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>2.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>2.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>2.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>2.5.6</version>
        </dependency>
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.icegreen</groupId>
            <artifactId>greenmail</artifactId>
            <version>1.3.1b</version>
            <scope>test</scope>
        </dependency>
    </dependencies
</project>
2、account-email主代码

AccountEmailService文件

package com.juvenxu.mvnbook.account.email;

/**
 * Hello world!
 *
 */
public interface AccountEmailService 
{
    void sendMail(String to,String subject,String htmlText) throws AccountEmailException;
}

AccountEmailServiceImpl文件

package com.juvenxu.mvnbook.account.email;

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

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

/**
 * Hello world!
 *
 */
public class AccountEmailServiceImpl implements AccountEmailService
{
	private JavaMailSender javaMailSender;
	private String 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("fail to send mail!",e);
		};
	};
	
	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;
	};
}

service.properties文件

email.protocol=smtp
email.host=smtp.163.com
email.port=25
email.username=yjs@126.com
email.password=1986
email.systemEmail=yjs@126.com
email.auth=true

account-email.xml,spring配置文件

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="classpath:service.properties"/>
	</bean>
	
	<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
		<property name="protocol" value="${email.protocol}"/>
		<property name="host" value="${email.host}"/>
		<property name="port" value="${email.port}"/>
		<property name="username" value="${email.username}"/>
		<property name="password" value="${email.password}"/>
		<property name="javaMailProperties">
			<props>
				<prop key="mail.${email.protocol}.auth">${email.auth}</prop>
			</props>
		</property>
	</bean>
	
	<bean id="accountEmailService" class="com.juvenxu.mvnbook.account.email.AccountEmailServiceImpl">
		<property name="javaMailSender" ref="javaMailSender"/>
		<property name="systemEmail" value="${email.systemEmail}"/>	
	</bean>
</beans>
3、测试代码
package com.juvenxu.mvnbook.account.email;

import static junit.framework.Assert.assertEquals;
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;

/**
 * Hello world!
 *
 */
public class AccountEmailServiceTest
{
	private GreenMail greenMail;
		
	@Before
	public void startMailServer() throws Exception 
	{
		greenMail = new GreenMail(ServerSetup.SMTP);
		greenMail.setUser("yjshengshe@126.com","8787llm1986");
		greenMail.start();
	};
	
	@Test
	public void testSendMail() throws Exception
	{
		ApplicationContext ctx = new ClassPathXmlApplicationContext("account-email.xml");
		AccountEmailService accountEmailService = (AccountEmailService)ctx.getBean("accountEmailService");
		String subject = "test subject";
		String htmlText = "<h3>test</h3>";
		accountEmailService.sendMail("yjshengshe@126.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
		assertEquals(1,1);
	}
	
	@After
	public void stopMailServer() throws Exception
	{
		greenMail.stop();
	}
}
4、构建account-email

mvn clean install

5、account-email备注

account-email定义了一个邮件发送的模块,演示了maven模块的搭建和发布,由于邮件服务器的问题,在实际测试过程中,存在邮件发送没有权限的问题,所以上面示例中,并没有真正的发送邮件,单元测试结果也是写死的。

依赖的配置

一个依赖的声明包括以下配置:

<dependencies>
    <dependency>
        <groupId></groupId>
        <artifactId></artifactId>
        <version></version>
        <scope></scope>
        <type></type>
        <optional></optional>
        <exclusions>
            <exclusion>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

根元素project下可以包含一个或多个dependency元素,每个依赖包含的元素有:

1、groupId、artifactId、version:依赖的基本坐标。

2、type:依赖的类型,对应着项目坐标定义的packaging,大部分情况下,该元素不必声明,其默认值为jar.

3、scope:依赖的范围。

4、optional:标记依赖是否可选.

5、exclusions:用来排除传递性依赖。

依赖范围

依赖范围主要用来控制依赖与编译、测试、运行三种classpath的关系,maven有以下几种依赖范围:

1、compile:编译依赖范围。如果没有特别说明,组件默认依赖范围为compile,使用此依赖范围的组件,适用于compile、test、runtime三种关系。

2、test:测试依赖范围。使用此依赖范围的组件,只适用于测试classpath。

3、provided:已提供依赖范围,使用此依赖范围的maven依赖,对于编译和测试的classpath有效,在运行时无效,典型的例子就是servlet-api。

4、runtime:运行时依赖范围,对于测试和运行时的classpath有效,编译时无效,典型例子就是JDBC驱动。

5、system:系统依赖范围,该参数依赖范围与provided类似,使用system依赖范围时,必须通过systemPath元素显式的指定依赖文件路径。

6、import:导入依赖范围,该依赖范围不会对上面三种classpath产生实际影响。

依赖范围编译classpath测试classpath运行时classpath示例
compileYYYspring-core
testYjunit
providedYYservlet-api
runtimeYYjdbc驱动
systemYYmaven仓库之外的类库文件
import

传递性依赖

1、何为传递性依赖

在引入依赖组件时,mavn会根据组件依赖配置范围自动加载组件依赖的配置,此为传递性依赖。组件配置时,如果没有配置依赖范围(scope),maven自动默认为依赖范围为compile。示例如下:

1、account-email项目有一个spring-core:2.5.6的依赖,默认依赖范围。

2、spring-core:2.5.6组件有一个commons-logging依赖,默认依赖范围。

3、account-email有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就成为account-email的compile范围依赖,commons-logging是account-email的一个传递性依赖。

graph LR 
A[account-email] --> B[spring-core] 
B[spring-core] --> C[commons-logging]

2、传递性依赖和依赖范围

依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。传递性依赖范围如下:

:—::—::—::—::—:
compiletestprovidedruntime
compilecompileruntime
testtesttest
providedprovidedprovidedprovided
runtimeruntimeruntime

传递性依赖的规则如下:
1、当第二直接范围为compile时,传递性依赖范围和第一直接范围一致。

2、当第二直接范围为test时,传递性依赖范围不传递。

3、当第二直接范围为provided时,传递性依赖范围只传递provided,依赖范围为provided。

4、当第二直接范围为runtime时,传递性依赖和第一直接范围一致,除了compile范围,当第一直接范围为compile时,传递性依赖范围为runtime。

依赖调解

maven引入的传递性依赖机制,大大简化了依赖声明,通常情况下,我们只需要关心直接依赖就可以了。但有时候,当传递性依赖造成问题的时候,我们就需要知道该传递性依赖是从哪条路径引入的。依赖调解原则如下:

1、路径最近者优先。

2、第一声明者优先。

可选依赖

可以选择的依赖关系,在maven中使用以下配置:

true

依赖范围传递时,可选依赖不会被传递,但是在理想情况下,从构件单一职责原则来说,是不应该使用可选依赖的。

最佳实践

1、排除依赖

传递性依赖会给项目隐式的引入很多依赖,在极大的简化了项目依赖管理的同时,但是有时候也会带来问题。比如A依赖与B,B又依赖与C,如果C配置了一个快照版本的项目,这个时候根据maven依赖传递特性,A也会加载C快照依赖,由于快照版本的不稳定性,会导致A版本的稳定运行。在比如因为版权的问题,不能加载某个依赖,这个时候就需要配置排除依赖,不加载该项目。排除依赖配置如下:

<exclusions>
    <exclusion>
        <groupId>com.juvenxu.mvnbook>
        <artifactId>holloworld</artifactId>
    </exclusion>
</exclusion>

只需要配置groupId、artifactId就可以声明排除依赖。

2、归类依赖

在项目构件过程中,使用了springframework相关的很多构件,他们都来自同一个项目的不同模块,如果项目升级,这些模块一般都会同时升级。因此需要以在一个地方统一定义一个版本号,在dependency中引用这个版本号,这样版本升级时,只需要修改版本号就可以了,具体配置如下:

<properties>
    <spring.version>2.2.2</spring.version>
</properties>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
3、优化依赖

maven对项目中的直接依赖和传递性依赖进行分析,并根据规则判断每个依赖的范围,从而确保每个依赖关系的唯一性,确保后的依赖叫做已解析依赖(resolved dependency),可使用如下命令查看已解析依赖:

mvn dependency:list

以上指令列出了项目所有的已解析的依赖,包括依赖名称和依赖范围。

mvn dependency:tree

以上指令对项目所有的已解析依赖进行层级展示,直接声明的依赖为第一层级,第一层级的依赖为第二层级,后面的依次类推,通过tree结构,可以很清楚的看出依赖组件的依赖层级。

对应项目中编译需要的构件,最好直接声明,而不是通过传递性依赖加载进行,因为通过直接声明可以很清晰的通过查看POM.xml文件发现构件的版本、名称,而通过传递性依赖加入的构件,只有通过mvn dependency:tree 之类命令才能查看。

mvn dependency:analyze

以上指令对项目所有的依赖进行分析,分析结果包括used undeclared dependencies 和 unused declared dependencies 两类。

used undeclared dependencies

used undeclared dependencies:使用到了,但是项目中没有显式声明的依赖,这种情况r意味着风险,因此,需要显式声明任何项目中用到的直接依赖。

unused declared dependencies

unused declared dependencies:没有使用的,但是声明了的依赖,这种情况下,需要谨慎对待,因为maven dependencies:analyze 只会分析编译阶段用到的依赖关系,不会分析测试和运行阶段的依赖关系,所以删除依赖声明一定要注意。

小结

本章主要介绍了maven的坐标和依赖。通过一个实际案例讲解了maven项目POM.xml文件编写、源代码编写,测试代码编写,项目编译、测试、发布、部署等过程,同时对maven依赖范围和传递性依赖,依赖调解,可选依赖,排除依赖、归类依赖、依赖优化等进行了描述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值