Статьи

jQuery Ajax — интеграция сервлетов: создание законченного приложения

В Интернете есть много учебных пособий, которые объясняют некоторые вещи о веб-разработке на Java с использованием сервлетов и страниц JSP, однако я никогда не находил краткого, достаточно простого для начинающих , учебное пособие. Подобное руководство должно объяснить весь процесс создания простого веб-приложения, включая интерфейс, бэкэнд и, что наиболее важно, способы, которые кто-то может использовать для взаимодействия с ними обоими . Недостаточно показать, как получать информацию с сервера, важно также знать, как дифференцировать информацию в структурированном виде , а также знать, как вносить изменения в бэкэнд через среду приложения .

В этом посте мы хотим достичь цели — провести весь процесс создания полноценного «игрушечного» веб-приложения . Это «игрушечное» приложение в том смысле, что оно делает только две вещи, и мы не используем никаких дополнительных функций, чтобы сделать окружающую среду красивой. Цель приложения будет простой:

  • Добавьте название группы со списком альбомов (разделенных запятыми) и нажмите кнопку «Отправить» , чтобы добавить их в базу данных.
  • Нажмите «Показать группы!» кнопку, чтобы получить список групп, или «Показать группы и альбомы!» Кнопка, чтобы получить список групп с их альбомами.

Внешний вид приложения настолько прост, насколько это возможно , но его код — это все, что вам нужно для создания собственных динамических веб-приложений, которые чаще всего называются приложениями CRUD ( C reate, R ead, U pdate, D elete ). Они называются так, потому что все их функциональные возможности могут быть абстрагированы от этих самых основных команд.

Прежде чем приступить к созданию приложения шаг за шагом, давайте рассмотрим все инструменты, с которыми мы будем работать в этом примере:

  • Затмение Луны
  • Java 7
  • Tomcat 7 (сервер веб-приложений)
  • Gson 2.3 (библиотека Google Java)
  • JQuery 2.1.1 (библиотека Javascript)

1. Интерфейс (страница JSP)

Здесь особо нечего сказать. Если вы следовали другим примерам, вы узнаете, как легко создать динамический веб-проект в Eclipse и создать страницу index.jsp в папке WebContent . Это будет главная страница нашего приложения, и мы не будем использовать другие страницы. Конечно, это всегда зависит от типа приложения, которое вам нужно создать, но для наших нужд здесь достаточно одной страницы.

index.jsp

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title>Ajax - Servlets Integration Example</title>
         
        <!-- Load the scripts needed for the application. -->
        <script type="text/javascript" src="resources/jquery-2.1.1.min.js"></script>
        <script type="text/javascript" src="resources/buttonEventsInit.js"></script>
        <script type="text/javascript" src="resources/resultsPrinter.js"></script>
        <script type="text/javascript" src="resources/insertBandInfo.js"></script>
    </head>
     
    <body>
        <h1>Ajax - Servlets Integration Example</h1>
        <p>This is an example of how to use Ajax with a servlet backend.</p></br>
         
        <h3>Select a button to get the relevant information.</h3>
         
        <!-- Buttons that will call the servlet to retrieve the information. -->
        <button id="bands" type="button">Show bands!</button>
        <button id="bands-albums" type="button">Show bands and albums!</button>
         
        <!-- We need to have some empty divs in order to add the retrieved information to them. -->
        <div id="band-results"></div></br></br>
        <div id="bands-albums-results"></div></br></br>
         
         
        <h3>Add the band information and press submit!</h3>
        <h4>Band name: </h4><input type="text" id="band-name-input" value=""><br>
        <h4>Albums: </h4><input type="text" id="album-input" value="">(Separated by commas)<br>
        <input type="submit" id="submit-band-info" value="Submit">
    </body>
</html>

Итак, довольно простые вещи, как вы можете видеть. Мы загружаем файлы javascript, которые нам понадобятся, из которых только один является внешним, JQuery . JQuery — это библиотека Javascript, которая так же известна , как и мощна . Это позволяет нам легко получать информацию о различных элементах нашей страницы, а также связывать события с этими элементами. Возьмите этот фрагмент кода для простого примера:

