手把手教你如何从maven迁移到gradle

2022-07-08 14:06:02 浏览数 (1)

From mobile apps to microservices, from small startups to big enterprises, Gradle helps teams build, automate and deliver better software, faster.

目前很多项目构建选择稳健的Maven工具,然而,作为一名Java开发,随着spring全家桶切换成gralde构建,以及越来越多的开源项目从Maven过度到Gradle,Gradle成了程序开发必备的技能之一。如果你还没开始Gradle,别着急,去看下Spring吧,他也才是2020年切换成Gradle的,为时不晚!

环境配置


Windows下载安装Gradle

https://services.gradle.org/distributions/gradle-7.0.2-bin.zip

将下载好的zip解压,配置环境变量添加C:Gradlegradle-7.0.2bin

打开终端,输入gradle -v,出现如下内容

代码语言:javascript复制
❯ gradle -v

------------------------------------------------------------
Gradle 7.0.2
------------------------------------------------------------

(environment specific information)

恭喜你,环境配置完成!

Spring boot 配置


先看一个简单的spring-boot的maven依赖

代码语言:javascript复制
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

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

</project>

上面这段代码相信各位同学都不陌生,一个很简单的spring boot项目,项目的parent是spring starter,依赖了这段maven配置如何”翻译“成gradle?非常简单!

代码语言:javascript复制
plugins {
  id 'org.springframework.boot' version '2.4.5'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
  useJUnitPlatform()
}

依赖Scope对比


熟悉maven的同学都知道,maven有五种scope

maven中常用的socpe类型有

  • compile: 编译时依赖 运行时依赖,默认的scope,最常用
  • runtime: 运行时依赖,例如mysql-connector包,只在运行时依赖
  • provided: 仅编译时依赖,最常用例如lombok,运行时不需要该包;或者编译期间需要,运行时由容器或者其他库提供
  • test: 测试依赖

gradle常用的依赖scope有

  • 编译 运行时依赖 implemention,但是不传递编译依赖,只传递运行依赖 api既传递编译依赖,也传递运行依赖
  • 运行时依赖 runtimeOnly,类似maven的runtime
  • 编译时依赖 compileOnly,类似maven的provided
  • 测试依赖 testImplemention, 类似maven的test

与gradle的对比如下

代码语言:javascript复制
Maven:
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-starter-web</artifactId>
      <scope>compile</scope>
</dependency>

Gradle: 
implementation("org.springframework.boot:spring-boot-starter-starter-web")
代码语言:javascript复制
Maven:
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-starter-web</artifactId>
      <scope>runtime</scope>
</dependency>

Gradle:
runtimeOnly("org.springframework.boot:spring-boot-starter-starter-web")
代码语言:javascript复制
Maven:
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-starter-web</artifactId>
      <scope>provided</scope>
</dependency>

Gradle:
compileOnly("org.springframework.boot:spring-boot-starter-starter-web")
代码语言:javascript复制
Maven:
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-starter-web</artifactId>
      <scope>test</scope>
</dependency>

Gradle:
testImplemention("org.springframework.boot:spring-boot-starter-starter-web")

Optional的配置


回顾下maven中的option如何配置,以及什么作用

代码语言:javascript复制
<dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <optional>true</optional> <!-- value will be true or false only -->
</dependency>

假设有一个名为X2的项目具有与Hibernate类似的功能。它支持许多数据库,如MySQL、PostgreSQL和多个版本的Oracle。每个受支持的数据库都需要对驱动程序jar的附加依赖。编译时需要所有这些依赖关系来构建X2。但是,您的项目只使用一个特定的数据库,其他数据库不需要驱动程序。X2可以将这些依赖项声明为可选的,这样当您的项目在其POM中将X2声明为直接依赖项时,X2支持的所有驱动程序不会自动包含在项目的类路径中。您的项目必须包含对它所使用的数据库的特定驱动程序的显式依赖关系。

其实 Optional依赖不是optional的,在上述例子中,如果使用Porject-A的功能,但是没有引用Project-A的依赖,就会ClassNotFoundException异常。关于optional的诟病,推荐你参考下Gradle的博客文章

《Optional dependencies are not optional》

https://blog.gradle.org/optional-dependencies

既然如此,gradle对其进行了语义上的改进,将其声明为一个feature,看起来更加直观

代码语言:javascript复制
java {
    registerFeature('mysqlSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mysql-support', '1.0')
    }
    registerFeature('postgresSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-postgres-support', '1.0')
    }
    registerFeature('mongoSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mongo-support', '1.0')
    }
}

dependencies {
    mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
    postgresSupportImplementation 'org.postgresql:postgresql:42.2.5'
    mongoSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}

这里定义了一个feature的三个实现,意味着本模块同时支持如下三种数据库,但是不传递依赖,使用者按需导入runtime依赖。

  • mysql-support provides both the db-support and mysql-support capabilities
  • postgres-support provides both the db-support and postgres-support capabilities
  • mongo-support provides both the db-support and mongo-support capabilities

