Maven应用构建打包

words: 7k    views:    time: 37min

Maven是Java应用中一种很常见的构建方式,这里记录下我们使用Maven来构建springboot应用的一次实践以及遇到的问题

实践

在项目实施过程中,一般都会将一些通用的处理提取成公共的依赖,形成所谓的框架。这里我们参考springboot的方式也整理了自己的一套commons包,涵盖了项目中常见的各种问题处理,希望能应用到公司的各个项目中,并且尽可能不需要做任何的改动

  • commons-dependencies:统一管理依赖的版本定义,使用dependencyManagement来定义依赖,这样约定我们所有的服务模块都使用相同的依赖版本;
  • commons-framework:抽象出来的公共处理,不依赖于具体的项目业务,在spring的基础上做了一些偏于项目的公共处理,但是又与具体的业务模块没有直接关系;
  • commons-parent:在上面两个依赖的基础上,进一步对各种plugin和配置做了统一的定义;

如果基于commons包来构建项目工程,其依赖关系可以如下图所示

最底层还是springboot等那些常见的依赖,最上层是我们的服务应用,commons依赖只是在中间做了一些约定和配置,以及一些公共的抽象处理

commons-dependencies

commons-dependencies/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<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.xxx</groupId>
<artifactId>commons-dependencies</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<name>commons-dependencies</name>
<url>https://www.xxx.com/</url>
<description>依赖管理</description>

<!-- ... -->

<properties>
<spring.boot.version>2.7.0</spring.boot.version>
<grpc.version>1.48.0</grpc.version>
<commons.io.version>2.11.0</commons.io.version>
<!-- 其它版本定义... -->
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!-- 其它依赖定义... -->
</dependencies>
</dependencyManagement>
</project>

commons-framework

commons-framework/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.xxx</groupId>
<artifactId>commons-framework</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<name>commons-framework</name>
<url>https://www.xxx.com/</url>
<description>公共依赖</description>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<!-- ... -->

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.cowave</groupId>
<artifactId>commons-dependencies</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其它依赖项... -->
<!-- kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<scope>provided</scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<scope>provided</scope>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
scope作用域

其实除了下面这些,我们还希望有一种作用域,比如对于lombol,能够让它只参与编译但不参与运行,并且能传递依赖。最后我们的解决办法是在framework中将lombok的作用域定义为compile,然后在打包插件中再通过配置将这个依赖剔除掉。

compile

默认的scope,适用于所有阶段。最终会打包到artifact中,如果构建的是一个WAR类型artifact,那么compile引用的Jar会被集成到WAR文件中。

provided

适用于编译和测试阶段。这种在类似commons-framework这样的公用模块中比较常用,它不会打包到artifact中,也不会传递依赖关系。其含义是认为对应的依赖会由运行这个应用的JDK或者容器提供。

runtime

适用于运行和测试阶段。与compile相比,依赖的项目无需参与编译。比如Jdbc的驱动包,项目编译只需要JDK提供的JDBC接口,只有在执行测试或者运行时才需要具体的Jdbc驱动。

test

仅适用于测试阶段。不参与编译和打包,只在编译或运行测试代码的时候才使用,比如Junit。

system

类似于provided,区别在于你需要告诉Maven如何去找到这个依赖,所以当引用的依赖在Maven仓库中不存在时,可以使用,但是不推荐。

pom.xml
1
2
3
4
5
6
7
<dependency>
<groupId>org.xxx</groupId>
<artifactId>test</artifactId>
<version>1.18.12</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/test.jar</systemPath>
</dependency>

如果希望依赖参与打包,spring-boot-maven-plugin需要额外指定,而且打包时不会检查其依赖

pom.xml
1
2
3
4
5
6
7
8
9
10
11
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
import

只能在dependencyManagement中使用,用于从其它的pom文件中导入其所有依赖设置,比如对commons-dependencies的引用

commons-parent

commons-parent依赖了framework,并使用commons-dependencies限制依赖版本,另外提供了对properties和plugin的一些定义

