Ответ 1
Действительно, ключевым словом является "ajax": асинхронный JavaScript и XML. Однако в последние годы это чаще, чем асинхронный JavaScript и JSON. В принципе, вы позволяете JS выполнять асинхронный HTTP-запрос и обновлять дерево DOM HTML на основе данных ответа.
Поскольку это довольно утомительная работа, чтобы заставить его работать во всех браузерах (особенно Internet Explorer и других), существует множество библиотек JavaScript который упрощает это в одиночных функциях и охватывает как можно больше ошибок/причуд для браузера под капотами, таких как jQuery, Prototype, Mootools. Поскольку jQuery наиболее популярен в наши дни, я буду использовать его в приведенных ниже примерах.
Пример Kickoff, возвращающий String
в виде обычного текста
Создайте /some.jsp
, как показано ниже (примечание: код не ожидает, что JSP файл будет помещен во вложенную папку, если вы это сделаете, измените URL сервлета соответственно):
<!DOCTYPE html>
<html lang="en">
<head>
<title>SO question 4112686</title>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
$.get("someservlet", function(responseText) { // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response text...
$("#somediv").text(responseText); // Locate HTML DOM element with ID "somediv" and set its text content with the response text.
});
});
</script>
</head>
<body>
<button id="somebutton">press here</button>
<div id="somediv"></div>
</body>
</html>
Создайте сервлет с помощью метода doGet()
, который выглядит следующим образом:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String text = "some text";
response.setContentType("text/plain"); // Set content type of the response so that jQuery knows what it can expect.
response.setCharacterEncoding("UTF-8"); // You want world domination, huh?
response.getWriter().write(text); // Write response body.
}
Сопоставьте этот сервлет по шаблону URL /someservlet
или /someservlet/*
, как показано ниже (очевидно, шаблон URL свободен по вашему выбору, но вам нужно будет изменить URL someservlet
в примерах кода JS поверх всех место соответственно):
@WebServlet("/someservlet/*")
public class SomeServlet extends HttpServlet {
// ...
}
Или, если вы еще не находитесь в контейнере, совместимом с Servlet 3.0 (Tomcat 7, Glassfish 3, JBoss AS 6 и т.д. или новее), затем сопоставьте его в web.xml
старомодным способом (см. также наша страница вики-сервлетов):
<servlet>
<servlet-name>someservlet</servlet-name>
<servlet-class>com.example.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>someservlet</servlet-name>
<url-pattern>/someservlet/*</url-pattern>
</servlet-mapping>
Теперь откройте http://localhost:8080/context/test.jsp в браузере и нажмите кнопку. Вы увидите, что содержимое div будет обновлено с ответом сервлета.
Возврат List<String>
в виде JSON
С JSON вместо открытого текста в качестве формата отклика вы можете даже сделать несколько шагов дальше. Это позволяет увеличить динамику. Во-первых, вы хотели бы иметь инструмент для преобразования между объектами Java и строками JSON. Их также много (см. Нижнюю часть эту страницу для обзора). Мой личный фаворит Google Gson. Загрузите и поместите его файл JAR в папку /WEB-INF/lib
вашего веб-приложения.
Вот пример, который показывает List<String>
как <ul><li>
. Сервлет:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<String> list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
String json = new Gson().toJson(list);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
Код JS:
$(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
$.get("someservlet", function(responseJson) { // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
var $ul = $("<ul>").appendTo($("#somediv")); // Create HTML <ul> element and append it to HTML DOM element with ID "somediv".
$.each(responseJson, function(index, item) { // Iterate over the JSON array.
$("<li>").text(item).appendTo($ul); // Create HTML <li> element, set its text content with currently iterated item and append it to the <ul>.
});
});
});
Обратите внимание, что jQuery автоматически анализирует ответ как JSON и предоставляет вам объект JSON (responseJson
) в качестве аргумента функции, когда вы устанавливаете тип содержимого ответа на application/json
. Если вы забыли установить его или полагаться на значение по умолчанию text/plain
или text/html
, то аргумент responseJson
не даст вам объект JSON, а просто ванильную строку, и вам нужно будет вручную поиграть с JSON.parse()
, что, таким образом, совершенно не нужно, если вы задаете тип контента на первом месте.
Возврат Map<String, String>
в виде JSON
Вот еще один пример, который показывает Map<String, String>
как <option>
:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Map<String, String> options = new LinkedHashMap<>();
options.put("value1", "label1");
options.put("value2", "label2");
options.put("value3", "label3");
String json = new Gson().toJson(options);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
И JSP:
$(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
$.get("someservlet", function(responseJson) { // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
var $select = $("#someselect"); // Locate HTML DOM element with ID "someselect".
$select.find("option").remove(); // Find all child elements with tag name "option" and remove them (just to prevent duplicate options when button is pressed again).
$.each(responseJson, function(key, value) { // Iterate over the JSON object.
$("<option>").val(key).text(value).appendTo($select); // Create HTML <option> element, set its value with currently iterated key and its text content with currently iterated item and finally append it to the <select>.
});
});
});
с
<select id="someselect"></select>
Возврат List<Entity>
в виде JSON
Вот пример, который показывает List<Product>
в <table>
, где класс Product
имеет свойства Long id
, String name
и BigDecimal price
. Сервлет:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Product> products = someProductService.list();
String json = new Gson().toJson(products);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
Код JS:
$(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
$.get("someservlet", function(responseJson) { // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response JSON...
var $table = $("<table>").appendTo($("#somediv")); // Create HTML <table> element and append it to HTML DOM element with ID "somediv".
$.each(responseJson, function(index, product) { // Iterate over the JSON array.
$("<tr>").appendTo($table) // Create HTML <tr> element, set its text content with currently iterated item and append it to the <table>.
.append($("<td>").text(product.id)) // Create HTML <td> element, set its text content with id of currently iterated product and append it to the <tr>.
.append($("<td>").text(product.name)) // Create HTML <td> element, set its text content with name of currently iterated product and append it to the <tr>.
.append($("<td>").text(product.price)); // Create HTML <td> element, set its text content with price of currently iterated product and append it to the <tr>.
});
});
});
Возврат List<Entity>
в формате XML
Вот пример, который делает то же самое, что и предыдущий пример, но затем вместо XML вместо JSON. При использовании JSP в качестве выходного генератора XML вы увидите, что это менее утомительно для кодирования таблицы и всего. JSTL намного эффективнее, поскольку вы можете использовать его для перебора результатов и выполнения форматирования данных на стороне сервера. Сервлет:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Product> products = someProductService.list();
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/xml/products.jsp").forward(request, response);
}
Код JSP (примечание: если вы поместите <table>
в <jsp:include>
, он может быть повторно использован в другом месте в ответе, отличном от ajax):
<?xml version="1.0" encoding="UTF-8"?>
<%@page contentType="application/xml" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<data>
<table>
<c:forEach items="${products}" var="product">
<tr>
<td>${product.id}</td>
<td><c:out value="${product.name}" /></td>
<td><fmt:formatNumber value="${product.price}" type="currency" currencyCode="USD" /></td>
</tr>
</c:forEach>
</table>
</data>
Код JS:
$(document).on("click", "#somebutton", function() { // When HTML DOM "click" event is invoked on element with ID "somebutton", execute the following function...
$.get("someservlet", function(responseXml) { // Execute Ajax GET request on URL of "someservlet" and execute the following function with Ajax response XML...
$("#somediv").html($(responseXml).find("data").html()); // Parse XML, find <data> element and append its HTML to HTML DOM element with ID "somediv".
});
});
Теперь вы, вероятно, поймете, почему XML настолько мощнее, чем JSON для конкретной цели обновления HTML-документа с использованием Ajax. JSON смешно, но ведь вообще полезно только для так называемых "публичных веб-сервисов". Структуры MVC, такие как JSF, используют XML под обложками для их магии ajax.
Адаптация существующей формы
Вы можете использовать jQuery $.serialize()
, чтобы легко изменить существующие формы POST, не занимаясь сбором и передачей входных параметров отдельной формы. Предполагая, что существующая форма отлично работает без JavaScript/jQuery (и, таким образом, изящно изнашивается, когда у enduser отключен JavaScript):
<form id="someform" action="someservlet" method="post">
<input type="text" name="foo" />
<input type="text" name="bar" />
<input type="text" name="baz" />
<input type="submit" name="submit" value="Submit" />
</form>
Вы можете постепенно улучшать его с помощью ajax, как показано ниже:
$(document).on("submit", "#someform", function(event) {
var $form = $(this);
$.post($form.attr("action"), $form.serialize(), function(response) {
// ...
});
event.preventDefault(); // Important! Prevents submitting the form.
});
Вы можете в сервлете различать обычные запросы и запросы ajax, как показано ниже:
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String foo = request.getParameter("foo");
String bar = request.getParameter("bar");
String baz = request.getParameter("baz");
boolean ajax = "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
// ...
if (ajax) {
// Handle ajax (JSON or XML) response.
} else {
// Handle regular (JSP) response.
}
}
плагин jQuery Form делает меньше или больше того же, что и выше, пример jQuery, но имеет дополнительную прозрачную поддержку форм multipart/form-data
по мере необходимости по загрузке файлов.
Вручную отправка параметров запроса в сервлет
Если у вас нет формы вообще, но просто захотелось взаимодействовать с сервлетами "в фоновом режиме", в соответствии с которым вы хотели бы POST некоторые данные, вы можете использовать jQuery $.param()
, чтобы легко преобразовать объект JSON в строку запроса с запросом URL.
var params = {
foo: "fooValue",
bar: "barValue",
baz: "bazValue"
};
$.post("someservlet", $.param(params), function(response) {
// ...
});
Тот же метод doPost()
, как показано выше, может быть повторно использован. Обратите внимание, что выше синтаксис также работает с $.get()
в jQuery и doGet()
в сервлете.
Вручную отправляет объект JSON в сервлет
Если вы, однако, намерены отправить объект JSON в целом, а не как отдельные параметры запроса по какой-либо причине, тогда вам нужно будет сериализовать его в строку, используя JSON.stringify()
(не входит в jQuery) и инструктируйте jQuery для установки типа содержимого запроса на application/json
вместо (по умолчанию) application/x-www-form-urlencoded
. Это невозможно сделать с помощью $.post()
функции удобства, но это необходимо сделать с помощью $.ajax()
, как показано ниже.
var data = {
foo: "fooValue",
bar: "barValue",
baz: "bazValue"
};
$.ajax({
type: "POST",
url: "someservlet",
contentType: "application/json", // NOT dataType!
data: JSON.stringify(data),
success: function(response) {
// ...
}
});
Обратите внимание, что многие стартеры смешивают contentType
с dataType
. contentType
представляет тип тела запрос. dataType
представляет (ожидаемый) тип тела response, который обычно не нужен, поскольку jQuery уже автоматически определяет его на основе заголовка ответа Content-Type
.
Затем, чтобы обработать объект JSON в сервлет, который не отправляется как параметры отдельного запроса, а в целом строка JSON указанным выше образом, вам нужно только вручную проанализировать тело запроса с помощью инструмента JSON вместо используя getParameter()
обычным способом. А именно, сервлеты не поддерживают отформатированные запросы application/json
, а только отформатированные запросы application/x-www-form-urlencoded
или multipart/form-data
. Gson также поддерживает разбор строки JSON в объект JSON.
JsonObject data = new Gson().fromJson(request.getReader(), JsonObject.class);
String foo = data.get("foo").getAsString();
String bar = data.get("bar").getAsString();
String baz = data.get("baz").getAsString();
// ...
Обратите внимание, что это все более неуклюже, чем просто использование $.param()
. Обычно вы хотите использовать JSON.stringify()
только в том случае, если целевой услугой является, например, JAX-RS (RESTful), который по какой-то причине способен потреблять только строки JSON, а не обычные параметры запроса.
Отправка перенаправления из сервлета
Важно понимать и понимать, что любой вызов sendRedirect()
и forward()
сервлетом по запросу ajax будет только перенаправлять или перенаправлять сам запрос ajax, а не основной документ/окно, откуда возник запрос ajax. JavaScript/jQuery в таком случае будет только получать перенаправленный/переадресованный ответ в качестве переменной responseText
в функции обратного вызова. Если он представляет собой целую HTML-страницу, а не специфичный для ajax XML или JSON-ответ, то все, что вы можете сделать, это заменить текущий документ на него.
document.open();
document.write(responseText);
document.close();
Обратите внимание, что это не изменяет URL-адрес, поскольку enduser видит в адресной строке браузера. Таким образом, есть проблемы с возможностью закладки. Поэтому гораздо лучше просто вернуть "инструкцию" для JavaScript/jQuery для выполнения перенаправления вместо возврата всего содержимого перенаправленной страницы. Например. путем возврата логического или URL-адреса.
String redirectURL = "http://example.com";
Map<String, String> data = new HashMap<>();
data.put("redirect", redirectURL);
String json = new Gson().toJson(data);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
function(responseJson) {
if (responseJson.redirect) {
window.location = responseJson.redirect;
return;
}
// ...
}