( Примечание редактора : При ~ 7500 словах вы, вероятно, не захотите пытаться читать это на мобильном устройстве. Добавьте его в закладки и вернитесь позже.)
Вступление
Что такое Spring MVC?
Spring MVC – это веб-фреймворк Spring. Он позволяет создавать веб-сайты или сервисы RESTful (например, JSON/XML) и хорошо интегрирован в экосистему Spring, например, он питает @Controllers и @RestControllers ваших приложений Spring Boot.
Это на самом деле не помогает, не так ли?
К счастью, есть также длинный ответ : Остальная часть этого документа.
(Если вы не уверены в том, что такое Spring или Spring Boot, вы можете прочитать Что Такое Spring Framework? , во-первых.)
Основы веб-Java: Http-сервлеты
При написании веб-приложений на Java, с Spring или без него (MVC/Boot), вы в основном говорите о написании приложений, которые возвращают два разных формата данных:
HTML → Ваше веб-приложение создает HTML-страницы, которые можно просматривать в браузере.
JSON/XML → Ваше веб-приложение предоставляет службы RESTful, которые создают JSON или XML. Веб-сайты с поддержкой Javascript или даже другие веб-сервисы могут затем использовать данные, предоставляемые этими сервисами.
(Да, существуют и другие форматы данных и варианты использования, но пока мы их проигнорируем.)
Как бы вы написали такие приложения без каких-либо рамок? Просто с помощью простой Java?
Ответ на этот вопрос необходим для действительно понимания Spring MVC, поэтому не забегайте вперед, потому что вы думаете, что это не имеет ничего общего с Spring MVC.
Ответ
На самом низком уровне каждое веб-приложение Java состоит из одного или нескольких Httpсервлет . Они генерируют ваш HTML, JSON или XML.
Фактически, (почти) каждый фреймворк из 1 миллиона доступных веб-фреймворков Java ( Spring MVC , Калитка , Распорки ) встроена поверх Http-сервлетов.
Как писать HTML-страницы с помощью Http-сервлетов
Давайте взглянем на супер простой HttpServlet, который возвращает очень простую статическую HTML-страницу.
package com.marcobehler.springmvcarticle;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyServletV1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (req.getRequestURI().equals("/")) {
resp.setContentType("text/html");
resp.getWriter().print("Welcome!
This is a very cool page!
");
}
else {
throw new IllegalStateException("Help, I don't know what to do with this url");
}
}
}
Давайте разберемся с этим.
public class MyServletV1 extends HttpServlet {
Ваш сервлет расширяет Класс HttpServlet Java.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Чтобы обработать (любой) запрос GET, вам необходимо переопределить метод doGet() из суперкласса. Для запросов на публикацию вы бы переопределили doPost() . Аналогично для всех других HTTP-методов.
if (req.getRequestURI().equals("/")) {
Ваш сервлет должен убедиться, что входящий URL-адрес является запросом, который он знает, как обрабатывать. На данный момент сервлет только обрабатывает “/”, т.е. он обрабатывает На данный момент сервлет только обрабатывает “/”, т.е. он обрабатывает , но НЕ
resp.setContentType("text/html");
Вам необходимо установить правильный Тип содержимого в ServletResponse, чтобы браузер знал, какой контент вы отправляете. В данном случае это HTML.
resp.getWriter().print("Welcome!
This is a very cool page!
");
Помните: веб-сайты – это всего лишь HTML-строки! Таким образом, вам нужно сгенерировать HTML-строку, в любом случае, вы хотите, и отправить ее обратно с ServletResponse. Один из способов сделать это – обратиться к автору ответа.
После написания сервлета вы зарегистрируете его в контейнере сервлета, например Tomcat или Jetty . Если вы используете встроенную версию любого контейнера сервлетов, весь код, необходимый для запуска вашего сервлета, будет выглядеть следующим образом:
package com.marcobehler.springmvcarticle;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;
public class TomcatApplicationLauncher {
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector();
Context ctx = tomcat.addContext("", null);
Wrapper servlet = Tomcat.addServlet(ctx, "myServlet", new MyServletV2());
servlet.setLoadOnStartup(1);
servlet.addMapping("/*");
tomcat.start();
}
}
Давайте разберемся с этим.
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.getConnector();
Вы настраиваете новый сервер Tomcat, который будет запускаться на порту 8080.
Context ctx = tomcat.addContext("", null);
Wrapper servlet = Tomcat.addServlet(ctx, "myServlet", new MyServletV2());
Вот как вы регистрируете свой сервлет в Tomcat. Это первая часть, где вы просто рассказываете Tomcat о своем сервлете.
servlet.addMapping("/*");
Вторая часть – сообщить Tomcat, за какие запросы отвечает сервлет, т.Е. за сопоставление. Сопоставление /* означает, что оно отвечает за любой входящий запрос ( /пользователи , /зарегистрироваться , /оформить заказ ).
tomcat.start();
Это оно. Теперь вы запускаете свой метод main() , перейдите на порт 8080 в своем любимом веб-браузере ( http://localhost:8080/ ), и вы увидите красивую HTML-страницу.
Таким образом, по сути, до тех пор, пока вы продолжаете расширять свои методы doGet() и doPost() , все ваше веб-приложение может состоять всего из одного сервлета. Давайте попробуем это сделать.
Как писать конечные точки JSON с помощью Http-сервлетов
Представьте, что помимо вашей (довольно пустой) страницы HTML-индекса, вы теперь также хотите предложить REST API для вашего скоро разрабатываемого интерфейса. Таким образом, ваш интерфейс React или AngularJS будет вызывать URL-адрес, подобный этому:
`/api/users/{userId}`
Эта конечная точка должна возвращать данные в формате JSON для пользователя с заданным идентификатором пользователя. Как мы могли бы улучшить наш MyServlet , чтобы сделать это, опять же, без разрешенных фреймворков?
package com.marcobehler.springmvcarticle;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyServletV2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (req.getRequestURI().equals("/")) {
resp.setContentType("text/html");
resp.getWriter().print("Welcome!
This is a very cool page!
");
} else if (req.getRequestURI().startsWith("/api/users/")) {
Integer prettyFragileUserId = Integer.valueOf(req.getRequestURI().lastIndexOf("/") + 1);
resp.setContentType("application/json");
// User user = dao.findUser(prettyFragileUserId)
// actually: jsonLibrary.toString(user)
resp.getWriter().print("{\n" +
" \"id\":" + prettyFragileUserId + ",\n" +
" \"age\": 55,\n" +
" \"name\" : \"John Doe\"\n" +
"}");
} else {
throw new IllegalStateException("Help, I don't know what to do with this url");
}
}
}
Давайте разберемся с этим.
} else if (req.getRequestURI().startsWith("/api/users/")) {
Мы добавляем еще один if в наш метод doGet для обработки вызовов /api/пользователей/|.
Integer prettyFragileUserId = Integer.valueOf(req.getRequestURI().lastIndexOf("/") + 1);
Мы выполняем некоторый чрезвычайно хрупкий синтаксический анализ URL-адресов. Последняя часть URL-адреса – это идентификатор пользователя, например 5 для /api/пользователей/5 . Мы просто предполагаем здесь, что пользователь всегда передает действительный int, который вам действительно нужно будет проверить!
resp.setContentType("application/json");
Запись JSON в браузер означает установку правильного типа содержимого.
// User user = dao.findUser(prettyFragileUserId)
// actually: jsonLibrary.toString(user)
resp.getWriter().print("{\n" +
" \"id\":" + prettyFragileUserId + ",\n" +
" \"age\": 55,\n" +
" \"name\" : \"John Doe\"\n" +
"}");
Опять же, ДЖЕЙСОН – это просто текст, поэтому мы можем записать его непосредственно в HttpServletResponse. вероятно, вы бы использовали библиотеку JSON для преобразования нашего пользовательского Java-объекта в эту строку, но для простоты я не буду показывать это здесь.
Проблема с нашим подходом “Один сервлет для управления всеми”
В то время как наш сервлет выше работает, на горизонте возникает довольно много проблем:
Вашему сервлету необходимо выполнить множество ручных операций, специфичных для HTTP, проверить URI запросов, разобраться со строками и т. Д. Другими словами: он должен знать
ЧТОпользователи хотят делать.Затем ему также необходимо найти данные для того, что вы хотите отобразить. Другими словами: он должен знать
КАК. В нашем примере выше это означало бы поиск пользователя в базе данных, которую мы удобно прокомментировали.Затем ему также необходимо преобразовать эти данные в JSON или в HTML и установить соответствующие типы ответов.
Довольно много разных обязанностей, а? Разве не было бы лучше, если бы вам не нужно было заботиться обо всей этой сантехнике? Больше никаких URI запросов и синтаксического анализа параметров, больше никаких преобразований JSON, больше никаких ответов сервлетов?
Это именно где появляется Spring MVC .
Что такое диспетчерский сервлет Spring MVC?
Что, если я скажу вам, что Spring MVC на самом деле всего лишь один сервлет, как наш uberservlet выше? (И да, это, конечно, немного ложь)
Познакомьтесь с Диспетчерским сервлетом .
Познакомьтесь с Диспетчерским сервлетом . Диспетчерский сервлет Spring MVC обрабатывает каждый входящий HTTP-запрос (он также называется front controller ). Познакомьтесь с
Познакомьтесь с || Диспетчерским сервлетом || . Диспетчерский сервлет Spring MVC обрабатывает || каждый || входящий HTTP-запрос (он также называется || front controller || ). Теперь, что означает || обрабатывать ||, например, поток HTTP-запросов?
Познакомьтесь с || Диспетчерским сервлетом || . Диспетчерский сервлет Spring MVC обрабатывает || каждый || входящий HTTP-запрос (он также называется || front controller || ). Теперь, что означает || handle ||, например, представьте себе “рабочий процесс регистрации пользователя”, когда пользователь заполняет форму и отправляет ее на сервер, чтобы вернуть небольшую HTML-страницу успеха. Примерный поток HTTP-запросов ctly?
Познакомьтесь с || Диспетчерским сервлетом || . Диспетчерский сервлет Spring MVC обрабатывает || каждый || входящий HTTP-запрос (он также называется || front controller || ). Теперь, что означает || handle ||, например, представьте себе “рабочий процесс регистрации пользователя”, когда пользователь заполняет форму и отправляет ее на сервер, чтобы вернуть небольшую HTML-страницу успеха. Образец HTTP в этом случае вашему диспетчерскому сервлету необходимо выполнить следующие действия: поток запросов точно?
Познакомьтесь с
Диспетчерским сервлетом. Диспетчерский сервлет Spring MVC обрабатываеткаждый
входящий HTTP-запрос (он также называетсяfront controller). Теперь, что означаетhandle , например, представьте себе “рабочий процесс регистрации пользователя”, когда пользователь заполняет форму и отправляет ее на сервер, чтобы вернуть небольшую HTML-страницу успеха. Образец HTTP R В этом случае вашему диспетчерскому сервлету необходимо выполнить подгонку, необходимо просмотреть URI входящего запроса метода HTTP и любые параметры запроса. следующие вещи: равномерно протекают? Познакомьтесь с Диспетчерским сервлетом. Диспетчерский сервлет Spring MVC обрабатывает
каждыйвходящий HTTP-запрос (он также называетсяfront controller
). Теперь, что означаетhandle
Обзор диспетчерского сервлета
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно.
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные?
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Например: Как вы пишете HTML-сайты с помощью Spring MVC? Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете HTML-сайты с помощью Spring MVC?
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете, как писать HTML с помощью @Controllers HTML-сайты с Spring MVC?
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете Всякий раз, когда вы хотите написать HTML для клиента, такого как браузер с Spring MVC (и это включает Spring Boot), вам захочется написать класс @Controller. Как писать HTML с помощью @Controllers HTML-сайты с Spring MVC? Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете Всякий раз, когда хотите написать HTML для клиента, такого как браузер, давайте сделаем это сейчас. Spring MVC (и это включает в себя Spring Boot), вы захотите написать класс @Controller. Как писать HTML с помощью @Controllers HTML-сайты с Spring MVC?
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете Всякий раз, когда хотите написать HTML для клиента, такого как браузер, давайте сделаем это сейчас. Spring MVC (и это включает в себя Spring Boot, как написать @Controller в Spring t), вы захотите написать класс @Controller. Как писать HTML с помощью @Controllers HTML-сайты с Spring MVC?
Обзор DispatcherServlet Весь процесс выглядит следующим образом, при этом игнорируется достаточное количество промежуточных классов, поскольку DispatcherServlet не выполняет всю работу самостоятельно. Что такое представление модели на приведенном выше рисунке? Как именно DispatcherServlet преобразует данные, легче всего понять, взглянув на реальный пример. ? Ибо давайте выясним это в следующем разделе. пример: Как вы пишете Всякий раз, когда хотите написать HTML для клиента, такого как браузер, давайте сделаем это сейчас. Spring MVC (и это включает Spring Boot Для нашего рабочего процесса регистрации пользователей сверху ( ДОЛЖНОСТЬ и возраст 33 ), вы бы написали следующий класс. Как написать @Controller в Spring t), вам захочется написать класс @Controller. Как писать HTML с помощью @Controllers HTML-сайты с Spring MVC?
package com.marcobehler.springmvcarticle;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class RegistrationController {
@PostMapping("/register")
public String registerUser(@RequestParam(required = false) Integer age, @RequestParam String name, Model model) {
User user = new User(name, age);
// TODO save user to database
// userDao.save(user);
// TODO send out registration email
// mailService.sendRegistrationEmail(user);
model.addAttribute("user", user);
return "registration-success";
}
}
Давайте разберемся с этим.
@Controller
public class RegistrationController {
Класс контроллера в Spring просто аннотируется аннотацией @Controller , ему не нужно реализовывать определенный интерфейс или расширяться из другого класса.
@PostMapping("/register")
Эта строка сообщает нашему диспетчерскому сервлету, что всякий раз, когда поступает запрос POST на путь /регистрация , включая любые параметры запроса (например, ?имя пользователя=), он должен отправить запрос этому самому методу контроллера.
public String registerUser(@RequestParam(required = false) Integer age, @RequestParam String name, Model model) {
Примечание Наименование нашего метода ( регистрация пользователя ) на самом деле не имеет значения, его можно назвать как угодно.
Однако мы указываем, что каждый запрос должен включать два параметра запроса, которые могут быть частью URL-адреса ( ? возраст=10 и имя= Джо ) или быть в теле запроса на отправку. Кроме того, требуется только параметр имя (параметр возраст необязателен)
И параметр возраст , если пользователь его вводит, автоматически преобразуется в целое число (исключение выдается, если предоставленное значение не является допустимым целым числом)
И последнее, но не менее важное: Spring MVC автоматически вводит параметр model в наш метод контроллера. Эта модель представляет собой простую карту, на которой вам нужно разместить все данные, которые вы хотите отобразить на своей окончательной HTML-странице, но подробнее об этом через секунду.
User user = new User(name, age);
// TODO save user to database
// userDao.save(user);
// TODO send out registration email
// mailService.sendRegistrationEmail(user);
Вы делаете все, что вам нужно, с входящими данными запроса. Создайте пользователя, сохраните его в базе данных, отправьте электронное письмо. Это ваша бизнес-логика.
model.addAttribute("user", user);
Вы добавляете своего пользователя в модель в разделе “пользователь”. Это означает, что позже вы сможете ссылаться на него в своем HTML-шаблоне, например “${user.name }”. Подробнее об этом через секунду.
return "registration-success";
Ваш метод возвращает простую строку с значение регистрация-успех . Это не просто какая-либо строка, это ссылка на ваше представление, то есть HTML-шаблон, который вы хотите, чтобы Spring отображал.
Как выглядят виды?
Давайте пока проигнорируем, как (или, скорее, где) Spring MVC попытается найти это представление, то есть ваш шаблон; вместо этого давайте посмотрим что ваш registration-success.html шаблон должен выглядеть так.
Title
Это всего лишь простая HTML-страница, которая содержит одну строку шаблон-y . Он выводит имя пользователя, который только что зарегистрировался.
Вопрос в том, что это за синтаксис th:text= ? Это специфично для весны? Это что-то другое?
И ответ заключается в том, что Spring MVC на самом деле ничего не знает о HTML-шаблонах. Для выполнения всей работы по созданию шаблонов HTML требуется сторонняя библиотека шаблонов, и не обязательно важно, какую библиотеку вы выберете.
В приведенном выше случае вы смотрите на шаблон Thymeleaf , который является очень популярным выбором при работе над проектами Spring MVC.
Библиотеки Spring MVC и шаблонов
Существует несколько различных библиотек шаблонов, которые хорошо интегрируются с Spring MVC, из которых вы можете выбрать: Thymeleaf , Velocity , Freemarker , Усы и даже JSP (хотя это не библиотека шаблонов).
На самом деле, вы должны явно выбрать библиотеку шаблонов, потому что, если у вас нет такой библиотеки шаблонов, добавленной в ваш проект и настроенной правильно, то ваш метод @Controller не будет отображать вашу HTML-страницу – потому что он не будет знать, как это сделать.
Это также означает, что вы должны изучить и понять синтаксис конкретной библиотеки шаблонов в зависимости от проекта, в котором вы работаете, потому что все они немного отличаются друг от друга. Весело, правда?
Что такое решатель представлений?
На секунду давайте подумаем о том, где Spring на самом деле попытается найти ваши HTML-шаблоны, которые возвращает ваш @Controller.
Класс, который пытается найти ваш шаблон, называется ViewResolver . Поэтому всякий раз, когда запрос поступает в ваш контроллер, Spring просматривает настроенные решатели представлений и запрашивает их, чтобы найти шаблон с заданным именем. Если у вас не настроены какие-либо распознаватели представлений, это не сработает.
Представьте, что вы хотите интегрироваться с Thymeleaf . Следовательно, вам понадобится ThymeleafViewResolver.
package com.marcobehler.springmvcarticle;
import org.springframework.context.annotation.Bean;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
public class ThymeleafConfig {
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates");
templateResolver.setSuffix(".html");
// some other lines neglected...
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
// some other lines neglected...
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
Давайте разберемся с этим.
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
В конце концов, ThymeleafViewResolver просто реализует интерфейс Spring ViewResolver . Учитывая имя шаблона (помните: регистрация прошла успешно ), распознаватели представлений могут найти фактический шаблон.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
Для правильной работы ThymeleafViewResolver требуется пара других классов, специфичных для Thymeleaf. Одним из таких классов является SpringResourceTemplateResolver . Он выполняет фактическую работу по поиску вашего шаблона.
Примечание
SpringResourceTemplateResolver – это класс Thymeleaf
templateResolver.setPrefix("classpath:/templates");
templateResolver.setSuffix(".html");
Вы в основном говорите (с помощью синтаксиса Spring Resources ): “Все мои шаблоны находятся в пути к классам, в папке /шаблоны “. И, по умолчанию, все они заканчиваются на .html . Это означает:
Всякий раз, когда ваш @Controller возвращает строку типа регистрация прошла успешно , ThymeleafViewResolver будет искать шаблон: classpath:/templates/registration-success.html .
Боковое примечание: Spring MVC, Пружинный ботинок и контроллеры
Возможно, вы думаете: Марко, мне никогда не приходилось настраивать такой ViewResolver за всю мою жизнь работая над проектами Spring Boot. И это правильно. Потому что Spring Boot автоматически настраивает его для вас , всякий раз, когда вы добавляете зависимость, такую как spring-boot-starter-thymeleaf в свой проект.
Он также настраивает ViewResolver для просмотра вашего каталога src/main/ресурсы/шаблон по умолчанию.
Итак, Spring Boot на самом деле просто предварительно настраивает Spring MVC для вас. Имейте это в виду.
Краткое описание: Модель-Представление-Контроллер
Увидев полный пример @Controller и ViewResolver, намного проще говорить о Spring M odel- V концепция iew- C контролера.
С помощью пары аннотаций (@Controller, @postmapping, @RequestParam) вы можете написать
контроллер, который заботится о получении данных запроса и обрабатывает их соответствующим образом.Ваша
модельсодержит все данные (и только данные), которые вы хотите отобразить в своем представлении. Ваша задача – заполнить эту модельную карту.Ваш
просмотр– это всего лишь HTML-шаблон. Ему все равно, откуда вы получили данные (модели). Или каков текущий HTTP-запрос. Или даже независимо от того, есть ли у вас активный HTTP-сеанс или нет.
Все дело в разделении забот.
Несмотря на то, что на первый взгляд наш класс Spring @Controller немного перегружен аннотациями, он читается намного лучше, с гораздо меньшим количеством подключений HTTP, чем наш uber-сервлет с самого начала.
Подробнее о контроллерах @
Мы уже видели некоторые удобства, которые Spring MVC предоставляет нам при обработке HTTP-входов.
Вам не нужно возиться с URI запроса, вместо этого вы можете использовать аннотацию.
Вам не нужно возиться с преобразованиями типов параметров запроса, или если параметр является необязательным или обязательным, вы можете вместо этого использовать аннотацию.
Давайте рассмотрим наиболее распространенные аннотации, которые помогут вам обрабатывать входящие HTTP-запросы.
@@Сопоставление с получением и @Сопоставление с запросом
Вы уже видели аннотацию @GetMapping выше. Он равен аннотации `@RequestMapping `. Давайте посмотрим, как:
@GetMapping("/books")
public void book() {
//
}
/* these two mappings are identical */
@RequestMapping(value = "/books", method = RequestMethod.GET)
public void book2() {
}
Сопоставление @GetMapping , Сопоставление @[Опубликовать|Поместить|Удалить|Исправить] эквивалентно сопоставлению @RequestMapping(метод=XXX) . Это просто более новый способ (Spring 4.3+) указания сопоставления, поэтому вы найдете аннотацию @RequestMapping, часто используемую в старых, устаревших проектах Spring.
@RequestParam запрос
Параметры HTTP-запроса, будь то в вашем URL ( ?ключ=значение ) или в теле запроса отправленной формы, можно прочитать с помощью аннотации @RequestParam .
Вы уже видели, что он выполняет базовое преобразование типов (например, из строкового параметра HTTP в int), а также проверяет наличие обязательных или необязательных параметров.
@PostMapping("/users") /* First Param is optional */
public User createUser(@RequestParam(required = false) Integer age, @RequestParam String name) {
// does not matter
}
Если вы забудете указать необходимый параметр в своем запросе, вы получите 400 Неверный запрос код ответа и, при использовании Spring Boot, объект ошибки по умолчанию, который выглядит следующим образом:
{"timestamp":"2020-04-26T08:34:34.441+0000","status":400,"error":"Bad Request","message":"Required Integer parameter 'age' is not present","path":"/users"}
Если вы хотите еще большего удобства, вы можете позволить Spring напрямую конвертировать все @RequestParams в объект без каких-либо необходимых аннотаций. Просто укажите свой объект как `параметр метода `.
Вам просто нужно убедиться, что в вашем классе есть соответствующие геттеры/сеттеры.
@PostMapping("/users") /* Spring will convert this automatically if you have getters and setters */
public User createUser(UserDto userDto) {
//
}
@Переменный путь
Рядом с параметрами запроса другим популярным способом указания переменных будет непосредственно в URI запроса, как @PathVariable . Таким образом, для получения профиля пользователя с идентификатором пользователя=123 вы должны вызвать следующий URL: GET/users/123
@GetMapping("/users/{userId}")
public User getUser(@PathVariable String userId) {
// ...
return user;
}
- Вам просто нужно убедиться, что значение вашего параметра совпадает со значением
{}в аннотации к запросу.
Кроме того, Переменные пути также могут быть обязательными или необязательными.
@GetMapping("/users/{userId}")
public User getUser(@PathVariable(required = false) String userId) {
// ...
return user;
}
И переменные пути, конечно, могут быть напрямую переведены в объект Java (при условии, что объект имеет соответствующие геттеры/сеттеры).
@GetMapping("/users/{userId}")
public User getUser(UserDto userDto) {
// ...
return user;
}
Резюме: @Контроллеры
Короче говоря, при написании HTML-страниц с помощью Spring MVC вам нужно будет сделать всего несколько вещей:
Напишите свой @Controllers, посыпанный парой аннотаций. Spring позаботится о том, чтобы предоставить вам входные данные запроса (параметры запроса, переменные пути) удобным способом.
Выполните любую логику, которая вам нужна для заполнения вашей модели. Вы можете удобно внедрить модель в любой метод контроллера.
Сообщите вашему @Controller, какой HTML-шаблон вы хотите отобразить, и верните имя шаблона в виде строки.
Всякий раз, когда поступает запрос, Spring обязательно вызывает ваш метод контроллера, берет полученную модель и представление, преобразует их в HTML-строку и возвращает обратно в браузер.
(При условии, конечно, что вы настроили соответствующую библиотеку шаблонов, которую Spring Boot автоматически сделает за вас, если вы добавите необходимые зависимости в свой проект.)
Это оно.
Как написать XML/JSON с помощью @Restcontroller
Все немного по-другому, когда вы пишете службы RESTful. Ваш клиент, будь то браузер или другой веб-сервис, будет (обычно) создавать запросы JSON или XML. Клиент отправляет, скажем, запрос JSON, вы обрабатываете его, а затем отправитель ожидает возврата JSON.
Таким образом, отправитель может отправить вам этот фрагмент JSON как часть тела HTTP-запроса.
POST http://localhost:8080/users
###
{"email": "angela@merkel.de"}
Но на стороне Java (в вашей программе Spring MVC) вы не хотите связываться с необработанными строками JSON. Ни при получении запросов, подобных приведенным выше, ни при отправке ответов обратно клиенту.
Вместо этого вы хотели бы просто иметь объекты Java, которые автоматически преобразуются к весне.
public class UserDto {
private String email;
//...
}
Это также означает, что вам не нужна вся та обработка modelandview, которую вам приходилось выполнять при рендеринге HTML в ваших @контроллерах. Для служб RESTful у вас нет библиотеки шаблонов, которая считывает HTML-шаблон и заполняет его данными модели для генерации ответа JSON для вас.
Вместо этого вы хотите перейти непосредственно из HTTP-запроса → Java-объект и из Java-объекта → HTTP-ответ.
Как вы могли догадаться, это именно то, что Spring MVC позволяет вам делать, написав @Restcontroller .
Как написать @Restcontroller
Первое, что вам нужно сделать для вывода XML/JSON, это написать @RestController вместо @Controller. (Хотя @RestController ЯВЛЯЕТСЯ @контроллером, см. FAQ , в чем точная разница).
Если бы мы написали контроллер REST для банка, который возвращает список транзакций пользователя, это могло бы выглядеть примерно так:
package com.marcobehler.springmvcarticle;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
@RestController
public class BankController {
@GetMapping("/transactions/{userId}")
public List transactions(String userId) {
// find transaction by user
// List = dao.findByUserId(userId);
List transactions = Collections.emptyList();
return transactions;
}
}
Давайте разберемся с этим.
@RestController
public class BankController {
Вы аннотировали класс контроллера банка аннотацией @RestController , которая сигнализирует Spring о том, что вы не хотите писать HTML-страницы с помощью обычного процесса ModelAndView.
Вместо этого вы хотите записать XML/JSON (или какой-либо другой формат) непосредственно в тело ответа HTTP.
public Listtransactions(String userId) {
Ваш контроллер больше не возвращает строку (представление). Вместо этого он возвращает Список<Транзакция> , который вы хотите, чтобы Spring преобразовал в соответствующую структуру JSON или XML. Вы в основном хотите, чтобы ваши объекты Java транзакции стали такими (кто-то очень рано утром проголодался по фаст-фуду).:
[
{
"occurred": "28.04.2020 03:18",
"description": "McDonalds - Binging",
"id": 1,
"amount": 10
},
{
"occurred": "28.04.2020 06:18",
"description": "Burger King - Never enough",
"id": 2,
"amount": 15
}
]
Но как Spring MVC узнает, что ваш список транзакций должен быть преобразован в JSON? Почему бы не XML? Или YAML? Как ваш метод @RestController узнает, каким должен быть предполагаемый формат ответа?
Для этого в Spring есть концепция Согласования контента .
Как работает согласование содержимого (ответа): Принять заголовок
Короче говоря, согласование содержимого означает, что клиент должен сообщить вашему серверу, какой формат ответа он хочет получить от вашего @RestController.
Как? Указав заголовок Accept в HTTP-запросе.
GET http://localhost:8080/transactions/{userid}
Accept: application/json
Spring MVC посмотрит на это Примите заголовок и знайте: клиент хочет вернуть JSON (приложение/json), поэтому мне нужно преобразовать мой Список<Транзакция> в JSON. (Краткое примечание: существуют и другие способы выполнения согласования содержимого , но заголовок Accept используется по умолчанию.)
Давайте назовем это согласованием содержимого response , потому что речь идет о формате данных HTTP-ответа, который вы отправляете обратно своему клиенту.
Но согласование содержимого также работает для входящих запросов. Давайте посмотрим, как это сделать.
Как работает согласование содержимого запроса: Заголовок типа содержимого
При создании API-интерфейсов RESTful существует чрезвычайно высокая вероятность того, что вы также захотите, чтобы ваши клиенты могли отправлять данные в формате JSON или XML. Давайте снова рассмотрим пример из начала главы, где вы предлагаете конечную точку REST для регистрации новых пользователей:
POST http://localhost:8080/users
###
{"email": "angela@merkel.de"}
Как Spring узнает, что текст запроса выше содержит JSON, а не XML или YAML?
Возможно, вы правильно догадались, вам нужно будет добавить еще один заголовок, на этот раз это заголовок Content-Type .
POST ... Content-Type: application/json; charset=UTF-8 ### ...
Как будет выглядеть соответствующий метод @RestController для этого запроса?
package com.marcobehler.springmvcarticle;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookingController {
@PostMapping("/transactions")
public Transaction transaction(@RequestBody TransactionDto dto) {
// do something with the dto..create the booking..convert it to a transaction
Transaction transaction = null;
return transaction;
}
}
Давайте разберемся с этим.
public Transaction transaction(@RequestBody TransactionDto dto) {
Подобно @RequestParam или @Pathvariable, вам понадобится другая аннотация, называемая @RequestBody .
@RequestBody в сочетании с правильным Типом содержимого будет сигнализировать Spring о том, что ему необходимо просмотреть тело HTTP-запроса и преобразовать его в любой тип содержимого, указанный пользователем: в нашем случае JSON.
// do something with the dto..create the booking..convert it to a transaction
Transaction transaction = null;
return transaction;
}
Тогда вашему методу больше не нужно заботиться о необработанной строке JSON, он может просто работать с транзакцией D, сохранять ее в базе данных, преобразовывать в объект транзакции, все, что вы хотите.
В этом сила Spring MVC.
Как Spring преобразует форматы данных?
Есть только одна небольшая проблема: Spring знает о заголовках типа Accept и Content, но не знает, как конвертировать между объектами Java и JSON. Или XML. Или YAML.
Для выполнения грязной работы требуется соответствующая сторонняя библиотека (также называемая сортировкой/отменой или сериализацией/десериализацией .)
И классы, которые интегрируются между Spring MVC и этими сторонними библиотеками, называются HttpMessageConverters .
Что такое HttpMessageConverter?
HttpMessageConverter – это интерфейс с четырьмя методами (обратите внимание, я немного упростил интерфейс для более простого объяснения, так как в реальной жизни он выглядит немного более продвинутым).
может читать (тип носителя) = Может ли этот конвертер читать (JSON | XML | YAML | и т. Д.)? Тип носителя, передаваемый здесь, обычно является значением из заголовка запроса
Тип содержимого.может записывать (Тип носителя) → Может ли этот конвертер записывать (JSON | XML | YAML | etc)? Тип носителя, передаваемый здесь, обычно является значением из заголовка запроса
Accept.чтение (объект, входной поток, тип носителя) → Прочитайте мой Java-объект из (JSON | XML | YAML | и т. Д.) Входной поток
запись (объект, поток вывода, тип носителя) → Запишите мой объект Java в поток вывода как (JSON | XML | YAML | и т. Д.)
Короче говоря, преобразователю сообщений необходимо знать, какие типы носителей он поддерживает (например, приложение/json), а затем необходимо реализовать два метода для фактического чтения/записи в этом формате данных.
Какие существуют HttpMessageConverters?
К счастью, вам не нужно писать эти конвертеры сообщений самостоятельно. Spring MVC поставляется с классом, который автоматически регистрирует для вас пару HttpMessageConverters по умолчанию – если у вас есть соответствующие сторонние библиотеки на пути к классу.
Если вы не знаете об этом, это будет звучать как волшебство. В любом случае, взгляните на Spring AllEncompassingFormHttpMessageConverter (Мне нравится это имя).
static {
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
Давайте разберемся с этим.
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
Весенние проверки MVC если класс javax.xml.bind. Связующее присутствует, и если да, то предполагается, что вы добавили в свой проект необходимую библиотеку для выполнения преобразований JAXB .
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
Spring MVC проверяет, есть ли два класса ..Джексон.. Объектный преобразователь и ..Джексон.. JsonGenerator присутствуют, и если да, то предполагается, что вы добавили Джексон в ваш проект, чтобы выполнить преобразования JSON.
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
Spring MVC проверяет, соответствует ли класс ..Джексону.. Xml-картограф присутствует, и если да, то предполагается, что вы добавили Поддержка XML Джексона в ваш проект для выполнения преобразований XML.
И так далее. И пару строк спустя Spring просто добавляет HttpMessageConverter для каждой библиотеки, которую он “обнаружил”.
if (jaxb2Present && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
addPartConverter(new GsonHttpMessageConverter());
}
Побочное примечание: Контроллеры Spring MVC, Spring Boot и Rest
При создании проектов Spring Boot вы автоматически будете использовать Spring MVC под капотом. Но Spring Boot также по умолчанию подключает Джексона.
Это является причиной того, что вы можете немедленно написать конечные точки JSON с помощью Spring Boot, потому что правильные HttpMessageConverts будут добавлены автоматически для вас.
Краткое описание: @Restcontroller
По сравнению с потоком HTML поток JSON/XML немного проще, так как вы обходите весь этот рендеринг ModelAndView.
Вместо этого ваши @контроллеры напрямую возвращают объекты Java, которые Spring MVC удобно сериализует в JSON/XML или любой другой формат, запрошенный пользователем с помощью HttpMessageConverters.
Однако вам нужно убедиться в двух вещах:
Имейте соответствующие сторонние библиотеки на пути к классам.
Убедитесь, что вы отправили правильный
Принимайтеилизаголовки типа содержимогос каждым запросом.
часто задаваемые вопросы
Вы где-нибудь публиковали исходный код этой статьи?
Вы можете найти рабочий исходный код для большей части этой статьи в следующем репозитории GitHub: https://github.com/marcobehler/spring-mvc-article
Просто клонируйте проект и запустите класс Spring Mvc Article Application для запуска веб-приложения.
В чем разница между Spring MVC и Spring Boot?
Короче говоря: нет разницы , Spring Boot использует и строит поверх Spring MVC.
Для более подробного объяснения вам нужно будет прочитать мой Что такое Spring Framework? статья первая.
Каков самый быстрый способ создать новое приложение Spring MVC?
Если вы не хотите использовать Spring MVC без него (в настоящее время это довольно редко), самым быстрым способом будет создание нового проекта Spring Boot.
Перейти к Перейти к
.
Обязательно выберитеSpring Web
Это позволит вам создавать веб-приложения/приложения RESTful с помощью Spring MVC.
Какой тип ввода HTTP-запроса понимает Spring MVC?
Spring MVC понимает в основном все, что предлагает HTTP – с помощью сторонних библиотек.
Это означает, что вы можете отправлять в него тела запросов JSON, XML или HTTP (составных) загрузок файлов, и Spring удобно преобразует эти входные данные в объекты Java.
Какие HTTP-ответы может написать Spring MVC?
Spring MVC может записать все, что вы хотите, в HttpServletResponse – с помощью сторонних библиотек.
Будь то HTML, JSON, XML или даже тела ответов WebSocket. Еще лучше то, что он берет ваши объекты Java и генерирует эти тела ответов для вас.
В чем разница между @Controller и @RestController
@Controllersпо умолчанию возвращает HTML вашим пользователям с помощью библиотеки шаблонов, если только вы не добавите аннотацию @ResponseBody к определенным методам, которые также позволяют возвращать XML/JSON.@@Restcontrollerисходный код показывает, что он на самом деле является @контроллером с добавленной аннотацией @ResponseBody. Что эквивалентно написанию @контроллеров , в которых @ResponseBody аннотирован на каждом отдельном методе .
@Controller
@ResponseBody
public @interface RestController {
- Поэтому @Restcontroller по умолчанию возвращает XML/JSON вместо HTML.
Примечание
XML и JSON – это просто самые популярные форматы данных, которые вы будете использовать в приложении Spring MVC. Однако ваши @Controllers/@Restcontroller могут возвращать что-либо еще, например YAML. Вам нужно только убедиться, что в вашем приложении зарегистрирован правильный HttpMessageConverter .
Какую библиотеку шаблонов мне следует выбрать?
На протяжении многих лет я лично работал почти со всеми библиотеками шаблонов, и, хотя существует определенный стимул использовать Thymeleaf в весенних проектах, у меня нет особых предпочтений. Итак, либо идите с Thymeleaf (если у вас нет опыта работы с какой-либо другой структурой) или выберите ту, которая вам наиболее удобна.
Почему мой @контроллер выводит 404? Все сопоставления верны.
Относительно распространенная ошибка заключается в том, что @Controller возвращает объекты, которые вы хотите преобразовать в JSON или XML, но вам не хватает аннотации @ResponseBody.
В этом случае Spring вернет довольно бессмысленное 404 Не найдено исключение.
package com.marcobehler.springmvcarticle;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class _404WithMissingResponseBodyController {
@GetMapping("/users/{id}") /* This won't work and lead to a 404 */
public User getUser_404(@PathVariable String id) {
return new User("Everyone's name is John", id);
}
@GetMapping("/users2/{id}")
@ResponseBody /* This will work */
public User getUser_200(@PathVariable String id) {
return new User("Everyone's name is John", id);
}
}
Исправьте : Добавьте @ResponseBody или превратите свой @контроллер в @RestController .
Что произойдет, если вы определите одно и то же сопоставление запросов для двух разных методов?
Если два метода имеют разные методы HTTP, это не будет проблемой.
/* this works */
@PostMapping("/users")
public void method1() {
}
@GetMapping("/users")
publi void method(2) {
}
Однако, если вы сопоставите одни и те же методы HTTP с одним и тем же путем, у вас возникнет проблема.
/* this won't work */
@PostMapping("/users")
public void method1() {
}
@PostMapping("/users")
public void method(2) {
}
При запуске приложения это приведет к исключению IllegalStateException , намекающему на ваше неоднозначное сопоставление.
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'howToPassAndRetrieveRequestParametersController' method
com.marcobehler.springmvcarticle.HowToPassAndRetrieveRequestParametersController#createUser(User)
to {POST /users3}: There is already 'howToPassAndRetrieveRequestParametersController' bean method
Нужно ли мне кодировать URL-адрес @RequestParams?
Да, потому что Spring автоматически URL декодирует их. Распространенная ошибка:
Представьте, что ваше приложение отправляет электронные письма с подтверждением всякий раз, когда регистрируется новый пользователь, и пользователь регистрируется со знаком “+” в своем адресе электронной почты, например, marco+wantsnospam@marcobehler.com .
@GetMapping("/confirm")
public void confirm(@RequestParam String email, @RequestParam String token){
// confirm user...
}
Если вы забыли правильно закодировать URL-адрес со знаком + в своем письме с подтверждением и отправить строку как есть на ваш @Controller, какое значение будет содержать электронное письмо @RequestParam?
Это будет “Марко[космос] wantsnospam@marcobehler.com “, “, так как Spring заменит их + пробелом, что является правильной обработкой RFC 3986
Исправление : Убедитесь, что URL-адреса, которые вы отправляете в свое приложение, правильно закодированы: , так как Spring автоматически их декодирует.
Как получить доступ к текущей HttpSession пользователя?
В вашем Spring MVC @Controller или @RestController вы можете просто указать HttpSession в качестве аргумента метода, и Spring автоматически введет его (создав его, если он еще не существует)
@RestController
public class HttpSessionController {
@GetMapping("/session")
public String getSession(HttpSession httpSession) {
System.out.println("httpSession = " + httpSession);
return httpSession.getId();
}
}
Вы не можете сделать это со случайными @компонентами или @службами, но вы все равно можете @автоматически подключать к ним HttpSession.
@Service
class SomeOtherService {
@Autowired
private HttpSession httpSession;
public HttpSession getHttpSession() {
return httpSession;
}
}
Как получить доступ к запросу HttpServletRequest?
В вашем Spring MVC @Controller или @RestController вы можете просто указать HttpServletRequest в качестве аргумента метода, и Spring автоматически введет его (создав его, если он еще не существует)
@RestController
public class HttpServletRequestController {
@Autowired
private SomeRequestService someRequestService;
@GetMapping("/request")
public String getRequest(HttpServletRequest request) {
System.out.println("request = " + request);
return request.toString();
}
}
Вы не можете сделать это со случайными @компонентами или @службами, но вы все равно можете @автоматически подключать к ним запрос HttpServletRequest.
@Service
class SomeRequestService {
@Autowired
private HttpServletRequest httpServletRequest;
public HttpServletRequest getRequest() {
return httpServletRequest;
}
}
Как читать HTTP-заголовки?
Существует множество способов доступа к заголовкам запросов, в зависимости от того, хотите ли вы только один из них или карту со всеми из них. В любом случае вам нужно аннотировать их с помощью @RequestHeader.
Какую бы версию вы ни выбрали, старайтесь соответствовать своему выбору.
package com.marcobehler.springmvcarticle;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.Map;
@Controller
public class HttpHeaderController {
@GetMapping("/headers1")
public void singleHeader(@RequestHeader("x-forwarded-for") String xForwardedFor) {
// ...
}
@GetMapping("/headers2")
public void headersAsMap(@RequestHeader Map headers) { // or MultiValueMap
// ...
}
@GetMapping("/headers3")
public void headersAsObject(HttpHeaders headers) {
// ...
}
}
Как читать и записывать файлы cookie?
Для чтения файлов cookie вы можете использовать аннотацию @CookieValue в своих контроллерах. Вам придется записывать файлы cookie непосредственно в HttpServletResponse.
package com.marcobehler.springmvcarticle;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@Controller
public class CookieController {
@GetMapping("/cookie")
public void handle(@CookieValue("JSESSIONID") String cookie, HttpServletResponse response) {
response.addCookie(new Cookie("SOME_COOKIE_NAME", "This is a crazy new cookie!"));
//...
}
}
Как получить IP-адрес пользователя?
Это вопрос с подвохом. Существует метод, называемый HttpServletRequest.getRemoteAddr() , который, однако, возвращает вам только IP пользователя или последнего прокси-сервера, отправившего запрос, которым в 99,99% случаев является ваш Nginx или Apache.
Следовательно, вам нужно будет проанализировать заголовок X-Forwarded-For для правильного IP-адреса. Но что произойдет, если ваше приложение, кроме того, будет работать за CDN, например CloudFront? Тогда ваш X-Forwarded-For будет выглядеть так:
X-Переадресованный Для: может быть, Какой-то Поддельный Ip, реальный Ip, облачный Ip
Проблема в том, что вы не можете прочитать заголовок слева направо, так как пользователи могут предоставить и, следовательно, подделать свой собственный заголовок X-Forwarded-For. Вы всегда должны идти от правильно влево и исключить все известные IP-адреса. В случае CloudFront это означает, что вам нужно знать диапазоны IP-адресов CloudFront и удалить их из заголовка. Ага!
Это приводит к довольно сложному коду разрешения IP-адресов. Угадайте, сколько проектов ошибаются в этом!
package com.marcobehler.springmvcarticle;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class IpController {
private static final String[] HEADERS_TO_TRY = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR"};
@GetMapping("/ip")
public String getClientIpAddress(HttpServletRequest request) {
for (String header : HEADERS_TO_TRY) {
String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return getRealClientIpAddress(ip);
}
}
return request.getRemoteAddr();
}
/**
* Goes through the supplied ip string (could be one or multiple). Traverses it through the right side...
* and removes any known ip address ranges
*
* @param ipString
* @return
*/
public String getRealClientIpAddress(String ipString) {
String[] manyPossibleIps = ipString.split(",");
for (int i = manyPossibleIps.length - 1; i >= 0; i--) {
String rightMostIp = manyPossibleIps[i].trim();
if (isKnownAddress(rightMostIp)) {
continue; // skip this ip as it is trusted
} else {
return rightMostIp;
}
}
return ipString;
}
private boolean isKnownAddress(String rightMostIp) {
// do your check here..for cloudfront you'd need to download their ip address ranges
// from e.g. http://d7uri8nf7uskq.cloudfront.net/tools/list-cloudfront-ips
// and compare the current ip against them
return false;
}
}
Как вы можете обрабатывать загрузку файлов в приложении Spring MVC?
Учитывая, что у вас есть правильная форма загрузки HTML-файла, которая выглядит примерно так:
Вам просто нужен @контроллер с соответствующим @Postmapping и параметром MultipartFile, который содержит ваши данные для загрузки и удобные методы сохранения файла на вашем диске.
package com.marcobehler.springmvcarticle;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@Controller
public class FileUploadController {
@PostMapping("/upload")
public String handleFileUpload(@RequestParam MultipartFile file) throws IOException {
// don't generate upload files like this in a real project.
// give them random names and save their uploaded name as metadata in a database or similar
final Path uploadDestination = Paths.get("C:\\uploads").resolve(file.getName());
file.transferTo(uploadDestination);
return "redirect:/";
}
}
Как обрабатывать байтовые загрузки (xls, pdf, csv, jpg, zip-файлы) с помощью контроллеров Spring?
Существует множество способов заставить это работать, начиная с прямой записи в HttpServletResponse или возврата байта[] в результате.
Однако наиболее упругой и гибкой версией является возврат *Идентификатор ответа<Ресурс>*\ s. В зависимости от того, где вы сохранили файл, вы могли бы использовать другой ресурс.
На диске → Файловая системаресурс
На пути к классу вашего проекта → ClassPathResource
Транслируйте его “откуда-нибудь” → InputStreamResource
Сделайте его доступным в виде байта[] в памяти → ByteArrayResource
Все, что осталось сделать, это установить соответствующие HTTP-заголовки ответа (имя файла, тип содержимого и т.д.).
package com.marcobehler.springmvcarticle;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
@Controller
public class FileDownloadController {
@RequestMapping(value = "/download/{jpgName}", method = RequestMethod.GET)
public ResponseEntity downloadJpg(
@PathVariable String jpgName) throws IOException {
// Resource downloadResource = new InputStreamResource(soimeinputStream)
// Resource downloadResource = new ByteArrayResource(someByteArray)
// Resource downloadResource = new FileSystemResource(someFile)
final ClassPathResource downloadResource = new ClassPathResource(jpgName);
if (!downloadResource.exists()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
HttpHeaders headers = new HttpHeaders();
// 1. set the correct content type
headers.setContentType(MediaType.IMAGE_JPEG);
// 2. set the correct content length, maybe stored in a db table
headers.setContentLength(downloadResource.contentLength());
// 3. if you want to force downloads, otherwise attachments might be displayed directly in the brwoser
headers.setContentDispositionFormData("attachment", jpgName);
return new ResponseEntity<>(downloadResource, headers, HttpStatus.OK);
}
}
Как я могу обрабатывать исключения из моих @контроллеров по всему миру?
Существует буквально миллион способов обработки исключений с помощью Spring MVC, если вы не хотите обрабатывать их непосредственно в своих методах @Controller, а скорее в одном центральном месте.
Создайте класс @ControllerAdvice или @RestControllerAdvice в сочетании с аннотациями @ResponseStatus и @ExceptionHandler. Пара заметок:
Вы можете догадаться о разнице между этими двумя, поняв разницу между @Controllers и @RestControllers.
@ResponseStatus позволяет определить код состояния HTTP, который должен быть возвращен клиенту после обработки вашего исключения.
@ExceptionHandler указывает исключение, которое должно вызвать ваш метод обработчика.
В остальном это похоже на написание обычного @Controller или @RestController.
package com.marcobehler.springmvcarticle;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(SomeConflictException.class)
public String handleConflict(SomeConflictException e, Model model) {
// do something
model.addAttribute("message", e.getMessage());
return "new-template";
}
@ResponseStatus(HttpStatus.NOT_IMPLEMENTED) // 409
@ExceptionHandler(NotYetImplementedExceptoin.class)
public void handleBandwithLimitExceeded(NotYetImplementedExceptoin e) {
// do nothing;
}
}
Как вернуть любой код статуса (400, 404 и т.д.) с ваших контроллеров @?
Вызовите исключение ResponseStatusException с соответствующим кодом состояния и, возможно, причиной.
Альтернативой было бы возвращение объекта ResponseEntity, но в большинстве случаев исключение приятнее.
package com.marcobehler.springmvcarticle;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.server.ResponseStatusException;
@Controller
public class HttpStatusCodeController {
@GetMapping("/somePath")
public void alwaysThrowsException() {
//throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Meeepp, not found.");
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Well, that just wasn't right!");
}
}
Как насчет концепции XYZ Spring MVC?
Официальная документация Spring MVC буквально содержит сотни страниц, описывающих, как работает веб-фреймворк.
Итак, если вы хотите узнать больше о моделях, Представлениях, Обработчиках представлений, Инициализации, корневых контекстах, Фильтрах, кэшировании и т. Д., Я приглашаю вас ознакомиться с этим. Это просто не входит в рамки данного руководства, чтобы охватить все.
Плавник
Это была настоящая поездка. В конце концов, я надеюсь, что вы убрали пару вещей из этой статьи:
Spring MVC – это старый добрый фреймворк MVC, который позволяет довольно легко создавать HTML-сайты или веб-сервисы JSON/XML.
Он хорошо интегрируется с множеством библиотек шаблонов и библиотек преобразования данных, а также с остальной частью экосистемы Spring, такой как Spring Boot.
В основном это позволяет вам сосредоточиться на написании бизнес-логики, не слишком беспокоясь о коде подключения сервлетов, анализе HTTP-запросов/ответов и преобразовании данных.
Вот и все на сегодня. Спасибо за чтение.
Подтверждения
Большое “спасибо” Патрисио “Пато” Мошковичу , который не только вычитал эту статью, но и предоставил бесценную обратную связь!
Оригинал: “https://dev.to/marcobehler/spring-mvc-in-depth-guide-4adf”