教你一步步如何用Gradle来搭建Vert.x 4 应用

本文介绍了如何使用Gradle创建Vert.x应用的build.gradle文件,包括根据系统属性打包、配置文件管理、启动类和业务Verticle的实现,以及使用Shadow插件构建fat.jar。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建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:   2022221日 下午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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游泳的老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值