1
2
3
$("#my-button").click(function() {
    alert("My button was clicked!");
});

Этот фрагмент здесь означает: « При нажатии на элемент с идентификатором« my-button »я хочу вызвать функцию, которая создает всплывающее окно с предупреждением« Моя кнопка была нажата! » «. Таким образом, мы передаем целую функцию, которая что-то делает, в качестве аргумента для привязки события . Мы расскажем больше о JQuery позже. Вы можете скачать JQuery здесь .

Здесь следует принять во внимание некоторые вещи:

  • Мы дали идентификатор всем важным элементам, которые мы собираемся использовать. Таким образом, кнопки , поля ввода и пустые элементы <div> имеют уникальный идентификатор.
  • Мы создали 2 пустых элемента <div>, которые будут содержать результаты. Этот шаблон проектирования часто используется в тех случаях, когда вам нужен контейнер для вещей, и вам нужно, чтобы этот контейнер всегда был в определенной позиции . Таким образом, нам не нужно проверять, куда мы будем помещать информацию, поскольку на странице зарезервировано место для этого. Кроме того, второй div (группы с альбомами) всегда будет под первым (только названия групп). Когда мы нажимаем кнопку только для информации о группах, она будет добавлена ​​поверх групп с альбомами.

2. Запрашивая у сервера данные (внешний и внутренний)

2.1 Выполнение запроса GET с использованием Ajax из внешнего интерфейса.

Итак, первое, что нам нужно сделать, — это найти способ запросить у сервера данные, которые нам нужны, в данном случае названия групп или группы и альбомы. Мы уже добавили два идентификатора к соответствующим кнопкам ( «бэнды» и «бэнды и альбомы» ), поэтому нам необходимо привязать к ним событие, чтобы каждый раз при нажатии кнопки вызывать сервер . Для этого мы будем использовать Javascript, который содержится в файле buttonEventsInit.js .

ПРИМЕЧАНИЕ. Каждый файл Javascipt сохраняется в каталоге WebContent / resources, чтобы предоставить браузеру доступ для их получения.

buttonEventsInit.js

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// When the page is fully loaded...
$(document).ready(function() {
     
    // Add an event that triggers when ANY button
    // on the page is clicked...
    $("button").click(function(event) {
         
        // Get the button id, as we will pass it to the servlet
        // using a GET request and it will be used to get different
        // results (bands OR bands and albums).
        var buttonID = event.target.id;
         
        // Basic JQuery Ajax GET request. We need to pass 3 arguments:
        //      1. The servlet url that we will make the request to.
        //      2. The GET data (in our case just the button ID).
        //      3. A function that will be triggered as soon as the request is successful.
        // Optionally, you can also chain a method that will handle the possibility
        // of a failed request.
        $.get('DBRetrievalServlet', {"button-id": buttonID},
            function(resp) { // on sucess
                // We need 2 methods here due to the different ways of
                // handling a JSON object.
                if (buttonID === "bands")
                    printBands(resp);
                else if (buttonID === "bands-albums")
                    printBandsAndAlbums(resp);
            })
            .fail(function() { // on failure
                alert("Request failed.");
            });
    }); 
});

Давайте объясним, что здесь происходит. Как только страница загружена (мы делаем это, чтобы убедиться, что все элементы на месте), мы привязываем событие click к каждому элементу кнопки на странице. С этого момента каждый раз, когда нажимается кнопка , на сервер будет отправляться запрос GET с информацией о том, какая кнопка была нажата. Сервер отправит обратно правильный ответ (в форме объекта JSON , мы объясним позже об этом), и мы будем делать разные вещи с этим объектом в зависимости от нажатой кнопки (потому что каждая кнопка будет получать JSON с различной структурой объект).

Ознакомьтесь с комментариями к приведенному выше примеру относительно правильного способа отправки запроса GET на сервер. Вам нужно будет указать URL-адрес (который также может быть URL-адресом сервлета), данные и функцию, которая будет запускаться, имея в качестве аргумента ответ (объект JSON) сервера .