commons-parent/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.xxx</groupId>
<artifactId>commons-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<name>commons-parent</name>
<url>https://www.xxx.com/</url>
<description>公共parent</description>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<plugin.classFinal.phase>none</plugin.classFinal.phase> <!-- package -->
</properties>

<!-- ... -->

<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
<compilerArguments>
<extdirs>lib</extdirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<finalName>${project.name}-${project.version}</finalName>
<classesDirectory>${project.build.directory}/classes</classesDirectory>
<excludes>
<exclude>config/**</exclude>
<exclude>smart-doc.json</exclude>
<exclude>**/*.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.20.0</version>
<configuration>
<targetJdk>${maven.compiler.target}</targetJdk>
<failurePriority>2</failurePriority>
<failOnViolation>true</failOnViolation>
<printFailingErrors>true</printFailingErrors>
<verbose>true</verbose>
<rulesets>
<ruleset>rulesets/java/ali-comment.xml</ruleset>
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
<ruleset>rulesets/java/ali-constant.xml</ruleset>
<ruleset>rulesets/java/ali-exception.xml</ruleset>
<ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
<ruleset>rulesets/java/ali-naming.xml</ruleset>
<ruleset>rulesets/java/ali-oop.xml</ruleset>
<ruleset>rulesets/java/ali-orm.xml</ruleset>
<ruleset>rulesets/java/ali-other.xml</ruleset>
<ruleset>rulesets/java/ali-set.xml</ruleset>
</rulesets>
</configuration>
<executions>
<execution>
<id>pmd-check</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.alibaba.p3c</groupId>
<artifactId>p3c-pmd</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.shalousun</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>2.4.7</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>html</goal>
</goals>
</execution>
</executions>
<configuration>
<configFile>./src/main/resources/smart-doc.json</configFile>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<useReleaseProfile>false</useReleaseProfile>
<arguments>-Dmaven.javadoc.skip=true</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-plugin</artifactId>
<version>1.13.0</version>
<configuration>
<connectionType>developerConnection</connectionType>
</configuration>
</plugin>
<plugin>
<groupId>net.roseboy</groupId>
<artifactId>classfinal-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<password>#</password>
<packages>com.cowave</packages>
<libjars>${project.name}-${project.version}.jar,commons-framework-*.jar</libjars>
</configuration>
<executions>
<execution>
<phase>${plugin.classFinal.phase}</phase>
<goals>
<goal>classFinal</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.0</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
<exclude>
<groupId>com.alibaba.p3c</groupId>
<artifactId>p3c-pmd</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<profiles>
<profile>
<id>unix</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>antrun-build</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<chmod file="${basedir}/bin/*.sh" perm="ugo+rx" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>exec-maven-plugin</artifactId>
<groupId>org.codehaus.mojo</groupId>
<version>3.0.0</version>
<executions>
<execution>
<id>exec-sources</id>
<phase>compile</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${basedir}/bin/install.sh</executable>
<arguments>
<argument>sources</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>exec-build</id>
<phase>install</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${basedir}/bin/install.sh</executable>
<arguments>
<argument>build</argument>
</arguments>
</configuration>
</execution>
</executions>
<configuration>
<workingDirectory>${basedir}</workingDirectory>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>

<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.20.0</version>
<configuration>
<targetJdk>${maven.compiler.target}</targetJdk>
<rulesets>
<ruleset>rulesets/java/ali-comment.xml</ruleset>
<ruleset>rulesets/java/ali-concurrent.xml</ruleset>
<ruleset>rulesets/java/ali-constant.xml</ruleset>
<ruleset>rulesets/java/ali-exception.xml</ruleset>
<ruleset>rulesets/java/ali-flowcontrol.xml</ruleset>
<ruleset>rulesets/java/ali-naming.xml</ruleset>
<ruleset>rulesets/java/ali-oop.xml</ruleset>
<ruleset>rulesets/java/ali-orm.xml</ruleset>
<ruleset>rulesets/java/ali-other.xml</ruleset>
<ruleset>rulesets/java/ali-set.xml</ruleset>
</rulesets>
</configuration>
</plugin>
</plugins>
</reporting>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.cowave</groupId>
<artifactId>commons-dependencies</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.cowave</groupId>
<artifactId>commons-framework</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
plugin插件

