2017年7月18日 星期二

[ Gradle IA ] Ch3 - Building a Gradle project by example

Preface 
This chapter covers 
■ Building a full-stack Java project with Gradle
■ Practicing efficient web application development
■ Customizing default conventions to adapt to custom requirements
■ Using the Gradle wrapper

Chapter 2 introduced Gradle’s feature set and showed how it compared to other JVM build tools. Some simple examples gave you a first impression of the tool’s expressive build language. By running your first build script, you saw how easy it is to become productive on the command line. Now it’s time to strengthen this newly acquired knowledge by building a real-world Java project. 

When starting a brand-new application, Java doesn’t guide you toward a standardized project structure. You may ask yourself where to put source, configuration, and library files. What if you want to separate your application code from your test source files? Gradle provides a build-by-convention approach for certain domains like Java projects by introducing predefined project layouts with sensible defaults. Stuck with a legacy application that has a different directory structure? No problem! Gradle allows for adapting its conventions to your needs. 

In this chapter, you’ll explore the inner workings of Gradle’s standardization paradigm by building a Java project and learning how to tailor it to nonconventional use cases. In the next step, you’ll extend your application by a web component and introduce productivity tools for fast development turnarounds. Then we’ll round out this chapter by looking at the Gradle wrapper, which allows you to create transferable and reproducible builds without having to install the Gradle runtime. 

Introducing the case study 
This section introduces a simple application to illustrate the use of Gradle: a To Do application. Throughout the book, we’ll apply the content to demonstrate Gradle’s features in each phase of the build pipeline. The use case starts out as a plain Java application without a GUI, simply controlled through console input. Over the course of this chapter, you’ll extend this application by adding components to learn more advanced concepts. 

The To Do application will act as a vehicle to help you gain a broad knowledge of Gradle’s capabilities. You’ll learn how to apply Gradle’s standard plugins to bootstrap, configure, and run your application. By the end of this chapter, you’ll have a basic understanding of how Gradle works that you can apply to building your own webbased Java projects with Gradle. 

The To Do application 
Today’s world is busy. Many of us manage multiple projects simultaneously, both in our professional and private lives. Often, you may find yourself in situations where you feel overwhelmed and out of control. The key to staying organized and focused on priorities is a well-maintained to-do list. Sure, you could always write down your tasks on a piece of paper, but wouldn’t it be convenient to be able to access your action items everywhere you go? Access to the internet is almost omnipresent, either through your mobile phone or publicly available access points. You’re going to build your own web-based and visually appealing application, as shown in figure 3.1. 

Task management use cases 
Now that you know your end goal, let’s identify the use cases the application needs to fulfill. Every task management system consists of an ordered list of action items or tasks. A task has a title to represent the action needed to complete it. Tasks can be added to the list and removed from the list, and marked active or completed to indicate their status. The list should also allow for modifying a task’s title in case you want to make the description more accurate. Changes to a task should automatically get persisted to a data store. 

To bring order to your list of tasks, you’ll include the option to filter tasks by their status: active or completed. For now, we’ll stick with this minimal set of features. Figure 3.2 shows a screenshot of the user interface rendered in a browser. 

Let’s take a step back from the user interface aspect and build the application from the ground up. In its first version, you’ll lay out its foundation by implementing the basic functionality controlled through the command line. In the next section, we’re going to focus on the application’s components and the interactions between them. 

Examining the component interaction 
We found that a To Do application implements the typical create, read, update, and delete (CRUD) functionality. For data to be persisted, you need to represent it by a model. You’ll create a new Java class called ToDoItem, a plain old Java object (POJO) acting as a model. To keep the first iteration of the solution as simple as possible, we won’t introduce a traditional data store like a database to store the model data. Instead, you’ll keep it in memory, which is easy to implement. The class implementing the persistence contract is called InMemoryToDoRepository. The drawback is that you can’t persist the data after shutting down the application. Later in the book, we’ll pick up this idea and show how to write a better implementation for it. 

Every standalone Java program is required to implement a main class, the application’s entry point. Your main class will be called ToDoApp and will run until the user decides to exit the program. You’ll present users with a menu of commands through which they can manage their to-do list by typing in a letter triggering a specific action. Each action command is mapped to an enum called CommandLineInput. The class CommandLineInputHandler represents the glue between user interaction and command execution. Figure 3.3 illustrates the object interaction arranged in a time sequence for the use case of listing all available tasks. 

Now you’re ready to implement the application’s functionality. In the next section, we’ll dive right into the code. 

Building the application’s functionality 
In the last section, we identified the classes, their functions, and the interaction between them. Now it’s time to fill them with life. First, let’s look at the model of a todo action item. 

