构建、依赖管理
# 00.构建、依赖管理
构建,在项目中是一个重要的概念
# 构建
在介绍和学习构建工具之前,读者应该有基本的JavaSE知识、了解XML;如果你已经接触过一些软件构建和部署过程,那么对于了解构建会有一定的帮助。
任何工具都是为了解决某个痛点而创造出来的,而构建工具主要是为了解决开发软件过程中的构建问题。那么什么是构建呢?
其实,构建(也叫build)是几乎每一位程序员都在做的工作。
我们日常开发的过程为:
- 根据需求编写代码
- 世界上没有不存在 bug 的代码,为了减少 bug,写完了代码,还要写一些单元测试,然后一个个的运行来检验代码质量,并自动生成测试文档
- 编译代码
- 运行代码,对实际功能进行测试
- 打包和部署到服务器上:再优雅的代码也是要打包的,我们需要把代码与各种配置文件、资源整合到一起,定型打包,如果是 web 项目,还需要将之发布到服务器。
- 重启项目
其中除了写代码,其他的步骤基本都是固定的、繁琐的,甚至可以说无意义的重复,完全可以交给机器来做,第2~5步骤,我们可以称为构建。为了自动和简化上面的工作,市面上出现了很多的构建工具,例如Make,Ant,Maven和Gradle等,使得软件的构建可以像全自动流水一样,只需要一条简单的命令,就会自动完成所有的构建。
# 依赖管理
什么是框架:框架就是别人写好的第三方工具项目,例如Log4j,就是Apache公司提供的一个项目
框架与jar包的关系:一个框架可能有多个jar包,这取决于框架的规模。如果一个框架功能非常多,全都引入到一个jar包会使得jar包非常大,这是不合理的;因为我们可能只用到一小部分功能。合理的方式是一个大的框架根据模块分成几个jar包,用户按需引入。
依赖:如果某个项目使用了框架,我们就称该项目依赖于该框架。例如我们自己写的代码,用到了Log4j,那么我们的项目就依赖于Log4j。
在这个开源的年代里,几乎任何Java应用都会借用一些第三方的开源类库,例如日志框架Log4j,测试框架Junit,JDBC等工具类,这些类库都可通过依赖的方式引入到项目中来,在编译和使用的时候,在classpath里指定这些jar包的路径即可。
但随着依赖的增多,版本不一致、版本冲突、依赖臃肿等问题都会接踵而来。举个例子,我们需要用很多个框架,用到很多个jar包,我们可以这样做:
- 找到各个框架的官网上(先不说找官网本身要花多少时间)
- 由于各个官网的风格不同,还得找下载链接(有时候可能还要登录才能下载,例如去Oracle官网下载Java)
- 为了方便,统一将所有框架的jar包,拷到某个目录下,在大型的项目中,可能多达上百个jar包,体积巨大,造成依赖臃肿
- 有时候还有版本问题,例如Java的SpringBoot框架,在出第3版本的时候,不支持Java8
- 还有依赖问题:某个框架A,依赖另一个框架B,而框架C也依赖框架B,而框架A需要版本为1.0的框架B,框架C需要2.0的框架B…………这可能会造成冲突
- 如果我们要更新某个框架的版本(例如Log4j曾暴露出一个高危安全漏洞),又要去官网下载,然后替换一次……
如果要我们手工管理这些依赖,需要花费大量的时间。笔者曾接触过一个2010年开发的项目,用的就是上述管理方式……深刻体会到了依赖管理是一件非常麻烦的事情,比如在本地开发的时候,都不知道哪些jar包是要用到的,哪些是没用到的,如果少了就得找是哪个jar包。
因此,为了解决这个问题,目前几乎每个编程语言的都有自己的依赖管理工具,例如Java的Maven,C/C++的vcpkg,C#
的NuGet,Python的pip,JS的Node……这里就不一一列举了。
另外,不少构建工具都提供了依赖管理的功能,使得依赖管理有秩序,解决了复杂的依赖管理问题,Java的Maven就是除了用于构建项目以外,也有提供依赖管理的功能。
# 依赖的分类
我们可以将依赖分为几类:
二方库:自己所在的公司内部的其他项目提供的依赖
三方库:非自己所在的公司提供的依赖。
举个例子,我们使用的Log4j就是第三方库,类似的还有Junit等;
笔者曾负责的系统中,使用了短信服务。这个短信服务是公司内的另一个项目提供的(因为可能公司内很多项目都会用到短信,因此单独作为一个项目)。发送短信只需引入短信项目提供的jar包,调用提供的方法即可。
# 约定优于配置
约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做出决定的数量,活得简单的好处,而又不失灵活性。
举个例子:我们使用Log4j,都会使用配置文件,指定配置文件有两种:
一种就是不指定配置文件,Log4j会默认寻找Log4j开头的配置文件;
如果你不喜欢Log4j默认的配置文件名,想要自己创建一个fuckme.xml,那么就得指定配置文件,额外添加一个配置,告诉Log4j使用什么配置文件:
java -cp ".;log4j-jcl-2.19.0.jar;log4j-api-2.19.0.jar;log4j-core-2.19.0.jar" -Dlog4j.configurationFile="fuckme.xml" HelloLog4j
1
使用第二种方式,其实是不推荐的。首先你得额外加配置,告诉Log4j配置文件是哪个;另外别人看到你这个文件名fuckme.xml,是不知道这个文件是做啥用的。如果使用的框架比较多,那么配置文件也会比较多,并且全都自己指定的话,那么就要加很多配置,另外别人看到都不知道这些文件的作用(可能自己过一段时间后都不知道了,跟别说接手别人的项目的时候)
而如果使用第一种方式,就可以减少很多的配置,并且别人根据文件名就可以知道这个文件是做什么的:例如Log4j.xml,别人一看就知道是Log4j框架的配置文件;web.xml就是web项目的配置文件。如果每个项目都使用默认配置,那么就很利于维护,从一个项目切换到另一个项目就不会那么难,因为大家都知道默认配置是怎样的,如果要修改某个配置文件要怎么做。
第一种方式,就是一种约定大于配置的方式,相当于框架的作者和开发者约定好:我们的默认配置是这个,你不配置就会使用默认配置。这就是标准的好处。
对于构建,约定大于配置的设计也非常有用。在Maven之前,十个项目可能有十种构建方式;有了Maven之后,所有项目的构建命令都是简单的,一致的,这极大地避免了不必要的学习成本,而且有利于促进项目团队的标准化。