parent主要的作用就是定义好各种要用的plugin,这样具体服务的pom.xml文件就不至于太臃肿,对于那些可选的插件可以定义在pluginManagement中

maven-compiler-plugin

compile是默认的操作,通过插件可以指定一些配置,比如<extdirs>表示可以额外从指定目录中获取仓库之外的依赖

maven-jar-plugin

jar也是默认的操作,通过插件可以配置jar包中包含或排除哪些内容,这里将config目录放在jar包外,方便运行时修改配置文件,另外也是希望与以前的项目保持同样的结构

spring-boot-maven-plugin

springboot提供的打包方式,将所有文件打包做成一个可以直接java -jar xxx.jar运行的包,这里配置排除了一些运行时不需要的jar

之前也用过maven-assembly-plugin来将所有文件打成一个tar包,但是在替换环境jar包调试时(坏习惯,以前常用的操作),经常会因为依赖的jar没有替换而导致问题,因为jar包只包含当前编译的代码。如果使用springboot打包就没有这种问题,它会将依赖打包成一个整体。另外,它还有对应的插件方便处理jar包加密问题。如果使用assembly打包,可以如下配置:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<properties>
<systime>${maven.build.timestamp}</systime>
<maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
<basedirectory>${project.name}_${project.version}</basedirectory>
</properties>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<configuration>
<finalName>${project.name}_${project.version}_${systime}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

对应的还要编写一个打包描述文件,意思就是选取需要打包的文件并放到目标目录,这里将bin下面的install.sh挪到了根目录下

assembly.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<assembly> 
<id>assembly-common</id>
<includeBaseDirectory>false</includeBaseDirectory>

<formats>
<format>tar.gz</format>
</formats>

<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>${basedirectory}/lib</outputDirectory>
</dependencySet>
</dependencySets>