THE TO DO MODEL CLASS 
Each instance of the ToDoItem class represents an action item in your to-do list. The attribute id defines the item’s unique identity, enabling you to store it in the in-memory data structure and read it again if you want to display it in the user interface. Additionally, the model class exposes the fields name and completed. For brevity, the getter and setter methods as well as the compareTo method are excluded from the snippet: 
package com.manning.gia.todo.model; 
  1. public class ToDoItem implements Comparable {  
  2.     private Long id;  
  3.     private String name;  
  4.     private boolean completed;  
  5.     (...)  
  6. }  
Now let’s look at the repository implementation for reading and writing the model. 

IN-MEMORY PERSISTENCE OF THE MODEL 
Storing data in memory is convenient and simplifies the implementation. Later in the book, you may want to provide more sophisticated implementations like database or file persistence. To be able to swap out the implementation, you’ll create an interface, the ToDoRepository, as shown in the following listing. 
- Listing 3.1 The repository interface 
  1. package todo.repository;  
  2.   
  3. import java.util.List;  
  4.   
  5. import todo.ToDoItem;  
  6.   
  7. public interface ToDoRepository {  
  8.     List findAll();  
  9.     ToDoItem findById(Long id);  
  10.     Long insert(ToDoItem toDoItem);  
  11.     void update(ToDoItem toDoItem);  
  12.     void delete(ToDoItem toDoItem);  
  13. }  
The interface declares all the CRUD operations you’d expect. You can find all existing to-do items, look up a specific one by ID, insert new action items, and update or delete them. Next, you’ll create a scalable and thread-safe implementation of this interface. The next listing shows the class InMemoryToDoRepository, which stores todo items in an instance of a ConcurrentHashMap
- Listing 3.2 In-memory persistence of to-do items 
  1. package todo.repository;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collections;  
  5. import java.util.List;  
  6. import java.util.concurrent.ConcurrentHashMap;  
  7. import java.util.concurrent.ConcurrentMap;  
  8. import java.util.concurrent.atomic.AtomicLong;  
  9.   
  10. import todo.ToDoItem;  
  11.   
  12. public class InMemoryToDoRepository implements ToDoRepository{  
  13.     private AtomicLong currentId = new AtomicLong();  
  14.     private ConcurrentMap toDos = new ConcurrentHashMap();  
  15.   
  16.     @Override  
  17.     public List findAll() {  
  18.         List toDoItems = new ArrayList(toDos.values());  
  19.         Collections.sort(toDoItems);  
  20.         return toDoItems;  
  21.     }  
  22.   
  23.     @Override  
  24.     public ToDoItem findById(Long id) {  
  25.         return toDos.get(id);  
  26.     }  
  27.   
  28.     @Override  
  29.     public Long insert(ToDoItem toDoItem) {  
  30.         Long id = currentId.incrementAndGet();  
  31.         toDoItem.setId(id);  
  32.         toDos.putIfAbsent(id, toDoItem);  
  33.         return id;  
  34.     }  
  35.   
  36.     @Override  
  37.     public void update(ToDoItem toDoItem) {  
  38.         toDos.replace(toDoItem.getId(), toDoItem);  
  39.     }  
  40.   
  41.     @Override  
  42.     public void delete(ToDoItem toDoItem) {  
  43.         toDos.remove(toDoItem.getId());  
  44.     }  
  45. }  
So far, you’ve seen the data structure of a to-do item and an in-memory implementation for storing and retrieving the data. To be able to bootstrap the Java program, you’ll need to create a main class. 

THE APPLICATION’S ENTRY POINT 
The class ToDoApp prints the application’s options on the console, reads the user’s input from the prompt, translates the one-letter input into a command object, and handles it accordingly, as shown in the next listing. 
- Listing 3.3 Implementing the main class 
  1. package todo;  
  2.   
  3. import todo.utils.CommandLineInput;  
  4. import todo.utils.CommandLineInputHandler;  
  5.   
  6. public class ToDoApp {  
  7.     public static final char DEFAULT_INPUT = '\u0000';  
  8.       
  9.     public static void main(String args[])    
  10.     {  
  11.         CommandLineInputHandler commandLineInputHandler = new CommandLineInputHandler();  
  12.         char command = DEFAULT_INPUT;  
  13.         while(CommandLineInput.EXIT.getShortCmd() != command) {  
  14.             commandLineInputHandler.printOptions();  
  15.             String input = commandLineInputHandler.readInput();  
  16.             char[] inputChars = input.length() == 1 ? input.toCharArray() : new char[] { DEFAULT_INPUT };  
  17.             command = inputChars[0];  
  18.             CommandLineInput commandLineInput = CommandLineInput.getCommandLineInputForInput(command);  
  19.             commandLineInputHandler.processInput(commandLineInput);  
  20.         }  
  21.     }  
  22. }  
