第3章 项目与模块 我们知道,Eclipse采用工作空间(Workspace)与项目(Project)的方式进行管理,在同一工作空间下可以有多个项目。Eclipse中的项目不可再划分,如果需要使用其他外部的功能,则通常以引入外部项目归档文件(Java Archive)的方式进行,也就是引入外部Jar文件。 在这种情况下项目之间缺乏关联与结构化管理,同时项目本身可能由于应用功能的不断增加而变得庞大。对于单体结构的项目来讲,这将是一场噩梦。 IntelliJ IDEA中不再有工作空间的概念,同时在项目(Project)下使用了模块(Module)来对其进行划分,因此可以将一个项目划分为多个模块,以不同的模块来管理不同的功能。 读者可能会认为,一个工作空间下有多个项目与一个项目下有多个模块,既然都是一对多的管理方式,那么它们本身应该没有太大的差别。事实并不是这样,因为在Eclipse中项目是运行时的单位,而IntelliJ IDEA中模块才是运行时的基本单位,它对项目进行了更加细化的运行时管理,而且模块间的依赖关系也变得更加灵活和高效。 在最简单的情况下,一个模块就是一个项目。模块作为一种组成项目的构件,它既可以独立运行,也可以与其他模块组合在一起使用,只需维护好这些模块之间的依赖关系。 3.1项目结构 项目结构(Project Structure)用于对项目进行设置,如Modules、Libraries、Facets、Artifacts和SDK。了解项目结构可以帮助开发者更好地管理、分析与调试项目。 项目结构窗口只有在项目处于打开状态时才可以进行访问并操作,如图3.1所示。 图3.1Project Structure 要打开Project Structure窗口,可以采用如下方式: (1) 选择菜单 File→Project Structure命令或使用快捷键 Ctrl+Shift+Alt+S。 (2) 单击工具栏上的按钮。 在Project Structure窗口中包含了与项目配置(Project Settings)和平台配置(Platform Settings)相关的众多选项,接下来逐一进行说明。 3.1.1工程 在工程(Project)选项卡中主要包含以下内容: (1) Project name指定项目的名称。 (2) Project SDK指定项目运行需要的SDK。 (3) Project language level指定SDK的语言级别。 图3.2支持的JDK特性 Java JDK 的每个新版本都会有新特性发布,而新版本一般也会向下兼容旧版本的特性。IntelliJ IDEA 中列出了对JDK新特性的支持,如图3.2所示。 可以看到,最新版本的IntelliJ IDEA已经支持JDK 14的特性。通常在SDK选取完成之后,IntelliJ IDEA会提供一个SDK默认的语言级别。如果目标级别没有明确定义,则认为它与源语言级别相同。 例如,如果使用的是JDK 1.8,则只能兼容JDK 1.8及其以下的特性,此时语言级别为SDK default(8Lambdas,type annotations etc.),如图3.3所示。 图3.3默认语言级别 如果项目配置了 JDK 1.8,但是只使用了JDK 1.7的特性,则语言级别可以选择为 7Diamonds,ARM,multicatch etc.。如果项目中使用了JDK1.8的新特性(如Lambda 语法),但是使用的却是 JDK 1.7,即使Project language level 选择了8Lambdas,type annotations etc.也是没意义的,同样会产生编译错误。 所以Project language level用来限定项目编译检查时最低要求的JDK特性。具体使用哪种语言特性不仅取决于当前项目的JDK依赖,还需要开发者对JDK特性有比较深入的了解。 Project compiler output用于指定当前项目编译结果的全局输出目录,同时各模块还可以自定义编译输出目录。如果模块不进行编译输出目录的定义,则将继承并使用全局的编译输出目录。 在上述内容中,Project SDK、Project language level和Project compiler output都是基于项目级别进行的定义。在大型且复杂的项目中通常包含了很多模块,并且每个模块使用的Project SDK和language level很有可能是不同的,因此可以对不同模块进行更细级别的配置来覆盖项目级别的配置以实现兼容。 如果项目中使用了Maven构建管理工具并且在pom.xml文件中指定了编译器版本,则将会对项目中(包括模块内)指定的编译器级别产生限定,这种强制性的指定有可能会导致编译错误,此时需要修改pom.xml文件并重新刷新Maven配置。 3.1.2模块 模块(Modules)选项卡用于对项目中的模块进行管理,它可以覆盖Project级别的选项配置并将配置的粒度细化,如图3.4所示。 图3.4Modules模块管理 Modules列出了当前项目管理的所有模块,默认新建的项目就是一个模块。 用户可以将其他项目引入当前项目作为一个子模块,也可以在当前项目中新建一个子模块(项目)。这样做是有好处的,通过模块化管理可以轻松地将各种功能独立开来,这种类似单元组装的方式可以最大限度地实现组件的复用,也便于对项目进行更好的组织管理。后续会讲解如何创建与管理模块。 选择模块列表中的某一模块(如HelloWorld)后,窗口右侧会展示该模块的具体信息。对于项目来讲,其子模块所在的存储位置并没有统一的要求,通常组成项目的各模块都位于当前工程目录下的多个子目录中,子模块与项目在磁盘上的存储位置没有必要的联系。 在右侧的模块信息中,首先展示了模块在当前项目中的名称。如果一个模块出现在多个项目中,则它可能在这些项目中具有不同的名称,但事实上还是同一个模块所对应的项目。 Sources选项卡下指定了模块使用的语言级别,Project标签下设置的语言级别是整个项目的语言级别,而当前模块下可以对项目级别的设置进行覆盖,毕竟很多时候一个项目所需要的各个模块可能是在不同版本或不同语言级别的JDK下开发完成的。 Sources选项卡下还提供了Mark标记管理。这些标记用于标识工程中的各种内容,IntelliJ IDEA根据这些标记进行相应内容的识别,如哪些目录用于存放源代码、哪些目录用于存放静态文件、哪些目录用于存放测试代码、哪些目录被排除编译等。 在Mark标记主区域展示了模块的工程结构,通过选择区域内的某一目录并单击区域上方(Mark as部分)的不同标签,可以将其标记为某个指定用途的目录,打标成功的目录会变为与标记相同的颜色。 例如,对于标记为Sources的目录,IntelliJ IDEA会使用javac命令去编译其中的源码文件。如果模块中无Sources标记,则其中的源码文件通常会显示为图标,这表明源码没有加入IntelliJ IDEA的Source管理,即Java class located out of the source root状态,此时源文件将无法编译或运行。 被标记的目录会展示在右侧的Content Root列表下,各种标记的含义如下: (1) Source Roots/Source Folders: 标记为此类的目录会作为构建过程的一部分进行编译,Source Roots的子文件夹代表Java的包结构。 (2) Resource Roots/Resource Folders: 标记为此类的目录用于管理资源文件,如图片、xml配置文件和properties属性文件等。在构建过程中,所有Resources标记目录下的资源会被复制到Output文件夹,并且在项目打包时会被复制到jar或war中,同时忽略标记为Excluded的内容。 (3) Excluded Roots: 标记为此类的目录会被IntelliJ IDEA忽略,同时在进行搜索时IntelliJ IDEA也不会去查找这个目录下的内容。将一些不重要的目录标记为Excluded Roots可以提高IntelliJ IDEA的用户体验。 用户可以单击右侧的修改路径图标或删除图标来管理标记,也可以通过Mark as按钮组标记或取消。有些时候用户新建或导入的项目在内容标记上是不完整的,因此需要手工进行处理。 Paths选项卡指定了模块的编译输出路径,用户可以保持继承自项目的默认配置,也可以手工指定项目类与测试类的编译输出路径,如图3.5所示。 图3.5Paths路径配置 在Compiler output选项下,Inherit project compile output path会继承项目的编译输出路径,也就是在Project选项中设置过的Output路径。Use module compile output path用于手工指定源码与测试类的编译输出路径,其中Output path代表源码的编译输出路径。Test output path代表测试代码的编译输出路径。Exclude output paths可以排除输出目录。 JavaDoc用于管理与模块关联的外部存储位置的JavaDoc列表,并对模块内的JavaDoc进行覆盖。 External Annotations用于管理外部注解以将其与模块关联。 Dependencies选项卡中Module SDK用于从系统配置的SDK中进行选择并指定模块级别的SDK版本。如果需要的SDK不在列表中,则可执行菜单Add SDK命令,然后选择所需的SDK类型,如图3.6所示。 图3.6配置模块SDK 以添加JDK为例,在打开的对话框中选择JDK主目录,然后单击OK按钮确定,如图3.7所示。 图3.7添加JDK 单击右侧的Edit按钮可以查看或编辑SDK。还可以对依赖项及依赖应用范围进行配置,如图3.8所示。 图3.8管理依赖(非HelloWorld项目截图) 用户可以在Scope选项列指定依赖的应用范围以使其在不同运行环境下生效,如编译、测试或运行时等,其具体含义如下。 (1) Compile: 对项目类和测试类来讲,编译和运行都有效。 (2) Test: 仅对测试类来讲,编译和运行都有效。 (3) RunTime: 对项目类和测试类来讲,仅运行时有效。 (4) Provided: 对项目类来讲,仅编译时有效,而对测试类来讲,构建和运行时有效。 最后在Dependencies storage format中选择用于存储依赖关系的格式(IntelliJ IDEA模块或Eclipse项目),该选项会对使用不同开发工具的团队提供帮助。 3.1.3类库 类库(Libraries)选项卡列出了当前项目使用的外部资源类库,这些资源既可以直接加载使用,也可以通过项目管理工具(如Apache Maven)来管理并使用,如图3.9所示。 图3.9管理依赖 如果当前项目需要添加新的类库,则可单击资源列表上方的按钮或使用快捷键Alt+Insert,如图3.10所示。 用户根据需要选择添加类库的方式,选择Java方式可以直接加载外部资源。如果使用了构建管理工具,则可以采用From Maven的管理方式,关于Maven的使用可参考第6章。选择Java方式后打开如图3.11所示的对话框。 因为IntelliJ IDEA采用了模块管理的方式,所以在多模块项目中添加资源时需要指定将资源应用于哪些模块,选择完成后单击OK按钮确认。 图3.10添加依赖 图3.11指定模块(第3章HelloWorld示例) 那么模块如何知道哪些资源是自己的呢?之前提到过,在模块下的同名.iml文件中存放了模块的配置信息,打开同名.iml文件查看配置,代码如下: //第3章/DataModel.iml <component name="NewModuleRootManager" inherit-compiler-output="true"> <exclude-output /> <content URL="file://$MODULE_DIR$"> <sourceFolder URL="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder URL="file://$MODULE_DIR$/test" isTestSource="true" /> </content> <orderEntry type="JDK" JDKName="1.8" JDKType="JavaSDK" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="library" name="antlr-2.7.7" level="application" /> <orderEntry type="module" module-name="BaseModel" /> </component> 可以看到,模块配置中加载了外部资源并将其指定为library类型。level属性指定了这些资源的应用范围究竟是工程级别还是应用级别。 每次通过手工方式添加外部依赖时,IntelliJ IDEA都会自动为依赖内容生成一个类库名称,选择的外部资源文件都会自动加入这个类库的管理。通常类库名称会依据所选择的目标文件中第一个文件的名称来命名。如果用户想要更改其名称,则可在右侧窗口上方的名称文本框中进行修改,如图3.12所示。 图3.12Libraries类库 3.1.4特性 特性(Facets)描述了模块中使用的框架、技术和语言并体现了模块的应用特征,这些Facets特性让IntelliJ IDEA知道如何对待模块中的内容,并与相应的框架和语言保持一致,如图3.13所示。 图3.13Facets特性管理 Facets分类整理了项目的各种特性,如项目基于Spring进行构建同时又是Web项目,所以此时会同时展示Spring与Web选项。 大多数Facets可以无冲突地添加到项目中,它是自动且隐蔽的。IntelliJ IDEA可以很好地识别出项目的特性并进行归类管理,以Web特性为例进行说明。 在图3.13中,Name用于定义Web特性的名称,也可以使用系统默认提供的名称。 Deployment Descriptors描述符主要用于管理应用的部署描述。其中Type是只读字段,用于展示部署描述符类型。 Type类型值可以是Web Module Deployment Descriptor、EJB Module Deployment Descriptor或 Application Module Deployment Descriptor,这主要取决于当前项目的类型。 Path只读字段用于展示项目的部署配置文件的位置,如web.xml或application.xml等。 图3.14新建部署描述符 单击按钮或使用快捷键Alt+Insert添加部署描述符,在打开的Deployment Descriptor Location对话框中选择部署描述符的位置,如图3.14所示。 单击OK按钮完成添加。要修改部署描述符,单击修改按钮或使用快捷键Enter可以重新配置。单击删除按钮或使用快捷键Alt+Delete从列表中删除选定的描述符。如果希望同时删除磁盘上的描述符,则可以在打开的Delete Deployment Descriptor对话框中勾选Also delete file from disk选项,如图3.15所示。 在图3.13中,Add Application Server specific descriptor用于添加一个支持应用服务的部署描述符,常见的应用服务有JBoss服务、Glassfish服务、WebSphere服务、Tomcat服务、Jetty服务和Weblogic服务等。如果用户拥有应用服务开发经验,则一定会知道其中的一种或几种,如图3.16所示。 图3.15删除部署描述符 图3.16应用部署描述 Web Resource Directories选项用于将第三方或未分类资源路径映射到部署根目录,如图3.17所示。 图3.17Web Resource Directories 其中Web Resource Directory用于指定Web Resource所在的本地目录。在Web Resource目录下包含了Web开发所需的文件,如JSP、HTML、XML等。 Path Relative to Deployment Root用于展示Web Resource相对于Web根目录部署的相对路径,如“/”代表Web根目录。Web Resource目录下的内容会被复制到由Path Relative to Deployment Root所指定的Web模块部署目录中。 图3.18新建Web Resource目录映射 单击按钮或使用快捷键Alt+Insert打开Web Resource Directory Path 对话框,如图3.18所示。 最后是Source Roots选项,这部分内容展示当前模块中的source root列表,如Web项目的源码路径及资源路径等。 还有一些Facet是继承自其他Facet的,要添加这些Facets就必须先完成对其父Facet的添加,同时这些Facets也依赖于IntelliJ IDEA的相关插件是否已开启。 Facets特性主要用于配置工程结构以便应用于Artifacts中,也可以这么说: 特性的定义是为了发布做准备。 3.1.5项目生成 项目生成(Artifacts)代表了项目应用的目标,如可进行部署的War文件、应用归档文件(Java Archive File)等。对于可运行项目来讲,在Artifacts创建完成后就可以将其部署到应用服务器了。 项目的Artifacts可能是一个归档文件或者目录。在部署Tomcat应用时通常会在webapps目录下放置一个项目文件夹,这个文件夹就是标准的Artifacts结构。如果项目最终需要生成JAR文件供外部使用,那么它也是一种Artifacts。 那么如何对Artifacts进行创建呢?单击Artifacts列表上方的按钮或使用快捷键Alt+Insert,打开如图3.19所示的列表。 图3.19新建Artifacts 根据需要选择合适的生成目标,如果当前项目只是一个普通应用,那么可以选择JAR类别,此操作类似于从Eclipse中导出JAR资源的操作,如图3.20所示。 图3.20Eclipse下导出JAR 文件 在图3.20中,如果要导出带有可运行方法(主方法与测试用例)的JAR文件,就要使用Runnable JAR file了。 IntelliJ IDEA中提供了类似的操作,它同样支持生成不同类型的JAR文件。用户首先需要选择待导出的模块,或是将全部模块一起导出,如图3.21所示。 图3.21IntelliJ IDEA导出JAR File 如果待导出模块中并不包含可运行方法(主方法与测试用例),则Main Class位置可以为空。如果需要包含可运行方法,则用户可以单击右侧的文件夹图标,在弹出的方法列表里选择将要使用的方法或直接输入待运行方法。 在JAR files from libraries选项下,如果选择extract to the target JAR选项,则会生成对应的JAR文件。如果选择copy to the output directory and link via manifest选项,则下方的Directory for METAINF/MANIFEST.MF会变为可编辑状态,以便为项目生成启动文件MANIFEST.MF,并放置到项目中用户指定的目录位置。如果Main Class中包含内容,则Directory for METAINF/MANIFEST.MF一定会处于可编辑状态。 选择目标Artifacts可以对其进行设置,如图3.22所示。 图3.22配置Artifacts 其中Name用于指定Artifacts配置的名称,用户可以保持默认或自定义。Type用于指定Artifacts的类型。Output directory用于指定执行构建时Artifacts将被放置的目录。 在Artifacts配置完成后,执行菜单Build→Build Artifacts命令手工执行目标的构建工作,或通过执行Run/Debug的方式来构建一个Artifacts,前提是用户已经建立了Artifacts任务,此时在执行Run/Debug配置的时候Artifacts会自动构建。 基于Artifacts进行的构建在部署Web项目与运行调试时会经常用到,通常会有war和war exploded两种方式。它们的区别在于当采用调试模式运行项目时,以war exploded形式发布的应用是可以进行热部署与调试的,所以建议采用这种方式进行应用的发布。 项目结构窗口不仅对项目结构进行了管理,还为项目后期的部署运行提供了定义与准备。 3.1.6开发集成工具 在SDKs选项卡中进行全局开发集成工具(SDK)配置,如Java SDK、Python SDK等,如图3.23所示。 图3.23配置全局SDK 单击按钮或使用快捷键Alt+Insert打开新建SDK列表,如图3.24所示。 单击Download按钮JDK将会在线下载最新的JDK版本,如图3.25所示。 图3.24新建SDK 图3.25在线下载JDK 用户也可以手工进行其他SDK的安装,第1章中我们已经进行过相关操作,因此不再过多描述。 3.1.7全局类库 全局类库(Global Libraries)中配置了全局可用的类库,如图3.26所示。 图3.26全局类库 全局类库可以被模块共用,用户也可以在项目中配置单独使用的类库。 3.2模块的创建与使用 在IntelliJ IDEA中项目(Project)是由多个模块(Module)组成的,模块既是独立的功能单位,同时彼此之间又存在联系,所以项目中Project是规模范围定义的目录,Module才是项目里面的真正内容。 模块可以独立地编译、运行、测试和调试,它提供了一种降低大型项目复杂性的方法,还可以作为公用部分被多个项目共同使用。在每个模块目录下都存在一个后缀为.iml的文件,此文件包含了模块的配置信息,如依赖等。 IntelliJ Idea项目默认为单Module的(模块即项目),开发者既可以为项目创建新的模块,也可以导入已经存在的项目作为模块。 3.2.1新建模块 要管理项目中的模块,首先需要保证项目存在且处于打开状态。要创建新的模块,执行菜单File→New→Module命令打开新建模块窗口,如图3.27所示。 图3.27新建模块 图3.27中左侧列出了可以创建的模块视图,如Java视图代表普通的Java程序,它适用于简单结构的项目。Java Enterprise视图用于开发和部署可移植、健壮、可伸缩且安全的服务器端 Java应用程序,Web项目大多在这个视图中进行开发,功能也较为丰富。 注意: 社区版(Community)的IntelliJ IDEA是没有Java Enterprise选项的。 除了上述两种视图外,用户还可以在Spring视图下创建标准的Spring项目,在Spring Initializr下创建基于Spring Boot的微服务项目。如果选项卡中没有此项,则旗舰版的用户需要安装对应的Spring Boot插件并重启,而社区版用户需要将软件更换为旗舰版。 在新建窗口右侧指定模块的SDK版本,同时在Additional Libraries and Frameworks列表中勾选需要附加的类库或框架。如要创建带有Hibernate持久化功能的模块,则应在功能列表中勾选Hibernate选项,如图3.28所示。 图3.28附加功能 勾选Create default hibernate configuration and main class选项可以为项目模块创建默认的Hibernate配置及示例类。 在Libraries选项下可以指定使用本地Hibernate类库或是在线下载,选择Use libraray并单击Create按钮选择本地类库文件,如图3.29所示。 选择所有文件后单击OK按钮完成添加,然后继续向下执行模块创建,如图3.30所示。 3.2.2导入模块 虽然IntelliJ IDEA 支持多模块管理,但是不推荐将没有任何关联的项目以模块的形式添加到同一项目中。 图3.29添加本地类库 图3.30创建DataModel模块 要导入已经存在的模块,可在Project Structure窗口的Modules选项卡下单击按钮或使用快捷键Alt+Insert,在弹出的下拉列表中选择Import Module,如图3.31所示。 图3.31导入模块 选择本地模块项目所在位置,如果项目中存在.iml文件,则可以直接选择,如图3.32所示。 图3.32导入本地项目作为模块 可以看到,IntelliJ IDAE 对于不同类型项目的支持十分丰富,图3.32中列出了引入项目时可以加载的关键配置文件类型。导入时项目类型如图3.33所示。 图3.33选择待引入的模块类型 模块导入完成后的项目结构如图3.34所示。 图3.34项目模块结构 3.3本章小结 本章主要介绍了IntelliJ IDEA中的项目结构与模块,对于这些概念的理解可以帮助读者更好地组织与管理项目。 在大型项目中通常包含几十个甚至上百个模块,如何维护这些模块之间的结构关系将会是一件具有挑战性的工作。在结合Maven等构建工具管理项目时,开发者一定要规划好项目结构并实现最优化管理。