本篇向大家介绍本系列demo教程中最后一个知识点就是多模块开发,多模块听着高大上,其实就是依靠maven相互之间的依赖,把多个模块融合进一个项目中而已,说的再直白一些就是像日常开发那样把其他模块导入一个模块,这个模块用来启动并调用所有模块而已。下面通过一个例子整体走一遍模块化开发。

第一步:建立父工程,注意通常情况下,父工程是一个spring initializr模式下创建出来的,但同样存在少量的人会先创建maven在转换成spring boot项目,这个其实就是手动添加所有依赖而已,那种方便用就用那种。
在这里插入图片描述

父模块任何环境依赖都不要,只需要指定springboot版本就行

在这里插入图片描述
建好的项目结构默认是下面这个样子
在这里插入图片描述
我们需要把父项目里,写代码的资源目录删掉,当然,如果看其他的不顺眼你也可以一起删掉,原则上保留.idea、.mvn、父项目的pom文件即可。
在这里插入图片描述
父项目的pom中查看是否指定了pom打包方式

<packaging>pom</packaging>

第二步:建立子项目为模块,我们总共创建三个子模块,commons为公共模块,service为Service功能模块,web为Controller功能以及启动模块,因此在依赖关系上组成web依赖于service,service依赖commons

在父项目名上右键单击创建module
在这里插入图片描述

子模块创建的时候,你要注意使用maven模式还是spring initializr,因为如果用spring initializr创建的是一个完整相对独立的springboot项目,它的父子关系、jar依赖以及作为子模块有一些东西是不需要的这些你都要自己操作,而maven创建就比较方便

在这里插入图片描述

我们先创建一个普通的maven项目为commons模块,maven项目创建时可以直接选择父工程
在这里插入图片描述
commons是一个公共包它不需要任何的支持,所以它的项目结构很简洁
在这里插入图片描述
下面我们创建service模块,同样也是一个普通的maven项目,当然如果你觉得有必要可以创建一个springboot项目
在这里插入图片描述
在这里插入图片描述

最后是web模块,由于web模块还负责启动项目的功能,因此我们要用spring initializr创建一个相对完整的springboot项目,应当注意有的时候,会发生一个小概率问题,就是由于操作问题,在创建好springboot框架的子模块后,该模块缺少springboot的上下文配置,直观的表现为配置文件无提示,并且代码源为灰色普通目录,这种情况右键代码源路径把它设置成代码源就行,而配置文件识别见知识点8,在设置配置文件的时候最好关注一下springboot的Web环境是否正常

在这里插入图片描述
web模块需要web环境的支持
在这里插入图片描述
由于不是maven创建,所以需要手动维护父子项目关系,在web模块pom中修改parent标签为父项目

<parent>
    <artifactId>boot-par</artifactId>
    <groupId>com.wy</groupId>
    <version>0.1</version>
    <relativePath>../pom.xml</relativePath>
</parent>

web模块的pom文件中还要依赖service模块

<dependency>
    <groupId>com.wy</groupId>
    <artifactId>boot-service</artifactId>
    <version>0.1</version>
</dependency>

service模块要依赖commons模块,也就是说我们要在service模块的pom中添加commons的依赖。

<dependency>
    <groupId>com.wy</groupId>
    <artifactId>boot-commons</artifactId>
    <version>0.1</version>
</dependency>

在父项目中要确保声明了所有的子模块

<modules>
    <module>boot-commons</module>
    <module>boot-service</module>
    <module>boot-web</module>
</modules>

到此项目结构就准备好了。最后要注意二种情况,第一个如果你的子模块是直接从外部复制进去的,一定要维护好父子pom的同时,确保模块名字没有和路径名字是一样的,不要出现路径名[模块名]这种格式,如果出现了,右键模块rename修改即可。第二个是复制过来的模块代码源路径常常后面存在resource root的字样,这种情况选择File --> Project Structure -->Modules ,随后找到模块的source内容,右侧会有灰色的内容删除掉即可。

第三步:写一套测试代码。首先在commons中写一个工具类,简单的输出一句话。

