Basic must-see-deep understanding of the difference between gradle and maven

In-depth understanding of the difference between gradle and maven

Both gradle and maven can be used to build java programs, and even in some cases, the two can be converted to each other, so what are the common and different points of the two? How do we choose which technology to use in the project? Let's take a look.

Comparison of gradle and maven

Although both gradle and maven can be used as build tools for java programs. But there are still big differences between the two. We can analyze from the following aspects.

Scalability

Google chose gradle as the Android build tool for no reason. One of the very important reasons is that gradle is flexible enough. On the one hand, because gradle uses groovy or kotlin as the scripting language, this greatly improves the flexibility of the script, but the essential reason is that the gradle infrastructure can support this flexibility.

You can use gradle to build native C/C++ programs and even extend it to any language.

Relatively speaking, maven is less flexible, and it is more troublesome to customize, but maven projects are easier to understand and easy to use.

So if your project does not have too many custom build requirements, it is still recommended to use maven, but if you have custom build requirements, then you should invest in gradle.

Performance comparison

Although everyone's machine performance is relatively strong now, it seems that the performance advantage is not so urgent when doing project construction, but for large projects, a build may take a long time, especially for automated construction and CI In terms of environment, of course, I hope this build is as fast as possible.

Both Gradle and Maven support parallel project construction and dependency resolution. But three features of gradle make gradle run a bit faster than maven:

  • Incremental build

In order to improve the efficiency of construction, gradle proposes the concept of incremental construction. In order to achieve incremental construction, gradle divides each task into three parts, namely input input, task itself and output output. The figure below is a typical java compiled task.

Basic must-see-deep understanding of the difference between gradle and maven

Take the above picture as an example, input is the target jdk version, source code, etc., and output is the compiled class file.

The principle of incremental construction is to monitor the changes of input. Only when the input is changed, will the task task be re-executed, otherwise gradle thinks that the previous execution result can be reused.

So when writing a gradle task, you need to specify the input and output of the task.

And it should be noted that only those that change the output result can be called input. If you define variables that are completely unrelated to the initial result as input, the changes in these variables will cause gradle to re-execute the task, resulting in unnecessary performance loss.

Also pay attention to tasks with uncertain execution results, for example, the same input may get different output results, then such tasks will not be able to be configured as incremental build tasks.

  • Build cache

Gradle can reuse the output of the same input as a cache. You may have questions. Doesn't this cache and incremental compilation mean the same thing?

Yes on the same machine, but the cache can be shared across machines. If you are in a CI service, build cache will be very useful. Because the developer's build can directly pull the build results from the CI server, which is very convenient.

  • Gradle daemon

Gradle will start a daemon to interact with each build task. The advantage is that it does not need to initialize the required components and services for each build.

At the same time, because the daemon is a process that runs all the time, in addition to avoiding the overhead of each JVM startup, it can also cache the project structure, files, tasks, and other information to improve the running speed.

We can run gradle -status to view the running daemons process.

Since Gradle 3.0, daemons are enabled by default. You can use org.gradle.daemon=false to disable daemons.

We can intuitively feel the performance comparison of gradle and maven through the following figures:

  • Comparison of using gradle and maven to build Apache Commons Lang 3:

Basic must-see-deep understanding of the difference between gradle and maven

  • Comparison of using gradle and maven to build small projects (10 modules, 50 source files and 50 test files for each module):

Basic must-see-deep understanding of the difference between gradle and maven

  • Comparison of using gradle and maven to build large projects (500 modules, 100 source files and 100 test files for each module):

Basic must-see-deep understanding of the difference between gradle and maven

You can see that the performance improvement of gradle is very obvious.

The difference of dependence

Both gralde and maven can cache dependent files locally, and both support parallel download of dependent files.

In maven, only one dependency can be overwritten by the version number. Gradle is more flexible. You can customize dependencies and replacement rules. Through these replacement rules, gradle can build very complex projects.

Migrate from maven to gradle

Because maven appeared earlier, basically all java projects support maven, but not all projects support gradle. If you have an idea of ​​migrating a maven project to gradle, let's take a look.

According to our previous introduction, you can find that gradle and maven are essentially different. Gradle organizes tasks through task DAG graphs, while maven executes tasks through goals attached to phases.

Although the construction of the two is very different, thanks to the various conventions and rules that gradle and maven meet, it is not so difficult to migrate from maven to gradle.

To migrate from maven to gradle, you must first understand the maven build life cycle. The maven life cycle includes clean, compile, test, package, verify, install, and deploy phases.

We need to convert the life cycle phase of maven to gradle life cycle tasks. Here you need to use gradle's Base Plugin, Java Plugin and Maven Publish Plugin.

First look at how to introduce these three plugins:

plugins {
    id 'base'
    id 'java'
    id 'maven-publish'
}

Clean will be converted into clean task, compile will be converted into classes task, test will be converted into test task, package will be converted into assemble task, verify will be converted into check task, install will be converted into Maven Publish Plugin publishToMavenLocal task, deploy will be converted into publish task in Maven Publish Plugin.

With the correspondence between these tasks, we can try to convert from maven to gradle.

Automatic conversion

In addition to using the gradle init command to create a gradle shelf, we can also use this command to convert a maven project into a gradle project. The gradle init command will read the pom file and convert it into a gradle project.

Conversion dependency

Both gradle and maven dependencies include group ID, artifact ID and version number. The two are essentially the same, but the form is different. Let's look at an example of conversion:

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
</dependencies>

The above is a maven example, let's see how to write the gradle example:

dependencies {
    implementation 'log4j:log4j:1.2.12'  
}

You can see that gradle is much easier to write than maven.

Note that the implementation here is actually implemented by Java Plugin.