So far, we’ve discussed the components of the application and their interactions in the context of a specific use case: finding all to-do items of a user. Listing 3.3 should give you a rough idea of the components responsibilities and how they work internally. Don’t worry if you don’t understand every little implementation detail of the class definitions presented here. What’s more important is the automation of the project. We’ll look at specific concerns like setting up the project with Gradle, compiling the source code, assembling the JAR file, and running the application in the rest of the chapter. It’s time for Gradle to hit the stage. 

Building a Java project 
In the last section, we identified the Java classes required to write a standalone To Do application. To assemble an executable program, the source code needs to be compiled and the classes need to be packaged into a JAR file. The Java Development Kit (JDK) provides development tools like javac and jar that help with implementing these tasks. Unless you’re a masochist, you don’t want to run these tasks manually each and every time your source code changes. Gradle plugins act as enablers to automate these tasks. A plugin extends your project by introducing domain-specific conventions and tasks with sensible defaults. One of the plugins that Gradle ships with is the Java plugin. The Java plugin goes far beyond the basic functionality of source code compilation and packaging. It establishes a standard layout for your project and makes sure that tasks are executed in the correct order so they make sense in the context of a Java project. It’s time to create a build script for your application and apply the Java plugin. 

Using the Java plugin 
In chapter 1, you learned that every Gradle project starts with the creation of the build script named build.gradle. Create the file and tell your project to use the Java plugin like this: 
  1. apply plugin: 'java'  
One line of code is enough to build your Java code, but how does Gradle know where to find your source files? One of the conventions the Java plugin introduces is the location of the source code. By default, the plugin searches for production source code in the directory src/main/java. You’ll take all the classes of your To Do application and put them under the appropriate directory. 
Automatic project generation 
Wouldn’t it be great if you didn’t have to create the source directories manually? Maven has a concept called project archetypes, a plugin to generate a project structure from an existing template. Unfortunately, at the time of writing this functionality hasn’t become a Gradle core feature. The plugin Gradle Templates created by the Gradle community proposes a solution to this issue. It’s available at https://github.com/townsfolk/gradle-templates. A first attempt at initializing a Gradle project is automatically made by the build setup plugin, which you can use even without a build script. This plugin allows for generating the project file (and other related files you’ll learn about later). To generate the Gradle build script, execute gradle setupBuild from the command line.

When creating the source files, keep in mind that the package you used for the classes directly translates into subdirectories under the root source directory. After creating the build script and moving your source code into the correct location, your project structure should look like this: 

BUILDING THE PROJECT 
You’re ready to build the project. One of the tasks the Java plugin adds to your project is named buildThe build task compiles your code, runs your tests, and assembles the JAR file, all in the correct order. Running the command gradle build should give you an output similar to this: 

Each line of the output represents an executed task provided by the Java plugin. You may notice that some of the tasks are marked with the message UP-TO-DATE. That means that the task was skipped. Gradle’s incremental build support automatically identified that no work needed to be done. Especially in large enterprise projects, this feature proves to be a real timesaver. In Chapter 4 you’ll learn how to apply this concept to your own tasks. In the command-line output, you can see concrete examples of skipped tasks: compileTestJava and testClasses. As you provide any unit tests in the default directory src/test/java, Gradle happily moves on. If you want to learn how to write tests for your application and integrate them into the build, see Chapter 7. Here’s the project structure after executing the build: 

On the root level of your project, you’ll now also find a directory named build, which contains all output of the build run, including class files, test reports, the assembled JAR file, and temporary files like a manifest needed for the archive. If you’ve previously used the build tool Maven, which uses the standard output directory target, the structure should look familiar. The name of the build output directory is a configurable standard property common to all Gradle builds. You’ve seen how effortless it is to build a Java project by convention without any additional configuration from your side. The JAR file was created under build/libs and is ready for execution. It’s important to understand that the name of the JAR file is derived from the project name. Let’s see the To Do application in action. 

RUNNING THE PROJECT 
Running a Java program is easy. For now, you’ll just use the JDK’s java command from the root directory of your project: 
# java -cp build/classes/main todo.ToDoApp
--- To Do Application ---
Please make a choice:
(a)ll items
(f)ind a specific item
(i)nsert a new item
(u)pdate an existing item
(d)elete an existing item
(e)xit