package com.wy.utils;

/**
 * @创建人 wangyang
 * @创建时间 2022/10/2
 * @描述
 */
public class OutPutUtil {
    
    public static void outMes(){
        System.out.println("commos模块的输出");
    }
    
}

随后在Service模块中写一个测试Service调用commons的工具类

package com.wy;

import com.wy.utils.OutPutUtil;

/**
 * @创建人 wangyang
 * @创建时间 2022/10/2
 * @描述
 */
 @Service
public class OutPutService {

    public void outMes(){
        OutPutUtil.outMes();
        System.out.println("这里是Service");
    }

}

这里注意子模块是可以使用父项目的依赖的,所以不要疑惑service模块没有依赖怎么可以用Service注解。顺带着还要说明springboot可以直接使用ssm的service和dao开放方式,并不是说只能继承IService等资源,区别就在于如果继承你可以拥有框架内部给你写好的基本方法

如果你在写代码的时候commons的模块代码引用不到,就点击一下maven的刷新
在这里插入图片描述

最后在web模块中添加写一个测试用的Controller

package com.example.bootweb.controller;

import com.wy.OutPutService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

/**
 * @创建人 wangyang
 * @创建时间 2022/10/2
 * @描述
 */
@RestController
public class OutController {

    @Resource
    private OutPutService outPutService;

    @RequestMapping("/getMes")
    public String outMes(){
    	outPutService.outMes();
        return "请求成功";
    }
}

并且在web模块的启动类上添加一个扫描包的注解,如果从前面知识点看过来的会疑惑这个例子中没有写Dao层,为什么要写扫描包的注解?对此要解释一下,前面知识点使用的注解是@MapperScan,是MybatisPlus提供的用来指定Dao层包的,但是我们现在要用的注解是@ComponentScan,是springboot提供的用它来指定根包,将不同模块中的Bean提交到Spring容器里面

package com.example.bootweb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;

@SpringBootApplication
@ComponentScan(basePackages = {"com.wy","com.example"})
public class BootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootWebApplication.class, args);
    }

}

在把web模块中的springboot配置文件设置一个端口

server.port=91

到此测试代码就写完了。

第四步:打包,但是我们要对每个模块pom依赖中的编译插件,做一下设置,让maven知道每个模块怎么编译,不设置的话会爆一些默认资源没有的错误。

首先父工程的编译插件不变

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

在web模块中你要手动指定启动类

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.example.bootweb.BootWebApplication</mainClass>
    </configuration>
</plugin>

而其它非启动模块,如service和commons模块中设置不让springboot-maven编译插件将当前模块作为启动模块编译

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <skip>true</skip>
    </configuration>
</plugin>

核对整个项目的项目信息是非正确,例如下面的标签

<groupId>com.wy</groupId>
<artifactId>shopprotection</artifactId>
<version>0.2</version>
<packaging>jar</packaging>

下面这些可以选,是用来生成文档的时候用的
<name>项目名称</name>
<description>项目说明</description>
<url>项目路径</url>

最后一定要确保所有的模块pom文件声明了jar打包方式,如果你不申明很可能会报'packaging' with value 'jar' is invalid. Aggregator projects require 'pom' as packaging.的错误,并且注意父模块的打包方式应该是pom

<packaging>jar</packaging>

此时,运行父项目的maven命令进行打包,等待结束可以在控制台中看到编译成功,要注意,因为是首次编译,所以我们直接用父项目的命令将所有模块全部编译,如果你后面对某个模块单独的更新迭代,那么用模块的命令就可以
在这里插入图片描述
第六步:运行,我们通过web模块的启动类使用内部web软件运行项目,并发出请求。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然你可以不在idea里面运行,而是在黑窗口中直接运行web项目的jar包

