当前位置: 首页 > 编程日记 > 正文

Maven最佳实践:划分模块

所有用Maven管理的真实的项目都应该是分模块的,每个模块都对应着一个pom.xml。它们之间通过继承和聚合(也称作多模块,multi-module)相互关联。那么,为什么要这么做呢?我们明明在开发一个项目,划分模块后,导入Eclipse变成了N个项目,这会带来复杂度,给开发带来不便。

为了解释原因,假设有这样一个项目,很常见的Java Web应用。在这个应用中,我们分了几层:

  • Dao层负责数据库交互,封装了Hibernate交互的类。
  • Service层处理业务逻辑,放一些Service接口和实现相关的Bean。
  • Web层负责与客户端交互,主要有一些Structs的Action类。

对应的,在一个项目中,我们会看到一些包名:

  • org.myorg.app.dao
  • org.myorg.app.service
  • org.myorg.app.web
  • org.myorg.app.util

这样整个项目的框架就清晰了,但随着项目的进行,你可能会遇到如下问题:

  1. 这个应用可能需要有一个前台和一个后台管理端(web或者swing),你发现大部分dao,一些service,和大部分util是在两个应用中可。这样的问题,你一周内遇到了好几次。
  2. pom.xml中的依赖列表越来越长以重用的,但是,由于目前只有一个项目(WAR),你不得不新建一个项目依赖这个WAR,这变得非常的恶心,因为在Maven中配置对WAR的依赖远不如依赖JAR那样简单明了,而且你根本不需要org.myorg.app.web。有人修改了dao,提交到svn并且不小心导致build失败了,你在编写service的代码,发现编译不过,只能等那人把dao修复了,你才能继续进行,很多人都在修改,到后来你根本就不清楚哪个依赖是谁需要的,渐渐的,很多不必要的依赖被引入。甚至出现了一个依赖有多个版本存在。
  3. build整个项目的时间越来越长,尽管你只是一直在web层工作,但你不得不build整个项目。
  4. 某个模块,比如util,你只想让一些经验丰富的人来维护,可是,现在这种情况,每个开发者都能修改,这导致关键模块的代码质量不能达到你的要求。

我们会发现,其实这里实际上没有遵守一个设计模式原则:“高内聚,低耦合”。虽然我们通过包名划分了层次,并且你还会说,这些包的依赖都是单向的,没有包的环依赖。这很好,但还不够,因为就构建层次来说,所有东西都被耦合在一起了。因此我们需要使用Maven划分模块。

一个简单的Maven模块结构是这样的:

---- app-parent

|-- pom.xml (pom)

|

|-- app-util

|        |-- pom.xml (jar)

|

|-- app-dao

|        |-- pom.xml (jar)

|

|-- app-service

|        |-- pom.xml (jar)

|

|-- app-web

|-- pom.xml (war)

上述简单示意图中,有一个父项目(app-parent)聚合很多子项目(app-util, app-dao, app-service, app-web)。每个项目,不管是父子,都含有一个pom.xml文件。而且要注意的是,小括号中标出了每个项目的打包类型。父项目是pom,也只能是pom。子项目有jar,或者war。根据它包含的内容具体考虑。

这些模块的依赖关系如下:

app-dao      --> app-util

app-service --> app-dao

app-web     --> app-service

注意依赖的传递性(大部分情况是传递的,除非你配置了特殊的依赖scope),app-dao依赖于app-util,app-service依赖于app-dao,于是app-service也依赖于app-util。同理,app-web依赖于app-dao,app-util。

用项目层次的划分替代包层次的划分能给我们带来如下好处:

  1. 方便重用,如果你有一个新的swing项目需要用到app-dao和app-service,添加对它们的依赖即可,你不再需要去依赖一个WAR。而有些模块,如app-util,完全可以渐渐进化成公司的一份基础工具类库,供所有项目使用。这是模块化最重要的一个目的。
  2. 由于你现在划分了模块,每个模块的配置都在各自的pom.xml里,不用再到一个混乱的纷繁复杂的总的POM中寻找自己的配置。
  3. 如果你只是在app-dao上工作,你不再需要build整个项目,只要在app-dao目录运行mvn命令进行build即可,这样可以节省时间,尤其是当项目越来越复杂,build越来越耗时后。
  4. 某些模块,如app-util被所有人依赖,但你不想给所有人修改,现在你完全可以从这个项目结构出来,做成另外一个项目,svn只给特定的人访问,但仍提供jar给别人使用。
  5. 多模块的Maven项目结构支持一些Maven的更有趣的特性(如DepencencyManagement),这留作以后讨论。