The Java program starts up, prints a list of all available to-do actions, and awaits your input from the command prompt. 
Java standalone application support 
Gradle can simplify building a standalone Java application even further. Another standard Gradle extension worth mentioning is the application plugin. The plugin provides tasks for simplifying the act of running and bundling an application.

That’s it—you effortlessly implemented a Java application and built it with Gradle. All it took was a one-liner in your build script as long as you stuck to the standard conventions. Next, we’ll look at how to customize the build-by-convention standards. 

Customizing your project 
The Java plugin is a small opinionated framework. It assumes sensible default values for many aspects of your project, like its layout. If your view of the world is different, Gradle gives you the option of customizing the conventions. How do you know what’s configurable? A good place to start is Gradle’s Build Language Reference, available at http://www.gradle.org/docs/current/dsl/. Remember the command-line option properties from chapter 2? Running gradle properties gives you a list of configurable standard and plugin properties, plus their default values. You’ll customize the project by extending the initial build script. 

MODIFYING PROJECT AND PLUGIN PROPERTIES 
In the following example, you’ll specify a version number for your project and indicate the Java source compatibility. Previously, you ran the To Do application using the java command. You told the Java runtime where to find the classes by assigning the build output directory to the classpath command-line option via -cp build/classes/main. To be able to start the application from the JAR file, the manifest MANIFEST.MF needs to contain the header Main-Class. The following listing demonstrates how to configure the default values in the build script and add a header attribute to the JAR manifest. 
- Listing 3.4 Changing properties and adding a JAR header 
  1. version = 0.1  
  2. sourceCompatibility = 1.6  
  3. jar {  
  4.     manifest {  
  5.         attributes 'Main-Class''com.manning.gia.todo.ToDoApp'  
  6.     }  
  7. }  
After assembling the JAR file, you’ll notice that the version number has been added to the JAR filename. Instead of GradleIA.jar, it reads GradleIA-0.1.jar. Now that the generated JAR file contains the main class header, you can run the application with java –jar build/libs/GradleIA-0.1.jar. Next, we’ll look at how to retrofit the project structure to a legacy layout. 

RETROFITTING LEGACY PROJECTS 
Rarely do enterprises start new software projects with a clean slate. All too often, you’ll have to integrate with a legacy system, migrate the technology stack of an existing project, or adhere to internal standards or limitations. A build tool has to be flexible enough to adapt to external constraints by configuring the default settings. 

In this section we’ll explore examples that demonstrate the customizability of the To Do application. Let’s assume you started the project with a different directory layout. Instead of putting source code files into src/main/java, you chose to use the directory src. The same concept applies if you want to change the default test source directory. Additionally, you’d like to let Gradle render its build output into the directory out instead of the standard value build. The next listing shows how to adapt your build to a custom project layout. 
- Listing 3.5 Changing the project default layout 
  1. sourceSets {  
  2.     main {  
  3.         java {  
  4.             srcDirs = ['src']  
  5.         }  
  6.     }  
  7.     test {  
  8.         java {  
  9.             srcDirs = ['test']  
  10.         }  
  11.     }  
  12. }  
  13. buildDir = 'out'  
The key to customizing a build is knowledge of the underlying properties and DSL elements. Next, we’ll look at how to use functionality from external libraries. 

Configuring and using external dependencies 
Let’s think back to the main method in the class ToDoApp. You wrote some code to read the user’s input from the console and translate the first character into a to-do command. To do so, you needed to make sure that the entered input string had a length of only one digit. Otherwise, you’d assign the Unicode null character: 
  1. String input = commandLineInputHandler.readInput();  
  2. char[] inputChars = input.length() == 1 ? input.toCharArray() : new char[] { DEFAULT_INPUT };  
  3. command = inputChars[0];  
I bet you can improve on this implementation by reusing a library that wraps this logic. The perfect match is the class CharUtils from the Apache Commons Lang library. It provides a method called toChar that converts a String to a char by using just the first character, or a default character if the string’s value is empty. The following code snippet shows the improved version of your input parsing code: 
  1. import org.apache.commons.lang3.CharUtils;  
  2.   
  3. String input = commandLineInputHandler.readInput();  
  4. command = CharUtils.toChar(input, DEFAULT_INPUT);  
So how do you tell Gradle to reference the Apache Commons Lang library? We’ll look at two DSL configuration elements: repositories and dependencies. 

DEFINING THE REPOSITORY 
In the Java world, dependencies are distributed and used in the form of JAR files. Many libraries are available in a repository, such as a file system or central server. Gradle requires you to define at least one repository to use a dependency. For your purposes, you’re going to use the publicly available, internet-accessible repository Maven Central: 
  1. repositories {   
  2.     // Shortcut notation for configuring Maven Central 2 repository accessible under http://repo1.maven.org/maven2  
  3.     mavenCentral()  
  4. }  
