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 | 示例 |
---|---|---|---|---|
compile | Y | Y | Y | spring-core |
test | Y | junit | ||
provided | Y | Y | servlet-api | |
runtime | Y | Y | jdbc驱动 | |
system | Y | Y | maven仓库之外的类库文件 | |
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的关系,还对传递性依赖产生影响。传递性依赖范围如下:
:—: | :—: | :—: | :—: | :—: |
---|---|---|---|---|
compile | test | provided | runtime | |
compile | compile | – | runtime | |
test | test | – | test | |
provided | provided | – | provided | provided |
runtime | runtime | – | runtime |
传递性依赖的规则如下:
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依赖范围和传递性依赖,依赖调解,可选依赖,排除依赖、归类依赖、依赖优化等进行了描述。