坐标与依赖
Maven的一大功能是管理项目依赖,为了能自动化解析任何一个java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础——坐标!
何为Maven坐标
Maven定义了这样一组规则:世界上任何一个构件都可以使用Maven坐标唯一标识,Maven坐标的元素包括groupId,artifactId,version,packing,classifier。只要提供正确的Maven坐标,Maven就能找到对应的构件。
比如说,需要使用Java5平台上TestNG的5.8版本时,就可以告诉Maven:“groupId=org.testng;artifactId=testng;version=5.8;classifier=jdk15”,Maven就会从仓库中寻找对应的构件来供我们使用。
Maven坐标为各种构建引入了秩序,任何一个构件都必须明确定义自己的坐标,而一组Maven坐标是通过一些元素定义的,如下所示:
<groupId > junit</groupId >
<artifactId > junit</artifactId >
<version > 4.7</version >
<packaging > jar</ packaging >
这是Junit的坐标定义,下面解释下各个元素的定义:
groupId:定义当前Maven隶属的实际项目。首先Maven项目和实际项目不一定是一对一关系。比如SpringFramework这一实际项目,它对应的Maven项目会有很多,比如spring-core,spring-context等。这是由于Maven中模块的概念,因此一个实际项目往往会被分为很多模块。其次,groupId不应该对应项目隶属的组织或公司,因为一个组织下可能会有多个实际项目,如果groupId只定义到组织级别,而后面我们会看到artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义。最后groupId的表示方式与Java包名的表示方式类似,通常与域名反向一一对应。
artifactId:该元素定义实际项目中的一个Maven项目(模块),推荐的做法是使用实际项目名称作为artifactId的前缀。默认情况下,Maven生成的构件,其文件名会以artifactId作为开头。
version:该元素定义Maven项目当前所处的版本。Maven定义了一套完整的版本规范,以及快照(SNAPSHOT)的概念。
packaging:钙元素定义Maven项目的打包方式。首先打包方式通常与所生成构件的文件扩展名对应。其次,打包方式会影响到构建的生命周期,比如jar打包和war打包会使用不同的命令。最后当不定义packaging的时候,默认的打包方式为jar。
classifier:钙元素用来帮助定义构建输出的一些附属构件。注意:不能直接定义classifier,因为附属构件不是由项目直接生成的,而是由附加的插件帮助生成。
上述部分,groupId,artifactId,version三个元素是必须定义的,这也是Maven定义构件的三个重要元素,类似于3D空间的X,Y,Z坐标。packaging是可选的,classifier是不能直接定义的。
account-email
在弄清楚Maven坐标的定义后,来看下AccountService项目下,account-email模块的pom.xml文件的内容:
<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 >
<packaging > jar</packaging >
<name > account-email</name >
<url > http://maven.apache.org</url >
<properties >
<project.build.sourceEncoding > UTF-8</project.build.sourceEncoding >
</properties >
<dependencies >
<dependency >
<groupId > junit</groupId >
<artifactId > junit</artifactId >
<version > 4.7</version >
<scope > test</scope >
</dependency >
<dependency >
<groupId > com.my.account</groupId >
<artifactId > account-common</artifactId >
<version > 1.0.0-SNAPSHOT</version >
</dependency >
<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 > com.icegreen</groupId >
<artifactId > greenmail</artifactId >
<version > 1.3.1b</version >
<scope > test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.apache.maven.plugins</groupId >
<artifactId > maven-compiler-plugin</artifactId >
<configuration >
<source > 1.5</source >
<target > 1.5</target >
</configuration >
</plugin >
</plugins >
</build >
</project >
因为该模块属于账户注册服务项目的一部分,因此其groupId对应了account项目。接着,该模块的artifactId仍然以account作为前缀,以方便区分其他项目的构建。最后1.0.0-SNAPSHOT表示该版本处于开发状态,还不稳定。
再看dependencies元素,其包含了多个dependency子元素,这是POM文件中定义项目依赖的位置。以Junit为例,它定义的内容为:
<dependency >
<groupId > junit</groupId >
<artifactId > junit</artifactId >
<version > 4.7</version >
<scope > test</scope >
</dependency >
这些便是依赖的坐标,任何一个Maven项目都需要定义自己的坐标,当这个Maven项目成为其他Maven项目的依赖的时候,这组坐标就体现了其价值。这个依赖的特殊地方在于,它定义了scope子元素,scope用来定义依赖范围,因为是Junit因此这个定义为test,表示只有在测试的时候才需要,也就是说对于项目主代码,该依赖是没有任何作用的。
最后,POM中有一段关于maven-compiler-plugin的配置,其目的是开启java 5的支持。
account-email的主代码
account-email项目java主代码位于src\main\java,资源文件位于src\main\resources目录下。
account-email只有一个简单的接口,如下所示:
package com.my.account.account_email;
import com.my.account.account_common.exception.AccountEmailException;
public interface AccountEmailService {
void sendMain(String to,String subject,String htmlText) throws AccountEmailException;
}
对于接口的实现代码如下:
package com.my.account.account_email.business;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import com.my.account.account_common.exception.AccountEmailException;
import com.my.account.account_email.AccountEmailService;
public class AccountEmailServiceImpl implements AccountEmailService {
private JavaMailSender javaMailSender;
private String systemEmail;
public void sendMain (String to, String subject, String htmlText)
throws AccountEmailException {
MimeMessage msg = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(msg);
try {
messageHelper.setFrom(systemEmail);
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(htmlText, true );
javaMailSender.send(msg);
} catch (MessagingException e) {
e.printStackTrace();
throw new AccountEmailException("发送邮件失败" , e);
}
}
public void setJavaMailSender (JavaMailSender javaMailSender) {
this .javaMailSender = javaMailSender;
}
public void setSystemEmail (String systemEmail) {
this .systemEmail = systemEmail;
}
}
对于字段javaMailSender的注入,使用Spring的IOC机制进行。
代码中并没有邮件服务器配置信息,这主要是依靠SpringFramework的依赖注入,这些配置都通过外部的配置注入到了javaMailSender中,配置如下:
<?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:email.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.my.account.account_email.business.AccountEmailServiceImpl" >
<property name ="javaMailSender" ref ="javaMailSender" />
<property name ="systemEmail" value ="${email.systemEmail}" > </property >
</bean >
</beans >
至于邮件的配置信息,由properties文件定义如下:
email.protocol =smtp
email.host =localhost
email.port =25
email.username =test@xv.com
email.password =123456
email.auth =true
email.systemEmail = test@xv.com
account-emaill的测试代码
测试相关的java代码位于src\test\java目录,相关的资源文件在src\test\resource目录下。
测试代码如下:
package com .my .account .account _email
import static org.junit .Assert .assertEquals
import javax.mail .Message
import javax.mail .MessagingException
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
import com .my .account .account _common.exception .AccountEmailException
public class AccountEmailServiceImplTest {
private GreenMail greenmail
@Before
public void startMailServer() {
greenmail = new GreenMail(ServerSetup.SMTP )
greenmail.setUser ("test@xv.com" , "123456" )
greenmail.start ()
}
@Test
public void testSendMail() throws AccountEmailException,
InterruptedException, MessagingException {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"account-email.xml" )
AccountEmailService accountEmailService = (AccountEmailService) ctx
.getBean ("accountEmailService" )
String subject = "Test Subject"
String htmlText = "<h3>Test</h3>"
accountEmailService.sendMain ("test2@xv.com" , subject, htmlText)
greenmail.waitForIncomingEmail (2000 , 1 )
Message[] messages = greenmail.getReceivedMessages ()
assertEquals(1 , messages.length )
assertEquals(subject, messages[0 ].getSubject ())
assertEquals(htmlText, GreenMailUtil.getBody (messages[0 ]).trim ())
}
@After
public void stopMailServer(){
greenmail.stop ()
}
}
构建account-email
接下来可以使用命令:
mvn clean test
执行测试,Maven会编译主代码和测试代码,并执行测试。
使用如下命令,将项目生成的构件account-email-1.0.0-SNAPSHOT.jar安装到本地仓库中。
mvn clean install
依赖配置
一个依赖声明可以包含如下一些元素:
<project >
<dependencies >
<dependency >
<groupId > …</groupId >
<artifactId > ….</artifactId >
<version > ….</version >
<type > ….</type >
<scope > ….</scope >
<optional > </optional >
<exclusions >
<exclusion > </exclusion >
</exclusions >
</dependency >
</dependencies >
</project >
根元素project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖,每个项目依赖可以包含的元素有:
groupId,artifactId和version:依赖的基本坐标。对于任何一个依赖来说基本坐标都是最重要的,Maven根据坐标才能找到需要的依赖。
type:依赖的类型,对应项目坐标定义的packaging,其默认值为jar
scope:依赖的范围
optional:标记依赖是否可选。
exclusions:用来排除传递性依赖。
依赖范围
首先,我们要了解,Maven在编译项目主代码的时候需要使用一套classpath。其次,Maven在编译和执行测试的时候会使用另外一套claspath。最后实际运行Maven项目的时候又会使用一套classpath。
依赖范围就是用来控制依赖与这三种classpath的关系。Maven有以下几种依赖范围:
compile:编译依赖范围,如果没有指定,默认就使用该依赖范围。这种依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。
test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效。在编译主代码或者运行项目的使用时,将无法使用此类依赖。
provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。例如servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
runtime:运行时依赖范围。使用此以来范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现。项目主代码的编译只需要JDK提供的JDBC接口,只有执行测绘或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
system:系统依赖范围。该依赖与三种classpath的关系和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。systemPath可以引用环境变量,如:
<dependency >
<groupId > …</groupId >
<artifactId > … </artifactId >
<version > …</version >
<scope > system</scope >
<systemPath > ${java.home}/lib/rt.jar</systemPath >
</dependency >
import:导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。该依赖范围主要与dependencyManagement元素配合使用。
依赖范围与classpath的关系
传递性依赖
什么是传递性依赖?
打个简单的比方,有项目A,如果不使用Maven,那么项目A就需要下载它所依赖的B类库,而B类库又依赖C类库,因此在工作中我们往往会下载一个非常大的包来解决这个问题。
Maven的传递性依赖机制可以非常好的解决这一个问题。
以account-email为例,该项目有一个org.springframework:spring-core:2.5.6的依赖,实际上spring-core也有它自己的依赖,我们可以直接访问位于中央仓库的该构件的POM,可以看到该文件包含了一个commons-logging依赖,该依赖没有声明依赖范围,那么其依赖范围就是默认的compile。同时account-email的spring-core的依赖范围也是compile。
account-mail有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile范围的依赖,commons-logging是account-eamil的一个传递性依赖,如下图所示:
Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpa的关系,还对传递性依赖产生影响。
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C就是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。如下图所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围:
根据上表,可以发现这样的规律:
当第二直接依赖范围是compile的时候,传递性依赖范围与第一直接依赖范围一致。当第二直接依赖范围是test的时候,依赖不会得以传递,当第二直接依赖范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖范围同样为provided;当第二直接依赖的范围是runtime的时候,传递性依赖范围与第一直接依赖范围一致,但compile例外,此时依赖范围为runtime。
依赖调解
假如项目A有这样的依赖关系:A>B>C>X(1.0)、A>D>X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被解析呢?这个时候Maven的依赖调解机制就发生了作用,Maven依赖调解的第一原则是:路径近者优先。因此最后使用的是2.0版本的X。
但是有些时候依赖调解的第一原则不能解决所有问题,比如这样的依赖关系:A>B>Y(1.0)、A>C>Y(2.0),路径长度一样,那么会使用哪个版本的Y呢?Maven定义了依赖调解的第二原则:第一声明者优先, 也就是说会使用1.0版本的Y。
在路径长度相等的情况下,在POM中依赖声明的顺序决定了谁会被解析调用,顺序最靠前的那个依赖优胜。
可选依赖
假设有这样一个依赖关系,A>B>X and Y ,B对于X和Y的依赖都是可选依赖:A>B,B>X(可选),B>Y(可选)。根据传递性依赖的定义,如果所有这三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖。然而,这里由于X、Y都是可选依赖,依赖将不会得以传递。如下图所示:
那为什么会有可选依赖这一个特性呢?可能项目B实现了两个特性,其中特性A依赖与X,特性B依赖与Y,而且这两个特性是互斥的,用户不可能同时使用两个特性,比如持久层的隔离包,它支持多种数据库,如果在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。
项目B的依赖声明如下:
<dependencies >
<dependency >
<groupId > mysql</groupId >
<artifactId > mysql-connector-java</artifactId >
<version > 5.1.10</version >
<optional > true</optional >
</dependency >
<dependency >
<groupId > postgresql</groupId >
<artifactId > postgresql</artifactId >
<version > 8.4-701.jdbc3</version >
<optional > true</optional >
</dependency >
</dependencies >
使用optional元素表示mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。
当项目A依赖于项目B的时候,如果实际使用基于Mysql数据库,那么在项目A中就需要显示地声明mysql-connector-java这一依赖。如下所示:
<dependency >
<groupId > mysql</groupId >
<artifactId > mysql-connector-java</artifactId >
<version > 5.1.10</version >
</dependency >
在理想的情况下,是不应该使用可选依赖的,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。在上面的例子中,比较好的做法是为Mysql和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,在各自POM中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户根据需要选择其中之一使用,由于传递性依赖的作用,就不用再声明JDBC驱动依赖。
排除依赖
传递性依赖会给项目隐式的引入很多依赖,这简化了项目依赖的管理,但是也会带来一些问题。比如当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前项目,这时候就要排除掉SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。
代码如下所示:
<dependency >
<groupId > com.my.account</groupId >
<artifactId > account-common</artifactId >
<version > 1.0.0-SNAPSHOT</version >
<exclusions >
<exclusion >
<groupId > com.my.account</groupId >
<groupId > project-c</groupId >
</exclusion >
</exclusions >
</dependency >
<dependency >
<groupId > com.my.account</groupId >
<artifactId > project-c</artifactId >
<version > 1.0.0</version >
</dependency >
代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或多个exclusion元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明exclusion只需要groupId和artifactId,而不需要versin元素,这是因为只要groupId和artifactId就能唯一定位依赖图中的某个依赖,换句话说就是Maven解析后的依赖中,不可能出现groupId和artifact乡土但是version不同的两个依赖,该例的依赖逻辑图如下所示:
归类依赖
先看代码如下:
<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 >
<packaging > jar</packaging >
<name > account-email</name >
<url > http://maven.apache.org</url >
<properties >
<project.build.sourceEncoding > UTF-8</project.build.sourceEncoding >
</properties >
<dependencies >
<dependency >
<groupId > junit</groupId >
<artifactId > junit</artifactId >
<version > 4.7</version >
<scope > test</scope >
</dependency >
<dependency >
<groupId > com.my.account</groupId >
<artifactId > account-common</artifactId >
<version > 1.0.0-SNAPSHOT</version >
</dependency >
<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 > com.icegreen</groupId >
<artifactId > greenmail</artifactId >
<version > 1.3.1b</version >
<scope > test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.apache.maven.plugins</groupId >
<artifactId > maven-compiler-plugin</artifactId >
<configuration >
<source > 1.5</source >
<target > 1.5</target >
</configuration >
</plugin >
</plugins >
</build >
</project >
可以看到在上面的代码中有很多springframework的依赖,它们是来自同一项目的不同模块,因此这些依赖的版本都是相同的,如果将来需要升级springframework,这些依赖的版本都会一起升级。这一种情况在java代码中似曾相识,看下下面的代码:
public double c (double r){
return 2 *3.14 *r;
}
public double s (double r){
return 3.14 *r*r;
}
上面两个方法一个计算周长一个计算面积,一般来说,我们通常会将字面量定义成一个常量,这样在以后的修改的时候能够做到修改一处即可,如下代码:
public final double PI = 3.14 ;
public double c (double r){
return 2 *PI*r;
}
public double s (double r){
return PI*r*r;
}
使用常量不仅让代码变得简洁,更重要的是可以避免重复,在需要更改PI的值的时候,只需要更改一处,降低了错误发生的概率。
同理对于POM文件来说,我们也可以在一个唯一的地方定义版本,并且在dependency声明中引用这一版本。这样在springframework升级的时候只需要修改一处即可,如下所示:
<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 >
<packaging > jar</packaging >
<name > account-email</name >
<url > http://maven.apache.org</url >
<properties >
<project.build.sourceEncoding > UTF-8</project.build.sourceEncoding >
<springframework.version > 2.5.6</springframework.version >
<com.my.account.version > 1.0.0-SNAPSHOT</com.my.account.version >
<javax.mail.version > 1.4.1</javax.mail.version >
<com.icegreen.version > 1.3.1b</com.icegreen.version >
<junit.version > 4.7</junit.version >
<source.version > 1.5</source.version >
<target.version > 1.5</target.version >
</properties >
<dependencies >
<dependency >
<groupId > junit</groupId >
<artifactId > junit</artifactId >
<version > ${junit.version}</version >
<scope > test</scope >
</dependency >
<dependency >
<groupId > com.my.account</groupId >
<artifactId > account-common</artifactId >
<version > ${com.my.account.version}</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-core</artifactId >
<version > ${springframework.version}</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-beans</artifactId >
<version > ${springframework.version}</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-context</artifactId >
<version > ${springframework.version}</version >
</dependency >
<dependency >
<groupId > org.springframework</groupId >
<artifactId > spring-context-support</artifactId >
<version > ${springframework.version}</version >
</dependency >
<dependency >
<groupId > javax.mail</groupId >
<artifactId > mail</artifactId >
<version > ${javax.mail.version}</version >
</dependency >
<dependency >
<groupId > com.icegreen</groupId >
<artifactId > greenmail</artifactId >
<version > ${com.icegreen.version}</version >
<scope > test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.apache.maven.plugins</groupId >
<artifactId > maven-compiler-plugin</artifactId >
<configuration >
<source > ${source.version}</source >
<target > ${target.version]</target >
</configuration >
</plugin >
</plugins >
</build >
</project >
优化依赖
在软件开发过程中,程序员会通过重构等方式不断地优化自己的代码,使其变得更简洁,更灵活。同理对于Maven的依赖我们也可以进行一定程度的优化。
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
可以运行如下命令查看已解析依赖。account-email解析后的依赖如下:
mvn dependency :list
当这些依赖经Maven解析后就会构成一个依赖树,通过这颗依赖书就能很清楚的看到某个依赖是通过哪条传递路径引入的。可以运行如下命令查看依赖树:
mvn dependency :tree
account-email的依赖树如下所示:
使用上面两条命令可以帮助我们了解项目中依赖的具体信息,在此基础上,还可以使用dependency:analyze分析当前项目的依赖,执行如下命令:
mvn dependency :analyze
结果如下所示:
可以看到结果中重要的是[WARNING],Unused declared dependencies found,意指项目中未使用的,但显式声明的依赖。这里有spring-core和spring-beans。对于这样一类依赖,我们不能简单的直接删除,而是要仔细的分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖就发现不了。以目前的信息来说,我们就不应该删除spring-core和spring-beans这两个依赖。
总结:
坐标和依赖是Maven中两个核心的概念,弄清楚坐标是什么,依赖是怎么回事,对于我们在日常工作中是至关重要的,重要的是如何定义依赖,以及如何通过Maven的特性对依赖进行管理和优化。