微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。
转自: 刘欣 码农翻身 2016-07-10
手工Build的烦恼
要不是为了和女朋友留在一个城市,小李肯定去北上广奋斗去了。
现在他只能留在这个2.5线城市,进入这家软件开发公司,7,8个人,10来条枪,是个典型的软件小作坊。
上班第一天,CTO兼架构师兼项目经理兼开发组长老张把小李叫去,谆谆教导说:
“小李啊,我看了你的简历,我对你在公司的发展还是挺看好的,不过作为新人,你对新业务还不熟悉,没法开发核心系统,这段时间,你要一边学习,一边帮着项目做个很重要的工作:Build“
小李心说你还给我拽英语啊, 虽然心里这么想, 小李还是不动声色,面带微笑的问:
“这Build是什么东西?”
老张说:“我非常忙, 没时间给你解释,这儿有个文档,你看看就知道了”
说着,老张甩给了他几张纸 ,补充到: “有问题问小王, 他比你早来一个月,做Build已经很熟了”
小李仔细看了一遍,上面写着:
XXX公司Build 流程 (测试环境)
(1) 设置Eclipse 工作区, 编码为UTF-8, java 编译级别为JDK 1.7
(2) 从SVN下载最新源代码到Eclipse工作区
(3) 确保Eclispe工作区没有编译错误
(4) 手工修改下面 20个 配置文件
database.properties
cache.properties
user.properties
。。。。。。
(5) 把Eclipse中的Web项目导出成War 包
小王还特别在这里用红色的笔加了标注: Web项目所依赖的其他java项目也会被自动包含到War包的 WEB-INF/lib目录下
(6) 上传到测试服务器,安装
(7) 做冒烟测试
小李笑了:这不就是一个编译,打包,部署,测试的流程吗? 还Build !
正在这时,开发骨干小梁叫小李了:“小李, 我改了几个Bug,马上要测试,赶紧给我做一个测试环境的Build”
小李不敢怠慢,立刻按照文档做了一遍,花了将近半个小时才折腾完。
可是到了最后一步,做冒烟测试的时候, 系统却启动不了了 !
小李查了好久才发现,原来测试环境的JDK是1.6 , 但是Build文档上写的是1.7 当然跑不起来了。
小李暗暗的骂前任小王: 你小子肯定知道这里有个坑, 怎么不在文档上标注出来?
赶紧做个新的Build 放到测试环境, 这次冒烟测试顺利通过了。
刚松了口气, 测试小赵就叫了起来: “小梁, 你那个Bug 没有修复啊”
开发骨干小梁本能的反应到: “这不可能! 我的代码本地都测试过了, 代码也提交了“
小梁接着把矛头就指向小李: “哎对了小李,你的Build是不是又搞错了。”
小李心头一惊 , 赶紧去查,果然,在第4步,手工修改配置文件的时候把数据库改错了 ,指向了开发库,而不是测试库。
赶紧改吧, 原来做Build的小王也跑过来凑热闹, 在前Build专员小王,开发小梁和测试小赵三双眼睛的严厉注目下, 小李头上都要冒汗了。
还好,第三次终于成功了。 所有的测试都顺利通过。
(实际上,小李在紧张的忙碌中也忘了去更新那个文档,把JDK 改成1.6)
就这样过了一周, 小李每天都得战战兢兢的做四五个Build, 虽然做的越来越熟,出错越来越少, 但是每天还是占用了不少时间。
大好年华就要在Build中蹉跎了吗, 坚决不行。
自动化Build
小李决定把这个手工的、费事的、容易出错的Build给自动化起来, 将来谁要是做测试环境的Build,只要运行一个命令即可。
用什么语言来实现呢? 当然是Java大法好 ! 小李在大学修炼了那么久,自认为对OO,设计模式已经炉火纯青了, 现在终于有了用武之地。
小李白天工作, 晚上回到住处就开发这个自动化的Build, 每天干到12点才罢休。
但是小李不觉得累, 每天都恋恋不舍的去上床睡觉, 因为创造一个新工具,造福大家的想法一直激励着自己,有时候甚至觉得很快乐。
一个月后, 自动化工具新鲜出炉, 这其实是一套Java 的API, 小李把它称为BuildTool V1.0 专门用于下载源码,编译,打包,部署,测试。
例如,如果你想编译java 代码, 可以这么写:
小李对于FileSet这个抽象很得意, 它能代表一个文件集合, 既可以是java 源文件路径, 也可以是 classpath 的路径。
其他的API像下载源码, 打包,部署,测试也是类似。
现在小李真的只需要运行一个命令,就可以为测试环境生成一个build :
java BuildTool test
工作量一下子少了好多, 并且机器运行,基本上不会出错。
小李因为这个自动化的BuildTool, 获得的公司的嘉奖,还涨了一点工资。
对小李来说,这都不是最重要的, 最重要的通过设计和实现这个BuildTool, 自己的能力有了很大的提升。
自己已经 不仅仅是一个只会用SSH框架的一个HTML填空人员了!
码农翻身评: 大部分人只会抱怨项目很无趣,没有挑战, 遇到问题也只会安于现状,
少数人会发现工作中的“痛点”问题,并且真正动手解决它, 给公司带来了价值, 这是提高自己, 让自己和别人区分开来的重要方法。
JAVA vs XML
今年的形势很好,公司业务发展的不错,招了一批新人,一下子接了3,4 个新项目, 小李主动请缨,替这些项目建立一个自动化的Build 。
但是小李很快就发现了问题, 直接用Java语言来编写,功能虽然能实现, 但是看起来就太繁琐了。
自己写的代码过几天看也得思考一下才能明白。
是自己的BuildTool API设计的不好吗? 那可是精心设计的啊。
仔细思考了两天,小李终于意识到了问题所在: 不是自己设计的不好, 是Java 语言太“低级”了 !
自动化Build要描述的其实是任务,是业务层面的东西。
而用java 是个什么都能干的通用语言, 用它来写肯定引入了太多的细节,导致了阅读和编写的难度!
小李想:能不能开发一套新的,专门为自动化Build 所使用的语言呢?
(码农翻身注: 这其实就是所谓的领域特定语言(Domain Specific Language , 简称 DSL ))
但是开发一套新语言那成本可就有点高了, 有点得不偿失。
小李百思不得其解, 直到有一天听到小梁和项目经理在讨论hibernate 的配置文件,突然想到 像spring , hibernate 都是用XML来描述系统的。
“那我的BuildTool也完全可以用XML来描述啊”小李赶紧把那个编译java 的程序用XML描述了一下:
果然是清爽多了! 和原来的Java程序比起来, 这段XML几乎就是自解释的 !
XML可扩展性极强, 可以任意自定义标签诸如<javac> <srcDir> <classpath> 用它来描述Build的逻辑。
但是唯一不爽的地方就是: XML 无法像 Java程序那样运行, 只是纯文本而已。
不过这也无妨,只要用Java写一个解析器,用来解析这些XML文件然后在Java中执行就可以了。有了BuildTool V1.0作为基础, 写一个解析器不是什么难事, 很快 BuildTool V2.0 就新鲜出炉了。
小李不再帮其他项目组去写Build 程序,因为用XML描述以后,大家很快就能学会, 并且乐在其中。
CTO老张看到这个工具,大为赞赏, 它给小李说: “别叫什么Build Tool, 太俗, 别人听了一点感觉都没有, 我给你起个名,叫 ANT ”
"ANT? " 小李似乎看到很多小蚂蚁在不辞劳苦帮着做Build, 心里暗暗佩服老张: 这个名字起的太好了, 姜还是老的辣啊。
转自: 刘欣 码农翻身 2016-07-12
前言: 接上一篇《 小李的Build之路(上) 》
小李发明的ANT确实是好用, 现在不仅仅是小李的公司, 连其他公司的朋友听说了,也拿去使用, 交口称赞。
只是小李发现了一点奇怪的现象,每个人在开始写新项目的Ant build文件之前, 都会找到自己说:
“小李, 把你那个build.xml 文件发我一份吧, 让我参考下。”
小李的那一份build.xml其实是自己项目的第一个ant 脚本, 为啥大家都要把它
Copy走呢? 刚开始的时候小李以为大家不会写,要按照自己的模板照葫芦画瓢。
偶然有一次,小李看到了别人项目的Ant build脚本, 不由得大吃一惊, 这简直和自己原始的build.xml如出一辙。
小李赶紧把公司内所有项目的Ant脚本都要过来,仔细观察了一下, 很快就发现了这些脚本中蕴藏着一些共同的“模式”,这些模式主要体现在Build的步骤上:
1. 从版本控制系统下载代码
2. 编译java 文件,形成jar包
3. 运行单元测试
4. 生成代码覆盖度报告和测试报告
4. 打包形成war 文件
5. 上传到测试服务器上,进行安装
其实这也难怪,实际的Build不就是这样的嘛。 但是中间也有不同之处:
(1) 路径不同,例如
java 源文件 下载下来以后放的位置不同,五花八门
编译成的java class 放置的位置不同
测试报告放置不同
war包生成后放置的路径不同
。。。
(2) 项目依赖不同,例如
各个项目依赖的第三方jar包可能是不一样的
各个项目都有一个Web子项目,它依赖于其他java 项目,所以在build的时候,要先build这些java 项目才行
例如下图中的OnlineShop,这是个Web项目, 它依赖于ApplicationConfg, LoggingFramework, OnlineShopApi这三个子项目。
项目依赖这个没办法, 毕竟是各个项目的业务所要求的,小李没有办法改变。
但是那些不同的路径真的是必要的吗? 能不能让大家的路径都保持一致呢 ?
一个新的主意像闪电一样划过黑暗的夜空:
确实可以保持一致, 但是大家都要遵循一定的约定
如果大家都这么做,小李就可以增强一下Ant,只要运行ant complie , 就会 自动 的去src/main/java 找到源文件进行编译, 只要运行ant test, 就会 自动 去src/test/java 找到测试用例, 编译并运行。
换句话说, 只要遵循目录的约定, 大家就不用费心费力的指定各种路径了, 一切在背后由工具自动搞定, 这样的话Build脚本就可以极大的简化了 ,只需要寥寥几行即可。
这只是一个java项目,要是多个java项目,有依赖关系,像上面提到的 OnlineShop 依赖OnlineShopAPI, AppplicationConfig, LoggingFramework , 该怎么处理?
这也不难, 小李想, 首先每个java项目都得遵守上述约定,其次需要定义项目之间的依赖关系, 也可以用XML描述出来。
每个java项目都需要有个叫pom.xml的文件, 例如OnlineShop这个项目的pom如下:
这样以来工具就能自动找到被依赖的项目, 然后去编译打包它了。
此外,各个java项目之间也需要按约定来组织目录,例如:
+- pom.xml
| +- pom.xml
| +- src
| +- main
| +- webapp
| +- pom.xml
| +- src
| +- main
| +- java
| +- pom.xml
| +- src
| +- main
| +- java
| +- pom.xml
| +- src
| +- main
| +- java
如果扩展一下, 把第三方的jar 文件例如JUnit 也可以给用这种方式来描述:
想到这一层,小李不禁激动起来,因为第三方的jar 管理一直是一个令人头疼的问题,最早的时候大家都是手工的Copy来Copy去, 由于版本不同导致的错误特别难于发现。
每个人在建立自己Eclipse workspace的时候, 得拿到所有依赖的jar包, 并且在项目上设置好, 可是非常的费劲啊。
如果利用这种 声明的办法 , 每个人岂不卸下了一个巨大的包袱 ?
当然公司需要建立一个公用的第三方jar 文件版本库, 把公司最常用的第三方jar包都放进去, 工具在分析项目的配置文件pom.xml的时候,就可以去公司的版本库里去读取并且下载到本地。
将来有新人进入公司, 只要给他一个pom.xml , 用Eclipse导入,就能轻松的把一个可以直接运行的workspace建立起来, 再也不需要设置那些烦心的jar了。
如果将来在网络上建立公开的软件版本库, 任何人都可以从那里去下载各种软件包,那受惠的可不仅仅是自己公司了, 而是所有人,真是一个激动人心的场景啊。
不过还是从自己公司开始吧, 小李冷静下来分析了一下: 让所有的项目组都使用约定的目录,并且建立一个公司级别的软件库,自己可是没有这样的权限啊, 小李去找CTO老张求助。
老张不愧是老江湖, 听了几分钟小李的介绍,马上就明白了, 并且把这个想法提升了一个高度:
“你这叫 约定重于配置 , 知道不? 从Ruby on Rails 开始,这个词开始流行了, 大家现在都很忙, Ant build脚本用的也没问题,先不改了”
小李还不死心: “可是这么做的话对以后的新项目大有好处啊,不用Copy 繁琐的build脚本了, 也不用费心的折腾workspace了”
“那也不能现在改,项目进度最重要,大家都没时间, 这样吧,等大家项目闲下来再改动如何? ” 老张妥协了一下。
可是在公司基本上就不会有空闲的时间, 一个个新需求压的大家透不过气来,偶尔有空闲时间,大家也都犯懒了, 总是想休息。
此外惯性的力量是惊人的,大家都愿意待在舒适区里, 不愿意变化, 虽然也看到了新工具的好处, 大家都懒得换新的。
时间过的很快,一年过去了, 小李看着自己辛辛苦苦加班写出来的Ant 2.0 , 还是无人采用, 很是伤心。
经过公司的允许, 小李决定把这个工具开源, 为了和Ant区分开来, 特地起了个新的名称: Maven。
Maven 迅速被大家用了起来,除了小李的公司。
又过了半年, 小李跳槽了。
(完)
(完)
Maven 三十分钟入门
阅读 1164
收藏 95
2017-05-08
原文链接: sadwxqezc.github.io
腾讯云移动开发者专区,一站式涵盖开发,测试,发布,营销、运维等全生命周期,有效降低技术门槛、减少研发成本、提升效率。立即了解详情:cloud.tencent.com
Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model(POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.
参考文档: Maven Documentation
<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> <groupId>com.mycompany.app</groupId> my-app <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> junit <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>
Pom文件是面向工程的一个管理模型,全称是Project Object Model。这个Pom文件中包含的基本内容有:
org.apache.maven.plugins
就是所有Maven plugins的groupId -<version>.<extension>
,比如说 my app-1.0.jar
my-app |-- pom.xml `-- src |-- main | `-- java | `-- com | `-- mycompany | `-- app | `-- App.java `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
上面是一个Maven项目的基本结构,项目的相关资源位于 ${basedir}/src/main/java
中,而测试的资源位于 ${basedir}/src/test/java
中,而pom文件位于 pom.xml
中。
1. mvn compile
执行 mvn compile
,编译的结果会默认放在 ${basedir}/target/classes
目录下。
2. mvn test
执行 mvn test
,会执行 ${basedir}/src/test/java
中的单元测试。如果只想编译测试代码而不执行,则执行 mvn test-compile
。
3. mvn package
执行 mvn package
会对项目进行打包,假如当前在pom中的packaging设定为jar,那么执行该命令后会在 ${basedir}/target
目录下生成对应的jar包。
4. mvn install
如果想把 mvn package
生成的Jar文件安装在本地库中以让其它项目引用,则可以执行 mvn install
命令,将生成的jar包放在 ${user.home}/.m2/repository
中。
Plugins用来定制Maven项目的编译过程,假如要配置Java Compiler允许JDK 5.0的资源,那么只需要在Pom中增加如下内容:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> maven-compiler-plugin <version>3.3</version> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> </plugins> </build>
<configuration>
中的配置会应用在对应Plugin的所有Goal。
${basedir}/src/main/resources
目录下的所有文件都会被打包到Jar文件中,比如如下一个文件结构:
my-app |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- mycompany | | `-- app | | `-- App.java | `-- resources | `-- META-INF | `-- application.properties `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.java
该项目打包成Jar文件后的内部组织结构为:
|-- META-INF | |-- MANIFEST.MF | |-- application.properties | `-- maven | `-- com.mycompany.app | `-- my-app | |-- pom.properties | `-- pom.xml `-- com `-- mycompany `-- app `-- App.class
Maven支持文件过滤的功能,可以在build时候为文件提供变量赋值,比如说上面的 application.properties
文件中有如下定义:
# application.properties application.name=${project.name} application.version=${project.version}
那么需要在pom文件中做如下的定义:
<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> <groupId>com.mycompany.app</groupId> my-app <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> junit <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> </project>
执行 mvn process-resources
命令后,在 target/classes
下找到 application.properties
可以看到如下结果:
# application.properties application.name=Maven Quick Start Archetype application.version=1.0-SNAPSHOT
可见其中形如 ${<property name>}
的变量已经被替换成了对应的值,如果要引入其它文件中定义的属性,只需要在pom文件中定义 <filters>
,比如:
<build> <filters> <filter>src/main/filters/filter.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build>
那么Maven会先读出 filter.properties
中的属性,然后把这些属性注入对应的resources中。
<dependencies>
标签下列出了所有的外部依赖,比如下面的Pom文件添加了Junit的依赖:
<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> <groupId>com.mycompany.app</groupId> my-app <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> junit <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>
<dependency>
的 <scope>
标签的值可以为compile,test和runtime,当Maven编译项目时,它首先会在 ${user.home}/.m2/repository
这个本地库目录下寻找所需的依赖,如果没有会去远程的库上寻找,并将其下载到本地库中,默认的远程库地址为 http://repo.maven.apache.org/maven2/
。
当需要发布一个包的远程仓库时,需要配置库的地址和相应的权限,一个范例如下:
<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> <groupId>com.mycompany.app</groupId> my-app <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> junit <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.codehaus.plexus</groupId> plexus-utils <version>1.0.4</version> </dependency> </dependencies> <build> <filters> <filter>src/main/filters/filters.properties</filter> </filters> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> <!-- | | | --> <distributionManagement> <repository> <id>mycompany-repository</id> <name>MyCompany Repository</name> <url>scp://repository.mycompany.com/repository/maven2</url> </repository> </distributionManagement> </project>
它所需要的权限配置在 settings.xml
中:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> ... <servers> <server> <id>mycompany-repository</id> <username>jvanzyl</username> <!-- Default value is ~/.ssh/id_dsa --> <privateKey>/path/to/identity</privateKey> (default is ~/.ssh/id_dsa) <passphrase>my_key_passphrase</passphrase> </server> </servers> ... </settings>
Maven很好的支持了多个Modules的管理,假如一个Maven项目结构如下:
+- pom.xml +- my-app | +- pom.xml | +- src | +- main | +- java +- my-webapp | +- pom.xml | +- src | +- main | +- webapp
对于根目录下的pom文件,内容如下:
<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> <groupId>com.mycompany.app</groupId> app <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>my-app</module> <module>my-webapp</module> </modules> </project>
其中的 <modules>
定义了其管理的两个子modules。
假如my-webapp需要依赖my-app包,那么在 my-webapp/pom.xml
中增加:
... <dependencies> <dependency> <groupId>com.mycompany.app</groupId> my-app <version>1.0-SNAPSHOT</version> </dependency> ... </dependencies>
然后在my-app和my-webapp的pom文件中都增加parent配置:
<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"> <parent> <groupId>com.mycompany.app</groupId> app <version>1.0-SNAPSHOT</version> </parent> ...
然后在最顶层目录下执行 mvn clean install
,会创建 my-webapp/target/my-webapp.war
,其中包含:
$ jar tvf my-webapp/target/my-webapp-1.0-SNAPSHOT.war 0 Fri Jun 24 10:59:56 EST 2005 META-INF/ 222 Fri Jun 24 10:59:54 EST 2005 META-INF/MANIFEST.MF 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/ 0 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/ 3239 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.xml 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/ 215 Fri Jun 24 10:59:56 EST 2005 WEB-INF/web.xml 123 Fri Jun 24 10:59:56 EST 2005 META-INF/maven/com.mycompany.app/my-webapp/pom.properties 52 Fri Jun 24 10:59:56 EST 2005 index.jsp 0 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/ 2713 Fri Jun 24 10:59:56 EST 2005 WEB-INF/lib/my-app-1.0-SNAPSHOT.jar
可见 my-app-1.0-SNAPSHOT.jar
已经被放到了 WEB-INF/lib
目录下。
一位阿里 Java 工程师的技术小站。作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”Java“即可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源)