2.2 Обработка запроса и отправка данных обратно клиенту.

Итак, что происходит с сервером, когда мы делаем запрос? Здесь мы используем несколько классов, поэтому давайте еще раз вспомним, что мы создаем приложение, которое будет содержать два вида каталогов: группы и группы с альбомами. Итак, мы используем:

  • MusicDatabase.java: класс, который использует шаблон Singleton для предоставления постоянного объекта, который будет содержать информацию, которую необходимо отправить обратно клиенту.
  • DBRetrievalServlet.java: сервлет, который будет использоваться для обработки запроса GET и использования других классов, предоставит ответ с запрашиваемой информацией.
  • BandWithAlbums.java: класс, который будет использоваться для создания новых «объектов хранения данных» , в наших случаях содержащих название группы и список альбомов.

Итак, давайте рассмотрим каждый из этих классов и объясним, как они используются.

DBRetrievalServlet.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package servlets;
import informationClasses.MusicDatabase;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@WebServlet("/DBRetrievalServlet")
public class DBRetrievalServlet extends HttpServlet {
     
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
          
        // We set a specific return type and encoding
        // in order to take advantage of the browser capabilities.
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
 
         
        // Depending on the GET parameters, passed from the Ajax call,
        // we are able to differentiate the requests and call the appropriate
        // method. We can always use more classes for more use-cases.
         
        // The response object returns the information (as a JSON object in String form)
        // to the browser.
        String buttonID = request.getParameter("button-id");
        switch (buttonID) {
            case "bands":
                response.getWriter().write(MusicDatabase
                        .getInstance()
                        .getBands());
                break;
                 
            case "bands-albums":
                response.getWriter().write(MusicDatabase
                        .getInstance()
                        .getBandsAndAlbums());
                break;
        }
    }
}

Мы получаем параметр «идентификатор кнопки» , который содержится внутри информации, отправляемой клиентом (в объекте запроса), и в зависимости от того, какую кнопку мы нажали, нам нужна информация другого типа, вызывая экземпляр MusicDatabase и каждый раз вызывать другой метод.

ПРИМЕЧАНИЕ: шаблон синглтона

Об экземпляре MusicDatabase … Мы используем шаблон Singleton , что означает, что, поскольку мы хотим иметь только 1 экземпляр класса, мы не создаем новый экземпляр, вызывая new ключевое слово самостоятельно. Вместо этого мы вызываем метод из самого класса MusicDatabase и запрашиваем экземпляр.

  1. Если экземпляр еще не создан, мы создаем его и возвращаем.
  2. Если он был создан, мы возвращаем существующий экземпляр.

Нет внешнего доступа к конструктору и нет возможности случайно создать другой экземпляр. Вы можете найти больше информации о шаблоне Singleton онлайн.

MusicDatabase.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package informationClasses;
 
import java.util.ArrayList;
import java.util.List;
 
import jsonObjects.BandWithAlbums;
 
import com.google.gson.Gson;
 
public class MusicDatabase {
     
    private List bandNames;
    private List bandsAndAlbums;
     
     
    // Singleton methods
    private static MusicDatabase dbInstance = null;
     
    protected MusicDatabase() {
        bandNames = new ArrayList<>();
        bandsAndAlbums = new ArrayList<>();
    }
     
    public static MusicDatabase getInstance() {
       if(dbInstance == null) {
          dbInstance = new MusicDatabase();
       }
        
       return dbInstance;
    }
     
     
    public void setBandAndAlbums(String bandName, ArrayList bandAlbums) {
        bandNames.add(bandName);
        bandsAndAlbums.add(new BandWithAlbums(bandName, bandAlbums));
    }
     
    public String getBands() {
        return new Gson().toJson(bandNames);
    }
     
    public String getBandsAndAlbums() {
        return new Gson().toJson(bandsAndAlbums);
    }
}