在另一个项目中如果想要使用mysql的功能,声明如下配置即可

代码语言:javascript复制
// Let's try to ask for MySQL support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }

看起来确实比maven清晰了不少。其实registerFeature存在compile和runtime依赖的。

考虑这样一种情况,你是spring boot项目的开发者,现在在开发spring-boot-autoconfigure自动配置项目,该项目是一个sub-module,不独立运行,仅仅做编译,被其他模块依赖,具体运行时依赖由各个spring-boot-starter-xxx提供,你这时需要大量的registerFeature,可能会显得特别臃肿。Spring编写了能像maven那样使用optional的插件,有需要的同学,直接复制代码粘贴到项目中即可使用。代码如下:

代码语言:javascript复制
public class OptionalDependenciesPlugin implements Plugin<Project> {

  /**
   * Name of the {@code optional} configuration.
   */
  public static final String OPTIONAL_CONFIGURATION_NAME = "optional";

  @Override
  public void apply(Project project) {
    Configuration optional = project.getConfigurations().create(OPTIONAL_CONFIGURATION_NAME);
    optional.attributes((attributes) -> attributes.attribute(Usage.USAGE_ATTRIBUTE,
        project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)));
    project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
      SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class)
          .getSourceSets();
      sourceSets.all((sourceSet) -> {
        sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(optional));
        sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(optional));
      });
      project.getTasks().withType(Javadoc.class)
          .all((javadoc) -> javadoc.setClasspath(javadoc.getClasspath().plus(optional)));
    });
    project.getPlugins().withType(EclipsePlugin.class,
        (eclipsePlugin) -> project.getExtensions().getByType(EclipseModel.class)
            .classpath((classpath) -> classpath.getPlusConfigurations().add(optional)));
  }

}

Tips :optional插件是编译和运行时都依赖,但其实spring-boot-autoconfigure中有些依赖也是不需要运行时的,例如,spring-web,该依赖已经在spring-boot-starter-web或者其他starter中提供了,如果不考虑测试的话,完全可以使用compileOnly。spring这里加上了runtime,应该仅仅是为了测试。毕竟spring-boot-configure模块不是一个独立启动的,仅仅是一个sub-module。

我该使用哪种依赖?


  • 当你不需要传递依赖时使用 implemention
  • 当你需要传递依赖时使用 api
  • 当你只需要测试时使用 testImplemention
  • 当你只需要编译和测试,例如spring的autoconfigure,使用optioanl插件,也就是gradle的registerFeature
  • 当你只需要编译,测试也不需要,因为你确定容器或者其他模块一定会存在这个依赖,例如servlet api,使用compileOnly,也就是maven中的provided
  • 当你按需导入实现,例如数据库连接mysql postgresql oracle等,只需要在运行时需要,使用runtimeOnly

Tasks 任务


一个简单的任务

代码语言:javascript复制
abstract class GreetingTask extends DefaultTask{

    @TaskAction
    def greet(){
        println("hello from task")
    }
}

tasks.register("hello", GreetingTask)

在Tasks列表中的other栏下会出现 hello

在命令行输入gradle -q 或者双击hello就可以执行该任务

代码语言:javascript复制
> gradle -q hello
hello from task

一个可配置的任务

代码语言:javascript复制
abstract class GreetingTask extends DefaultTask {
    @Input
    abstract Property<String> getGreeting()

    GreetingTask() {
        greeting.convention('hello from GreetingTask')
    }

    @TaskAction
    def greet() {
        println greeting.get()
    }
}

// Use the default greeting
tasks.register('hello', GreetingTask)

// Customize the greeting
tasks.register('greeting',GreetingTask) {
    greeting = 'greetings from GreetingTask'
}

上面的代码给任务提供了一个@Input参数,执行该任务:

代码语言:javascript复制
> gradle -q hello greeting
hello from GreetingTask
greetings from GreetingTask

具体关于Tasks的知识由于篇幅原因,放到后面的文章去讲。

DependencyManagement


在maven中,在父pom工程中定义依赖的版本使用<dependencyManagement>标签,标识不引入依赖,只是声明依赖。在gradle中,可以使用 io.spring.dependency-management插件。

代码语言:javascript复制
plugins {
  //声明dependency-management插件
  id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
}

//配置所有项目都使用该插件
configure(allprojects) { project ->
  apply plugin: "io.spring.dependency-management"

使用方式如下

代码语言:javascript复制
dependencyManagement {
    imports {
      mavenBom "com.fasterxml.jackson:jackson-bom:2.12.3"
    }
    dependencies {
      dependency "org.slf4j:slf4j-api:1.7.30"
    }
}

总结


gradle是一个项目构建目前最流行的工具,正如gradle官网所说,

Build Anything

Automate Everything

Deliver Faster

希望这篇文章能帮到你!

0 人点赞