接下来讨论一下POM配置细节,实际上非常简单,先看app-parent的pom.xml:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3. <modelVersion>4.0.0</modelVersion>  
  4. <groupId>org.myorg.myapp</groupId>  
  5. <artifactId>app-parent</artifactId>  
  6. <packaging>pom</packaging>  
  7. <version>1.0-SNAPSHOT</version>  
  8. <modules>  
  9. <module>app-util</module>  
  10. <module>app-dao</module>  
  11. <module>app-service</module>  
  12. <module>app-web</module>  
  13. </modules>  
  14. </project>  

Maven的坐标GAV(groupId, artifactId, version)在这里进行配置,这些都是必须的。特殊的地方在于,这里的packaging为pom。所有带有子模块的项目的packaging都为pom。packaging如果不进行配置,它的默认值是jar,代表Maven会将项目打成一个jar包。

该配置重要的地方在于modules,例子中包含的子模块有app-util, app-dao, app-service, app-war。在Maven build app-parent的时候,它会根据子模块的相互依赖关系整理一个build顺序,然后依次build。

这就是一个父模块大概需要的配置,接下来看一下子模块符合配置继承父模块。、

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3. <parent>  
  4. <artifactId>app-parent</artifactId>  
  5. <groupId>org.myorg.myapp</groupId>  
  6. <version>1.0-SNAPSHOT</version>  
  7. </parent>  
  8. <modelVersion>4.0.0</modelVersion>  
  9. <artifactId>app-util</artifactId>  
  10. <dependencies>  
  11. <dependency>  
  12. <groupId>commons-lang</groupId>  
  13. <artifactId>commons-lang</artifactId>  
  14. <version>2.4</version>  
  15. </dependency>  
  16. </dependencies>  
  17. </project>  

app-util模块继承了app-parent父模块,因此这个POM的一开始就声明了对app-parent的引用,该引用是通过Maven坐标GAV实现的。而关于项目app-util本身,它却没有声明完整GAV,这里我们只看到了artifactId。这个POM并没有错,groupId和version默认从父模块继承了。实际上子模块从父模块继承一切东西,包括依赖,插件配置等等。

此外app-util配置了一个对于commons-lang的简单依赖,这是最简单的依赖配置形式。大部分情况,也是通过GAV引用的。

再看一下app-dao,它也是继承于app-parent,同时依赖于app-util:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3. <parent>  
  4. <artifactId>app-parent</artifactId>  
  5. <groupId>org.myorg.myapp</groupId>  
  6. <version>1.0-SNAPSHOT</version>  
  7. </parent>  
  8. <modelVersion>4.0.0</modelVersion>  
  9. <artifactId>app-dao</artifactId>  
  10. <dependencies>  
  11. <dependency>  
  12. <groupId>org.myorg.myapp</groupId>  
  13. <artifactId>app-util</artifactId>  
  14. <version>${project.version}</version>  
  15. </dependency>  
  16. </dependencies>  
  17. </project>  

该配置和app-util的配置几乎没什么差别,不同的地方在于,依赖变化了,app-dao依赖于app-util。这里要注意的是version的值为${project.version},这个值是一个属性引用,指向了POM的project/version的值,也就是这个POM对应的version。由于app-dao的version继承于app-parent,因此它的值就是1.0-SNAPSHOT。而app-util也继承了这个值,因此在所有这些项目中,我们做到了保持版本一致。

这里还需要注意的是,app-dao依赖于app-util,而app-util又依赖于commons-lang,根据传递性,app-dao也拥有了对于commons-lang的依赖。

app-service我们跳过不谈,它依赖于app-dao。我们最后看一下app-web:

Xml代码  收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3. <parent>  
  4. <artifactId>app-parent</artifactId>  
  5. <groupId>org.myorg.myapp</groupId>  
  6. <version>1.0-SNAPSHOT</version>  
  7. </parent>  
  8. <modelVersion>4.0.0</modelVersion>  
  9. <artifactId>app-web</artifactId>  
  10. <packaging>war</packaging>  
  11. <dependencies>  
  12. <dependency>  
  13. <groupId>org.myorg.myapp</groupId>  
  14. <artifactId>app-service</artifactId>  
  15. <version>${project.version}</version>  
  16. </dependency>  
  17. </dependencies>  
  18. </project>  