Методы, которые мы собираемся изучить здесь, — это getBands() и getBandsAndAlbums() . Нам нужны только эти 2 метода, и они довольно просты, потому что у нас есть некоторые вещи, которые нам здесь помогают:

  • Gson — это библиотека Java от Google, которая позволяет нам легко создавать объекты JSON из объекта Java. Этим объектом может быть что угодно, от простой структуры данных до объектов, содержащих информацию, другие структуры данных и т. Д. В нашем случае у нас есть 2 такие структуры данных:
    • A List<String> bandNames , который содержит только имена групп в виде строк.
    • A List<BandWithAlbums> , который содержит объекты, которые в свою очередь содержат название группы и список их альбомов.
  • Класс BandWithAlbums , который позволяет нам хранить больше информации о группе. Это класс хранения данных, который содержит имя группы в виде String и список их групп в виде List<String> . Возвращая этот объект, вы также возвращаете всю связанную информацию.

В общем, библиотека Gson, использующая команду new Gson().toJson(Object obj) , может преобразовать большинство объектов и структур данных в формат JSON, который браузер может легко использовать через Javascript.

ПРИМЕЧАНИЕ. Чтобы это работало, необходимо добавить библиотеку Gson в путь к классам.

BandWithAlbums.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package jsonObjects;
 
import java.util.ArrayList;
 
public class BandWithAlbums {
 
    String bandName;
    ArrayList bandAlbums;
     
    public BandWithAlbums(String bandName, ArrayList bandAlbums) {
        this.bandName = bandName;
        this.bandAlbums = bandAlbums;
    }
}

Простой класс хранения данных, как мы уже говорили ранее. Это представляет «группу», в том смысле, что она содержит название, а также альбомы группы.

2.3 Представление данных в браузере.

Итак, как только у нас есть данные, которые мы запрашиваем, мы можем увидеть в файле buttonEventsInit.js , что у нас есть выбор вызова двух разных методов, в зависимости от идентификатора кнопки, которая сделала вызов. Мы собираемся показать, что делают эти два метода, и как мы собираемся представить данные обратно в браузер .

resultsPrinter.js

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Both those functions get a json object as an argument,
// which itself also holds other objects.
 
// 1. The first function is supposed to get an object
//  containing just a list of band names.
// 2. The second function is supposed to get an object containing
//  bands with albums, which essentially means a list of objects
//  which hold (1) a band name and (2) a list of albums.
 
function printBands(json) {
     
    // First empty the <div> completely and add a title.
    $("#band-results").empty()
        .append("<h3>Band Names</h3>");
         
    // Then add every band name contained in the list. 
    $.each(json, function(i, name) {
        $("#band-results").append(i + 1, ". " + name + " </br>");
    });
};
 
function printBandsAndAlbums(json) {
     
    // First empty the <div> completely and add a title.
    $("#bands-albums-results").empty()
        .append("<h3>Band Names and Albums</h3>");
     
    // Get each band object...
    $.each(json, function(i, bandObject) {
         
        // Add to the <div> every band name...
        $("#bands-albums-results").append(i + 1, ". " + bandObject.bandName + " </br>");
        // And then for every band add a list of their albums.
        $.each(bandObject.bandAlbums, function(i, album) {
            $("#bands-albums-results").append("--" + album + "</br>");
        });
    });
};

Чтобы понять, как работают эти функции, нам нужно взглянуть на объекты ответа, которые сервер вернул клиенту. В первом случае мы ожидаем просто список имен групп, поэтому ожидаемый объект будет просто списком:

1
["The Beatles", "Metallica"]

С другой стороны, во втором случае мы ожидаем получить полную информацию о полосе, и в этом случае объект json будет выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
[
    {
        bandName: "The Beatles",
        bandAlbums: ["White Album", "Let it be"]
    },
    {
        bandName: "Metallica",
        bandAlbums: ["St Anger", "The Black Album"]
    }
]

Таким образом, нам нужно два разных способа обработки запроса. Однако в каждом случае мы empty() div, который мы собираемся использовать, и добавляем информацию, которую мы только что получили от сервера, используя некоторые очень удобные функции JQuery.

Таким образом, первая часть нашего приложения завершена. Посмотрите на скриншоты, чтобы увидеть результаты.

Результаты, представленные в браузере.

Результаты, представленные в браузере.

