创建Vert.x的build.gradle
文件
//@wjw_note: 根据传进来的profile系统属性来打包.例如: gradlew clean build -x test -Dprofile=prod
//@wjw_note: 单jar运行: java -jar build\libs\my-vertx-first-app-1.0-SNAPSHOT--prod-fat.jar -Dprofile=prod
plugins {
id 'java'
id 'application'
//gradle的插件,shadow这个插件可以构建的时候把引用的jar包合进去
//加上此插件后会生成一个shadowJar的task
id "com.github.johnrengelman.shadow" version "7.0.0"
}
//@wjw_note: 根据传进来的profile系统属性来打包.
def profileName = System.getProperty("profile") ?: "dev"
//def profileName = System.getProperty("profile")
if(profileName==null) {
throw new BuildCancelledException("must pass The environment variable 'profile'\r\n"
+"For example: gradlew clean build -x test -Dprofile=dev")
}
processResources {
include 'META-INF/services/**'
include '**/public/**'
include '**/static/**'
include '**/templates/**'
include '**/tpl/**'
include '**/i18n/**'
include { FileTreeElement details ->
details.isDirectory()==true || details.file.name.contains("-${profileName}.") /* 根据传入的profileName系统属性来过滤 */
}
}
group = "name.quanke.study.vertx.first"
version = "1.0-SNAPSHOT--${profileName}"
description = """WJW的第一个Vert.x 4 应用"""
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
repositories {
mavenLocal()
maven { url "https://maven.aliyun.com/nexus/content/groups/public/" } //优先使用阿里的镜像
mavenCentral()
}
def vertxVersion = "4.2.5"
def junitJupiterVersion = "5.7.0"
def mainVerticleName = "name.quanke.study.vertx.first.MyFirstVerticle"
def launcherClassName = "io.vertx.core.Launcher"
def watchForChange = "src/**/*"
def doOnChange = "${projectDir}/gradlew classes"
application {
mainClass = launcherClassName
}
dependencies {
implementation platform("io.vertx:vertx-stack-depchain:$vertxVersion") //Vert.x Stack Depchain,集中了Vert.x的依赖版本管理,这样后面在导入Vert.x模块时就不必再填写版本号了!
implementation "io.vertx:vertx-core"
implementation "io.vertx:vertx-config"
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation "io.vertx:vertx-junit5"
testImplementation "org.junit.jupiter:junit-jupiter:$junitJupiterVersion"
}
//@wjw_note(for fatjar):
shadowJar {
archiveClassifier = "fat"
manifest { //<--add
attributes "Main-Class": launcherClassName
attributes "Main-Verticle": mainVerticleName
}
mergeServiceFiles()
}
run {
//@wjw_note需要终端交互的必须设置
setStandardInput System.in
setStandardOutput System.out
setErrorOutput System.err
applicationDefaultJvmArgs = ["-Dprofile=${profileName}"]
println "applicationDefaultJvmArgs=${applicationDefaultJvmArgs}"
args = ["run", mainVerticleName, "--launcher-class=$launcherClassName", "-conf src/main/resources/conf/conf-${profileName}.json"]
}
test {
useJUnitPlatform()
}
task createWrapper(type: Wrapper) {
gradleVersion = '7.4'
}
定制project.name
在项目目录下创建settings.gradle
,文件类容是
rootProject.name = 'my-vertx-first-app'
if (!JavaVersion.current().java8Compatible) {
throw new IllegalStateException('''A vertx-first-app:
| This needs Java 8,
| You are using something else,
| Refresh. Try again.'''.stripMargin())
}
定制Git的.gitignore
文件模版
在项目目录下创建.gitignore
,文件类容是
.gradle
/build/
/bin/
/classes/
/config-repo/
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# logs
/test/reports
/logs
*.log
*.log.*
创建启动类
实际在生产环境中打成单一jar来运行时候使用Vert.x的自带的启动类io.vertx.core.Launcher
.
这个自建启动类主要是为了在IDE里开发调试时候使用,目的是:
- 设置环境为开发环境,关闭文件缓存和模板缓存
- 防止调试的时候出现
BlockedThreadChecker
日志信息
package name.quanke.study.vertx.first;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
public class MyVertixApp {
public static void main(String[] args) {
{//@wjw_note: 设置环境为开发环境,关闭文件缓存和模板缓存!
//1. During development you might want to disable template caching so that the template gets reevaluated on each request.
//In order to do this you need to set the system property: vertxweb.environment or environment variable VERTXWEB_ENVIRONMENT to dev or development.
//By default caching is always enabled.
//2. these system properties are evaluated once when the io.vertx.core.file.FileSystemOptions class is loaded,
//so these properties should be set before loading this class or as a JVM system property when launching it.
System.setProperty("vertxweb.environment", "dev");
System.setProperty("vertx.disableFileCaching", "true");
}
//防止调试的时候出现`BlockedThreadChecker`日志信息
VertxOptions options = new VertxOptions();
long blockedThreadCheckInterval = 60 * 60 * 1000L;
if (System.getProperties().getProperty("vertx.options.blockedThreadCheckInterval") != null) {
blockedThreadCheckInterval = Long.valueOf(System.getProperties().getProperty("vertx.options.blockedThreadCheckInterval"));
}
options.setBlockedThreadCheckInterval(blockedThreadCheckInterval);
// 创建 Verticle
MyFirstVerticle verticle = new MyFirstVerticle();
Vertx vertx = Vertx.vertx(options);
// 部署 Verticle
vertx.deployVerticle(verticle);
}
}
创建业务处理Verticle
package name.quanke.study.vertx.first;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
public class MyFirstVerticle extends AbstractVerticle {
private Logger logger;
public MyFirstVerticle() {
logger = LoggerFactory.getLogger(this.getClass());
}
@Override
public void start(Promise<Void> promise) {
String vertx_config_path;
{ //->校验是否指定了`profile`参数,和相应的配置文件是否存在!
Properties sysProperties = System.getProperties();
String profile = sysProperties.getProperty("profile");
if (profile == null) {
System.out.println("Please set 'profile'");
this.vertx.close();
return;
}
System.out.println("!!!!!!=================Vertx App profile:" + profile + "=================!!!!!!");
//@wjw_note: 为了从classpath里加载配置文件!
//也可以通过系统属性`vertx-config-path`来覆盖: java -jar my-vertx-first-app-1.0-SNAPSHOT--prod-fat.jar -Dvertx-config-path=conf/conf-prod.json
vertx_config_path = sysProperties.getProperty("vertx-config-path");
if (vertx_config_path == null) { //如果系统属性`vertx-config-path`没有被设置
vertx_config_path = "conf/conf-" + profile + ".json";
}
} //<-校验是否指定了`profile`参数,和相应的配置文件是否存在!
//加载配置文件
ConfigStoreOptions classpathStore = new ConfigStoreOptions()
.setType("file")
.setConfig(new JsonObject().put("path", vertx_config_path));
ConfigRetrieverOptions configOptions = new ConfigRetrieverOptions().addStore(classpathStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, configOptions);
retriever.getConfig().onSuccess(json -> {
{//@wjw_note: 加载log的配置文件!
try {
String log_config_path = json.getString("logging");
System.out.println("Logback configure file: " + this.getClass().getClassLoader().getResource(log_config_path).getPath());
LogBackConfigLoader.load(this.getClass().getClassLoader().getResource(log_config_path));
} catch (Exception e) {
e.printStackTrace();
promise.fail(e);
}
}
vertx.createHttpServer()
.requestHandler(r -> {
r.response().putHeader("content-type", "text/plain; charset=utf-8");
r.response().end("Hello from my first " + "Vert.x 4 application(应用)--" + System.currentTimeMillis());
})
.listen(json.getInteger("http.port", 8080))
.onSuccess(server -> {
promise.complete();
logger.info("Wer Server start OK! listen port: " + server.actualPort());
})
.onFailure(throwable -> promise.fail(throwable));
}).onFailure(throwable -> promise.fail(throwable));
}
}
编写Logback配置文件加载类
* author: @wjw
* date: 2022年2月21日 下午5:20:03
* note:
*/
package name.quanke.study.vertx.first;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
/**
* LogBack手动加载配置文件.
*/
public class LogBackConfigLoader {
/**
* 从外部的文件系统或者中加载LogBack配置文件
*
* @param externalConfigFileLocation the external config file location
* @throws IOException Signals that an I/O exception has occurred.
* @throws JoranException the joran exception
*/
public static void load(String externalConfigFileLocation) throws IOException, JoranException {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
File externalConfigFile = new File(externalConfigFileLocation);
if (!externalConfigFile.exists()) {
throw new IOException("Logback External Config File Parameter does not reference a file that exists");
} else {
if (!externalConfigFile.isFile()) {
throw new IOException("Logback External Config File Parameter exists, but does not reference a file");
} else {
if (!externalConfigFile.canRead()) {
throw new IOException("Logback External Config File exists and is a file, but cannot be read.");
} else {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(externalConfigFileLocation);
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
}
}
}
}
/**
* 从URL中加载LogBack配置文件.特别适合从classpath中来加载
*
* @param url the url
* @throws JoranException the joran exception
*/
public static void load(URL url) throws JoranException {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
configurator.doConfigure(url);
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
}
}
编写配置文件
-
在
src/main/resources
下创建conf
目录 -
创建以profile系统属性具体值为后缀的Vertx配置文件:
conf-${profile}.json
, 例如conf-dev.json
,conf-test.json
,conf-test.json
;文件类容如下
{
"http.port" : 8081,
"logging": "conf/log-dev.xml"
}
- 根据
conf-${profile}.json
里的logging
具体值来创建Logback的配置文件,下面是一个conf/log-dev.xml
文件示例:
<?xml version="1.0" encoding="UTF-8"?>
<!--scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
configuration 子节点为 appender、logger、root
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="LOG_DIR" value="./logs" />
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符 -->
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 输出控制台的所有信息到日志文件里 -->
<appender name="FILE-ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ALL</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_DIR}/all_dev_%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 30 days worth of history, but at most 10GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成INFO级别日志文件 -->
<appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_DIR}/info_dev_%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 30 days worth of history, but at most 10GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成WARN级别日志文件 -->
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_DIR}/warn_dev_%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 30 days worth of history, but at most 10GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成ERROR级别日志文件 -->
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_DIR}/error_dev_%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!-- each file should be at most 100MB, keep 30 days worth of history, but at most 10GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE-ALL" />
<appender-ref ref="FILE-INFO" />
<appender-ref ref="FILE-WARN" />
<appender-ref ref="FILE-ERROR" />
</root>
</configuration>
用gradlew构建
根据传进来的profile
系统属性来打包.
例如: gradlew clean build -x test -Dprofile=prod
用gradlew运行
gradlew run -Dprofile=dev
使用单一jar运行来
java -jar build\libs\web-templ-thymeleaf-1.0-SNAPSHOT-prod-fat.jar -Dprofile=prod