We sometimes use the scope option in maven dependencies to indicate the scope of the dependency. Let's see how these scopes can be converted:

  • compile:

There are two configurations to replace compile in gradle, we can use implementation or api.

The former can be used in any gradle that uses Java Plugin, while the api can only be used in projects that use Java Library Plugin.

Of course, there is a difference between the two. If you are building applications or webapps, then implementation is recommended. If you are building Java libraries, then api is recommended.

  • runtime:

Can be replaced with runtimeOnly.

  • test:

There are two types of tests in gradle. One is needed when compiling the test project, then testImplementation can be used, and the other is needed when running the test project, then testRuntimeOnly can be used.

  • provided:

Can be replaced by compileOnly.

  • import:

In maven, import is often used in dependencyManagement, usually used to import dependencies from a pom file, so as to ensure the consistency of the dependent project version in the project.

In gradle, you can use platform() or enforcedPlatform() to import pom files:

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE') 

    implementation 'com.google.code.gson:gson' 
    implementation 'dom4j:dom4j'
}

For example, in the above example, we imported spring-boot-dependencies. Because the version number of the dependency is already defined in this pom, we don't need to specify the version number when we introduce gson later.

The difference between platform and enforcedPlatform is that enforcedPlatform will overwrite the imported version number of the pom:

dependencies {
    // import a BOM. The versions used in this file will override any other version found in the graph
    implementation enforcedPlatform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')

    // define dependencies without versions
    implementation 'com.google.code.gson:gson'
    implementation 'dom4j:dom4j'

    // this version will be overridden by the one found in the BOM
    implementation 'org.codehaus.groovy:groovy:1.8.6'
}

Convert repositories

Gradle can be compatible with the use of maven or lvy repository. Gradle does not have a default warehouse address, so you must manually specify one.

You can use maven repository in gradle:

repositories {
    mavenCentral()
}

We can also directly specify the address of the maven warehouse:

repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }
}

If you want to use the local maven warehouse, you can use it like this:

repositories {
    mavenLocal()
}

But mavenLocal is not recommended, why?

mavenLocal is just a local cache of maven, and its content is not complete. For example, a local maven repository module may only contain jar files, but not source or javadoc files. Then we will not be able to view the source code of this module in gradle, because gradle will first look for this module in the local maven path.

And the local repository cannot be trusted, because the content inside can be easily modified without any verification mechanism.

Control dependent version

If there are two dependencies of different versions for the same module in the same project, by default Gradle will select the dependency package with the highest version after parsing the DAG.

But this is not necessarily correct, so we need to customize the functions that depend on the version.

The first is the use of platform() and enforcedPlatform() mentioned above to import BOM (packaging type is POM) files.

If our project depends on a certain module, and this module depends on another module, we call it transitive dependency. In this case, if we want to control the version of the transitive dependency, for example, upgrade the version of the transitive dependency to a new version, then we can use dependency constraints:

dependencies {
    implementation 'org.apache.httpcomponents:httpclient'
    constraints {
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because 'previous versions have a bug impacting this application'
        }
        implementation('commons-codec:commons-codec:1.11') {
            because 'version 1.9 pulled from httpclient has bugs affecting this application'
        }
    }
}

Note that dependency constraints are only valid for transitive dependencies. If the commons-codec in the above example is not transitive, it will not have any effect.

At the same time, Dependency constraints need the support of Gradle Module Metadata, which means that this feature is only supported if your module is released in gradle, and it is not supported if it is released in maven or ivy.

The above is the version upgrade of transitive dependencies. The same is a transitive dependency. If this project also needs to use this transitively dependent module, but needs to use a lower version (because the default gradle will use the latest version), you need to use the version downgrade.

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
    implementation('commons-codec:commons-codec') {
        version {
            strictly '1.9'
        }
    }
}

We can specify a specific version in the implementation.

Strict means to force the matching of a specific version number. In addition to strictly, there is also require, which means that the required version number is greater than or equal to the given version number. prefer, if no other version number is specified, then use prefer. Reject, refuse to use this version.

In addition, you can also use the Java Platform Plugin to specify a specific platform to limit the version number.

Finally, look at how to exclude a dependency:

dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
}

Multi-module project

Multi-module projects can be created in maven:

<modules>
    <module>simple-weather</module>
    <module>simple-webapp</module>
</modules>

We can do the same thing in gradle settings.gradle:

rootProject.name = 'simple-multi-module'  

include 'simple-weather', 'simple-webapp'

profile and attributes

Profile can be used in maven to distinguish different environments. In gradle, we can define different profile files, and then load them through scripts:

build.gradle:

if (!hasProperty('buildProfile')) ext.buildProfile = 'default'  

apply from: "profile-${buildProfile}.gradle"  

task greeting {
    doLast {
        println message  
    }
}

profile-default.gradle:

ext.message = 'foobar'

profile-test.gradle:

ext.message = 'testing 1 2 3'

We can run it like this:

> gradle greeting
foobar

> gradle -PbuildProfile=test greeting
testing 1 2 3

Resource handling

There is a process-resources stage in maven, which can execute resources:resources to copy resource files.

The processResources task of the Java plugin in Gradle can also do the same thing.

For example, I can perform the copy task:

task copyReport(type: Copy) {
    from file("buildDir/reports/my-report.pdf")
    into file("buildDir/toArchive")
}

More complicated copy:

task copyPdfReportsForArchiving(type: Copy) {
    from "buildDir/reports"
    include "*.pdf"
    into "buildDir/toArchive"
}

Of course there are more complex applications for copying. I won't explain it in detail here.

Guess you like

Origin blog.csdn.net/doubututou/article/details/112919208