不是每个人都喜欢继承spring-boot-starter-parentPOM。你可能需要使用公司标准parent,或你可能倾向于显式声明所有
Maven配置。
如果你不使用 spring-boot-starter-parent ,通过使用一个scope=import 的依赖,你仍能获取到依赖管理的好处:
<
dependencyManagement
>
<
dependencies
>
<
dependency
>
<
groupId
>org.springframework.boot
</
groupId
>
<
artifactId
>spring-boot-dependencies
</
artifactId
>
<
version
>1.3.0.BUILD-SNAPSHOT
</
version
>
<
type
>pom
</
type
>
<
scope
>import
</
scope
>
</
dependency
>
</
dependencies
>
</
dependencyManagement
>
1.4改变Java版本
spring-boot-starter-parent选择相当保守的Java兼容策略。如果你遵循我们的建议,使用最新的Java版本,你可以添加一
个 java.version属性:
<properties>
<java.version>1.8</java.version>
</properties>
1.5 使用Spring Boot Maven插件
SpringBoot包含一个Maven插件,它可以将项目打包成一个可执行jar。如果想使用它,你可以将该插件添加到<plugins>节
点处:
<?
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
>
<
build
>
<
plugins
>
<
plugin
>
<
groupId
>org.springframework.boot
</
groupId
>
<
artifactId
>spring-boot-maven-plugin
</
artifactId
>
<
version
>1.3.0.BUILD-SNAPSHOT
</
version
>
<
executions
>
<
execution
>
<
goals
>
<
goal
>repackage
</
goal
>
</
goals
>
</
execution
>
</
executions
>
</
plugin
>
</
plugins
>
</
build
>
</
project
>
注:如果使用Spring-Boot-tarter-parent pom,你只需要添加该插件而无需配置它,除非你想改变定义在partent中的设置。
该配置会在Maven生命周期的 package阶段重新打包一个jar或war。下面的示例显示在target目录下既有重新打包后的jar,
也有原始的jar:
1.6
Linux
下打包方法:
使用 mvn clean package 命令打包
如果还没有安装maven :
yum -y install apache-maven
或者单独下载安装:
wget http://apache.fayea.com/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
tar zxvf apache-maven-3.3.9-bin.tar.gz
设置环境变量:
MVN_HOME=/usr/local/app/apache-maven-3.3.9
export PATH=$PATH:$MVN_HOME/bin
然后可以使用以下命令编译:
mvn clean package
可以追加参数 -Dmaven.test.skip=true 跳过
测试
。
target/myproject-1.0.0.jar target/myproject-1.0.0.jar.original
如果使用一个里程碑或快照版本,你还需要添加正确的pluginRepository元素:
<
pluginRepositories
>
<
pluginRepository
>
<
id
>spring-snapshots
</
id
>
<
url
>http://repo.spring.io/snapshot
</
url
>
</
pluginRepository
>
<
pluginRepository
>
<
id
>spring-milestones
</
id
>
<
url
>http://repo.spring.io/milestone
</
url
>
</
pluginRepository
>
</
pluginRepositories
>
<?
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"
>
<
packaging
>jar
</
packaging
>
</
project
>
生成的存档在 package 阶段会被SpringBoot增强。你想启动的main类即可以通过指定一个配置选项,也可以通过为manifest添加一个Main-Class属性这种常规的方式实现。如果你没有指定一个main类,该插件会搜索带有publicstaticvoidmain(String[]args)方法的类。
为了构建和运行一个项目的artifact,你可以输入以下命令:
$ mvn package
$ java -jar target/spring-boot01-1.0-SNAPSHOT.jar
这种方式,只要控制台关闭,服务就不能访问了。下面我们使得 jar 包在后台运行:
java -jar spring-boot01-1.0-SNAPSHOT.jar > log.file 2>&1 &
为了构建一个即是可执行的,又能部署到一个外部容器的war文件,你需要标记内嵌容器依赖为"provided",例如:
<?
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"
>
<
packaging
>war
</
packaging
>
<
dependencies
>
<
dependency
>
<
groupId
>org.springframework.boot
</
groupId
>
<
artifactId
>spring-boot-starter-web
</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.springframework.boot
</
groupId
>
<
artifactId
>spring-boot-starter-tomcat
</
artifactId
>
<
scope
>provided
</
scope
>
</
dependency
>
</
dependencies
>
</
project
>
maven打包之后,会生成两个jar文件:
demo-0.0.1-SNAPSHOT.jar
demo-
0.0
.1-SNAPSHOT
.jar
.original
其中demo-0.0.1-SNAPSHOT.jar.original是默认的maven-jar-plugin生成的包。
demo-0.0.1-SNAPSHOT.jar是spring boot maven插件生成的jar包,里面包含了应用的依赖,以及spring boot相关的类。下面称之为fat jar。
先来查看spring boot打好的包的目录结构(不重要的省略掉):
├── META-INF
│ ├── MANIFEST.MF
├── application.properties
├── com
│ └── example
│ └── SpringBootDemoApplication.class
├── lib
│ ├── aopalliance-1.0.jar
│ ├── spring-beans-4.2.3.RELEASE.jar
│ ├── ...
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher.class
├── JarLauncher.class
├── JavaAgentDetector.class
├── LaunchedURLClassLoader.class
├── Launcher.class
├── MainMethodRunner.class
├── ...
依次来看下这些内容。
Manifest-Version: 1.0
Start-Class: com.example.SpringBootDemoApplication
Implementation-Vendor-Id: com.example
Spring-Boot-Version: 1.3.0.RELEASE
Created-By: Apache Maven 3.3.3
Build-Jdk: 1.8.0_60
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
可以看到有Main-Class是org.springframework.boot.loader.JarLauncher ,这个是jar启动的Main函数。
还有一个Start-Class是com.example.SpringBootDemoApplication,这个是我们应用自己的Main函数
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
这下面放的是应用的.class文件。
这里存放的是应用的Maven依赖的jar包文件。
比如spring-beans,spring-mvc等jar。
这下面存放的是Spring boot loader的.class文件。
我们直接启动:java -jar demo-0.0.1-SNAPSHOT.jar
我们有时候项目依赖外部的jar,我们使用Eclipse开发的时候我们直接通过build path添加jar就可以,但是使用mvn 打包的时候就会缺少这个包。
1. 使用system scope
我们直接引入rabbitmq-client.jar。这个方式比较灵活,到新的服务器上,无需做额外的操作。
<
dependency
>
<
groupId
>rabbitmq.client
</
groupId
>
<
artifactId
>rabbitmq.client
</
artifactId
>
<
version
>3.0
</
version
>
<
scope
>system
</
scope
>
<
systemPath
>${basedir}/src/main/WEB-INF/lib/rabbitmq-client.jar
</
systemPath
>
</
dependency
>
1、groupId和artifactId以及version都是可以随便填写的 ,scope必须填写为system,而systemPath我们现在我们jar包的目录地址就可以了
2、${basedir}就是项目根目录
2. 将jar包安装到本地repository中
这个需要在新机器上执行mvn install:install-file命令。
例如执行命令:
mvn install:install-file -Dfile=D:\JAR_LIB\rabbitmq-client.jar -DgroupId=com.rabbitmq -DartifactId=client -Dversion=3.5.0 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true
在项目中引用:
<
dependency
>
<
groupId
>com.rabbitmq
</
groupId
>
<
artifactId
>client
</
artifactId
>
<
version
>3.5.0
</
version
>
</
dependency
>
/groupId/artifactId/version/artifactId-verion.jar
本例中: lib/com/rabbitmq/client/3.5.0/rabbitmq-client-3.5.0.jar
修改启动类,继承 SpringBootServletInitializer 并重写 configure 方法
public
class SpringBootSampleApplication
extends SpringBootServletInitializer{
private
static
final Logger logger = LoggerFactory.getLogger(SpringBootSampleApplication.
class);
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(
this.getClass());
修改pom文件中jar 为 war
<!-- <packaging>jar</packaging> -->
<packaging>war</packaging>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
在我们开发过程中,我们需要经常修改,为了避免重复启动项目,我们可以启用热部署。
Spring-Loaded项目提供了强大的热部署功能,添加/删除/修改 方法/字段/接口/枚举 等代码的时候都可以热部署,速度很快,很方便。
想在Spring Boot中使用该功能非常简单,就是在spring-boot-maven-plugin插件下面添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
</dependencies>
添加以后,通过mvn spring-boot:run启动就支持热部署了。
注意:使用热部署的时候,需要IDE编译类后才能生效,你可以打开自动编译功能,这样在你保存修改的时候,类就自动重新加载了。
application.properties区分环境
spring boot 可以在 “配置文件”、“Java代码类”、“日志配置” 中来配置profile区分不同环境执行不同的结果
1、配置文件
使用配置文件application.yml 和 application.properties 有所区别
以application.properties 为例,通过文件名来区分环境 application-{profile}.properties
application.properties
app.name=MyApp
server.port=8080
spring.profiles.active=dev
application-dev.properties
server.port=8081
application-stg.properties
server.port=8082
在启动程序的时候通过添加 –spring.profiles.active={profile} 来指定具体使用的配置
例如我们执行 java -jar demo.jar –spring.profiles.active=dev 那么上面3个文件中的内容将被如何应用?
Spring Boot 会先加载默认的配置文件,然后使用具体指定的profile中的配置去覆盖默认配置。
app.name 只存在于默认配置文件 application.properties 中,因为指定环境中不存在同样的配置,所以该值不会被覆盖
server.port 默认为8080,但是我们指定了环境后,将会被覆盖。如果指定stg环境,server.port 则为 8082
spring.profiles.active 默认指定dev环境,如果我们在运行时指定 –spring.profiles.active=stg 那么将应用stg环境,最终 server.port 的值为8082
Maven环境配置
项目工程统一使用maven的profile插件定义不同的项目构建环境(dev, alpha, beta, prod),通过filter插件为不同环境下的配置项注入对应的参数值来实现动态配置目标。
2.3.1定义profile
在POM.xml中配置四个profile,对应项目所处的四个不同的环境-dev, alpha, beta, prod, profile的id属性即为每个环境赋予一个唯一的标示,元素的内容则是以key-value的形式出现的键值对,如我们定义了一个变量,其值在不同的环境下(不同id)被赋予了不同的值(dev, test, pre-prod, prod),要激活不同的环境打包,我们可以在命令行通过mvn package –P${profileId}来让其运行,为了开发便利,默认激活的是dev开发环境,即开发人员不需要通过命令行手动输入-p参数也能运行dev环境的打包。
<profile>
<!-- 本地参数 -->
<id>dev</id>
<properties>
<server.port>8081</server.port>
<server.address>0.0.0.0</server.address>
<profileActive>dev</profileActive>
</properties>
<build>
<filters>
<filter>
<groupId>${basedir}/src/main/resources/dev.properties</groupId>
</filter>
</profile>
下面几个脚本仅供参考:
打包:clean.sh
#0、check user
TIME_STAMP=`date +%Y%m%d%H%M`
WHO=`whoami`
if [ "$WHO" != 'www' ]; then
echo 'current user is not www'
echo 'exit'
CODE_HOME=/home/www/app
PROJECTNAME=
qrealtime
cd $CODE_HOME/$PROJECTNAME
git pull
mvn clean package
pid=`ps -ef |grep $PROJECTNAME |grep -v "grep" |awk '{print $2}' `
if [ $pid != "" ]; then
echo "App is running and
pid=$pid"
echo "App is not running."
PROJECTNAME=
qrealtime
cd $CODE_HOME/$PROJECTNAME
pid=`ps -ef |grep $PROJECTNAME |grep -v "grep" |awk '{print $2}'`
if [ $pid ]; then
echo "App is running and
pid=$pid"
nohup java -jar $CODE_HOME/$PROJECTNAME/target/$
<
span
style=
"font-family: 'microsoft yahei';"
>PROJECTNAME
</
span
>
<
span
style=
"font-family: 'microsoft yahei';"
>-0.0.1-SNAPSHOT.jar
> /dev/null 2
>&1 &
</
span
>
PROJECTNAME=
qrealtime
cd $CODE_HOME/$PROJECTNAME
pid=`ps -ef |grep $PROJECTNAME |grep -v "grep" |awk '{print $2}' `
if [ $pid ]; then
echo "App is running and
pid=$pid"
kill -9 $pid
if [[ $? -eq 0 ]];then
echo "sucess to stop $PROJECTNAME "
echo "fail to stop $PROJECTNAME "
restart
PROJECTNAME=
qrealtime
cd $CODE_HOME/$PROJECTNAME
pid=`ps -ef |grep $PROJECTNAME |grep -v "grep" |awk '{print $2}' `
if [ $pid ]; then
echo "App is running and
pid=$pid"
kill -9 $pid
nohup java -jar $CODE_HOME/$PROJECTNAME/target/$PROJECTNAME-0.0.1-SNAPSHOT.jar
> /dev/null 2
>&1 &
首先看Spring Boot应用程序的
Docker
化,由于Spring Boot内嵌了tomcat、Jetty等容器,因此我们对docker镜像的要求就是需要java运行环境。我的应用代码的的Dockerfile文件如下:
#基础镜像:仓库是java,标签用8u66-jdk
FROM java:8u66-jdk
#当前镜像的维护者和联系方式
MAINTAINER duqi duqi@example.com
#将打包好的spring程序拷贝到容器中的指定位置
ADD target/bookpub-0.0.1-SNAPSHOT.jar /opt/bookpub-0.0.1-SNAPSHOT.jar
#容器对外暴露8080端口
EXPOSE 8080
#容器启动后需要执行的命令
CMD java -Djava.security.egd=file:/dev/./urandom -jar /opt/bookpub-0.0.1-SNAPSHOT.jar
因为目前的示例程序比较简单,这个dockerfile并没有在将应用程序的数据存放在宿主机上。如果你的应用程序需要写文件系统,例如日志,最好利用
VOLUME /tmp
命令,这个命令的效果是:在宿主机的/var/lib/docker目录下创建一个临时文件并把它链接到容器中的/tmp目录。
把这个Dockerfile放在项目的根目录下即可,后续通过
docker-compose build
统一构建:基础镜像是只读的,然后会在该基础镜像上增加新的可写层来供我们使用,因此java镜像只需要下载一次。
docker-compose
是用来做docker服务编排,参看《Docker从入门到实践》中的解释:
Compose 项目目前在 Github 上进行维护,目前最新版本是 1.2.0。Compose 定位是“defining and running complex applications with Docker”,前身是 Fig,兼容 Fig 的模板文件。
Dockerfile 可以让用户管理一个单独的应用容器;而 Compose 则允许用户在一个模板(YAML 格式)中定义一组相关联的应用容器(被称为一个 project,即项目),例如一个 Web 服务容器再加上后端的
数据库
服务容器等。
单个docker用起来确实没什么用,docker技术的关键在于持续交付,通过与jekins的结合,可以实现这样的效果:开发人员提交push,然后jekins就自动构建并测试刚提交的代码,这就是我理解的持续交付。
使用java命令运行应用非常简单,但是通常我们都是通过ssh命令连接到服务器并运行它,一旦ssh连接断开,那么由它fork的java子进程也就随之销毁了。所以我们必须借助工具将应用作为服务运行在服务器上:
systemd 是Linux 下的一款系统和服务管理器。可以为Spring Boot应用编写启动脚本:
[Unit]
Description=Spring Boot Application
[Service]
ExecStart=/usr/bin/java -jar location_of_jar_file.jar --spring.config.location=location_of_config.properties --spring.profiles.active=profile
User=${your expected user}
[Install]
WantedBy=multi-user.target
[program:app]
command=/usr/bin/java -jar location_of_jar_file.jar --spring.config.location=location_of_config.properties --spring.profiles.active=profile
user=${your expected user}
autostart=
true
autorestart=
true
startsecs=
10
startretries=
3
与开发和测试环境不同的是,当应用部署到生产环境时,需要各种运维相关的功能的支持,包括性能指标、运行信息和应用管理等。所有这些功能都有很多技术和开源库可以实现。Spring Boot 对这些运维相关的功能进行了整合,形成了一个功能完备和可定制的功能集,称之为 Actuator。只需要在 POM 文件中增加对 “org.springframe.boot:spring-boot-starter-actuator” 的依赖就可以添加 Actuator。Actuator 在添加之后,会自动暴露一些 HTTP 服务来提供这些信息。这些 HTTP 服务的说明如
表 2
。
名称说明是否包含敏感信息
对于表中的每个服务,通过访问名称对应的 URL 就可以获取到相关的信息。如访问“/info”就可以获取到 info 服务对应的信息。服务是否包含敏感信息说明了该服务暴露出来的信息是否包含一些比较敏感的信息,从而确定是否需要添加相应的访问控制,而不是对所有人都公开。所有的这些服务都是可以配置的,比如通过改变名称来改变相应的 URL。下面对几个重要的服务进行介绍。
Spring Boot 默认提供了对应用本身、关系数据库连接、
MongoDB
、
Redis
和 Rabbit MQ 的健康状态的检测功能。当应用中添加了 DataSource 类型的 bean 时,Spring Boot 会自动在 health 服务中暴露数据库连接的信息。应用也可以提供自己的健康状态信息,如代码清单 7 所示。
@Component
public class AppHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return Health.up().build();
应用只需要实现 org.springframework.boot.actuate.health.HealthIndicator 接口,并返回一个 org.springframework.boot.actuate.health.Health 对象,就可以通过 health 服务来获取所暴露的信息。health 服务返回的结果
{"status":"UP","app":{"status":"UP"},"db":{"status":"UP","database":"HSQL Database Engine","hello":1}}
info 服务所暴露的信息是完全由应用来确定的。应用中任何以“info.”开头的配置参数会被自动的由 info 服务来暴露。只需要往 application.properties 中添加以“info.”开头的参数即可,如:
info.app_name=My First Spring Boot Application
info.app_version=1.0.0
当访问“/info”时,访问的 JSON 数据:
{"app_name":"My First Spring Boot Application","app_version":"1.0.0"}
当访问 metrics 服务时,可以看到 Spring Boot 通过 SystemPublicMetrics 默认提供的一些系统的性能参数值,包括内存、CPU、Java 类加载和线程等的基本信息。应用可以记录其他所需要的信息。Spring Boot 默认提供了两种类型的性能指标记录方式:gauge 和 counter。gauge 用来记录单个绝对数值,counter 用来记录增量或减量值。比如在一个 Web 应用中,可以用 counter 来记录当前在线的用户数量。当用户登录时,把 counter 的值加 1;当用户退出时,把 counter 的值减 1。
@RestController
public class GreetingsController {
@Autowired
private CounterService counterService;
@RequestMapping("/greet")
public String greet() {
counterService.increment("myapp.greet.count");
return "Hello!";
上面代码添加了对 Spring Boot 提供的 CounterService 的依赖。当 greet 方法被调用时,会把名称为“myapp.greet.count”的计数器的值加 1。也就是当用户每次访问“/greet”时,该计算器就会被加 1。除了 CounterService 之外,还可以使用 GaugeService 来记录绝对值。