With a repository in place, you’re ready to declare the library. Let’s look at the definition of the dependency itself. 

DEFINING THE DEPENDENCY 
A dependency is defined through a group identifier, a name, and a specific version. You’ll use version 3.1 of the library, as shown in this code snippet: 
  1. dependencies {  
  2.     compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1'  
  3. }  
In Gradle, dependencies are grouped by configurations. One of the configurations that the Java plugin introduces is compile. You can probably tell by the configuration’s name that it’s used for dependencies needed for compiling source code. 
How to find a dependency 
Finding out detailed information about a dependency on Maven Central is straightforward. The repository provides you with an easy-to-use search interface at http://search.maven.org/.

RESOLVING THE DEPENDENCY 
Gradle automatically detects new dependencies in your project. If the dependency hasn’t been resolved successfully, it downloads it with the next task invocation that requires it to work correctly—in this case, task compileJava
# gradle build
:compileJava
Download http://repo1.maven.org/maven2/org/apache/commons/
➥ commons-lang3/3.1/commons-lang3-3.1.pom
...
BUILD SUCCESSFUL

Total time: 0.561 secs

The dependent jar files are stored under: 
# tree ~/.gradle/caches/modules-2/files-2.1/ | grep common

Chapter 5 will give a deeper coverage of the topic of dependency management. I know that the To Do application in its current form doesn’t knock your socks off. It’s time to modernize it by adding a visually attractive user interface. 

Web development with Gradle 
In Java, server-side web components of the Enterprise Edition (Java EE) provide the dynamic extension capabilities for running your application within a web container or application server. As the name Servlet may already indicate, it serves a client request and constructs the response. It acts as the controller component in a Model-View-Controller (MVC) architecture. The response of a Servlet is rendered by the view component—the Java Server Page (JSP). Figure 3.4 illustrates the MVC architecture pattern in the context of a Java web application. 

A WAR (web application archive) file is used to bundle web components, compiled classes, and other resource files like deployment descriptors, HTML, JavaScript, and CSS files. Together they form a web application. To run a Java web application, the WAR file needs to be deployed to the server environment, a web container. 

Gradle provides out-of-the-box plugins for assembling WAR files and deploying web applications to a local Servlet container. Before we look at how to apply and configure these plugins, you’ll need to turn your standalone Java application into a web application. We focus next on the web components we’re going to introduce and how they interact with each other. 

Adding web components 
The Java enterprise landscape is dominated by a wide range of web frameworks, such as Spring MVC and Tapestry. Web frameworks are designed to abstract the standard web components and reduce boilerplate code. Despite these benefits, web frameworks can introduce a steep learning curve as they introduce new concepts and APIs. To keep the example as simple and understandable as possible, we’ll stick to the standard Java enterprise web components. 

Before jumping into the code, let’s see how adding web components changes the interaction between the existing classes from the previous section. The Servlet class you’re going to create is called ToDoServlet. It’s responsible for accepting HTTP requests, executing a CRUD operation mapped to a URL endpoint, and forwarding the request to a JSP. To present the user with a fluid and comfortable experience, you’ll implement the to-do list as a single-page application. This means you’ll only have to write one JSP, which you’ll name todo-list.jsp. The page knows how to dynamically render the list of to-do items and provides UI elements like buttons and links for initiating CRUD operations. Figure 3.5 shows the flow through your new system for the use case of retrieving and rendering all to-do items. 

As you can see in the figure, you could reuse the class ToDoItem to represent the model and the class InMemoryToDoRepository to store the data. Both classes work seamlessly with the controller and view components. Let’s look at the inner workings of the controller component. 