app-web依赖于app-service,因此配置了对其的依赖。

由于app-web是我们最终要部署的应用,因此它的packaging是war。为此,你需要有一个目录src/main/webapp。并在这个目录下拥有web应用需要的文件,如/WEB-INF/web.xml。没有web.xml,Maven会报告build失败,此外你可能还会有这样一些子目录:/js, /img, /css ... 。

看看Maven是如何build整个项目的,我们在 app-parent 根目录中运行 mvn clean install ,输出的末尾会有大致这样的内容:

...

...

[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[app-web] in [/home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT]
[INFO] Processing war project
[INFO] Webapp assembled in[50 msecs]
[INFO] Building war: /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war
[INFO] [install:install]
[INFO] Installing /home/juven/workspaces/ws-others/myapp/app-web/target/app-web-1.0-SNAPSHOT.war to /home/juven/.m2/repository/org/myorg/myapp/app-web/1.0-SNAPSHOT/app-web-1.0-SNAPSHOT.war
[INFO] 
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] app-parent ............................................ SUCCESS [1.191s]
[INFO] app-util .............................................. SUCCESS [1.274s]
[INFO] app-dao ............................................... SUCCESS [0.583s]
[INFO] app-service ........................................... SUCCESS [0.593s]
[INFO] app-web ............................................... SUCCESS [0.976s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Sat Dec 27 08:20:18 PST 2008
[INFO] Final Memory: 3M/17M
[INFO] ------------------------------------------------------------------------

注意Reactor Summary,整个项目根据我们希望的顺序进行build。Maven根据我们的依赖配置,智能的安排了顺序,app-util, app-dao, app-service, app-web。

最后,你可以在 app-web/target 目录下找到文件 app-web-1.0-SNAPSHOT.war ,打开这个war包,在 /WEB-INF/lib 目录看到了 commons-lang-2.4.jar,以及对应的app-util, app-dao, app-service 的jar包。Maven自动帮你处理了打包的事情,并且根据你的依赖配置帮你引入了相应的jar文件。

使用多模块的Maven配置,可以帮助项目划分模块,鼓励重用,防止POM变得过于庞大,方便某个模块的构建,而不用每次都构建整个项目,并且使得针对某个模块的特殊控制更为方便。本文同时给出了一个实际的配置样例,展示了如何使用Maven配置多模块项目。

转载于:https://www.cnblogs.com/soundcode/p/6490976.html

相关文章:

facebook 直播_什么时候是在Facebook Live上直播的最佳时间? 我分析了5,000个Facebook帖子以找出答案。...

facebook 直播by Ofir Chakon由Ofir Chakon 什么时候是在Facebook Live上直播的最佳时间&#xff1f; 我分析了5,000个Facebook帖子以找出答案。 (When is the best time to stream on Facebook Live? I analyzed 5,000 Facebook posts to find out.) Streaming on Facebook …

解决keepalived脑裂问题

检测思路&#xff1a;正常情况下keepalived的VIP地址是在主节点上的&#xff0c;如果在从节点发现了VIP&#xff0c;就设置报警信息 脚本如下&#xff1a; #!/bin/bash # 检查脑裂的脚本&#xff0c;在备节点上进行部署 LB01_VIP10.10.10.229 LB01_IP10.10.10.129 LB02_IP10.10…

iOS 根据中文字符串排序出字母索引

// 传入字符串数组 返回索引字典 - (NSDictionary *)createCharacter:(NSMutableArray *)strArr {NSMutableDictionary *dict [NSMutableDictionary dictionary];for (NSString *stringdict in strArr) {NSString *string stringdict;if ([string length]) {NSMutableString …

devops开发运维训练营_嗨,网络开发人员训练营的毕业生:这是您第一份工作需要了解的内容。...

devops开发运维训练营by Rachel Bird雷切尔伯德(Rachel Bird) 嗨&#xff0c;网络开发人员训练营的毕业生&#xff1a;这是您第一份工作需要了解的内容。 (Hey web dev bootcamp grads: Here’s what you need to know for your first job.) You worked your butt off and gai…

[bzoj1042][HAOI2008]硬币购物

有三种硬币&#xff0c;每种有自己的币值。 然后有n次询问&#xff0c;每次都给出每种硬币的数量和要付的钱s&#xff0c;求有多少种付法。n<1000 s<100000 ------ 不考虑限制&#xff0c;就是个简单dp.... 有限制的时候&#xff0c;我们可以考虑反过来用总的方案数量剪掉…

Windows netstat 查看端口、进程占用

目标&#xff1a;在Windows环境下&#xff0c;用netstat命令查看某个端口号是否占用&#xff0c;为哪个进程所占用. 操作&#xff1a;操作分为两步&#xff1a;&#xff08;1&#xff09;查看该端口被那个PID所占用;方法一&#xff1a;有针对性的查看端口&#xff0c;使用命令 …

iOS Named colors do not work prior to iOS 11.0问题解决

原文链接 https://stackoverflow.com/questions/48014246/named-colors-do-not-work-prior-to-ios-11-0-error-referring-to-a-storyboard/52967313#52967313 1 打开对应文件source code 2 粘贴查找 使用正则表达式 color key(.*) name.* 3 用以下代码覆盖 color key$1 …

如何在StackOverflow上获得第一个标签徽章-以及为什么它很重要。

by Angelos Chalaris通过安吉洛斯查拉利斯(Angelos Chalaris) 如何在StackOverflow上获得第一个标签徽章-以及为什么它很重要。 (How to get your first tag badge on StackOverflow — and why it’s important.) Every developer uses StackOverflow in different ways. Som…

int数据类型

1 a 18862 # 取商和余数3 print(a.__divmod__(10)) 4 5 # r反转,想当于 10-18866 print(a.__rsub__(10)) 7 8 # 取绝对值9 print(a.__abs__(), abs(a)) 10 11 #商取整 12 print(a.__floordiv__(10), a // 10) 转载于:https://www.cnblogs.com/xh4528/p/6497629.html

使用Google 官方的控件SwipeRefreshLayout实现下拉刷新功能

之前做东西的时候&#xff0c;经常会用到下拉刷新的功能&#xff0c;之前大家都在使用Github上的一个很著名的开源项目 PullToRefresh 但是&#xff0c;现在好消息来了&#xff0c;google在19.1版本的support-v4兼容包下面正式提供了官方的下拉刷新组件——SwipeRefreshLayout…

iOS 没到年底NSDate 时间出错问题

NSDate *currentDate [NSDate date];//获取当前时间&#xff0c;日期 NSDateFormatter *dateFormatter [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:"yyyy-MM-dd HH:mm:ss"]; // [dateFormatter setDateFormat:"YYYY-MM…

react 统一字段验证_如何使用React的受控输入进行即时表单字段验证

react 统一字段验证by Gosha Arinich通过Gosha Arinich 如何使用React的受控输入进行即时表单字段验证 (How to use React’s controlled inputs for instant form field validation) Controlled inputs enable simple things, like disabling the Submit button when some fi…

UISearchBar和 UISearchDisplayController的使用

感觉好多文章不是很全面&#xff0c;所以本文收集整合了网上的几篇文章&#xff0c;感觉有互相补充的效果。 如果想下载源码来看&#xff1a;http://code4app.com/search/searchbar 。本源码与本文无关 1、searchBar 本例子实现布局&#xff1a;上面是一个navigationController…

iOS 获取指定时间的前后N个月

https://www.cnblogs.com/SUPER-F/p/7298548.html 正数为后 负数为前 -(NSDate *)getPriousorLaterDateFromDate:(NSDate *)date withMonth:(NSInteger)month { NSDateComponents *comps [[NSDateComponents alloc] init]; [comps setMonth:month]; NSCalendar *calender …

JS高级程序设计第五章读书笔记

1.引用类型的值&#xff08;对象&#xff09;是引用类型的一个实例。在ES中&#xff0c;引用类型是一种数据结构&#xff0c;用于将数据和功能组织在一起。它们也长被称为类&#xff0c;但这并不妥当。因为ES在技术层面上是一门面对对象的语言&#xff0c;但它并不具备传统的面…

使用Tape和Vue Test Utils编写快速的Vue单元测试

by Edd Yerburgh埃德耶堡(Edd Yerburgh) 使用Tape和Vue Test Utils编写快速的Vue单元测试 (Write blazing fast Vue unit tests with Tape and Vue Test Utils) Tape is the fastest framework for unit testing Vue components.磁带是用于Vue组件进行单元测试的最快框架。 I…

js去除数组中重复值

//第三种方法加强版 Array.prototype.distinctfunction(){ var sameObjfunction(a,b){ var tag true; if(!a||!b)return false; for(var x in a){ if(!b[x]) return false; if(typeof(a[x])object){ tagsameObj(a[x],b[x]); }else{ if(a[x]!b[x]) return false; } } return ta…

CXFServlet类的作用

CXFServlet是Apache CXF框架中的一个核心组件,用于处理HTTP请求并将它们转换为Web服务调用。通过配置CXFServlet,你可以轻松地部署和管理SOAP和RESTful Web服务。

了解jvm对编程的帮助_这是您对社会责任编程的了解

了解jvm对编程的帮助by ?? Anton de Regt由?? 安东德雷格 这是您对社会责任编程的了解 (This is what you need to know about Socially Responsible Programming) 您的才华比银行帐户中的零值多 (Your talent is worth more than lots of zeroes in your bank account) L…

解压和生成 system.imgdata.img ( ext4格式)

另一篇文章讲述了如何解压和生成system.img&#xff0c; 那是针对yaffs2格式的文件系统镜像。 目前越来越多的Android手机放弃了nand, 更多采用了emmc为内部存储设备。 以emmc为存储设备的android手机&#xff0c;其文件系统(/system,/data两个分区&#xff09;一般采用ext4格式…

简单分析beyond作曲

本人绝对是业余的哈 业余到什么水平呢&#xff1f;正在练习爬格子&#xff0c;还是一个星期练几次那种 先说下《海阔天空》 6&#xff0c;5&#xff0c;4&#xff0c;3 1&#xff0c;2&#xff0c;3&#xff0c;4 简单是简单得不得了&#xff0c;声从低到高&#xff0c;然后再从…

1 OC 对象的本质(一个NSObject 对象占用的内存大小)

1 前言 目录 1 前言 2 一个NSObject占用多少内存 3 为什么呢 &#xff1f; 4 如何在内存中看呢&#xff1f; OC 的面向对象都是基于C/C 的数据结构实现的 结构体 2 clang 命令转换成c 代码 clang -rewrite-objc main.m -o main.cpp 以上的命令是不分平台进行编译的&…

Xiki:一个开发人员寻求增强命令行界面的能力

by Craig Muth通过克雷格穆斯(Craig Muth) Xiki&#xff1a;一个开发人员寻求增强命令行界面的能力 (Xiki: one developer’s quest to turbocharge the command line interface) I was sitting with my friend Charles in a trendy cafe next to Golden Gate Park in San Fra…

2 OC 对象的本质(一个Student 占用的内存大小)

一 Student 占用的内存空间 补充&#xff1a; 1 成员变量占用字节的大小&#xff1a; 2 内存对齐的规则&#xff1a;结构体的内存大小必须是最大成员变量的内存的倍数。 一个 Student 类,继承自NSObject&#xff0c;有两个属性&#xff0c;首先要知道&#xff0c;int 类型占用…

jdk动态代理源码学习

最近用到了java的动态代理&#xff0c;虽然会用&#xff0c;但不了解他具体是怎么实现&#xff0c;抽空看看了看他的源码。 说到Java的动态代理就不能不说到代理模式&#xff0c;动态代理也就是多了一个’动态’两字,在《大话设计模式》中不是有这句话吗&#xff1f;“反射&…

20162313苑洪铭 第一周作业

20162313苑洪铭 20016-2017-2 《程序设计与数据结构》第1周学习总结 教材学习内容总结 本周观看教材绪论 主要在教我建立一个简单的java程序 内容是林肯的名言 虽然看起来很简单 但是实际上问题重重 总而言之 这一周全是在出现故障的 教材学习中的问题和解决过程 教材学习好像并…

测试驱动开发 测试前移_测试驱动的开发可能看起来是工作的两倍-但无论如何您都应该这样做...

测试驱动开发 测试前移by Navdeep Singh通过Navdeep Singh 测试驱动的开发可能看起来是工作的两倍-但无论如何您都应该这样做 (Test-driven development might seem like twice the work — but you should do it anyway) Isn’t Test Driven Development (TDD) twice the wor…

3 OC 属性和方法

1 OC 的属性的生成 interface Student:NSObject {publicint _no;int _age;}property (nonatomic,assign)int height;end 当我们使用property 的时候&#xff0c;那么系统会自动的在其内部生成个属性 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.c…

ios绘图时的坐标处理

在iOS中&#xff0c;进行绘图操作时&#xff0c;一般主要是在UIView:drawRect中调用 UIGraphicsBeginImageContextWithOptions等一系列函数&#xff0c;有时候直接画图就行&#xff0c;比如UIImage的drawRect等&#xff0c;有时候需要进行稍微复杂的操作&#xff0c;比如颜色混…