<fileSets>
<fileSet>
<directory>${project.build.directory}/classes</directory>
<excludes>
<exclude>com/**</exclude>
<exclude>META-INF/**</exclude>
</excludes>
<outputDirectory>${basedirectory}</outputDirectory>
</fileSet>

<fileSet>
<directory>lib</directory>
<outputDirectory>${basedirectory}/lib</outputDirectory>
</fileSet>

<fileSet>
<directory>bin</directory>
<outputDirectory>${basedirectory}/bin</outputDirectory>
<excludes>
<exclude>install.sh</exclude>
</excludes>
</fileSet>
</fileSets>

<files>
<file>
<source>${project.build.directory}/${project.name}-${project.version}.jar</source>
<outputDirectory>${basedirectory}/lib</outputDirectory>
</file>
<file>
<source>${basedir}/bin/install.sh</source>
<outputDirectory>${basedirectory}</outputDirectory>
</file>
</files>
</assembly>
classfinal-maven-plugin

针对spring-boot-maven-plugin打出来的jar进行加密,目的是防止反编译。这里将执行阶段phase的值设置成了变量${plugin.classFinal.phase},并且默认值为none,表示关闭不执行。然后子模块只要在properties中将值设置为package就可以触发加密了,但是相应的在启动参数中也需要添加 -javaagent参数

maven-release-plugin/maven-scm-plugin

打包版本自动化控制的一个插件,分为快照版本和发行版本,所以scm要配两个仓库。按照规范,本地开发版本永远是snapshots快照,在发布一个releases之后,它会帮忙将代码一个tag并推送仓库,然后将本地升级到下一个快照

在jenkins中构建时,我们会添加一个参数来表示是否要发布版本

1
2
3
4
5
6
7
if [ "$release_version" = "Yes" ]; then
mvn release:clean
mvn release:prepare -B
mvn release:perform -B
cd target/checkout
fi
mvn clean install

如果这样需要指定拉取的本地分支,因为jenkin拉取代码时会分离Head,这会导致插件push代码失败

smart-doc-maven-plugin

smart文档在maven中的插件,根据代码注释生成应用接口文档

exec-maven-plugin/maven-antrun-plugin

在maven构建过程中也可以插入一些自己的操作,这里antrun作用是给脚本添加可执行权限,exec是执行目标脚本。但是如果在window上执行构建,shell脚本会报错,所以可以通过profiles来进行条件控制,只有操作系统是unix系列时,才触发执行。

在compile阶段执行source()准备资源,记录应用打包的版本和时间,以及对应的提交作者和版本时间,方便运行后在spring-boot-admin上获取展示;
在install阶段执行build()手动打包,首先maven-jar-plugin会将编译好的class打包成jar,接着spring-boot-maven-plugin会合并依赖的lib打一个可允许的jar,然后classfinal-maven-plugin对打好的jar再进行加密处理,这里是在前面的基础上打一个tar包,按照期望的目录的结构:bin、config、lib、log

bin/install.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
sources(){
#git fetch --all
#git reset --hard origin/master

buildTime=`date "+%Y-%m-%d %H:%M:%S"`
appName=`grep -B 4 packaging pom.xml | grep artifactId | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`
appVersion=`grep -B 4 packaging pom.xml | grep version | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`

#commit=`svn info|awk 'NR==9{print $4}'`
commit=`git log -n 1 --pretty=oneline | awk '{print $1}'`
branch=`git name-rev --name-only HEAD`
codeVersion="$branch $commit"

commit_msg=`git log --pretty=format:"%s" $s -1`
commit_time=`git log --pretty=format:"%cd" $s -1`
commit_author=`git log --pretty=format:"%an" $s -1`

if [ -f target/classes/META-INF/info.yml ];then
## info.application
replace target/classes/META-INF/info.yml name "$appName" 1
replace target/classes/META-INF/info.yml version "$appVersion" 1
replace target/classes/META-INF/info.yml build "$buildTime" 1
## info.commit
replace target/classes/META-INF/info.yml version \""$codeVersion"\" 2
replace target/classes/META-INF/info.yml Msg \""$commit_msg"\" 1
replace target/classes/META-INF/info.yml Time "$commit_time" 1
replace target/classes/META-INF/info.yml Author "$commit_author" 1
## spring.application.name
replace target/classes/META-INF/info.yml name "$appName" 2
fi
}

build(){
appName=`grep -B 4 packaging pom.xml | grep artifactId | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`
appVersion=`grep -B 4 packaging pom.xml | grep version | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`

buildTime=`date "+%Y-%m-%d %H:%M:%S"`
commit=`git log -n 1 --pretty=oneline | awk '{print $1}'`
branch=`git name-rev --name-only HEAD`
codeVersion="$branch $commit"

mkdir -p target/"$appName"_"$appVersion"/lib
cp -rf bin target/"$appName"_"$appVersion"
mv target/"$appName"_"$appVersion"/bin/install.sh target/"$appName"_"$appVersion"

cd target
if [ -f "$appName"-"$appVersion"-encrypted.jar ];then
cp "$appName"-"$appVersion"-encrypted.jar "$appName"_"$appVersion"/lib/"$appName"-"$appVersion".jar
else
cp "$appName"-"$appVersion".jar "$appName"_"$appVersion"/lib/"$appName"-"$appVersion".jar
fi

cp -rf classes/config "$appName"_"$appVersion"

find "$appName"_"$appVersion" -type f -name "*.sh" -exec chmod 744 {} \;
find "$appName"_"$appVersion" -type f -name "*.sh" -exec dos2unix {} \;

sed -i 's#^app_name=.*#app_name="'"$appName"'"#' "$appName"_"$appVersion"/bin/setenv.sh
sed -i 's#^app_version=.*#app_version="'"$appVersion"'"#' "$appName"_"$appVersion"/bin/setenv.sh
sed -i 's#^code_version=.*#code_version="'"$codeVersion"'"#' "$appName"_"$appVersion"/bin/setenv.sh
sed -i 's#^build_time=.*#build_time="'"$buildTime"'"#' "$appName"_"$appVersion"/bin/setenv.sh

build_time=`date "+%Y%m%d"`
build_tar="$appName"_"$appVersion"_"$build_time".tar.gz
tar zcvf $build_tar "$appName"_"$appVersion"
md5sum $build_tar > $build_tar.md5
}

如果使用上面的结果,可以很方便按照同样的结构来打一个Docker镜像

docker.build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mvn clean install -DskipTests
if [ ! $? == 0 ];then
exit
fi

app_name=`grep -B 4 packaging pom.xml | grep artifactId | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`
app_version=`grep -B 4 packaging pom.xml | grep version | awk -F ">" '{print $2}' | awk -F "<" '{print $1}'`

cd target

app_source="$app_name"_"$app_version"
find $app_source -type f -name "*.sh" -exec chmod 744 {} \;
find $app_source -type f -name "*.sh" -exec dos2unix {} \;

echo "FROM openjdk:17-oracle" >> Dockerfile
echo >> Dockerfile
echo "WORKDIR /opt/xxx/$app_name" >> Dockerfile
echo >> Dockerfile
echo "ADD $app_source/bin /opt/xxx/$app_name/bin/" >> Dockerfile
echo "ADD $app_source/lib /opt/xxx/$app_name/lib/" >> Dockerfile
echo "ADD $app_source/config /opt/xxx/$app_name/config/" >> Dockerfile
echo >> Dockerfile
echo "ENTRYPOINT [\"/opt/xxx/$app_name/bin/run.sh\", \"up\"]" >> Dockerfile

docker build -t xxx/$app_name:$app_version .
maven-pmd-plugin

代码静态检查插件,这里我们使用的alibaba的规范,在compile会先扫描代码,如果有级别高于2的问题,则拒绝编译

maven-site-plugin/reporting

用于生成Maven应用相关的Html文件,可以将pmd检查报告添加到网页中,这样相比pmd.xml可以有更好的浏览体验

xxx-model

对于多个微服务构成的项目,我们会先将这些服务中共用的数据模型抽象出来作为一个独立的依赖包,这样当服务相互调用进行序列化或反序列化时,就不需要重复进行类型定义了。至于它的pom.xml则可以非常简单

xxx-model/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xxx</groupId>
<artifactId>commons-parent</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>xxx-model</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>xxx-model</name>
<url>https://www.xxx.com/</url>
<description>xxx-模型管理</description>

<scm>
<tag>HEAD</tag>
<url>http://192.168.141.13:8083/xxx/xxx-model</url>
<connection>scm:git:http://192.168.141.13:8083/xxx/xxx-model.git</connection>
<developerConnection>scm:git:http://192.168.141.13:8083/xxx/xxx-model.git</developerConnection>
</scm>

<distributionManagement>
<repository>
<id>xxx</id>
<name>releases</name>
<url>http://192.168.141.13:8081/repository/maven-releases/</url>
<uniqueVersion>true</uniqueVersion>
</repository>
<snapshotRepository>
<id>xxx</id>
<name>snapshots</name>
<url>http://192.168.141.13:8081/repository/maven-snapshots/</url>
<uniqueVersion>true</uniqueVersion>
</snapshotRepository>
</distributionManagement>
</project>

xxx-service

对于具体的项目服务,其pom文件同样很简单,相比model只是声明了三个打包插件,而插件的配置已经在parent中定义好了

xxx-serviceA/pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xxx</groupId>
<artifactId>commons-parent</artifactId>
<version>1.0.0</version>
</parent>

<artifactId>xxx-serviceA</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>xxx-serviceA</name>
<url>https://www.xxx.com/</url>
<description>xxx-服务A</description>

<scm>
<tag>HEAD</tag>
<url>http://192.168.141.13:8083/xxx/xxx-serviceA</url>
<connection>scm:git:http://192.168.141.13:8083/xxx/xxx-serviceA.git</connection>
<developerConnection>scm:git:http://192.168.141.13:8083/xxx/xxx-serviceA.git</developerConnection>
</scm>

<distributionManagement>
<repository>
<id>xxx</id>
<name>releases</name>
<url>http://192.168.141.13:8081/repository/maven-releases/</url>
<uniqueVersion>true</uniqueVersion>
</repository>
<snapshotRepository>
<id>xxx</id>
<name>snapshots</name>
<url>http://192.168.141.13:8081/repository/maven-snapshots/</url>
<uniqueVersion>true</uniqueVersion>
</snapshotRepository>
</distributionManagement>

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

<profiles>
<profile>
<id>unix</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
<plugin>
<artifactId>exec-maven-plugin</artifactId>
<groupId>org.codehaus.mojo</groupId>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<dependencies>
<dependency>
<groupId>com.xxx</groupId>
<artifactId>xxx-model</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- 其它依赖... -->
</dependencies>
</project>

附录

  • jar引用失败

本地存在依赖,但是构建时依然提示依赖获取失败,并从远程获取。遇到这种问题可以尝试删除_remote.repositories文件,其作用是用来标示资源的来源,可以帮助Maven确定资源是否来自远程仓库,以及是否需要从远程仓库获取更新

1
find ./repository/ -name "_remote.repositories" | xargs rm -f;

有时我们在离线环境上构建,也会修改maven的mirror地址改为本地文件

1
<url>file:///usr/local/maven/repository</url>
  • nexus服务

使用docker可以快速搭建一个nexus服务,至于创建仓库上传jar包,可以参考http://119.23.147.120/archives/nexus-si-fu-da-jian-ji-lu 这里就不赘述了

docker-compose.yaml
1
2
3
4
5
6
7
8
9
10
11
12
version: "3"

services:
nexus:
image: sonatype/nexus3:3.19.1
container_name: nexus
ports:
- "8081:8081"
volumes:
- ./data:/nexus-data
- /etc/localtime:/etc/localtime
privileged: true

对于jar包和docker镜像,我们都有仓库进行管理,其实对于tar包也可以上传nexus进行管理,比如我们可以创建一个application-packages仓库,然后:

1
2
mvn deploy:deploy-file -Durl=http://192.168.141.13:8081/repository/application-packages/ -DrepositoryId=xxx \
-DgroupId=com.xxx -DartifactId=xxx-serviceA -Dversion=$app_version -Dpackaging=tar -Dfile=target/$app_tar

另外通过http的put请求也可以直接上传文件,这样就可以搞一个脚本来将外网下载下来的仓库上传到内网的nexus服务了

mavenimport.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

while getopts ":r:u:p:" opt; do
case $opt in
r) REPO_URL="$OPTARG"
;;
u) USERNAME="$OPTARG"
;;
p) PASSWORD="$OPTARG"
;;
esac
done
find . -type f -not -path './mavenimport\.sh*' -not -path '*/\.*' -not -path '*/\^archetype\-catalog\.xml*' -not -path '*/\^maven\-metadata\-local*\.xml' -not -path '*/\^maven\-metadata\-deployment*\.xml' | sed "s|^\./||" | xargs -I '{}' curl -u "$USERNAME:$PASSWORD" -X PUT -v -T {} ${REPO_URL}/{} ;

在repository目录中执行如下命令便可以上传所有jar

1
sh mavenimport.sh -u admin -p admin -r  http://192.168.141.13:8081/repository/release/
  • 上传Central

如果想将jar包上传Maven中央仓库,可以参考https://segmentfault.com/a/1190000038362019
已经写的非常详细了,可以参考之前上传的一个完整示例:https://github.com/shanhm1991/spring-fom/blob/master/pom.xml


参考: