Maven的拆分和聚合
# 110.Maven的拆分和聚合
使用拆分和聚合,可以更好的解耦
# 之前项目的问题
之前我们用Maven完成了一个表的CRUD,其实目前项目还是有一些问题的,我们考虑这样的场景:
- 我们目前的项目是分三层架构的:dao,service,controller
- 假设目前做的是一个电商项目,用户有买家和买家
- 目前有一个查看订单的功能,买家要看订单,需要查数据库,这是dao层的任务;
- 卖家要查订单,也要查数据库,也是dao层的任务
- 而此时卖家和买家用的不是同一个系统(例如某宝,卖家用的是一个后台系统),那么就会重复的代码
此时我们遇到的和问题和之前很类似:
- 在使用Maven之前,十个SSM项目有10套重复的依赖;使用Maven后,jar包就不会重复了
- 而我们可以将我们的dao层也看成是一个jar包!他们和Maven的依赖没有什么不同,这样我们就可以在多个项目都使用dao层打成的jar包,减少重复!这样如果要修改dao,只需修改一份就可以
Maven是这样解决的:Maven把一个完整的项目,分成不同的独立模块,这些模块都有各自独立的坐标。哪个地方需要其中某个模块,就直接引用该模块的坐标即可。有点类似乐高的拼图,我们可以使用拼图完成不同的模型
今后如果开发一个新项目,我们先考虑问题不是dao,service,utils,domain如何编写,我们要考虑的是这些模块是否已经存在,如果存在直接引用。以上说的就是Maven拆分的思想。
我们可以把拆分的模块聚合到一起编写一个完整的项目,这就是Maven聚合思想。此时该项目可以看成是一个父工程,其下有很多个子工程(模块)
# 父子工程的创建
我们首先创建父工程,父工程只需有一个pom.xml就可以了,我们在IDEA中创建project或module都可以。
创建后,我们可以直接将src目录删掉,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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
2
3
4
5
6
7
8
9
# 创建子模块
这里我们创建3个模块dao,service,controller即可,实际开发中可能有很多个模块,但创建起来都是一样的
我们在项目上右键--选择新建--选择新模块
先创建dao模块:这里我们不使用骨架,因此选择第一个“新建模块”,然后名称我们加个dao
然后我们打开dao模块的pom.xml(注意不是父工程的pom.xml)
其内容如下:
<?xml version="1.0" encoding="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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>LearnJavaMaven_dao</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
可以看到其多了一个parent标签。之前我们说过每个Maven项目都有自己的坐标,而我们这个子模块好像没有定义?这是因为子模块和父工程共用groupId和version,子模块只需定义自己的artifactId即可
我们再来看父工程的pom.xml:可以看到其多了一个modules标签,表明子模块。
<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.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>LearnJavaMaven_dao</module>
</modules>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
我们按照上述的方法,创建service和controller层。注意创建controller层的时候,我们选择骨架webapp。
service模块的pom.xml:
<?xml version="1.0" encoding="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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>LearnJavaMaven_service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
由于我们controller层用了骨架,pom.xml中有一些多余的依赖,我们可以删掉,之后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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>LearnJavaMaven_web</artifactId>
<packaging>war</packaging>
</project>
2
3
4
5
6
7
8
9
10
11
12
此时父工程就有3个module了:
<modules>
<module>LearnJavaMaven_dao</module>
<module>LearnJavaMaven_service</module>
<module>LearnJavaMaven_web</module>
</modules>
2
3
4
5
至此,父子工程创建完成
# 工程和模块,继承和依赖
刚刚我们创建了一个Maven工程,和几个Maven模块,那么工程和模块有什么区别呢?
工程不等于完整的项目,模块也不等于完整的项目,一个完整的项目看的是代码,代码完整,就可以说这是一个完整的项目,和此项目是工程和模块没有关系。
工程天生只能使用自己内部资源,工程天生是独立的,后天可以和其他工程或模块建立关联关系。
模块天生不是独立的,模块天生是属于父工程的,模块一旦创建,所有父工程的资源都可以使用。举个生活中的例子,工程类似一个班级,而模块就是每个班级的学生。学生可以使用班级里的公共资源(例如黑板等)
父子工程之间:子模块天生集成父工程,可以使用父工程所有资源。子模块之间天生是没有任何关系的,但子模块之间可以建立关系,例如dao可以给其他模块调用,只需通过引用坐标的方式来使用。
父子工程之间不用建立关系,继承关系是先天的,不需要手动建立。
平级直接的引用叫依赖,依赖不是先天的,依赖是需要后天建立的。
# 拆分模块
接下来我们将前面搭建的Maven的SSM工程,分成几个模块。
首先我们将依赖,全部导入到父工程的pom.xml中。
然后我们在web模块中引用service:
<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>
<parent>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>LearnJavaMaven_web</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
同理,service层引用dao层的:
<?xml version="1.0" encoding="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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>LearnJavaMaven_service</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.peterjxl</groupId>
<artifactId>LearnJavaMaven_dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 依赖的传递性
假设我们现在工程里有个依赖是Junit,其作用域scope配置的是test,那么模块能使用这个依赖吗?
其实模块也是依赖于父工程的(因为要模块需要父工程定义的依赖),那么dao模块是否能用Junit?
答案是不能的,这是Maven的规定。那么怎么样的依赖能被模块使用呢?我们可以看一张图:
首先我们的dao模块是直接依赖于parent的,其作用域是compile:
然后Junit是传递依赖,我们定位到compile行和test列:
如果对应上-,就表示传递丢失了,也就是dao不能使用Junit。
实际开发中,如果传递依赖丢失,表现形式就是jar包的坐标导不进去,我们的做法就是直接再导入一次。
除此之外,在依赖标签里面还有一个标签属性optional:该标签默认值为false,指的是父子项目之间的是否传递(之前在看Mybatis源代码的时候,下载下来后的pom.xml里面就会有这个标签),如果父项目引入一个依赖并且optional标签设置为true的话,那么子项目打包的时候也会打包进去,如果设置为false的话,那么就不会打包进去。
本小节的知识点用的较少,大家了解即可。
# 填充SSM的代码
接下来我们将之前搭建的SSM-Maven项目,填充到这次拆分了几个模块的项目中。
# dao模块
我们首先填充下dao模块,我们将ItemsDao接口,Items类复制进来;然后将ItemsDao.xml也复制进来。
然后我们新建一个resources/spring/applicationContext-dao.xml,用来配置dao的bean:
<?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.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///learnmaven"/>
<property name="username" value="LearnMavenUser"/>
<property name="password" value="LearnMavenUser@Password"/>
</bean>
<!-- 配置生成 SqlSession 对象的工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--扫描pojo包,给包下所有pojo对象起别名-->
<property name="typeAliasesPackage" value="com.peterjxl.domain"/>
</bean>
<!-- 扫描接口包路径,并生成所有接口的代理对象,放到 Spring 容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.peterjxl.dao"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
此时的dao模块目录结构为:
├── LearnJavaMaven_dao
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ └── peterjxl
│ │ │ │ ├── dao
│ │ │ │ │ └── ItemsDao.java
│ │ │ │ └── domain
│ │ │ │ └── Items.java
│ │ │ └── resources
│ │ │ ├── com
│ │ │ │ └── peterjxl
│ │ │ │ └── dao
│ │ │ │ └── ItemsDao.xml
│ │ │ └── spring
│ │ │ └── applicationContext-dao.xml
│ │ └── test
│ │ └── java
│ └── pom.xml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# service模块
同理,复制service层的代码,然后新建resources/spring/applicationContext-service.xml文件,将service的bean配置挪过来:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描service包下所有使用注解的类型,并交给 Spring 管理 -->
<context:component-scan base-package="com.peterjxl.service"/>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的通知 -->
<tx:advice id="active">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入(切面) -->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.peterjxl.service.impl.*.*(..))"/>
<aop:advisor advice-ref="active" pointcut-ref="pointcut"/>
</aop:config>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
此时目录结构如下:
├── LearnJavaMaven_service
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ └── peterjxl
│ │ │ │ └── service
│ │ │ │ ├── impl
│ │ │ │ │ └── ItemsServiceImpl.java
│ │ │ │ └── ItemsService.java
│ │ │ └── resources
│ │ │ └── spring
│ │ │ └── applicationContext-service.xml
│ │ └── test
│ │ └── java
│ └── pom.xml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# web模块
同理,引入controller的代码,JSP页面,然后是log4j.properties,springmvc.xml到resources目录下
然后是web.xml,此时我们会发现我们没有applicationContext.xml
;我们在resources目录下新建,并将两个模块的配置文件引入:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<import resource="classpath:spring/applicationContext-dao.xml"/>
<import resource="classpath:spring/applicationContext-service.xml"/>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此时目录结构如下
├── LearnJavaMaven_web
│ ├── src
│ │ └── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── peterjxl
│ │ │ └── controller
│ │ │ └── ItemsController.java
│ │ ├── resources
│ │ │ ├── applicationContext.xml
│ │ │ ├── log4j.properties
│ │ │ └── springmvc.xml
│ │ └── webapp
│ │ ├── WEB-INF
│ │ │ ├── pages
│ │ │ │ └── itemDetail.jsp
│ │ │ └── web.xml
│ │ └── index.jsp
│ └── pom.xml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 启动项目的3种方式
# 第一种模式:启动父工程
我们可以在Maven面板中,找到Tomcat7插件然后双击运行:(其实在命令行中输入mvn tomcat7:run 也是一样的)
访问项目地址:
# 第二种方式:启动web模块
之前我们说过看一个项目是否完整,得看它代码是否完整,而不是看它是模块还是工程。
注意,我们先要将service层的模块,和dao层的模块都安装到本地仓库中,否则会找不到依赖。我们可以直接install父工程:可以在命令行里使用mvn install命令,或者通过可视化界面来安装
然后我们启动:
再次访问,也是可以看到数据的。
# 第三种方式:使用本地Tomcat
我们新建一个运行配置:
然后找到Tomcat,并改个名字,最后添加一个war包
我们选第一个即可:
此时的运行结果是一样的:
# 源码
本项目已将源码上传到Gitee (opens new window)和GitHub (opens new window)上。并且创建了分支demo7,读者可以通过切换分支来查看本文的示例代码。