# 部署相关问题
# 启动问题
DynamicTp logging env init failed,if collectType is not logging,this error can be ignored- 轻量级线程池管理开源项目
# Spring Boot随机端口你都不会,怎么动态扩容
server.port=${random.int(2000,8000)}
上面的方法虽然暂时达到了想要的效果,但是有个问题:如果生成的这个随机端口已经被使用了,那么项目启动就会出现端口冲突。
# 通过System.setProperty设置有效随机端口
# server.port=0随机端口 (推荐)
通过设置server.port=0,在spring boot项目启动时,会自动去寻找一个空闲的端口,避免端口冲突。
# 分离第三方依赖独立打包pom配置
<build>
<!-- 打包输出的根目录 -->
<!-- <directory>target/${project.version}</directory> -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 剔除spring-boot打包的org和BOOT-INF文件夹(用于子模块打包) -->
<!-- <skip>true</skip>-->
<!-- 指定该jar包启动时的主类[建议] -->
<mainClass>com.chlm.mysession.MysessionApplication</mainClass>
<layout>ZIP</layout>
<includes>
<include>
<!-- 排除第三方依赖jar(只保留本项目的jar) -->
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<!-- 排除所有jar -->
<!-- <groupId>nothing</groupId>-->
<!-- <artifactId>nothing</artifactId>-->
</include>
</includes>
</configuration>
<!-- <executions>-->
<!-- <execution>-->
<!-- <goals>-->
<!-- <goal>repackage</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
</plugin>
<!-- 打源码包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<id>attach-sources</id>
<!-- 意思是在mvn生命周期为compile时将源文件打包,即只要执行的mvn命令包含compile阶段,就会将源代码打包。-->
<!-- phase还可以指定为verify、package、install等-->
<phase>compile</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 把项目依赖的第三方包打包在target/lib下 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- 配置maven install 跳过test,相当于命令:$mvn install -Dmaven.test.skip = true-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
<resources>
<!-- 打包src/main/java下的xml文件 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!-- 排除resources下的配置文件 -->
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<excludes>
<exclude>static/**</exclude>
<exclude>templates/**</exclude>
<exclude>*.yml</exclude>
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
<exclude>*.txt</exclude>
</excludes>
<targetPath>BOOT-INF/classes/</targetPath>
</resource>
<!-- 打包lib下的jar包 -->
<resource>
<directory>lib</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</resources>
</build>
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
# 我把SpringBoot项目从18.18M瘦身到0.18M,部署起来真省事
- 1、配置pom文件
进入项目根目录,执行命令:mvn clean install
将编译后的Jar包解压,拷贝 BOOT-INF 目录下的lib文件夹 到目标路径;
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.chlm.mysession.MysessionApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
2
3
4
5
6
7
8
9
10
11
12
13
14
- 2、修改pom.xml配置,编译出不带 lib 文件夹的Jar包
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.chlm.mysession.MysessionApplication</mainClass>
<layout>ZIP</layout>
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 配置maven install 跳过test,相当于命令:$mvn install -Dmaven.test.skip = true-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
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
- 3、运行编译后的Jar包
- 将 步骤1 解压出来的lib文件夹、步骤2编译的jar包放在同一个目录, 运行下面命令:
java -Dloader.path=lib -jar myJar.jar
根据实际情况写lib目录
# 让工程支持热部署
添加jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
2
3
4
5
# 部署tomcat问题汇总
# java连接mysql报错
java.sql.SQLException: Unknown system variable 'query_cache_size'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:545)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1936)
at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1422)
at com.mysql.cj.jdbc.ConnectionImpl.loadServerVariables(ConnectionImpl.java:2831)
at com.mysql.cj.jdbc.ConnectionImpl.initializePropsFromServer(ConnectionImpl.java:2381)
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:1739)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:1596)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:633)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:347)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:219)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:270)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
原因是mysql-connector-java的版本还是6.0.6,需要升级版本到8.0.11 ,这个报错就不存在了
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
2
3
4
5
此方法同样适用于gradle
# invalid_LOC_header(bad_signature)
找到错误的jar包,确保正确下载
# 调整打包方式为war
- 修改打包方式
<packaging>jar</packaging>
如下:
<packaging>war</packaging>
- 移除内置tomcat
<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>
2
3
4
5
6
7
8
9
10
或者:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
2
3
4
5
- 其他处置
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
@Mapper
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
2
3
4
5
直接在启动类中:
<!-- @MapperScan(value= {"com.ffCamera.mapper"}) -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.2</version>
</dependency>
2
3
4
5
6
- 罪魁祸首
导致springboot工程放在eclipse的tomcat下运行不起来,需要注释掉。 运行中会报错如下文件所示:
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>
2
3
4
5
这个错误关键在最后一个cause by。异常信息这个错误在网上查了很多资料,有的说和问题1解决方案一样。其实不是的。正确解决方案:
错误发生由于porm.xml中多了
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>
2
3
4
5
在output中多了这个jar包导致使用了iBatis的sqlSessionFactory这个bean,而这个bean没有setVfsImpl方法。
删除该段xml后使用正确的mybatis-spring-boot-starter中的sqlSessionFactoryBean这个bean,成功调用方法。
修改后记得调整打包的Artifacts。
# JSP支持
- 添加jar包
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 配置文件修改
先在src/main/下新建文件夹webapp/WEB-INF/jsp/ 然后修改application.properties
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
2
# SpringBoot的几种部署方式
在Java Archive(JAR)中作为独立应用程序进行部署
- 将Web应用程序存档(WAR)部署到servlet容器中,
- 在Docker Container中部署,
- 在NGINX Web服务器后面部署 - 直接设置,
部署在NGINX Web服务器后面 - 容器化设置。
# WAR
可以将Spring Boot应用程序打包到WAR文件中,以部署到现有的servlet容器(例如Tomcat,Jetty等)中。这可以按如下方式完成:
通过pom.xml文件指定WAR包<packaging>war</packaging>。这会将应用程序打包成WAR文件(而不是JAR)。
在第二步,将Tomcat(servlet容器)依赖关系的范围设置为provided(以便它不会部署到WAR文件中):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId
<scope>provided</scope>
</dependency>
2
3
4
5
通过扩展SpringBootServletInitializer并覆盖configure方法来初始化Tomcat所需的Servlet上下文,如下所示:
@SpringBootApplication
public class DemoApp extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(DemoApp.class);
}
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}
2
3
4
5
6
7
8
9
10
要将应用程序打包到war文件中,请mvn clean package在项目目录下运行标准maven命令。这将生成可以部署到servlet容器中的WAR包。
# DockerContainer
在将应用程序部署到Docker容器之前,我们首先将应用程序打包在(胖)JAR文件中。
在第一步,我们需要构建一个容器镜像。为此,我们首先在项目根目录中创建一个Dockerfile,如下所示:
# latest oracle openjdk is the basis
FROM openjdk:oracle
# copy jar file into container image under app directory
COPY target/demoApp.jar app/demoApp.jar
# expose server port accept connections
EXPOSE 8080
# start application
CMD ["java", "-jar", "app/demoApp.jar"]
2
3
4
5
6
7
8
请注意,在上面的代码片段中,我们假设应用程序JAR文件“ demoApp.jar”位于项目的目标目录下。我们还假设嵌入式servlet端口是8080(这是Tomcat的默认情况)。
我们现在可以使用以下命令构建Docker镜像(Dockerfile所在的位置):
docker image build -t demo-app:latest .
-t是要构建的镜像的名称和标记。构建镜像后,我们可以通过以下方式创建和运行容器:
docker container run -p 8080:8080 -d --name app-container demo-app
-p是发布(映射)主机端口到容器端口(在这种情况下,两个都是8080)。选项-d(detach)指定在后台运行容器,并用--name指定容器的名称。
# NGINX
在直接设置中,我们直接在localhost上运行Nginx Web服务器和Spring Boot应用程序(当然在不同的端口上)。我们让Ngnix代理REST请求到Spring Boot应用程序:
在Linux上安装Nginx Web服务器sudo apt-get install nginx,
/etc/ngnix/sites-available/default使用文本编辑器打开文件,
比如说,我们有两个Spring Boot应用程序需要代理。然后用两个Spring Boot应用程序的以下块替换文件中的“location”块。
location /app1 {
proxy_pass http://localhost:8080;
}
location /app2 {
proxy_pass http://localhost:9000;
}
2
3
4
5
6
在此基础上对将来的请求http://localhost/app1/将被定向到/http://localhost:8080/,和将来的请求http://localhost/app2/将被引导到/http://localhost:9000/。
# 负载均衡
如果您正在运行Spring Boot应用程序的多个实例,则可以启用Nginx以应用负载平衡。例如,如果我们在端口8080,8081和8082上运行3个app1实例。我们可以在这些服务器之间进行负载平衡,如下所示:
打开文件/etc/ngnix/sites-available/default并在文件顶部添加以下块(在服务器块之前):
#configure load-balancing
upstream backend {
server localhost:8080;
server localhost:8081;
server localhost:8082;
}
2
3
4
5
6
修改app1 的proxy_pass参数,如下所示:
location / app1 {
proxy_pass http:// backend;
}
2
3
基于此请求http://localhost/app1/将被发送到/http://localhost:8080/,/http://localhost:8081/或/http://localhost:8082/。
# tomcat配置
# 服务器地址和端口
server.port = 80
server.address = my_custom_ip
2
# 错误处理
Spring Boot提供标准错误网页。此页面称为Whitelabel
#禁用
server.error.whitelabel.enabled = false
2
Whitelabel的默认路径是*/error*。可以通过设置server.error.path参数来自定义它:
server.error.path = /user-error
2
还可以设置属性,以确定显示有关错误的信息。例如,我们可以包含错误消息和堆栈跟踪:
server.error.include-exception= true
server.error.include-stacktrace= always
2
# 服务器连接
在Spring Boot中,我们可以定义Tomcat工作线程的最大数量:
server.tomcat.max-threads= 200
配置Web服务器时,设置服务器连接超时也可能很有用。这表示服务器在连接关闭之前等待客户端发出请求的最长时间:
server.connection-timeout= 5s
我们还可以定义请求头的最大大小:
server.max-http-header-size= 8KB
请求正文的最大大小:
server.tomcat.max-swallow-size= 2MB
或者整个POST请求的最大大小:
server.tomcat.max-http-post-size= 2MB
# SSL
要在我们的Spring Boot应用程序中启用SSL支持,我们需要将server.ssl.enabled属性设置为true,并定义SSL协议:
server.ssl.enabled = true
server.ssl.protocol = TLS
我们要配置保存证书密钥库的密码,类型和路径:
server.ssl.key-store-password=my_password
server.ssl.key-store-type=keystore_type
server.ssl.key-store=keystore-path
2
3
我们还必须定义标识密钥库中密钥的别名:
server.ssl.key-alias=tomcat
有关SSL配置的更多信息,请访问:HTTPS using self-signed certificate in Spring Boot。
# Tomcat服务器访问日志
在尝试统计页面命中数,用户会话活动等时,Tomcat访问日志非常有用。
要启用访问日志,只需设置:
server.tomcat.accesslog.enabled = true
我们还应该配置其他参数,例如附加到日志文件的目录名,前缀,后缀和日期格式:
server.tomcat.accesslog.directory=logs
server.tomcat.accesslog.file-date-format=yyyy-MM-dd
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.suffix=.log
2
3
4
# springboot发布程序的原则
如果使用 SpringBoot 多模块发布到外部 Tomcat,可能会遇到各种各样的问题
# 8大原则
- 在发布模块打包,而不是父模块上打包
- 公共调用模块,打包类型设置为 jar 格式
- 发布模块打包类型设置为 war 格式
<packaging>war</packaging>
- 排除内置tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
2
3
4
5
当设置
scope=provided时,此 jar 包不会出现在发布的项目中,从而就排除了内置的 tomcat。
- 设置启动类
@SpringBootApplication
public class ApiApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ApiApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
- 如果使用拦截器一定要排除静态文件
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 排除静态文件
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
// do something
}
2
3
4
5
6
7
8
9
10
11
12
- 先装载公共模块,再发布项目
如果发布的模块引用了本项目的其他公共模块,需要先把本项目的公共模块装载到本地仓库。
操作方式,双击父模块的 install 即可, install 成功之后,点击发布模块的 package 生成 war 包,就完成了项目的打包,
- 部署项目
# 可能出现的问题
- SpringBoot 配置了端口号影不影响程序发布?
不影响,配置的 server.port 会被覆盖,以 tomcat 本身的端口号为准,tomcat 端口号在 tomcat/config/server.xml 文件中配置。
- 发布报错,不能找到其他模块或项目中的公共模块,怎么办?
因为没有执行父节点 maven 的 install 操作,install 就是把公共模块放入本地仓库,提供给其它项目使用。
- 不能找到 SpringBoot 运行的 main 类,怎么办?
因为没有设置启动类导致的,设置方式:
pom.xml 配置启动类,配置<configuration><mainClass>com.bi.api.ApiApplication</mainClass></configuration> 。
启动类继承 SpringBootServletInitializer 实现 SpringApplicationBuilder 方法,具体代码参考文中第五部分。
- 把 SpringBoot 项目部署到 Tomcat 7 一直提示找不到 xxx.jar 包?
这是因为 SpringBoot 版本太高,tomcat 版本太低的原因。如果你使用的是最新版的 SpringBoot,可以考虑把 tomcat 也升级为 tomcat 8.x+ 最新的版本,就可以解决这个问题。
# 优雅关闭springboot应用
随着线上应用逐步采用 SpringBoot 构建,SpringBoot应用实例越来多,当线上某个应用需要升级部署时,常常简单粗暴地使用
kill命令,这种停止应用的方式会让应用将所有处理中的请求丢弃,响应失败。这样的响应失败尤其是在处理重要业务逻辑时需要极力避免的
# 定制Tomcat_Connector行为
要平滑关闭 Spring Boot 应用的前提就是首先要关闭其内置的 Web 容器,不再处理外部新进入的请求。为了能让应用接受关闭事件通知的时候,保证当前 Tomcat 处理所有已经进入的请求,我们需要实现
TomcatConnectorCustomizer接口.
Connector 属于 Tomcat 抽象组件,功能就是用来接受外部请求,以及内部传递,并返回响应内容,是Tomcat 中请求处理和响应的重要组件,具体实现有HTTP Connector和AJP Connector
package org.springframework.boot.web.embedded.tomcat;
import org.apache.catalina.connector.Connector;
/**
* 回调接口,可用于自定义Tomcat {@link Connector}.
*
* @author Dave Syer
* @see ConfigurableTomcatWebServerFactory
* @since 2.0.0
*/
@FunctionalInterface
public interface TomcatConnectorCustomizer {
/**
* Customize the connector.
* @param connector the connector to customize
*/
void customize(Connector connector);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以上在spring-boot-2.1.7.RELEASE.jar
package com.ffCamera.service;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* <p>Title: CustomShutDownApp</p>
* <p>Description:定制tomcat行为 </p>
*
* @author huting
* @date 2019/8/20 10:09
*/
@Slf4j
@Setter
public class CustomShutDownApp implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
/**
* 超市时间(单位:秒)
*/
@Value(value = "${tomcat.close.timeOut}")
private Integer timeOut;
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
//暂停接收所有外部请求
this.connector.pause();
//获取connector对应的线程池
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor){
try {
log.warn("<<<<<应用即将关闭");
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(this.timeOut, TimeUnit.SECONDS)){
log.warn("应用等待关闭时间超过最大时长[{}]秒,将强行关闭",this.timeOut);
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(this.timeOut,TimeUnit.SECONDS)){
log.error("应用关闭失败>>>>>");
}
}
} catch (InterruptedException e){
log.error(e.getMessage());
Thread.currentThread().interrupt();
}
}
}
}
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
另外需要注意的是我们的类 CustomShutdown 实现了 ApplicationListener 接口,意味着监听着 Spring 容器关闭的事件,即当前的 ApplicationContext 执行 close 方法。
# 内嵌Tomcat添加Connector回调
/**
* 程序启动
* @param args
*/
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(FfCameraApplication.class, args);
Object webFactory = run.getBean("webServerFactory");
}
@Bean
public CustomShutDownApp customShutDownApp(){
return new CustomShutDownApp();
}
public ConfigurableServletWebServerFactory webServerFactory(final CustomShutDownApp customShutDownApp){
TomcatServletWebServerFactory tomcatServletWebServerFactory = new TomcatServletWebServerFactory();
tomcatServletWebServerFactory.addConnectorCustomizers(customShutDownApp);
return tomcatServletWebServerFactory;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这里的 TomcatServletWebServerFactory 是 Spring Boot 实现内嵌 Tomcat 的工厂类,类似的其他 Web 容器,也有对应的工厂类如 JettyServletWebServerFactory,UndertowServletWebServerFactory。他们共同的特点就是继承同个抽象类 AbstractServletWebServerFactory,提供了 Web 容器默认的公共实现,如应用上下文设置,会话管理等。
如果我们需要定义Spring Boot 内嵌的 Tomcat 容器时,就可以使用 TomcatServletWebServerFactory 来进行个性化定义,例如下方为官方文档提供自定示例:
public ConfigurableServletWebServerFactory webServerFactory(final CustomShutDownApp customShutDownApp){
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(9999);
return factory;
}
2
3
4
5
我们这里使用 addConnectorCustomizers 方法将自定义的 Connector 行为添加到内嵌的Tomcat 之上,为了查看加载效果,我们可以在 Spring Boot 程序启动后从容器中获取下webServerFactory 对象,然后观察,在它的 tomcatConnectorCustomizers 属性中可以看到已经有了 CustomeShutdownApp 对象。
# 开启Shutdown_Endpoint
我们可以利用 Spring Boot Actuator 来实现Spring 容器的远程关闭
Spring Boot Actuator是 Spring Boot 的一大特性,它提供了丰富的功能来帮助我们监控和管理生产环境中运行的 Spring Boot 应用。我们可以通过 HTTP 或者 JMX 方式来对我们应用进行管理,除此之外,它为我们的应用提供了审计,健康状态和度量信息收集的功能,能帮助我们更全面地了解运行中的应用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2
3
4
Spring Boot Actuator 采用向外部暴露 Endpoint (端点)的方式来让我们与应用进行监控和管理,引入 spring-boot-starter-actuator 之后,我们就需要启用我们需要的 Shutdown Endpoint,在配置文件 application.properties 中,设置如下
#actuator配置
management.endpoint.shutdown.enabled=true
management.endpoints.web.exposure.include=*
2
3
第一行表示启用 Shutdown Endpoint ,第二行表示向外部以 HTTP 方式暴露所有 Endpoint,默认情况下除了 Shutdown Endpoint 之外,其他 Endpoint 都是启用的。
除了 Shutdown Endpoint,Actuator Endpoint 还有十余种,有的是特定操作,比如 heapdump 转储内存日志;有的是信息展示,比如 health 显示应用健康状态。具体所有 Endpoint 信息可以参见官方文档-53. Endpoints 一节。
# 完成配置编码
到这里我们的前期配置工作就算完成了。当启动应用后,就可以通过POST 方式请求对应路径的 http://host:port/actuator/shutdown 来实现Spring Boot 应用远程关闭,是不是很简单呢。
curl -X POST "http://localhost:45600/ffCamera/actuator/shutdown"
# 制作脚本
#!/bin/bash
# 平滑关闭和启动 Spring Boot 程序
#设置端口
SERVER_PORT="8081"
#设置应用名称
JAR_NAME="springboot-shutdown-0.0.1-SNAPSHOT"
#设置 JAVA 启动参数
JAVA_OPTIONS="-server -Xms1024M -Xmx1024M -Dserver.port=$SERVER_PORT"
#Actuator 方式远程关闭应用
curl -X POST "http://localhost:$SERVER_PORT/actuator/shutdown"
echo ""
#循环遍历应用端口是否被使用,作为应用运作状态的标志
echo "关闭旧应用开始"
UP_STATUS=1
while(( $UP_STATUS>0 ))
do
UP_STATUS=$(lsof -i:"$SERVER_PORT" | wc -l)
done
echo "\n关闭旧应用结束"
echo "启动应用开始"
#非挂起方式启动应用,并且跟踪启动日志文件
nohup>"$SERVER_PORT".log java -jar "$JAVA_OPTIONS" "$JAR_NAME".jar 2>&1 &
echo "启动应用中" && tail -20f "$SERVER_PORT".log
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# jar部署配置文件外部化
# 访问命令行属性
在默认的情况下, SpringApplication 会将任何命令行选项参数(以 - 开头 --server.port=9000)转换为 property 并添加到Spring环境当中。
例如,启动项目的时候指定端口:
java -jar analysis-speech-tool-0.0.1-SNAPSHOT.jar --server.port=9000
SpringBoot 使用了一个非常特殊的 PropertySource 命令,目的是为了让属性值的重写按照一定的顺序来,而在这个顺序当中,命令行属性总是优先于其他属性源。
当然,如果不想将命令行属性添加到 Spring 环境当中,可以使用以下代码来禁用它们。
SpringApplication.setAddCommandLineProperties(false);
# 应用程序属性文件
SpringApplication 将从 application.properties 以下位置的文件中加载属性并且将其添加到 Spring 的环境当中:
当前目录下的 /config 子目录
当前目录classpath中的 /config 目录
classpath根目录
该列表按照优先级的顺序排列(在列表中较高的位置定义的属性将会覆盖在较低位置定义的属性)。
如果您不喜欢 application.properties 作为配置文件名,则可以通过指定 spring.config.name 环境属性来切换到另一个名称。还可以使用 spring.config.location 环境属性(以逗号分隔的目录位置列表或文件路径)引用显式位置。
比如:
java -jar myproject.jar --spring.config.name = myproject
java -jar myproject.jar --spring.config.location = classpath:/default.properties,classpath:/override.properties
java -jar -Dspring.config.location = D:\speech\default.properties nacos-config-0.0.1-SNAPSHOT.jar
2
3
# war部署
Spring Boot是支持发布jar包和war的,但它推荐的是使用jar形式发布。使用jar包比较方便,但如果是
频繁修改更新的项目,需要打补丁包,那这么大的jar包上传都是问题
# 修改Spring Boot启动类
启动类继承 SpringBootServletInitializer类,并覆盖 configure方法。
@SpringBootApplication
@MapperScan(value= {"com.ffCamera.mapper"})
@EnableScheduling
@Slf4j
@AllArgsConstructor
public class FfCameraApplication extends SpringBootServletInitializer implements ApplicationRunner {
private final CustomerProperties customerProperties;
/**
* 外部tomcat启动方式变更
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(FfCameraApplication.class);
}
/**
* 程序启动
* @param args 启动参数
*/
public static void main(String[] args) {
SpringApplication.run(FfCameraApplication.class, args);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
请注意一个注解@AllArgsConstructor,这个导致启动类没有了默认的无参构造函数,从外部容器启动将会出现问题,解决办法2个:
- 去掉这个注解@AllArgsConstructor
- 手动添加一个无参的构造函数,保留注解@AllArgsConstructor
# 修改jar为war包形式
在pom文件中,添加war包配置。
<packaging>
war
</packaging>
2
3
# 去除Spring Boot内置Tomcat
修改自带tomcat依赖范围为provided,防止与外部tomcat发生冲突。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
或者
<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>
2
3
4
5
6
7
8
9
10
# 添加war包打包插件
如果你用的是继承spring-boot-starter-parent的形式使用Spring Boot,那可以跳过,因为它已经帮你配置好了。如果你使用的依赖spring-boot-dependencies形式,你需要添加以下插件。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
2
3
4
5
6
7
failOnMissingWebXml需要开启为false,不然打包会报没有web.xml错误
# 其他需注意问题
- 如果项目中有用到HttpServletRequest之类的类,解决办法:需添加依赖如下
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
2
3
4
5
- springboot2.2.0.RELEASE版本对mybatis-plus的问题,出现其配置文件无法注入,解决办法如下:
Failed to bind properties under 'mybatis-plus.configuration.result-maps[0]' to org.apache.ibatis.mapping.ResultMap:
升级2.2.1.RELEASE或者退回到2.2.0.RELAESE之前的版本
- Failed to instantiate WebApplicationInitializer class
手动添加无参构造函数,原因可能是添加了有参构造函数,导致不会自动添加无参构造函数
# jar包转war包有什么影响
- 1、application配置文件中的server.xx等关于容器的配置就无效了,改配置需要在外部tomcat中进行。
- 2、Spring Boot的升级是否需要Tomcat跟着升级?需要观察。
- 3、打war包比打jar明显要变慢好多。。
# 常见几种方法关闭springboot
- 19.1.通过Springboot提供的actuator
- 19.2.获取程序启动时候的context
- 19.3.在springboot启动的时候将进程号写入一个app.pid文件
- 19.4.通过调用一个SpringApplication.exit()
- 19.5.通过自己实现
# 通过Springboot提供的actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2
3
4
然后将shutdown节点打开,也将/actuator/shutdown暴露web访问也设置上,除了shutdown之外还有health, info的web访问都打开的话将management.endpoints.web.exposure.include=*就可以。
@Slf4j
public class CustomShutDownApp{
@PreDestroy
public void preDestroy(){
log.warn("App is closing>>>>>");
}
2
3
4
5
6
public static void main(String[] args) {
SpringApplication.run(FfCameraApplication.class, args);
}
@Bean
public CustomShutDownApp customShutDownApp(){
return new CustomShutDownApp();
}
2
3
4
5
6
7
8
启动程序,执行curl -X POST "http://localhost:45600/ffCamera/actuator/shutdown",打印日志com.ffCamera.service.CustomShutDownApp : App is closing>>>>>
# 获取程序启动时候的context
这样程序在关闭的时候也会调用PreDestroy注解。
/* method 2: use ctx.close to shutdown all application context */
ConfigurableApplicationContext ctx = SpringApplication.run(ShutdowndemoApplication.class, args);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ctx.close();
2
3
4
5
6
7
8
# 在springboot启动的时候将进程号写入一个app.pid文件
# 通过自己实现
@RestController
public class ShutDownController implements ApplicationContextAware {
private ApplicationContext context;
@PostMapping("/shutDownContext")
public String shutDownContext() {
ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) context;
ctx.close();
return "context is shutdown";
}
@GetMapping("/")
public String getIndex() {
return "OK";
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 通过调用一个SpringApplication.exit()
/- method 4: exit this application using static method */
ConfigurableApplicationContext ctx = SpringApplication.run(ShutdowndemoApplication.class, args);
exitApplication(ctx);
public static void exitApplication(ConfigurableApplicationContext context) {
int exitCode = SpringApplication.exit(context, (ExitCodeGenerator) () -> 0);
System.exit(exitCode);
}
2
3
4
5
6
7
8