走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven

 

小李的Build之路(上)

转自: 刘欣 码农翻身 2016-07-10

  • 摘要:手工Build的烦恼要不是为了和女朋友留在一个城市,小李肯定去北上广奋斗去了。现在他只能留在这个2.5线城市,进入这家软件开发公司,7,8个人,10来条枪,是个典型的软件小作坊。上班第一天,CTO兼架构师兼项目经理兼开发组长老张把小李叫去,谆谆教导说:“小李啊,我看了你的简历,我对你在公司的发展还是挺看好的,不过作为新人,你对新业务还不熟悉,没法开发核心系统,这段时间,你要一边学习,一边帮着项目做个很重要的工作:Build“小李心说你还给我拽英语啊,虽然心里这么想,小李还是不
  • 手工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 代码, 可以这么写:

    走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven_第1张图片

    小李对于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可扩展性极强, 可以任意自定义标签诸如 用它来描述Build的逻辑。

     

    但是唯一不爽的地方就是: XML 无法像 Java程序那样运行, 只是纯文本而已。

     

    不过这也无妨,只要用Java写一个解析器,用来解析这些XML文件然后在Java中执行就可以了。有了BuildTool V1.0作为基础, 写一个解析器不是什么难事, 很快 BuildTool V2.0 就新鲜出炉了。

     

    小李不再帮其他项目组去写Build 程序,因为用XML描述以后,大家很快就能学会, 并且乐在其中。

    CTO老张看到这个工具,大为赞赏, 它给小李说: “别叫什么Build Tool, 太俗, 别人听了一点感觉都没有, 我给你起个名,叫 ANT ”

     

    "ANT? " 小李似乎看到很多小蚂蚁在不辞劳苦帮着做Build, 心里暗暗佩服老张: 这个名字起的太好了, 姜还是老的辣啊。

小李的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这三个子项目。

走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven_第2张图片

 

项目依赖这个没办法, 毕竟是各个项目的业务所要求的,小李没有办法改变。

 

但是那些不同的路径真的是必要的吗?   能不能让大家的路径都保持一致呢 ? 

 

一个新的主意像闪电一样划过黑暗的夜空: 

确实可以保持一致, 但是大家都要遵循一定的约定

走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven_第3张图片

如果大家都这么做,小李就可以增强一下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如下:

走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven_第4张图片

 

这样以来工具就能自动找到被依赖的项目, 然后去编译打包它了。

 

此外,各个java项目之间也需要按约定来组织目录,例如:

+- pom.xml

+- online-shop-web

| +- pom.xml

| +- src

|   +- main

|     +- webapp

+- online-shop-api

| +- pom.xml

| +- src

|   +- main

|     +- java

+- logging-framework

| +- pom.xml

| +- src

|   +- main

|     +- java

+- app-config

| +- pom.xml

| +- src

|   +- main

|     +- java

 

如果扩展一下, 把第三方的jar 文件例如JUnit 也可以给用这种方式来描述:

走进JavaWeb技术世界12:从手动编译打包到项目构建工具Maven_第5张图片

想到这一层,小李不禁激动起来,因为第三方的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

Maven

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

Maven概述

一个基本的Pom文件


  4.0.0
  com.mycompany.app
  my-app
  jar
  1.0-SNAPSHOT
  Maven Quick Start Archetype
  http://maven.apache.org
  
    
      junit
      junit
      4.11
      test
    
  

Pom文件是面向工程的一个管理模型,全称是Project Object Model。这个Pom文件中包含的基本内容有:

  1. project pom文件的顶层元素
  2. modelVersion Pom对象模型的版本
  3. groupId 是创建这个项目的组织或部门的唯一标志,比如说org.apache.maven.plugins就是所有Maven plugins的groupId
  4. artifactId 是这个项目生成结果的唯一标志,Maven生成结果的命名模式为-.,比如说my app-1.0.jar
  5. packaging 这个参数表明了项目生成结果的打包模式,比如JAR,WAR,EAR等,同时这个参数还表明了build过程采用的特定生命周期
  6. version 这个参数表明了项目生成结果的版本,在version里经常会看到SNAPSHOT标志,它表明了项目处于开发阶段。
  7. name 这个参数代表项目的展示名字,通常作用在Maven生成的文档中
  8. url 这个参数代表哪里可以找到该项目,通常作用在Maven生成的文档中
  9. description 这个参数描述了项目的基本信息,通常作用在Maven生成的文档中