运行时应当注意多模块开发的时候,使用springboot框架的子模块常常会在运行的时候出现打包无异常,但运行的时候报错org.springframework.stereotype,这种情况你需要在idea开发工具的maven插件上找到如下图标
在这里插入图片描述
随着idea的版本不同,图标有的是个dos窗口图标
在这里插入图片描述
找到后选中你的web模块然后点击图标,在打开的窗口中运行如下命令,首次运行需要手动输入
在这里插入图片描述
运行后会重构项目,并在父项目的主路径下多出几个文件,如下图
在这里插入图片描述
此时重新编译打包web模块既可以了


特别说明

模块化开发,本质上是为了解决两个问题,第一个是让maven项目相互依赖,最终组成一个相对完整的项目运行,就像上面例子一样,这样的好处就是解耦开发,增强代码的复用。第二个是模块开发可以使得maven帮助我们调整jar依赖的版本并且解决jar包的冲突。不过jar冲突方面maven只能解决90%的问题,还有另外10%是这包本身没有向下兼容,不过Apache的包基本都是向下兼容的,出现这类问题基本是其他的包

同时本例中service模块和commons模块包路径其实不是很合理,应该在所有子模块中考虑有一个用来区分不同模块的包名这样逻辑更清晰一些。你也可以结合知识点18将web模块部署到外部的tomcat上。

在正式开发中存在着保存当前版本jar到本地maven库的需求,这个就是你运行maven的install命令就可以,可是千万要记得要从最底层依赖开始install,比如上面的例子中你需要按照commons-》Service-》web这样的顺序install,不然会报错。

最后要说的一点相当重要!!!!!!上面的流程中对整个项目的pom依赖,一笔带过了,就是为了在这里说明。在正式的开发中,父项目的pom文件中一般不直接引入依赖,这会导致子模块打包时造成臃肿,就是说父项目的pom核心内容通常有如下六大块内容分别为parent标签用来继承springboot、项目说明标签规定打包方式等、modules标签包含模块名、properties配置类信息、dependencyManagement规定项目所用的依赖、build插件,子模块的pom中和父项目的区别不大,内容上同样使用parent标签用来继承父项目,不过要注意子模块的relativePath标签值为:<relativePath>../pom.xml</relativePath>表示父项目也在本地,对于子模块来说最应该注意的是第三方依赖的导入。因为父项目的pom中用的是dependencyManagement,这个标签不会正式的导入依赖,只是提供了一种版本说明,所以导致子模块在导入依赖的时候,如果不直接指定版本,则导入的依赖一定要在父项目的dependencyManagement中存在说明,不然导不进去。而如果父项目直接使用dependencies则不存在这个问题。下面给大家提供一个样例

父项目pom:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.wy</groupId>
	<artifactId>shop</artifactId>
	<version>0.7</version>
	<name>shop</name>
	<packaging>pom</packaging>

	<properties>
		<java.version>1.8</java.version>
		<spring-boot.version>2.7.4</spring-boot.version>
		<redis.version>2.7.3</redis.version>
	</properties>

	<modules>
		<module>common</module>
		<module>shopapi</module>
    </modules>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-web</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-test</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-data-redis</artifactId>
				<version>${redis.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

对应的子项目,在父项目中存在说明的依赖就可以不指定版本

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.wy</groupId>
        <artifactId>shop</artifactId>
        <version>0.7</version>
        <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.wy</groupId>
    <artifactId>shopapi</artifactId>
    <version>0.7.8</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--数据库配置-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!-- 公共包 -->
        <dependency>
            <groupId>com.wy</groupId>
            <artifactId>common</artifactId>
            <version>0.7.1</version>
        </dependency>

        <!--加入lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.wy.shopapi.ShopapiApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

如果你担心自己指定版本会不兼容,比如springboot是2.7.4,现在要用redis的data依赖,你不知道应该用那个版本,此时就去maven官方仓库https://mvnrepository.com/中找data-redis,选择一个版本打开详情,在赋值路径的下面,有依赖说明,不一定要刚好符合,只要很接近就行,以此为依据找一个合适的版本。
在这里插入图片描述
唯一要注意的是,有的时候maven会莫名其妙的卡住,导致子模块的某个依赖拉不进来,这个时候就需要你手动指定了。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