THE CONTROLLER WEB COMPONENT 
To make matters simple and centralized, you’ll write a single entry point for all URL endpoints you want to expose to the client. The following code snippet shows the most important parts of your controller web component, the class ToDoServlet
  1. package todo.web;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.List;  
  5.   
  6. import javax.servlet.RequestDispatcher;  
  7. import javax.servlet.ServletException;  
  8. import javax.servlet.http.HttpServlet;  
  9. import javax.servlet.http.HttpServletRequest;  
  10. import javax.servlet.http.HttpServletResponse;  
  11.   
  12. import todo.ToDoItem;  
  13. import todo.repository.InMemoryToDoRepository;  
  14. import todo.repository.ToDoRepository;  
  15.   
  16. public class ToDoServlet extends HttpServlet{  
  17.     private static final long serialVersionUID = 1L;  
  18.     private ToDoRepository toDoRepository = new InMemoryToDoRepository();  
  19.       
  20.     @Override  
  21.     protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  22.         String servletPath = request.getServletPath();  
  23.         String view = processRequest(servletPath, request);  
  24.         RequestDispatcher dispatcher = request.getRequestDispatcher(view);  
  25.         dispatcher.forward(request, response);  
  26.     }  
  27.       
  28.     private String processRequest(String servletPath, HttpServletRequest request) {  
  29.         if(servletPath.equals("/all")) {  
  30.             List toDoItems = toDoRepository.findAll();  
  31.             request.setAttribute("toDoItems", toDoItems);  
  32.             return "/jsp/todo-list.jsp";  
  33.         }  
  34.         else if(servletPath.equals("/delete")) {  
  35.             /*TBD*/  
  36.         }  
  37.            
  38.         // In case incoming request URL doesn’t match any handling, redirect to /all URL  
  39.         return "/all";  
  40.     }  
  41. }  
For each of the incoming requests, you get the Servlet path, handle the request in the method processRequest based on the determined CRUD operation, and forward it to the JSP todo-list.jsp using an instance of javax.servlet.RequestDispatcher. That’s it; you converted your task management program into a web application. In the examples I only touched on the most important parts of the code. For a deeper understanding, I encourage you to browse the full source code. Next, we’ll bring Gradle into play. 

Using the War and Jetty plugins 
Gradle provides extensive support for building and running web applications. In this section we’ll look at two plugins for web application development: War and Jetty. The War plugin extends the Java plugin by adding conventions for web application development and support for assembling WAR files. Running a web application on your local machine should be easy, enable rapid application development (RAD), and provide fast startup times. Optimally, it shouldn’t require you to install a web container runtime environment. Jetty is a popular, lightweight, open source web container supporting all of these features. It comes with an embedded implementation by adding an HTTP module to your application. Gradle’s Jetty plugin extends the War plugin, provides tasks for deploying a web application to the embedded container, and runs your application. 
Alternative embedded container plugins 
The Jetty plugin works great for local web application development. However, you may use a different Servlet container in your production environment. To provide maximum compatibility between runtime environments early on in the software development lifecycle, look for alternative embedded container implementations. A viable solution that works very similarly to Gradle’s standard Jetty extension is the third-party Tomcat plugin.

You already know the drill from the last section. First, you’ll apply the plugins and use the default conventions, and then you’ll customize them. Let’s focus on the War plugin first. 

THE WAR PLUGIN 
I already mentioned that the War plugin extends the Java plugin. In practice, this means that you don’t have to apply the Java plugin anymore in your build script. It’s automatically brought in by the War plugin. Note that even if you applied the Java plugin as well, there would be no side effect on your project. Applying plugins is an idempotent operation, and therefore is only executed once for a specific plugin. When creating your build.gradle file, use the plugin like this: 
  1. apply plugin: 'war'  
What exactly does that mean to your project? In addition to the conventions provided by the Java plugin, your project becomes aware of a source directory for web application files and knows how to assemble a WAR file instead of a JAR file. The default convention for web application sources is the directory src/main/webapp. With all the web resource files in the right location, your project layout should look like this: 

You implemented your web application with the help of classes that aren’t part of the Java Standard Edition, such javax.servlet.HttpServlet. Before you run the build, you’ll need to make sure that you declare those external dependencies. The War plugin introduces two new dependency configurations. The configuration you’ll use for the Servlet dependency is providedCompile. It’s used for dependencies that are required for compilation but provided by the runtime environment. The runtime environment in this case is Jetty. As a consequence, dependencies marked provided aren’t going to be packaged with the WAR file. Runtime dependencies like the JSTL library aren’t needed for the compilation process, but are needed at runtime. They’ll become part of the WAR file. The following dependencies closure declares the external libraries you need for your application: 
  1. dependencies {  
  2.     compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1'  
  3.     providedCompile 'javax.servlet:servlet-api:2.5'  
  4.     runtime 'javax.servlet:jstl:1.1.2'  
  5. }  
BUILDING THE PROJECT 
Building a web application in Gradle is as straightforward as building a standalone Java application. The assembled WAR file can be found in the directory build/libs after running the command gradle buildBy changing the nature of the project from a standalone application to a web application, the task jar was replaced by the task war, as shown in the following output: 
# gradle build
:compileJava
:processResources NO-SOURCE
:classes
:war
:assemble
:compileTestJava NO-SOURCE
:processTestResources NO-SOURCE
:testClasses UP-TO-DATE
:test NO-SOURCE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL

The War plugin makes sure that the assembled WAR file adheres to the standard structure defined by the Java EE specification. The war task copies the contents of the default web application source directory src/main/webapp to the root of the WAR file without modifying the structure. Compiled classes end up in the directory WEB-INF/classes, and runtime libraries, defined through the dependencies closure, get put in WEB-INF/lib. The following directory structure shows the contents of the assembled WAR file after running jar tf build/libs/GradleIA-0.1.war

By default, the WAR filename is derived from the project’s directory name. Even if your project doesn’t adhere to Gradle’s standard conventions, the plugin can be used to build a WAR file. Let’s look at some customization options. 

CUSTOMIZING THE WAR PLUGIN 
You’ve seen how easy it is to adapt a Java project to custom project structures. The same holds true for unconventional web project layouts. In the following example, we’re going to assume that all of your static files sit in the directory static, and that all of your web application content resides under the directory webfiles: 



The following code snippet shows how to configure the convention properties. The War plugin exposes the convention property webAppDirName. The default value src/main/webapp is easily switched to webfiles by assigning a new value. Directories can be selectively added to the WAR file by invoking the from method, as follows: 
  1. webAppDirName = 'webfiles'  
  2. war {  
  3.     from 'static'  
  4. }  
The previous example only showed an excerpt of the War plugin’s configuration options. You can easily include other external JAR files, use a web deployment descriptor from a nonstandard directory, or add another file set to the WEB-INFdirectory. If you’re looking for a configuration parameter, the best place to check is the War plugin DSL guide

You’ve seen how to build the WAR file from a web project with a standard structure or customized directory layout. Now it’s time to deploy the file to a Servlet container. In the next section, you’ll fire up Jetty to run the application on your local development machine. 

RUNNING IN AN EMBEDDED WEB CONTAINER 
An embedded Servlet container doesn’t know anything about your application until you provide the exact classpath and relevant source directories of your web application. Usually, you’d do that programmatically. Internally, the Jetty plugin does all this work for you. As the War plugin exposes all this information, it can be accessed at runtime by the Jetty plugin. This is a typical example of a plugin using another plugin’s configuration through the Gradle API. In your build script, use the plugin like this: 
  1. apply plugin: 'jetty'  
The task you’re going to use to run the web application is jettyRun. It’ll start the Jetty container without even having to create a WAR file. The output of running the task on the command line should look similar to this: 
# gradle jettyRun
:compileJava
:processResources UP-TO-DATE
:classes
> Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty

On the last line of the output, the plugin gives you the URL that Jetty listens to for incoming requests. Open your favorite browser and enter the URL. Finally, you can see the To Do web application in action. Gradle will leave the application running until you stop it by pressing Ctrl + C. How did Jetty know what port and context to use for running the application? Again, it’s conventions. The default port of a web application run by the Jetty plugin is 8080, and the context path GradleIA is derived from your project name. Of course, all of this is configurable. 
Rapid application development 
Having to restart the container for every single change you make to your application code is cumbersome and time-consuming. The Jetty plugin allows you to change static resources and JSP files on the fly without having to restart the container. Additionally, bytecode swap technologies like JRebel can be configured to perform hot deployment for class file changes.

CUSTOMIZING THE JETTY PLUGIN 
Let’s assume you’re not happy with the default values the Jetty plugin provides. Another application is already running on port 8080, and you got tired of typing in the long context path. Just provide the following configuration: 
  1. jettyRun {  
  2.     httpPort = 9090  
  3.     contextPath = 'todo'  
  4. }  
Great, you achieved what you wanted. Starting the application with this configuration will expose the URL http://localhost:9090/todo. There are many more options for configuring the Jetty plugin. A great place to start is with the API documentation of the plugin. This will help you understand all available configuration options. 

Gradle wrapper 
You put together a prototype of a task management web application. After you show it to your coworker, Mike, he says he wants to join forces and bring the application to the next level by adding more advanced features. The code has been committed to a version control system (VCS), so he can go ahead, check out the code, and get started working on it. 

Mike has never worked with the build tool Gradle, so he asks you how to install the runtime on his machine and which version to use. Because he didn’t go through the motions of initially setting up Gradle, he’s also concerned about potential differences between setting up Gradle on his Windows machine versus installing it on a Mac. From experience with other build tools, Mike is painfully aware that picking the wrong version of the build tool distribution or the runtime environment may have a detrimental effect on the outcome of the build. All too often, he’s seen that a build completes successfully on his machine but fails on another for no apparent reason. After spending hours troubleshooting, he usually discovers that the cause was an incompatible version of the runtime. 