Maven项目的基本结构

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中。

Maven的执行

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

Plugins用来定制Maven项目的编译过程,假如要配置Java Compiler允许JDK 5.0的资源,那么只需要在Pom中增加如下内容:


  
    
      org.apache.maven.plugins
      maven-compiler-plugin
      3.3
      
        1.5
        1.5
      
    
  

中的配置会应用在对应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文件中做如下的定义:


  4.0.0

  com.mycompany.app
  my-app
  1.0-SNAPSHOT
  jar

  Maven Quick Start Archetype
  http://maven.apache.org

  
    
      junit
      junit
      4.11
      test
    
  

  
    
      
        src/main/resources
        true
      
    
  

执行mvn process-resources命令后,在target/classes下找到application.properties可以看到如下结果:

# application.properties
application.name=Maven Quick Start Archetype
application.version=1.0-SNAPSHOT

可见其中形如${}的变量已经被替换成了对应的值,如果要引入其它文件中定义的属性,只需要在pom文件中定义,比如:

  
    
      src/main/filters/filter.properties
    
    
      
        src/main/resources
        true
      
    
  

那么Maven会先读出filter.properties中的属性,然后把这些属性注入对应的resources中。

Dependencies

标签下列出了所有的外部依赖,比如下面的Pom文件添加了Junit的依赖:


  4.0.0

  com.mycompany.app
  my-app
  1.0-SNAPSHOT
  jar

  Maven Quick Start Archetype
  http://maven.apache.org

  
    
      junit
      junit
      4.11
      test
    
  

标签的值可以为compile,test和runtime,当Maven编译项目时,它首先会在${user.home}/.m2/repository这个本地库目录下寻找所需的依赖,如果没有会去远程的库上寻找,并将其下载到本地库中,默认的远程库地址为 http://repo.maven.apache.org/maven2/

包的发布

当需要发布一个包的远程仓库时,需要配置库的地址和相应的权限,一个范例如下:


  4.0.0

  com.mycompany.app
  my-app
  1.0-SNAPSHOT
  jar

  Maven Quick Start Archetype
  http://maven.apache.org

  
    
      junit
      junit
      4.11
      test
    
    
      org.apache.codehaus.plexus
      plexus-utils
      1.0.4
    
  

  
    
      src/main/filters/filters.properties
    
    
      
        src/main/resources
        true
      
    
  
  
  
    
      mycompany-repository
      MyCompany Repository
      scp://repository.mycompany.com/repository/maven2
    
  

它所需要的权限配置在settings.xml中:


  ...
  
    
      mycompany-repository
      jvanzyl
      
      /path/to/identity (default is ~/.ssh/id_dsa)
      my_key_passphrase
    
  
  ...

多Modules管理

Maven很好的支持了多个Modules的管理,假如一个Maven项目结构如下:

+- pom.xml
+- my-app
| +- pom.xml
| +- src
|   +- main
|     +- java
+- my-webapp
| +- pom.xml
| +- src
|   +- main
|     +- webapp

对于根目录下的pom文件,内容如下:


  4.0.0

  com.mycompany.app
  app
  1.0-SNAPSHOT
  pom

  
    my-app
    my-webapp
  

其中的定义了其管理的两个子modules。

假如my-webapp需要依赖my-app包,那么在my-webapp/pom.xml中增加:

  ...
  
    
      com.mycompany.app
      my-app
      1.0-SNAPSHOT
    
    ...
  

然后在my-app和my-webapp的pom文件中都增加parent配置:


  
    com.mycompany.app
    app
    1.0-SNAPSHOT
  
  ...

然后在最顶层目录下执行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目录下。

个人公众号:程序员黄小斜

微信公众号【程序员黄小斜】新生代青年聚集地,程序员成长充电站。作者黄小斜,职业是阿里程序员,身份是斜杠青年,希望和更多的程序员交朋友,一起进步和成长!专注于分享技术、面试、职场等成长干货,这一次,我们一起出发。

关注公众号后回复“2019”领取我这两年整理的学习资料,涵盖自学编程、求职面试、算法刷题、Java技术学习、计算机基础和考研等8000G资料合集。

技术公众号:Java技术江湖

微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站,专注于 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!

关注公众号后回复“PDF”即可领取200+页的《Java工程师面试指南》强烈推荐,几乎涵盖所有Java工程师必知必会的知识点。

你可能感兴趣的:(Java,Web,JavaWeb技术世界)