Web Crawler — это программа, которая перемещается по сети и находит новые или обновленные страницы для индексации. Сканер начинает с исходных веб-сайтов или широкого диапазона популярных URL-адресов (также известных какfrontier) и выполняет поиск гиперссылок по глубине и ширине для извлечения.
Веб-сканер должен быть добрым и надежным. Доброта для сканера означает, что он соблюдает правила, установленные robots.txt, и избегает слишком частого посещения веб-сайта. Надежность означает способность избегать ловушек пауков и других злонамеренных действий. Другими хорошими атрибутами для Web Crawler являются возможность распространения среди нескольких распределенных компьютеров, расширяемость, непрерывность и возможность расставлять приоритеты в зависимости от качества страницы.
1. Шаги по созданию веб-сканера
Основные шаги для написания веб-сканера:
-
Выберите URL-адрес от границы
-
Получить код HTML
-
Разбор HTML для извлечения ссылок на другие URL
-
Проверьте, сканировали ли вы уже URL-адреса и / или видели ли вы тот же контент ранее
-
Если не добавить его в индекс
-
Для каждого извлеченного URL
-
Подтвердите, что он согласен с проверкой (robots.txt, частота сканирования)
По правде говоря, разработка и поддержка одного веб-краулера на всех страницах в Интернете… Трудно, если не невозможно, учитывая, что сейчас в сети более1 billion websites. Если вы читаете эту статью, скорее всего, вы ищете не руководство по созданию Web Crawler, а Web Scraper. Почему тогда статья называется «Basic Web Crawler»? Ну … Потому что это броско … Действительно! Мало кто знает разницу между сканерами и скребками, поэтому мы все склонны использовать слово «сканирование» для всего, даже для очистки данных в автономном режиме. Кроме того, поскольку для создания Web Scraper вам также необходим агент сканирования. И, наконец, потому что эта статья намерена проинформировать, а также привести пример.
2. Скелет гусеничного
Для парсинга HTML мы будем использоватьjsoup. Приведенные ниже примеры были разработаны с использованием jsoup версии 1.10.2.
pom.xml
Итак, начнем с основного кода для веб-сканера.
BasicWebCrawler.java
package com.example.basicwebcrawler; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; import java.util.HashSet; public class BasicWebCrawler { private HashSet links; public BasicWebCrawler() { links = new HashSet(); } public void getPageLinks(String URL) { //4. Check if you have already crawled the URLs //(we are intentionally not checking for duplicate content in this example) if (!links.contains(URL)) { try { //4. (i) If not add it to the index if (links.add(URL)) { System.out.println(URL); } //2. Fetch the HTML code Document document = Jsoup.connect(URL).get(); //3. Parse the HTML to extract links to other URLs Elements linksOnPage = document.select("a[href]"); //5. For each extracted URL... go back to Step 4. for (Element page : linksOnPage) { getPageLinks(page.attr("abs:href")); } } catch (IOException e) { System.err.println("For '" + URL + "': " + e.getMessage()); } } } public static void main(String[] args) { //1. Pick a URL from the frontier new BasicWebCrawler().getPageLinks("http://www.example.com/"); } }
Note
Не позволяйте этому коду работать слишком долго. Это может занять часы без конца.
Пример вывода:
Как мы уже упоминали ранее, веб-сканер ищет ссылки по ширине и глубине. Если мы представим ссылки на веб-сайте в древовидной структуре, корневым узлом или нулевым уровнем будет ссылка, с которой мы начнем, следующим уровнем будут все ссылки, которые мы нашли на нулевом уровне, и так далее.
3. Принимая во внимание глубину сканирования
Мы изменим предыдущий пример, чтобы установить глубину извлечения ссылки. Обратите внимание, что единственное истинное различие между этим примером и предыдущим состоит в том, что рекурсивный методgetPageLinks()
имеет целочисленный аргумент, который представляет глубину ссылки, которая также добавляется как условие в оператореif...else
.
WebCrawlerWithDepth.java
package com.example.depthwebcrawler; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; import java.util.HashSet; public class WebCrawlerWithDepth { private static final int MAX_DEPTH = 2; private HashSet links; public WebCrawlerWithDepth() { links = new HashSet<>(); } public void getPageLinks(String URL, int depth) { if ((!links.contains(URL) && (depth < MAX_DEPTH))) { System.out.println(">> Depth: " + depth + " [" + URL + "]"); try { links.add(URL); Document document = Jsoup.connect(URL).get(); Elements linksOnPage = document.select("a[href]"); depth++; for (Element page : linksOnPage) { getPageLinks(page.attr("abs:href"), depth); } } catch (IOException e) { System.err.println("For '" + URL + "': " + e.getMessage()); } } } public static void main(String[] args) { new WebCrawlerWithDepth().getPageLinks("http://www.example.com/", 0); } }
Note
Не стесняйтесь запускать приведенный выше код. Это заняло всего несколько минут на моем ноутбуке с глубиной 2. Пожалуйста, имейте в виду, что чем выше глубина, тем дольше будет финиш.
Пример вывода:
... >> Depth: 1 [https://docs.gradle.org/current/userguide/userguide.html] >> Depth: 1 [http://hibernate.org/orm/] >> Depth: 1 [https://jax-ws.java.net/] >> Depth: 1 [http://tomcat.apache.org/tomcat-8.0-doc/index.html] >> Depth: 1 [http://www.javacodegeeks.com/] For 'http://www.javacodegeeks.com/': HTTP error fetching URL >> Depth: 1 [http://beust.com/weblog/] >> Depth: 1 [https://dzone.com] >> Depth: 1 [https://wordpress.org/] >> Depth: 1 [http://www.liquidweb.com/?RID=example] >> Depth: 1 [http://www.example.com/privacy-policy/]
4. Очистка данных против Сканирование данных
Пока все хорошо для теоретического подхода по этому вопросу. Дело в том, что вы вряд ли когда-либо создадите универсальный сканер, и если вы хотите «настоящий» сканер, вы должны использовать уже существующие инструменты. Большая часть того, что делают среднестатистические разработчики, — это извлечение конкретной информации с определенных веб-сайтов, и, хотя это включает в себя создание веб-сканера, это на самом деле называется Web Scraping.
Есть очень хорошая статья Арпана Джа для PromptCloud оData Scraping vs. Data Crawling, которая лично мне очень помогла понять это различие, и я предлагаю прочитать ее.
Подводя итог, приведем таблицу из этой статьи:
Сбор данных | Сканирование данных |
---|---|
Включает извлечение данных из различных источников, включая Интернет. |
Относится к загрузке страниц из Интернета. |
Можно сделать в любом масштабе |
В основном делается в больших масштабах |
Дедупликация не обязательно является частью |
Дедупликация — важная часть |
Требуется агент сканирования и парсер |
Требуется только агент сканирования |
Время выйти из теории и привести в пример, как обещано во вступлении. Давайте представим сценарий, в котором мы хотим получить все URL-адреса статей, относящихся к Java 8, с сайта example.com. Наша цель — получить эту информацию в кратчайшие сроки и, таким образом, избежать обхода всего сайта. Кроме того, такой подход будет не только тратить ресурсы сервера, но и наше время.
5. Пример использования — извлеките все статьи о Java 8 на example.com
5.1 First thing we should do is look at the code of the website. Бегло взглянув на example.com, мы можем легко заметить разбиение на страницы на первой странице и то, что она следует шаблону/page/xx
для каждой страницы.
Это подводит нас к осознанию того, что к искомой информации легко получить доступ, получив все ссылки, которые включают/page/
. Поэтому вместо того, чтобы проходить через весь веб-сайт, мы ограничим наш поиск, используяdocument.select("a[href^="http://www.example.com/page/"]")
. С помощью этогоcss selector
мы собираем только ссылки, которые начинаются сhttp://example.com/page/
.
5.2 Next thing we notice is that the titles of the articles -which is what we want- are wrapped in <h2></h2>
and <a href=""></a>
tags.
Итак, чтобы извлечь заголовки статей, мы будем получать доступ к этой конкретной информации, используяcss selector
, который ограничивает наш методselect
этой точной информацией:document.select("h2 a[href^="http://www.example.com/"]");
5.3 Finally, we will only keep the links in which the title contains ‘Java 8’ and save them to a file
.
Extractor.java
package com.example.extractor; package com.example; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; public class Extractor { private HashSet links; private List> articles; public Extractor() { links = new HashSet<>(); articles = new ArrayList<>(); } //Find all URLs that start with "http://www.example.com/page/" and add them to the HashSet public void getPageLinks(String URL) { if (!links.contains(URL)) { try { Document document = Jsoup.connect(URL).get(); Elements otherLinks = document.select("a[href^="http://www.example.com/page/"]"); for (Element page : otherLinks) { if (links.add(URL)) { //Remove the comment from the line below if you want to see it running on your editor System.out.println(URL); } getPageLinks(page.attr("abs:href")); } } catch (IOException e) { System.err.println(e.getMessage()); } } } //Connect to each link saved in the article and find all the articles in the page public void getArticles() { links.forEach(x -> { Document document; try { document = Jsoup.connect(x).get(); Elements articleLinks = document.select("h2 a[href^="http://www.example.com/"]"); for (Element article : articleLinks) { //Only retrieve the titles of the articles that contain Java 8 if (article.text().matches("^.*?(Java 8|java 8|JAVA 8).*$")) { //Remove the comment from the line below if you want to see it running on your editor, //or wait for the File at the end of the execution //System.out.println(article.attr("abs:href")); ArrayList temporary = new ArrayList<>(); temporary.add(article.text()); //The title of the article temporary.add(article.attr("abs:href")); //The URL of the article articles.add(temporary); } } } catch (IOException e) { System.err.println(e.getMessage()); } }); } public void writeToFile(String filename) { FileWriter writer; try { writer = new FileWriter(filename); articles.forEach(a -> { try { String temp = "- Title: " + a.get(0) + " (link: " + a.get(1) + ")n"; //display to console System.out.println(temp); //save to file writer.write(temp); } catch (IOException e) { System.err.println(e.getMessage()); } }); writer.close(); } catch (IOException e) { System.err.println(e.getMessage()); } } public static void main(String[] args) { Extractor bwc = new Extractor(); bwc.getPageLinks("http://www.example.com"); bwc.getArticles(); bwc.writeToFile("Java 8 Articles"); } }
Выход:
Рекомендации
-
PromptCloud — сбор данных или сканирование данных
-
PromptCloud — как работает веб-сканер
-
Википедия — поисковый робот
-
Что такое Crawl Frontier?
Вы можете использовать эту статью, чтобы понять, как использовать Spring MVC для создания веб-сайтов или RESTful сервисов. А также получить обзор часто задаваемых вопросов, охватывающих наиболее распространенные задачи Spring MVC.
Примечание: Статья ~ 7500 слов, вероятно, не стоит читать ее на мобильном устройстве. Добавьте ее в закладки и вернитесь позже.
Содержание
- Введение
- HttpServlets 101
- DispatcherServlet
- Контроллеры — создание HTML
- REST контроллеры — создание XML / JSON
- Часто задаваемые вопросы
- Заключение
- Благодарности
Введение
Что такое Spring MVC?
Spring MVC — это веб-фреймворк Spring. Он позволяет создавать веб-сайты или RESTful сервисы (например, JSON/XML) и хорошо интегрируется в экосистему Spring, например, он поддерживает контроллеры и REST контроллеры в ваших Spring Boot приложениях.
Это не очень помогло, не так ли?
К счастью, есть и более длинный ответ: остальная часть этого документа.
(Если вы не уверены, что знаете что такое Spring или Spring Boot, вы можете сначала прочитать, Что такое Spring Framework?)
HttpServlets 101
При написании веб-приложений на Java с использованием Spring или без него (MVC/Boot) вы в основном имеете в виду написание приложений, которые возвращают два разных формата данных:
- HTML → Ваше веб-приложение создает HTML-страницы, которые можно просматривать в браузере.
- JSON/XML → Ваше веб-приложение предоставляет сервисы RESTful, которые генерируют JSON или XML. Сайты с большим количеством Javascript или даже другие веб-сервисы могут затем использовать данные, которые предоставляют эти сервисы.
- Да, есть и другие форматы данных и варианты использования, но пока мы их игнорируем.
Как бы вы написали такие приложения без каких-либо фреймворков? Только на простой Java?
На самом низком уровне каждое веб-приложение Java состоит из одного или нескольких HttpServlets. Они генерируют ваш HTML, JSON или XML. Фактически, каждый отдельный фреймворк из 1 миллиона доступных веб-фреймворков на Java (Spring MVC, Wicket, Struts) построена на основе HttpServlets.
(Примечание для придир: это может быть сделано без HttpServlets, но мы пока проигнорируем это.)
Создание HTML-страниц с помощью HttpServlets
Давайте посмотрим на супер простой 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("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");
}
else {
throw new IllegalStateException("Help, I don't know what to do with this url");
}
}
}
Давайте разберемся с этим кодом.
public class MyServletV1 extends HttpServlet {
Ваш сервлет расширяет класс Java HttpServlet.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Чтобы обработать (любой) запрос GET, вам необходимо переопределить метод doGet () из суперкласса. Для запросов POST вы должны переопределить doPost (). Аналогично для всех других HTTP методов.
if (req.getRequestURI().equals("/")) {
Ваш сервлет должен убедиться, что входящий URL-адрес является запросом, который он знает как обрабатывать. Пока сервлет обрабатывает только «/», то есть он обрабатывает www.marcobehler.com, но НЕ www.marcobehler.com/hello.
resp.setContentType("text/html");
Вам нужно установить правильный тип контента в ServletResponse, чтобы браузер знал, какой контент вы отправляете. В данном случае это HTML.
resp.getWriter().print("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");
Помните: веб-сайты — это просто строки HTML! Поэтому вам нужно сгенерировать HTML-строку любым удобным вам способом и отправить ее обратно с помощью ServletResponse. Один из способов сделать это с помощью response writer.
После написания вашего сервлета вы должны зарегистрировать его в контейнере сервлетов, таком как 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, за какие запросы отвечает сервлет, то есть за отображение. Отображение /* означает, что оно отвечает за любой входящий запрос (/users, /register, /checkout).
tomcat.start();
Вот и все. Теперь вы запускаете метод main(), переходите на порт 8080 в своем любимом веб-браузере (http://localhost:8080 /), и вы увидите красивую страницу HTML.
Таким образом, по сути, пока вы продолжаете расширять методы doGet () и doPost (), все ваше веб-приложение может состоять только из одного сервлета. Давайте попробуем это.
Создание JSON с помощью HttpServlets
Представьте себе, что помимо вашей (довольно пустой) HTML-страницы индекса вы теперь также хотите предложить REST API для вашего готовящегося к разработке внешнего интерфейса. Так что ваш интерфейс React или AngularJS будет вызывать URL-адрес примерно так:
/api/users/{userId}
Эта конечная точка должна возвращать данные в формате JSON для пользователя с заданным userId. Как мы могли бы доработать наш 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("<html><head></head><body><h1>Welcome!</h1><p>This is a very cool page!</p></body></html>");
} 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/users/.
Integer prettyFragileUserId = Integer.valueOf(req.getRequestURI().lastIndexOf("/") + 1);
Мы делаем очень слабый разбор URL. Последняя часть URL — это идентификатор пользователя userID, например, 5 для /api/users/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" +
"}");
Опять же, JSON — это просто текст, поэтому мы можем записать его непосредственно в HTTPServletResponse. Возможно, мы бы использовали библиотеку JSON для преобразования нашего пользовательского Java-объекта в эту строку, но для простоты я не буду показывать это здесь.
Проблема с нашим подходом «один сервлет для всего»
Хотя наш сервлет выше работает, на горизонте вас ожидает немало проблем:
- Ваш сервлет должен выполнить множество ручных HTTP-специфических операций, проверять URI запроса, перебирать строки и т.д. Другими словами: ему нужно знать ЧТО хотят пользователи.
- Затем он также должен найти данные для всего, что вы хотите отобразить. Другими словами: это должно знать, КАК. В нашем примере выше это будет поиск пользователя в базе данных, которую мы для простоты закомментировали.
- Затем необходимо также преобразовать эти данные в JSON или HTML и установить соответствующие типы ответов.
Довольно много разных обязанностей, не так ли? Разве не было бы лучше, если бы вам не приходилось заботиться обо всем этом стандартом коде? Больше нет парсинга URI запроса и параметров, нет больше преобразований JSON, больше нет ответов сервлета?
Именно здесь на помощь приходит Spring MVC.
DispatcherServlet
Мы расскажем о Spring MVC немного нетрадиционно и не будем подробно останавливаться на том, что означает Model-View-Controller. Вместо этого немного раздразним вас.
Что если я скажу вам, что Spring MVC — это всего лишь один сервлет, как наш выше супер-сервлет?
Встречайте DispatcherServlet.
(О да, в этом, конечно, немного обмана)
Что делает Spring MVC DispatcherServlet?
Как уже упоминалось выше, почти все веб-фреймворки Java основаны на сервлетах, поэтому Spring MVC также нужен сервлет, который обрабатывает каждый входящий HTTP-запрос (поэтому DispatcherServlet также называется фронт-контроллером).
Что же в точности означает обрабатывать HTTP-запрос, точно? Представьте себе «рабочий процесс регистрации пользователя», при котором пользователь заполняет форму и отправляет ее на сервер и в ответ получает небольшую HTML страницу об успешной регистрации.
В этом случае ваш DispatcherServlet должен выполнить следующие действия:
- Необходимо просмотреть URI входящего запроса HTTP и любые параметры запроса. Например: POST /register?name=john&age33.
- Он должен потенциально преобразовывать входящие данные (параметры/тело запроса) в симпатичные маленькие объекты Java и перенаправить их в класс контроллер или REST контроллер,, который вы написали.
- Ваш контроллер сохраняет нового пользователя в базе данных, возможно отправляет электронное письмо и т.д. Он, скорее всего, делегирует это другому сервисному классу, но давайте предположим, что пока это происходит внутри контроллера.
- Он должен взять любой вывод из вашего контроллера и преобразовать его обратно в HTML/JSON/XML.
Весь процесс выглядит следующим образом, пренебрежем для простоты большим количеством промежуточных классов, потому что DispatcherServlet не выполняет всю работу сам.
До сих пор изложение было немного расплывчато в отношении некоторых частей этого процесса. Что такое ModelAndView на рисунке выше? Как именно DispatcherServlet преобразует данные?
Как выглядит реальный процесс Let’s-write-HTML? Об этом узнаем в следующем разделе.
Контроллеры — создание HTML
Всякий раз, когда вы хотите написать HTML на клиенте, таком как браузер с Spring MVC (включая Spring Boot), вы захотите написать класс контроллера. Давайте сделаем это сейчас.
Как написать контроллер в Spring
Для нашего рабочего процесса регистрации пользователей выше (POST/register?name=john&age33) мы бы написали следующий класс.
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")
Эта строка сообщает нашему DispatcherServlet, что всякий раз, когда приходит запрос POST для пути /register, включая любые параметры запроса (например, ?Username=), он должен отправлять запрос именно этому методу контроллера.
public String registerUser(@RequestParam(required = false) Integer age, @RequestParam String name, Model model) {
Примечание Наименование нашего метода на самом деле не имеет значения, его можно назвать как угодно.
Однако мы указываем, что каждый запрос должен включать два параметра запроса, которые могут быть либо частью URL (?age=10&name=Joe), либо находиться в теле запроса POST. Кроме того, требуется только параметр name (параметр age является необязательным)
И параметр age, если пользователь предоставил его, автоматически преобразуется в Integer (исключение выдается, если предоставленное значение не является допустимым Integer)
Наконец, что не менее важно, 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);
Вы добавляете своего пользователя в модель с ключом «user». Это означает, что вы сможете ссылаться на него в своем HTML-шаблоне позже, например, «${user.name}». Подробнее об этом через секунду.
return "registration-success";
Ваш метод возвращает простую строку со значением registration-success. Это не просто строка, это ссылка на ваше представление, т.е. шаблон HTML, который вы хотите, чтобы Spring отображал.
Views (представления)
Давайте пока проигнорируем, как (или, скорее, где) Spring MVC попытается найти это представление, т.е. ваш шаблон, вместо этого давайте посмотрим, как должен выглядеть ваш шаблон registration-success.html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="'Hello ' + ${user.name} + '!'"></p>
</body>
</html>
Это простая HTML-страница, которая содержит одну строку шаблона. Он печатает имя пользователя, который только что зарегистрировался.
<p th:text="'Hello ' + ${user.name} + '!'"></p>
Вопрос в том, что означает синтаксис th:text=? Он специфический для Spring? Или это что-то еще?
И ответ таков: Spring MVC ничего не знает о шаблонах HTML. Для работы с HTML-шаблонами требуется сторонняя библиотека шаблонов, и не нужно заботиться о том, какую библиотеку вы выберете.
В приведенном выше примере вы видите шаблон Thymeleaf, который очень популярен при работе над проектами Spring MVC.
Spring MVC и библиотеки шаблонов
Существует несколько различных библиотек шаблонов, которые хорошо интегрируются с Spring MVC, из которых вы можете выбрать: Thymeleaf, Velocity, Freemarker, Mustache и даже JSP (хотя это не библиотека шаблонов).
Фактически, вы должны явно выбрать библиотеку шаблонов, потому что если у вас нет такой библиотеки шаблонов, добавленной в ваш проект и настроенной правильно, то ваш метод контроллера не будет отображать вашу HTML-страницу — потому что он не знает, как это сделать.
Это также означает, что вы должны изучить и понять синтаксис конкретной библиотеки шаблонов в зависимости от проекта, в котором вы работаете, потому что все они немного отличаются друг от друга. Весело, правда?
Что такое ViewResolver?
На секунду давайте подумаем, где Spring на самом деле попытается найти ваши HTML-шаблоны, которые возвращает ваш контроллер.
Класс, который пытается найти ваш шаблон, называется ViewResolver. Поэтому всякий раз, когда запрос поступает в ваш контроллер, Spring проверяет настроенные ViewResolvers и запрашивает их, чтобы найти шаблон с заданным именем. Если у вас нет настроенных ViewResolvers, это не сработает.
Представьте, что вы хотите интегрироваться с 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. Учитывая имя шаблона (помните: registration-success), ViewResolvers может найти фактический шаблон.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
Для правильной работы ThymeleafViewResolver требуется несколько других специфичных для Thymeleaf классов. Одним из этих классов является SpringResourceTemplateResolver. Это делает фактическую работу по поиску вашего шаблона.
Примечание SpringResourceTemplateResolver является классом Thymeleaf
templateResolver.setPrefix("classpath:/templates");
templateResolver.setSuffix(".html");
По существу, вы говорите (с помощью синтаксиса Spring Resources): «Все мои шаблоны находятся по пути classpath, в папке /templates». И по умолчанию все они заканчиваются на .html. Это означает:
Всякий раз, когда наш контроллер возвращает String, подобный registration-success, ThymeleafViewResolver будет искать шаблон: classpath:/templates/registration-success.html.
Заметка на полях: Spring Boot
Вы можете подумать: Марко, мне никогда не приходилось настраивать такой ViewResolver, работая над проектами Spring Boot. И это правильно. Потому что Spring Boot автоматически настраивает его для вас каждый раз, когда вы добавляете в свой проект зависимость, такую как spring-boot-starter-thymeleaf.
Он также настраивает ViewResolver так, чтобы он по умолчанию просматривал ваш каталог src/main/resources/template.
Итак, Spring Boot действительно предварительно настраивает Spring MVC для вас. Запомните.
Резюме: Model-View-Controller
Посмотрев полный пример Controller & ViewResolver, становится намного проще говорить о концепции Spring Model-View-Controller.
- С помощью нескольких аннотаций (Controller, @PostMapping, @RequestParam) вы можете написать контроллер, который будет заботиться о получении данных запроса и обрабатывать их соответствующим образом.
- Ваша модель содержит все данные (и только данные), которые вы хотите отобразить в своем представлении. Это ваша работа, чтобы заполнить эту карту модели.
- Ваше представление — это просто шаблон HTML. Неважно, откуда вы взяли данные (модели). Или каков текущий HTTP-запрос. Или даже если у вас есть активный HTTP-сеанс или нет.
Это все о разделении ответственностей.
На первый взгляд, немного перегруженный аннотациями, наш класс Spring контроллера читается намного лучше, с гораздо меньшим количеством подключений HTTP, чем наш супер-сервлет с самого начала.
Подробнее о контроллерах
Мы уже видели небольшое удобство, которое предоставляет нам Spring MVC при обработке входов HTTP.
- Вам не нужно возиться с requestURI, вместо этого вы можете использовать аннотацию.
- Вам не нужно возиться с преобразованиями типов параметров запроса или, разбираться является ли параметр необязательным или обязательным, вы можете использовать аннотацию вместо этого.
Давайте рассмотрим наиболее распространенные аннотации, которые помогут вам обрабатывать входящие HTTP-запросы.
@GetMapping и @RequestMappping
Вы уже видели аннотацию @GetMapping выше. Она эквивалентна аннотации *@RequestMapping*
. Давайте рассмотрим пример:
@GetMapping("/books")
public void book() {
//
}
/* these two mappings are identical */
@RequestMapping(value = "/books", method = RequestMethod.GET)
public void book2() {
}
@GetMapping, @[Post|Put|Delete|Patch]Mapping эквивалентно @RequestMapping(method=XXX). Это просто более новый способ (Spring 4.3+) для определения мапинга (связывания) с URL, поэтому вы найдете, что аннотация @RequestMapping часто используется в более старых, унаследованных проектах Spring.
@RequestParam
Для параметров HTTP-запроса, будь то в вашем URL (?Key=value) или в отправленном теле запроса формы, можно прочитать с помощью аннотации @RequestParam.
Вы уже видели, что он выполняет базовое преобразование типов (например, из параметра HTTP String в int), а также проверяет обязательные или дополнительные параметры.
@PostMapping("/users") /* First Param is optional */
public User createUser(@RequestParam(required = false) Integer age, @RequestParam String name) {
// does not matter
}
Если вы забудете указать в запросе обязательный параметр, вы получите код ответа 400 Bad Request и, при использовании 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 в объект без каких-либо необходимых аннотаций. Просто укажите ваш объект как «параметр метода«.
Вам просто нужно убедиться, что у вашего класса есть соответствующие методы getter/setter.
@PostMapping("/users") /* Spring преобразует это автоматически, если у вас есть getters and setters */
public User createUser(UserDto userDto) {
//
}
@PathVariable
Помимо параметров запроса, другим популярным способом указания переменных является непосредственное задание их в URI запроса, как @PathVariable. Поэтому, чтобы получить профиль пользователя с userId=123, вы должны вызвать следующий URL: GET / users/123
- Вам просто нужно убедиться, что значение вашего параметра соответствует значению между {} в аннотации сопоставления вашего запроса.
Кроме того, PathVariables также может быть обязательным или необязательным.
@GetMapping("/users/{userId}")
public User getUser(@PathVariable(required = false) String userId) {
// ...
return user;
}
И PathVariables, конечно, может быть напрямую преобразованы в Java-объект (при условии, что у объекта есть соответствующие методы getter/setter).
@GetMapping("/users/{userId}")
public User getUser(UserDto userDto) {
// ...
return user;
}
Резюме: Контроллеры
Короче говоря, при написании HTML-страниц с помощью Spring MVC вам придется сделать всего несколько вещей:
- Напишите свои контроллеры, «присыпанные» несколькими аннотациями. Spring позаботится о том, чтобы представить вам запрос ввода (параметры запроса, переменные пути) удобным способом.
- Выполните любую логику, необходимую для заполнения вашей модели. Вы можете удобно ввести модель в любой метод контроллера.
- Сообщите вашему контроллеру, какой шаблон HTML вы хотите отобразить, и верните имя шаблона в виде строки.
- Всякий раз, когда поступает запрос, Spring обязательно вызовет ваш метод контроллера и примет полученную модель и представление, отобразит его в HTML-строку и вернет его обратно в браузер.
- При условии, конечно, вы настроили соответствующую библиотеку шаблонов, что Spring Boot автоматически сделает для вас, если вы добавите необходимые зависимости в ваш проект.
Вот и все.
REST контроллеры — создание XML/JSON
Когда вы разрабатываете RESTFul сервисы, все немного по-другому. Ваш клиент, будь то браузер или другой веб-сервис, будет (обычно) создавать запросы JSON или XML. Клиент отправляет, скажем, запрос JSON, вы обрабатываете его, а затем отправитель ожидает возврата JSON.
Таким образом, отправитель может отправить вам этот фрагмент JSON как часть тела HTTP-запроса.
POST http://localhost:8080/users
###
{"email": "angela@merkel.de"}
Но на стороне Java (в вашей программе Spring MVC) вы не хотите иметь дело с JSON строками. Ни при получении запросов, как указано выше, ни при отправке ответов обратно клиенту. Вместо этого вы хотели бы просто иметь объекты Java, в которые Spring автоматически конвертирует JSON.
public class UserDto {
private String email;
//...
}
Это также означает, что вам не нужна вся эта обработка модели и представления, которые вам приходилось делать при рендеринге HTML в ваших контроллерах. Для RESTful сервисов у вас нет библиотеки шаблонов, читающей шаблон HTML и заполняющей его данными модели, чтобы сгенерировать для вас ответ JSON.
Вместо этого вы хотите перейти непосредственно из HTTP запрос → Java объект и из Java объект → HTTP ответ.
Как вы уже догадались, это именно то, что Spring MVC обеспечивает при написании REST контроллера.
Как написать REST контроллер
Первое, что вам нужно сделать для вывода XML/JSON, это написать аннотацию @RestController вместо Controller. (Хотя @RestController является Controller, см. 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<Transaction> transactions(String userId) {
// find transaction by user
// List<Transaction> = dao.findByUserId(userId);
List<Transaction> transactions = Collections.emptyList();
return transactions;
}
}
Давайте разберемся с этим кодом.
@RestController
public class BankController {
Вы снабдили класс BankController аннотацией @RestController, которая сообщает Spring, что вы не хотите писать HTML-страницы через обычный процесс ModelAndView. Вместо этого вы хотите записать XML/JSON (или какой-либо другой формат) непосредственно в тело ответа HTTP.
public List<Transaction> transactions(String userId) {
Ваш контроллер больше не возвращает String (представление). Вместо этого он возвращает List, который Spring необходимо преобразовать в соответствующую структуру JSON или XML. По сути, вы хотите, чтобы ваши Java объекты Transaction стали такими (кто-то жаждал фаст-фуд очень рано утром):
[
{
"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? Как ваш метод REST контроллер знает, каким должен быть предполагаемый формат ответа?
Для этого у Spring есть концепция согласования контента.
Короче говоря, согласование контента означает, что клиент должен сообщить вашему серверу, какой формат ответа он хочет получить от вашего REST контроллера.
Как? Указав заголовок Accept в HTTP-запросе.
GET http://localhost:8080/transactions/{userid}
Accept: application/json
Spring MVC разберет этот заголовок Accept и узнает: клиент хочет вернуть JSON (application/json), поэтому мне нужно преобразовать мой List в JSON. (Краткое примечание. Существуют и другие способы согласования содержимого, но заголовок Accept используется по умолчанию.)
Давайте назовем это согласование содержимого ответа, поскольку речь идет о формате данных ответа HTTP, который вы отправляете обратно своему клиенту.
Но согласование контента также работает для входящих запросов. Посмотрим как.
Согласование контента запроса — Content-Type Header (заголовок типа контента)
При создании RESTful API очень высока вероятность того, что ваши клиенты также смогут отправлять запросы в формате 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
###
...
Как будет выглядеть соответствующий REST контроллер для этого запроса?
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 в сочетании с правильным Content-Type будет сигнализировать Spring о том, что ему необходимо просмотреть тело HTTP-запроса и преобразовать его в любой Content-Type, указанный пользователем: JSON в нашем случае.
// do something with the dto..create the booking..convert it to a transaction
Transaction transaction = null;
return transaction;
}
Тогда вашему методу больше не нужно заботиться об необработанной строке JSON, он может просто работать с TransactionDTO, сохранять его в базе данных, преобразовывать в объект Transaction, что угодно. В этом сила Spring MVC.
Сам Spring не может конвертировать форматы данных
Есть только одна небольшая проблема: Spring знает о заголовках Accept и Content-Type, но не знает, как конвертировать между объектами Java и JSON. Или XML. Или ЯМЛ.
Для этой грязной работы требуется соответствующая сторонняя библиотека (также называемая маршалинг / демаршаллинг или сериализация / десериализация.)
А классы, которые интегрируются между Spring MVC и этими сторонними библиотеками, называются HttpMessageConverters.
Что такое HttpMessageConverter?
HttpMessageConverter — это интерфейс с четырьмя методами (обратите внимание, я немного упростил интерфейс для более простого объяснения, так как он выглядит немного более продвинутым в реальной жизни).
- canRead (MediaType) → Может ли этот конвертер читать (JSON | XML | YAML | и т. д.)? Переданный здесь MediaType обычно является значением из заголовка запроса Content-Type.
- canWrite (MediaType) → Может ли этот преобразователь писать (JSON | XML | YAML | и т. д.)? Тип MediaType, переданный здесь, обычно является значением из заголовка запроса Accept.
- read(Object, InputStream, MediaType) → Читать мой Java-объект из (JSON | XML | YAML | и т. д.) InputStream
- write(Object, OutputStream, MediaType) → Записать мой Java-объект в OutputStream как (JSON | XML | YAML | и т. д.)
Короче говоря, MessageConverter должен знать, какие MediaTypes он поддерживает (например, application/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);
Spring MVC проверяет наличие класса javax.xml.bind.Binder и, если он есть, предполагает, что вы добавили в свой проект необходимую библиотеку для выполнения преобразований JAXB.
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
Spring MVC проверяет наличие двух классов ..jackson..ObjectMapper и ..jackson..JsonGenerator и, если это так, предполагает, что вы добавили библиотеку Jackson в свой проект для выполнения преобразований JSON.
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
Spring MVC проверяет наличие класса ..jackson..XmlMapper и, если это так, предполагает, что вы добавили поддержку XML библиотеки Jackson s в свой проект для выполнения преобразований XML.
И так далее. И через пару строк Spring просто добавляет HttpMessageConverter для каждой библиотеки, которую он «обнаружил».
if (jaxb2Present && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
addPartConverter(new GsonHttpMessageConverter());
}
Заметка на полях: Spring Boot
При создании проектов Spring Boot вы автоматически будете использовать Spring MVC под капотом. Но Spring Boot также вызывает Jackson по умолчанию.
Вот почему вы можете сразу написать конечные точки JSON с помощью Spring Boot, потому что необходимые HttpMessageConverts будут добавлены автоматически для вас.
Резюме: REST контроллеры
По сравнению с HTML использование JSON / XML немного проще, так как вам не нужен рендеринг Model и View.
Вместо этого ваши контроллеры напрямую возвращают объекты Java, которые Spring MVC будет удобно сериализовать в JSON / XML или любой другой формат, который пользователь запросил с помощью HttpMessageConverters.
Однако вы должны убедиться в двух вещах, однако:
- Имеются соответствующие сторонние библиотеки на пути к классам.
- Отправлены правильные заголовки Accept или Content-Type с каждым запросом.
Часто задаваемые вопросы
Вы где-нибудь публиковали исходный код этой статьи?
Вы можете найти рабочий исходный код для большей части этой статьи в следующем репозитории GitHub:
https://github.com/marcobehler/spring-mvc-article
Просто клонируйте проект и запустите класс SpringMvcArticleApplication, чтобы запустить веб-приложение.
В чем разница между Spring MVC и Spring Boot?
Вкратце: нет никакой разницы, Spring Boot использует и строит приложение поверх Spring MVC.
Для более подробного объяснения вам нужно сначала прочитать статью Что такое Spring Framework?.
Какой самый быстрый способ создать новое приложение Spring MVC?
Если вы хотите упростить использование Spring MVC, самым быстрым способом будет создание нового Spring Boot проекта.
- Перейдите на сайт: https://start.spring.io/.
- Обязательно выберите Spring Web в качестве зависимости для вашего нового проекта.
Это позволит вам создавать веб / RESTful-приложения с помощью Spring MVC.
Какой тип ввода HTTP-запроса понимает Spring MVC?
Spring MVC понимает практически все, что предлагает HTTP — с помощью сторонних библиотек.
Это означает, что вы можете добавить в него тела запросов JSON, XML или HTTP (Multipart) Fileuploads, и Spring будет удобно конвертировать этот ввод в объекты Java.
Какие HTTP-ответы может создавать Spring MVC?
Spring MVC может записывать все что угодно в HttpServletResponse — с помощью сторонних библиотек.
Будь то HTML, JSON, XML или даже тела ответов WebSocket. Более того, он берет ваши объекты Java и генерирует эти тела ответов для вас.
В чем разница между контроллером и REST контроллером
- Контроллер по умолчанию возвращают HTML пользователям с помощью библиотеки шаблонов, если вы не добавите аннотацию @ResponseBody к определенным методам, которые также позволяют возвращать XML / JSON.
- Исходный код REST контроллера показывает, что на самом деле это контроллер с добавленной аннотацией @ResponseBody. Что эквивалентно написанию контроллера с аннотацией @ResponseBody для каждого метода.
@Controller
@ResponseBody
public @interface RestController {
- Поэтому REST контроллеры по умолчанию возвращает XML / JSON вместо HTML.
Примечание. XML и JSON — это просто самые популярные форматы данных, которые вы будете использовать в приложении Spring MVC. Однако ваши контроллеры / REST контроллеры могут возвращать что-либо еще, например, YAML. Вам нужно только убедиться, что правильный HttpMessageConverter зарегистрирован в вашем ApplicationContext.
Какую библиотеку шаблонов мне выбрать?
На протяжении многих лет я лично работал почти со всеми библиотеками шаблонов, и, хотя есть определенное стимулирование к использованию Thymeleaf в проектах Spring, у меня нет сильных предпочтений. Итак, либо воспользуйтесь Thymeleaf (если у вас нет опыта работы с другими системами), либо выберите тот, который вам наиболее удобен.
Почему мой контроллер выводит 404? Все мапинги верны.
Относительно распространенной ошибкой является то, что контроллер возвращает объекты, которые вы хотите преобразовать в JSON или XML, но вам не хватает аннотации @ResponseBody.
Spring возвратит довольно бессмысленное исключение 404 Not Found в этом случае.
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 или превратите ваш контроллер в REST контроллер.
Что произойдет, если вы определите один и тот же мапинг запросов для двух разных методов?
Если эти два метода имеют разные HTTP методы, это не будет проблемой.
/* это сработает */
@PostMapping("/users")
public void method1() {
}
@GetMapping("/users")
publi void method(2) {
}
Однако если вы сопоставите однотипные HTTP методы с одним и тем же путем, у вас возникнет проблема.
/* это не сработает */
@PostMapping("/users")
public void method1() {
}
@PostMapping("/users")
publi 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 в своем письме-подтверждении и отправляете строку как есть на свой контроллер, какое значение будет содержать электронная почта @RequestParam?
Это будет «marco[space]wantnospam@marcobehler.com», так как Spring заменит + пробелом, что является правильной обработкой RFC3986.
Исправление: Убедитесь, что URL-адреса, которые вы вводите в свое приложение, правильно закодированы: marco%2Bwantsnospam@marcobehler.com, так как Spring будет автоматически их декодировать.
Как получить доступ к текущей HttpSession пользователя?
В Spring MVC контроллере или REST контроллере вы можете просто указать 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 контроллере или REST контроллере вы можете просто указать 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<String,String> headers) { // or MultiValueMap<String,String>
// ...
}
@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-Forwarded-For: MaybeSomeSpoofedIp, realIp, cloudFrontIp
Проблема в том, что вы не можете прочитать заголовок слева направо, поскольку пользователи могут предоставить и, следовательно, подделать свой собственный заголовок 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-файла, которая выглядит примерно так:
<form method="POST" enctype="multipart/form-data" action="/upload">
File to upload:<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
Вам просто нужен контроллер с аннотацией @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 или возвращения массива byte[] в результате.
Тем не менее, самая Spring-и и гибкая версия заключается в возврате ‘ResponseEntity ‘. В зависимости от того, где вы сохранили файл, вы будете использовать различные ресурсы.
- На диске → FileSystemResource
- На пути к классам вашего проекта → ClassPathResource
- Потоковая передача из «где-то» → InputStreamResource
- Сделали его доступным как массив byte[] в памяти → 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<Resource> 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 есть несколько способов обработки исключений, если вы не хотите обрабатывать их непосредственно в своих контроллерах, а в одном центральном месте.
Создайте класс ControllerAdvice или RestControllerAdvice в сочетании с аннотациями @ResponseStatus и @ExceptionHandler. Несолько замечаний:
- Вы можете догадаться о разнице между этими двумя классами, понимая разницу между контроллером и REST контроллером.
- @ResponseStatus позволяет вам определить код статуса HTTP, который должен быть возвращен клиенту после обработки вашего исключения.
- @ExceptionHandler указывает исключение, которое должно вызывать ваш метод-обработчик.
- Кроме этого, это все похоже на написание обычного контроллера или REST контроллера.
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 буквально содержит сотни страниц, описывающих, как работает веб-фреймворк.
Поэтому, если вы хотите узнать больше о моделях, представлениях, ViewHandlers, InitBinders, RootContexts, фильтрах, кэшировании и т.д., я предлагаю вам прочитать документацию Spring MVC. Это просто выходит за рамки данного руководства, невозможно охватить это все.
Заключение
Это была хорошая прогулка. Наконец я надеюсь, что вы узнали множество вещей из этой статьи:
- Spring MVC — это старый добрый MVC-фреймворк, который позволяет довольно легко писать веб-сайты HTML или JSON/XML веб-службы.
- Он прекрасно интегрируется со множеством шаблонных библиотек и библиотек преобразования данных, а также с остальной частью экосистемы Spring, такой как Spring Boot.
- Главным образом он позволяет вам сосредоточиться на написании своей бизнес-логики, не беспокоясь о стандартом коде сервлета, разборе HTTP-запросов / ответов и преобразовании данных.
Вот и все на сегодня. Спасибо за чтение.
Благодарности
Большое спасибо Patricio «Pato» Moschcovich, который не только выполнил корректуру этой статьи, но и дал бесценный отзыв!
First you should become familiar with the HTTP Request Protocols. Then it is a simple matter of programming your website to become a socket server and when connected to you send over data that would make sense.
I’ve made a Webserver in Python only using the socket, os and sys library.
The basic HTTP Protocol is
the client will send the server
GET /path/file.extension HTTP/1.0 <- Basically GET is the type of request, /path/file.extension is basically the file being requested. and HTTP/1.0 is the protocol
Host: yourwebsite.url <- I don’t believe this is needed
User-Agent: HTTPTool/1.0 <- Basically is like the method they are using to send the HTTP request, like Chrome or Firefox
[blank]
The server would response kind of like
HTTP/1.0 200 OK <- once again, the protocol, then the message (404 is not found, etc.)
Date: Mon, 19 Nov 2012 14:15:45 GMT <- This isn’t necesary, but you might as well include it
Content-Type: text/html <- Type of content your sending, html is text/html theres also ones for images zips etc. Just google it (it’s pretty simple)
Content-Length: 12313131 <- How long (in characters) the data is. this is NEEDED
[blank]
< html >
< head >
< h2 >Hi< /h2 >
< /head >
< body >
Welcome to my poop
< /body >
< /html >
Then after the server has send the data, it closes the socket.
in Java, a string length is:
String blah = «foobar»;
int length = blah.length();
For more information about Sockets in Java read this: http://docs.oracle.com/javase/tutorial/networking/sockets/index.html
After that it is a matter of storing the words you want to look up in an array and handling the data send to the client. You also want to be able to understand POST. After that all you do is get the file they want to see, give it to them. and when they search something, look it up in a database return the link or return a item not found.
На чтение 8 мин Просмотров 5.8к. Опубликовано 07.11.2020
Если у вас есть интерес к веб-разработке, вы столкнётесь с первым препятствием, с которым сталкиваются многие люди в этой области: выбор того, как именно разрабатывать свои веб-сайты. Существует несколько языков программирования, фреймворков и программ, которые могут использовать веб-разработчики. Веб-разработка на Java — один из вариантов.
Когда дело доходит до веб-разработки, важно решить, какой язык программирования использовать. Одним из самых популярных языков веб-дизайна является Java. Ниже приводится руководство по веб-разработке на Java для тех, кто хочет начать свою карьеру в этой развивающейся области.
Содержание
- Что такое веб-разработка на Java
- Зачем использовать Java для веб-разработки
- Как стать веб-разработчиком на Java: пошаговое руководство
- Шаг 1. Изучите Java, HTML и CSS
- Шаг 2: Выберите свою IDE
- NetBeans
- Eclipse
- IntelliJ IDEA
- Шаг 3. Выберите свои веб-платформы Java
- Spring Framework
- Hibernate Framework
- Grails Framework
- ATG Framework
- Play Framework
- Заключение
Что такое веб-разработка на Java
Веб-разработка — это процесс, охватывающий все аспекты создания веб-сайта. Веб-разработчик может отвечать за разработку на стороне клиента или на стороне сервера. Некоторые разработчики работают над дизайном, а другие сосредоточены на функциональности веб-страницы.
Различные шляпы, которые носят веб-разработчики, означают, что в веб-разработке есть чему поучиться. Однако это не должно быть пугающим опытом. Если вы начнёте с правильных инструментов и приложите усилия, любой может создать веб-приложение с использованием Java.
Зачем использовать Java для веб-разработки
Java — один из самых популярных языков программирования. Помимо большой поддержки, Java позволяет пользователям создавать быстрые и настраиваемые приложения. Этот язык программирования имеет сходство с другими популярными языками, такими как C и C ++. Однако их разделяют некоторые ключевые различия.
Хотя Java является объектно-ориентированной, как языки C, она всё ещё имеет необъективные функции из-за поддержки примитивных типов данных. Java также очень универсальна. Разработчики используют язык программирования для мобильных приложений, настольных приложений и, конечно же, веб-приложений.
Важной особенностью Java является то, что по сравнению с некоторыми языками C, Java-код легче поддерживать. Это связано с тем, что Java не позволяет разработчикам выполнять команды, которые могут привести к плохому программированию. Определённых вещей, таких как нарушения доступа к памяти, не происходит, что означает, что вы избегаете ситуаций, которые приводят к сбою вашей программы или приложения.
Как стать веб-разработчиком на Java: пошаговое руководство
Есть несколько способов разработки веб-приложений на Java. Некоторые люди учатся этому на онлайн-курсах, а другие ходят в школу, чтобы развить эти навыки. Фактически, некоторые люди даже участвуют в краткосрочных учебных курсах по программированию, чтобы быстро получить навыки, необходимые для профессиональной работы в качестве Java-программиста.
Шаг 1. Изучите Java, HTML и CSS
Начиная свой путь в веб-разработке, первым делом необходимо изучить соответствующие языки программирования. Конечно, при веб-разработке на Java ваша цель — изучить язык программирования Java. Однако вы также должны изучить CSS и HTML.
HTML и CSS — жизненно важные компоненты любого веб-сайта. HTML — это то, с чего начинается веб-страница. Когда вы пишете код на Java, он становится HTML-документом для веб-браузеров. Понимание структуры HTML-документа — это часть веб-разработки, которую вы должны изучить, независимо от того, какой язык программирования вы используете.
С другой стороны, CSS — это то, что определяет стиль страницы и то, как она выглядит. Такие вещи, как используемые вами цвета, шрифты, даже макет самой страницы, — всё это аспекты CSS веб-сайта. CSS также помогает сделать веб-страницу более отзывчивой. С помощью правил CSS можно определить, как выглядит веб-сайт на рабочем столе, и использовать совершенно другой набор правил для мобильных пользователей.
HTML определяет содержимое вашей веб-страницы, а CSS определяет его внешний вид. Вы создаёте свою веб-страницу и её содержимое с помощью Java. Используя все эти элементы вместе, можно создать любую желаемую веб-страницу.
Шаг 2: Выберите свою IDE
При программировании первое, что нужно сделать, — это выбрать интегрированную среду разработки (IDE). Это надёжное программное обеспечение, которое значительно упрощает разработку веб-приложений и других программных проектов. Среди существующих сегодня IDE для Java ниже приведены некоторые из самых популярных.
NetBeans
Среди IDE для Java NetBeans — одна из лучших. Эта среда IDE, созданная разработчиками для разработчиков, помогает сделать кодирование максимально эффективным. Поддержка позволяет разработчикам создавать веб-приложения на уровне предприятия, а также настольные и мобильные приложения.
Эта IDE также работает в Windows, Linux, Mac и даже в Solaris. NetBeans решает всё, от анализа до кодирования, проектирования, отладки, тестирования и развёртывания, в одном удобном программном обеспечении.
Для этой среды IDE также доступны другие плагины, которые делают её ещё более надёжной, способной лучше выявлять ошибки в вашем коде и предоставлять вам больше ресурсов для разработки.
Eclipse
Eclipse — это среда разработки для Java, которая входит в тройку лучших для Java. Он поставляется с набором инструментов, которые ускоряют процесс разработки, включая функции моделирования, отчётности, тестирования и построения диаграмм.
В дополнение к IDE с длинным списком функций Eclipse также имеет набор библиотек и пакетов и взаимодействует со сторонними решателями для повышения эффективности вашей разработки.
IntelliJ IDEA
IntelliJ IDEA — последняя из трёх самых популярных Java IDE на сегодняшний день. Эта IDE имеет несколько функций, таких как анализ потока данных, интеллектуальное завершение, завершение статических элементов, межъязыковой рефакторинг и обнаружение дубликатов.
IntelliJ IDEA поддерживает разработку приложений Java и давно утерянных фреймворков Java, что делает его доступным для длинного списка дополнительных пакетов и коллекций библиотек, чтобы ещё больше сократить объём работы, необходимой для создания веб-сайта.
Шаг 3. Выберите свои веб-платформы Java
Когда дело доходит до веб-сервисов и разработки, существует несколько инструментов, которые значительно облегчат вашу работу. Эти инструменты представляют собой фреймворки. Фреймворки — это набор пакетов и библиотек, которые ускоряют программирование.
Фреймворки не только позволяют удобно кодировать и отлаживать, но и эти программы также содержат список функций, позволяющих уменьшить количество повторяемого кода, который вы пишете, найти ошибки и даже предсказать код, который вы пишете, чтобы ускорить процесс.
Есть несколько фреймворков на выбор. Ниже приведены лишь некоторые из них, которые помогут вам начать путешествие по веб-разработке на Java.
Spring Framework
Spring — это среда веб-приложений, которую использует большинство веб-разработчиков Java. Он поставляется с набором функций, в том числе отличными инструментами безопасности для защиты вашего сайта. Благодаря своей популярности, этот фреймворк поставляется с большим количеством документации, что упрощает изучение.
Hibernate Framework
Hibernate — это фреймворк, который существенно влияет на то, как мы смотрим на базы данных. Эта структура сопоставляет классы Java с таблицами базы данных. Hibernate помогает решить основные проблемы, связанные с подключением к базе данных Java (JDBC). Как правило, JDBC не поддерживает миграцию базы данных, что означает, что некоторые аспекты могут не работать.
Через Hibernate можно связать код с базой данных. Это позволяет веб-разработчикам создавать код, независимый от базы данных.
Grails Framework
Если вы новичок в веб-разработке, Grails — один из лучших фреймворков для изучения. Grails прост в освоении и представляет собой полнофункциональный фреймворк для новичков в программировании.
Эта структура использует язык программирования Groovy, но работает с Java. Фактически вы даже можете интегрировать Java-код с фреймворком и без проблем использовать в своём приложении сочетание Groovy и Java-программирования.
ATG Framework
ATG — это платформа для тех, кто хочет создавать веб-приложения в веб-коммерции. Эта структура поддерживает различные приложения B2C и B2B, независимо от того, насколько они огромны и сложны.
Если вы только начинаете веб-разработку и работаете только над небольшими приложениями, эта структура может быть дорогостоящей. Однако, если вы заинтересованы в создании сайтов электронной коммерции, тогда ATG — отличная платформа, которая поможет в разработке вашего сайта.
Play Framework
Play — это среда Java, использующая шаблон MVC. Эта среда с открытым исходным кодом похожа на другие популярные, такие как ASP.Net, Ruby on Rails и Django. Асинхронная обработка позволяет повысить производительность ваших веб-приложений и использует реактивные принципы, что означает отсутствие контейнеров или состояний.
Заключение
Веб-разработка на Java позволяет создавать невероятные веб-сайты, различающиеся по сложности и детализации.
Получив определённый уровень знакомства с программированием на Java, своей IDE и несколькими фреймворками, вы можете приступить к созданию сложных веб-приложений. Имейте в виду, что создание веб-сайта — непростая задача.
Вы должны не только хорошо разбираться в языке программирования Java, но также должны быть знакомы с HTML, CSS и IDE, которые вы используете, чтобы включить все функции, которые вы хотите видеть на своём веб-сайте.
Существуют и другие аспекты веб-разработки, такие как производительность, стабильность и безопасность, которые вступают в игру по мере того, как вы приобретаете больше опыта в создании веб-приложений.
Однако первый и самый важный шаг — это начать. По мере того, как вы приобретёте больше опыта и знаний, вы сможете создавать удивительные веб-страницы, содержащие все детали, которые вы или ваш клиент желаете. С помощью веб-разработки на Java можно создать любой сайт, какой бы сложный он ни был.
Третья статья по разработке web-приложения.
В данной статье мы спроектируем наше приложение, обговорим основные моменты, касающиеся его логики функционирования, а также приступим к работе со средой разработки (IDE).
Также мы рассмотрим способ подключение IDE к базе данных MySQL, создадим новую базу данных для нашего web-приложения.
Разрабатываемый нами сайт будет подобием мини-блога с возможностью чтения, добавления и редактирования статей. Также будет реализован функционал регистрации пользователей, комментирования статей и распределения прав доступа. Это первоначальный набор возможностей, который будет реализован, в дальнейшем будем вырабатывать новые требования к системе.
Структура сайта
Из приведенного выше описания можно составить структуру сайта:
- Главная страница сайта. Содержит список последних добавленных статей.
- Страница чтения статьи. Содержит полное содержание выбранной статьи.
- Страница регистрации.
- Зона администрирования. Ограниченный вход только для авторизованных пользователей, имеющих права администратора сайта.
Наглядно расписанную структуру можно увидеть на рисунке ниже:
Архитектура Java EE проекта
Прежде чем приступить к программированию, необходимо рассмотреть архитектуру проекта. Обозначим обязанности между функциональными компонентами и определим каким образом они будут вести взаимодействие друг с другом.
При работе с JSP можно уместить бизнес-логику внутри jsp страниц с использованием скриплетов <% %>, однако такой подход не рекомендуется даже для малых проектов. Причин, почему этот подход является плохим, можно найти множество на просторах интернета, в частности:
1
- Скриплеты не масштабируемы и трудно поддерживаемы.
- Смесь программирования, верстки и дизайна в одном флаконе: верстальщик явно не обрадуется, глядя на jsp страницу с кучей непонятного для него кода.
- Читать такие страницы крайне неудобно.
- Сложность тестирования.
И это список далеко неполный.
Из вышесказанного выведем еще одно важное правило:
1Не злоупотребляйте, а лучше старайтесь не использовать скриплеты внутри jsp страниц.
В данной серии статей мы будем следовать паттерну MVC. Паттерн достаточно известный и подробно вдаваться в его подробности не имеет смысла.
1
Опишем основные моменты.
- Модель (M Model): Хранит в себе бизнес-логику приложения, регулирует доступ к данным и их изменение.
- Вид (V View): View отображает содержимое модели, определят как необходимо представить данные, полученные от модели. View берет на себя сбор пользовательских данных и передачи их контроллеру.
- Контроллер (C Controller): Контроллер определяет поведение всего приложения, получает от view пользовательские данные, интерпретирует их в действия, выполняемые с помощью модели. Контроллер передает view указание, какое представление необходимо применить, на основе результатов работы модели и взаимодействия с пользователем.
Применительно к Java EE технологии шаблон MVC реализуется следующим образом. Сервлет используется как контроллер для обработки входящих запросов пользователей от представления, которое реализуется с помощью страниц jsp. Модель же представляет собой EJB сессионные компоненты, а также классы сущности (JPA), которые в свою очередь содержат в себе данные из БД.
Создание web-проекта
Приступим к работе со средой разработки.
1. Запустите Ваше IDE. Автор использует NetBeans.
2. Кликните по иконке «Создать проект…» , в списке категорий выберите «Java Web», в появившемся списке «Проекты» выберите «Веб-приложение».
3. Нажмите «Далее >». Введите наименование проекта «myblog».
4. Сервер и параметры настройки. Далее Вам необходимо выбрать сервер и версию Java EE платформы. Если в списке серверов нет Вашего сервера, то добавьте его, используя кнопку «Добавить».
После завершения всех настроек нажмите кнопку «Готово».
IDE сформирует каркас Вашего приложения. Имеется один файл index.jsp с простенькой разметкой. Это вполне работоспособный скелет приложения. Нажмите на кнопку «Запустить главный проект» или F6. В результате IDE развернет на выбранном вами сервере данное веб-приложение. Протестить его можно по перейдя по ссылке:
http://localhost:8080/myblog/
Чтобы посмотреть список развернутых приложений на сервере, перейдите на вкладку «Службы», раскройте выпадающий список «Серверы», в нем выберите ваш и в нем найдите список «приложения».
Взаимодействие IDE с базой данных
Подключим IDE к нашей базе данных. Для этого выполним простые действия.
1. На вкладке «Службы» кликните правой кнопкой мыши и в появившемся контекстном меню выберите пункт «Зарегистрировать сервер MySQL».
2. По умолчанию имя узла и номер порта сервера совпадают с предложенными. Вам нужно ввести ваше имя пользователя и пароль. Нажмите ОК.
В списке баз данных появится новое подключение. Выполните коннект к ней, используя соответствующий пункт в контекстном меню подключения.
Шаблоны страниц
Далее создадим несколько шаблонов наших страниц. Для этого нажмите на кнопку «Создать файл…» . Выберите тип файла «JSP» и нажмите «Далее…«, введите название страницы Article и нажмите «Готово«. Подобным же образом создайте jsp файл Registration.
Теперь создайте папку «css» в корневом каталоге и добавьте новую каскадную страницу стилей style.
В результате дерево проекта у Вас должно выглядеть следующим образом:
Можно посмотреть каждую из страниц, набрав в адресной строке браузера строку типа localhost:8080/article.jsp
Теперь произведем небольшую верстку сайта. Откройте файл index.jsp и добавьте в него следующий код.
<%--
Document : index
Created on : 06.08.2012, 23:36:00
Author : Egorov A.
--%><%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Главная страница блога</title>
</head>
<body>
<header>
<a href="/"><img alt="Логотип" id="top-image" src="#"/></a>
<div id="user-panel">
<a href="#"><img alt="Иконка юзера" scr=""/></a>
<a href="javascript:void(0);">[Панель для юзера]</a>
</div>
</header>
<div id="main">
<aside class="leftAside">
<h2>Темы статей</h2>
<ul>
<li><a href="#">Тема 1</a></li>
<li><a href="#">Тема 2</a></li>
<li><a href="#">Тема 3</a></li>
<li><a href="#">Тема 3</a></li>
</ul>
</aside>
<section>
<article>
<h1>Статья</h1>
<div class="text-article">
Текст статьи
</div>
<div class="fotter-article">
<span class="autor">Автор статьи: <a href="#">автор</a></span>
<span class="read"><a href="javascript:void(0);">Читать...</a></span>
<span class="date-article">Дата статьи: 20.12.2012</span>
</div>
</article>
<article>
<h1>Статья</h1>
<div class="text-article">
Текст статьи
</div>
<div class="fotter-article">
<span class="autor">Автор статьи: <a href="#">автор</a></span>
<span class="read"><a href="javascript:void(0);">Читать...</a></span>
<span class="date-article">Дата статьи: 20.12.2012</span>
</div>
</article>
</section>
</div>
<footer>
<div>
<span>Тестовое приложение JAVA EE</span>
<span><a target="_blanc" href="http://onedeveloper.ru/search?w=Java">Уроки по JavaEE</a></span>
</div>
</footer>
</body>
</html>
Файл article.jsp будет отличать лишь то, что блок article у него будет встречаться единожды, поэтому скопируйте этот же код в файл article.jsp за исключением того, что блог div id=»main» будет выглядеть вот так:
<div id="main">
<aside class="leftAside">
<h2>Темы статей</h2>
<ul>
<li><a href="#">Тема 1</a></li>
<li><a href="#">Тема 2</a></li>
<li><a href="#">Тема 3</a></li>
<li><a href="#">Тема 3</a></li> </ul>
</aside>
<section>
<article>
<h1>Статья</h1>
<div class="text-article">
Текст статьи
</div>
<div class="fotter-article">
<span class="autor">Автор статьи: <a href="#">автор</a></span>
<span class="date-article">Дата статьи: 20.12.2012</span>
</div>
</article>
</section>
</div>
Файл registration.jsp.
<%--
Document : registration
Created on : 07.08.2012, 23:23:25
Author : Egorov A.
--%><%@page contentType="text/html" pageEncoding="UTF-8"%>
<div id="main">
<aside class="leftAside">
<h2>Что нужно для регистрации</h2>
<p>Что бы регистрация прошла успешно, заполните все поля и нажмите на
кнопку "Зарегистрироваться"
</p>
</aside>
<section>
<article>
<h1>Регистрация</h1>
<div class="text-article">
<form method="POST" action="registration">
<p>
<label for="login">Логин</label>
<input type="text" name="login" id="login"/>
</p>
<p>
<label for="email">E-Mail</label>
<input type="email" name="email" id="email"/>
</p>
<p>
<label for="password">Пароль</label>
<input type="password" name="password" id="password"/>
<label for="password2">Повторите пароль</label>
<input type="password" name="password2" id="password2"/>
</p>
<p>
<button type="submit">Зарегистрироваться</button>
</p>
</form>
</div>
</article>
</section>
</div>
Файл style.css будет содержать следующий код.
/*
Document : style
Created on : 07.08.2012, 2:26:20
Author : Egorov A.
Description:
Purpose of the stylesheet follows.
*/
body{
width: 96%;
margin-left: 2%;
}
header{
height: 150px;
border-radius: 5px;
background: #ccccff
}
#top-image{
height: 130px;
width: 130px;
top: 20px;
background: #6699ff;
position: absolute;
left: 5%;
border-radius: 5px;
}
#user-panel{
position: absolute;
top: 20px;
right: 5%;
padding: 10px;
background: #9999ff;
border-radius: 5px;
}
#user-panel img{
width: 25px;
height: 25px;
}
#main{
margin-top: 3px;
}
#main .leftAside{
float: left;
position: relative;
width: 250px;
background: #ccccff;
border-radius: 5px;
height: 100%;
}
#main .leftAside p{
width: 96%;
margin-left: 2%;
text-align: justify;
}
#main .leftAside h2{
margin: 0px;
margin-left: 2%;
margin-top: 2%;
width: 96%;
padding: 5px 0px 5px 0px;
border-radius: 5px 5px 0px 0px;
background: #76a5fc;
text-align: center;
}
#main .leftAside ul{
margin: 0px;
margin-left: 2%;
margin-bottom: 2%;
padding: 0px;
width: 96%;
list-style: none;
}
#main .leftAside ul li{
margin: 0px;
cursor: pointer;
background: #76befc;
padding: 7px 5px;
border: 1px solid #6666ff;
border-bottom: none;
}
#main .leftAside ul li:hover{
-moz-transition: all 1s linear 0s;
-webkit-transition: all 1s linear 0s;
-o-transition: all 1s linear 0s;
transition: all 1s linear 0s;
background: #cce6fc;
padding-left: 10%;
}
#main .leftAside ul li:first-child{ }
#main .leftAside ul li:last-child{
border-bottom: 1px solid #6666ff;
border-radius: 0px 0px 5px 5px;
}
#main .leftAside a{
text-decoration: none;
padding: 3px 2px;
}
#main article{
margin-left: 260px;
margin-bottom: 5px;
background: #80abdb;
border: 1px solid #40658d;
border-radius: 5px;
}
#main article h1{
font-size: 2em;
padding: 3px 5px;
margin: 0px;
border-radius: 5px 5px 0px 0px;
}
#main article .text-article{
padding: 5px;
font-size: 15px;
background: #cce6fc;
}
#main article .text-article label{
margin-right: 10px;
}
#main article .fotter-article{
height: 23px;
background: #8babce;
border-radius: 0px 0px 5px 5px;
padding: 3px;
}
#main article .date-article, .read{
float: right;
margin-left: 10px;
}
footer{
height: 80px;
background: #6184aa;
border-radius: 0px 0px 5px 5px;
vertical-align: central;
}
footer div{
padding-top: 35px;
font-weight: bold;
padding-left: 10px;
}
footer span {
margin-right: 15px;
}
Можете вновь зайти на каждую из страниц и посмотреть что у нас получилось. Выглядеть должно примерно вот так:
На этом третья статья серии закончена.
В следующей статье мы займемся разработкой модели данных и создадим базу данных для нашего проекта, также познакомимся с дескриптором развертывания и jsp сегментами, и, на закуску, мы создадим сервлет-контроллер.