3. Обновление сервера из пользовательского ввода (фронтенд и бэкэнд)

3.1 Создание POST-запроса с использованием Ajax.

В этой части мы рассмотрим, как отправлять данные на сервер . Мы уже получили подсказку о том, как все работает, обработав запрос GET в предыдущей части учебного пособия, и шаги здесь не сильно отличаются, за исключением того, что пользователь может предоставить входные данные для приложения. Давайте посмотрим на код, который мы используем, и функциональность каждого файла.

insertBandInfo.js

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$(document).ready(function() {
     
    // Add an event that triggers when the submit
    // button is pressed.
    $("#submit-band-info").click(function() {
         
        // Get the text from the two inputs.
        var bandName = $("#band-name-input").val();
        var albumName = $("#album-input").val();
         
        // Fail if one of the two inputs is empty, as we need
        // both a band name and albums to make an insertion.
        if (bandName === "" || albumName === "") {
            alert("Not enough information for an insertion!");
            return;
        }
         
        // Ajax POST request, similar to the GET request.
        $.post('DBInsertionServlet',{"bandName": bandName, "albumName": albumName},
            function() { // on success
                alert("Insertion successful!");
            })
            .fail(function() { //on failure
                alert("Insertion failed.");
            });
    }); 
});

Если вы следовали предыдущей части урока, очень легко понять, что мы здесь делаем. Другое событие click, теперь нацеленное только на определенный идентификатор кнопки Submit , которое после проверки того, что два поля ввода действительно имеют входные данные, выполняет запрос POST (к новому сервлету, специально используемому для этой цели), отправляя данные что мы хотим (название группы и список альбомов).

3.2 Сохранение ввода пользователя в нашей «базе данных».

DBInsertionServlet.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package servlets;
 
import informationClasses.MusicDatabase;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@WebServlet("/DBInsertionServlet")
public class DBInsertionServlet extends HttpServlet {
 
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
         
        Map<String, String[]> bandInfo = request.getParameterMap();
         
        // In this case here we are not using the data sent to just do different things.
        // Instead we are using them as information to make changes to the server,
        // in this case, adding more bands and albums.
        String bandName = Arrays.asList(bandInfo.get("bandName")).get(0);
        String albums = Arrays.asList(bandInfo.get("albumName")).get(0);
 
        MusicDatabase.getInstance()
            .setBandAndAlbums(bandName, getAlbumNamesFromString(albums));
         
        // return success
        response.setStatus(200);
    }
 
    // Split the album String in order to get a list of albums.
    private ArrayList getAlbumNamesFromString(String albums) {
        return new ArrayList(Arrays.asList(albums.split(",")));
    }
}

Когда сервлет получает запрос, он извлекает bandName из карты запроса и String содержащую имена альбомов. Мы создаем список альбомов, разбивая String на части, когда находим запятую . В конце мы вызываем экземпляр MusicDatabase, в который мы добавляем название группы и список альбомов, и если вы проверите определение класса ранее, вы увидите, что:

  • Мы добавляем название группы в список bandNames .
  • Мы создаем новый объект Band (используя имя и список альбомов) и добавляем его в bandsWithalbums .

После этого сервлет завершает работу и отправляет клиенту ответ о состоянии SUCCESS. Мы добавили все в наши списки, и он будет готов к отправке в формате JSON, когда мы его попросим. Например, давайте посмотрим, что произойдет, если я сам добавлю новую группу.

Добавление новой группы.

Добавление новой группы.

Новая группа уже находится в моей «базе данных», и после того, как попросила снова увидеть информацию, она есть!

Новая группа уже находится в моей «базе данных», и после того, как попросила снова посмотреть информацию, она есть!

4. Скачать проект

Это был пример интеграции Ajax — Servlets. Мне бы хотелось думать, что я помог вам получить полное представление о том, как реализовать каждую часть веб-приложения (frontend-backend), и о простейших способах соединения частей вместе для создания программного обеспечения, которое позволяет вводить данные и вносить изменения в сервер, а также на клиенте!

Вы можете скачать полный исходный код этого примера здесь: AjaxServletsIntegration