Gradle provides a very convenient and practical solution to this problem: the Gradle wrapper. The wrapper is a core feature and enables a machine to run a Gradle build script without having to install the runtime. It also ensures that the build script is run with a specific version of Gradle. It does so by automatically downloading the Gradle runtime from a central location, unpacking it to your local file system, and using it for the build. The ultimate goal is to create reliable and reproducible builds independent of the operating system, system setup, or installed Gradle version. 
When to use the wrapper 
Using the wrapper is considered best practice and should be mandatory for every Gradle project. Gradle scripts backed by the wrapper are perfectly prepared to run as part of automated release processes like continuous integration and delivery.


Setting up the wrapper 
To set up the wrapper for your project, you’ll need to do two things: create a wrapper task and execute the task to generate the wrapper files (figure 3.6). 

To enable your project to download the zipped Gradle runtime distribution, define a task of type Wrapper and specify the Gradle version you want to use through the property gradleVersion
  1. task wrapper(type: Wrapper) {  
  2.     gradleVersion = '3.5.1'  
  3. }  
It’s not required to name the task wrapper—any name will do. However, wrapper is used throughout the Gradle online documentation and serves as a helpful convention. Execute the task: 
# gradle wrapper
:wrapper

As a result, you’ll find the following wrapper files alongside your build script: 

Keep in mind that you’ll only need to run gradle wrapper on your project once. From that point on, you can use the wrapper’s script to execute your build. The downloaded wrapper files are supposed to be checked into version control. For documentation reasons it’s helpful to also keep the task in your project. It’ll help you to upgrade your wrapper version later by changing the gradleVersion and rerunning the wrapper task. Instead of creating the wrapper task manually and executing it to download the relevant files, you can use the build setup plugin mentioned earlier. Executing the command gradle wrapper will generate the wrapper files with the current version of your Gradle runtime. 

Using the wrapper 
As part of the wrapper distribution, a command execution script is provided. For *nix systems, this is the shell script gradlew; for Windows operating systems, it’s gradlew.bat. You’ll use one of these scripts to run your build in the same way as you would with the installed Gradle runtime. Figure 3.7 illustrates what happens when you use the wrapper script to execute a task. 

Let’s get back to our friend Mike. He checked out the application code from the VCS. Included in the source code tree of the project, he’ll find the wrapper files. As Mike develops his code on a Windows box, he’ll need to run the wrapper batch file to execute a task. The following console output is produced when he fires up the local Jetty container to run the application: 
# ./gradlew build
Downloading https://services.gradle.org/distributions/gradle-3.5.1-bin.zip
...
Unzipping /root/.gradle/wrapper/dists/gradle-3.5.1-bin/5aglzaqc99i3lll5iwkbm96su/gradle-3.5.1-bin.zip to /root/.gradle/wrapper/dists/gradle-3.5.1-bin/5aglzaqc99i3lll5iwkbm96su
Set executable permissions for: /root/.gradle/wrapper/dists/gradle-3.5.1-bin/5aglzaqc99i3lll5iwkbm96su/gradle-3.5.1/bin/gradle
...
BUILD SUCCESSFUL

The distribution ZIP file is downloaded from a central server hosted by the Gradle project, stored on Mike’s local file system under $HOME_DIR/.gradle/wrapper/dists. The Gradle wrapper also takes care of unpacking the distribution and setting the appropriate permissions to execute the batch file. Note that the download only needs to happen once. Subsequent build runs reuse the unpacked installation of the runtime located in your Gradle home directory. What are the key takeaways? A build script executed by the Gradle wrapper provides exactly the same tasks, features, and behavior as it does when run with a local Gradle installation. Again, you don’t have to stick with the default conventions the wrapper gives you. Its configuration options are very flexible. We’ll look at them in the next section. 

Customizing the wrapper 
Some enterprises have very restrictive security strategies, especially if you work for a government agency, where access to servers outside of the network is prohibited. How do you enable your project to use the Gradle wrapper in that case? It’s all in the configuration. You’ll change the default properties to target an enterprise server hosting the runtime distribution. And while you’re at it, you’ll also change the local storage directory: 
  1. task wrapper(type: Wrapper) {  
  2.     // Requested Gradle version  
  3.     gradleVersion = '3.5.1'  
  4.     // Target URL to retrieve Gradle wrapper distribution  
  5.     distributionUrl = 'http://myenterprise.com/gradle/dists'  
  6.     // Path where wrapper will be unzipped relative to Gradle home directory  
  7.     distributionPath = 'gradle-dists'  
  8. }  
Pretty straightforward, right? There are many more options to explore. Make sure to check out the Gradle wrapper DSL documentation for detailed information

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...