Перейти к основному содержимому

Spring Boot со страницами JavaServer (JSP)

· 9 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

При создании веб-приложений JavaServer Pages (JSP) — это один из вариантов, который мы можем использовать в качестве механизма шаблонов для наших HTML-страниц.

С другой стороны, Spring Boot — это популярная платформа, которую мы можем использовать для начальной загрузки нашего веб-приложения.

В этом руководстве мы увидим, как мы можем использовать JSP вместе с Spring Boot для создания веб-приложения.

Во-первых, мы увидим, как настроить наше приложение для работы в различных сценариях развертывания. Затем мы рассмотрим некоторые распространенные варианты использования JSP. Наконец, мы рассмотрим различные варианты упаковки нашего приложения.

Небольшое замечание: JSP сам по себе имеет ограничения и тем более в сочетании с Spring Boot. Итак, мы должны рассматривать Thymeleaf или FreeMarker как лучшую альтернативу JSP.

2. Зависимости Maven

Давайте посмотрим, какие зависимости нам нужны для поддержки Spring Boot с JSP.

Мы также отметим тонкости между запуском нашего приложения как отдельного приложения и запуском в веб-контейнере.

2.1. Запуск в качестве отдельного приложения

Прежде всего, давайте включим зависимость spring-boot-starter-web .

Эта зависимость обеспечивает все основные требования для запуска веб-приложения с Spring Boot вместе со встроенным контейнером сервлетов Tomcat по умолчанию:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>

Ознакомьтесь с нашей статьей Сравнение контейнеров встроенных сервлетов в Spring Boot для получения дополнительной информации о том, как настроить контейнер встроенных сервлетов, отличный от Tomcat.

Следует особо отметить, что Undertow не поддерживает JSP при использовании в качестве встроенного контейнера сервлетов.

Затем нам нужно включить зависимость tomcat-embed-jasper , чтобы наше приложение могло компилировать и отображать JSP-страницы:

<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.44</version>
</dependency>

Хотя приведенные выше две зависимости могут быть предоставлены вручную, обычно лучше позволить Spring Boot управлять этими версиями зависимостей, пока мы просто управляем версией Spring Boot.

Это управление версиями можно выполнить либо с помощью родительского POM Spring Boot, как показано в нашей статье Spring Boot Tutorial — Bootstrap a Simple Application , либо с помощью управления зависимостями, как показано в нашей статье Spring Boot Dependency Management With a Custom Parent .

Наконец, нам нужно включить библиотеку jstl , которая обеспечит поддержку тегов JSTL, необходимую на наших страницах JSP:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

2.2. Запуск в веб-контейнере (Tomcat)

Нам по-прежнему нужны указанные выше зависимости при работе в веб-контейнере Tomcat.

Однако, чтобы избежать конфликта зависимостей, предоставляемых нашим приложением, с зависимостями, предоставляемыми средой выполнения Tomcat, нам нужно установить две зависимости с предоставленной областью действия :

<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.44</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.4.4</version>
<scope>provided</scope>
</dependency>

Обратите внимание, что нам пришлось явно определить spring-boot-starter-tomcat и пометить его предоставленной областью действия. Это потому, что это уже была транзитивная зависимость, предоставляемая spring-boot-starter-web .

3. Просмотр конфигурации резольвера

По соглашению мы размещаем наши JSP-файлы в каталоге ${project.basedir}/main/webapp/WEB-INF/jsp/ .

Нам нужно сообщить Spring, где найти эти файлы JSP, настроив два свойства в файле application.properties :

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

При компиляции Maven гарантирует, что в результирующем файле WAR указанный выше каталог jsp будет помещен в каталог WEB-INF , который затем будет обслуживаться нашим приложением.

4. Начальная загрузка нашего приложения

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

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

@SpringBootApplication(scanBasePackages = "com.foreach.boot.jsp")
public class SpringBootJspApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootJspApplication.class);
}
}

Однако, если нам нужно развернуть в веб-контейнере, нам нужно расширить SpringBootServletInitializer .

Это связывает Servlet , Filter и ServletContextInitializer нашего приложения с сервером времени выполнения , что необходимо для запуска нашего приложения:

@SpringBootApplication(scanBasePackages = "com.foreach.boot.jsp")
public class SpringBootJspApplication extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringBootJspApplication.class);
}

public static void main(String[] args) {
SpringApplication.run(SpringBootJspApplication.class);
}
}

5. Обслуживание простой веб-страницы

Страницы JSP основаны на стандартной библиотеке тегов JavaServer Pages Standard Tag Library (JSTL) для обеспечения общих функций шаблонов, таких как ветвление, итерация и форматирование, и даже предоставляют набор предопределенных функций.

Давайте создадим простую веб-страницу со списком книг, сохраненных в нашем приложении.

Скажем, у нас есть BookService , который помогает нам искать все объекты Book :

public class Book {
private String isbn;
private String name;
private String author;

//getters, setters, constructors and toString
}

public interface BookService {
Collection<Book> getBooks();
Book addBook(Book book);
}

Мы можем написать контроллер Spring MVC, чтобы представить это как веб-страницу:

@Controller
@RequestMapping("/book")
public class BookController {

private final BookService bookService;

public BookController(BookService bookService) {
this.bookService = bookService;
}

@GetMapping("/viewBooks")
public String viewBooks(Model model) {
model.addAttribute("books", bookService.getBooks());
return "view-books";
}
}

Обратите внимание, что BookController вернет шаблон представления с именем view-books . В соответствии с нашей предыдущей конфигурацией в application.properties Spring MVC будет искать view-books.jsp в каталоге /WEB-INF/jsp/ .

Нам нужно создать этот файл в этом месте:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>View Books</title>
<link href="<c:url value="/css/common.css"/>" rel="stylesheet" type="text/css">
</head>
<body>
<table>
<thead>
<tr>
<th>ISBN</th>
<th>Name</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<c:forEach items="${books}" var="book">
<tr>
<td>${book.isbn}</td>
<td>${book.name}</td>
<td>${book.author}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>

В приведенном выше примере показано, как использовать тег JSTL <c:url> для ссылки на внешние ресурсы, такие как JavaScript и CSS. Обычно мы размещаем их в каталоге ${project.basedir}/main/resources/static/ .

Мы также можем увидеть, как тег JSTL <c:forEach> можно использовать для перебора атрибута модели books , предоставляемого нашим BookController .

6. Обработка отправленных форм

Давайте теперь посмотрим, как мы можем обрабатывать отправку форм с помощью JSP.

Наш BookController должен будет предоставить конечные точки MVC для обслуживания формы для добавления книг и обработки отправки формы:

public class BookController {

//already existing code

@GetMapping("/addBook")
public String addBookView(Model model) {
model.addAttribute("book", new Book());
return "add-book";
}

@PostMapping("/addBook")
public RedirectView addBook(@ModelAttribute("book") Book book, RedirectAttributes redirectAttributes) {
final RedirectView redirectView = new RedirectView("/book/addBook", true);
Book savedBook = bookService.addBook(book);
redirectAttributes.addFlashAttribute("savedBook", savedBook);
redirectAttributes.addFlashAttribute("addBookSuccess", true);
return redirectView;
}
}

Мы создадим следующий файл add-book.jsp (не забудьте поместить его в соответствующий каталог):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Add Book</title>
</head>
<body>
<c:if test="${addBookSuccess}">
<div>Successfully added Book with ISBN: ${savedBook.isbn}</div>
</c:if>

<c:url var="add_book_url" value="/book/addBook"/>
<form:form action="${add_book_url}" method="post" modelAttribute="book">
<form:label path="isbn">ISBN: </form:label> <form:input type="text" path="isbn"/>
<form:label path="name">Book Name: </form:label> <form:input type="text" path="name"/>
<form:label path="author">Author Name: </form:label> <form:input path="author"/>
<input type="submit" value="submit"/>
</form:form>
</body>
</html>

Мы используем параметр modelAttribute , предоставленный тегом <form:form> , для привязки атрибута книги , добавленного в методе addBookView() в BookController , к форме, которая, в свою очередь, будет заполнена при отправке формы.

В результате использования этого тега нам нужно отдельно определить URL-адрес действия формы, поскольку мы не можем помещать теги внутри тегов. Мы также используем атрибут пути в теге <form:input> для привязки каждого поля ввода к атрибуту в объекте Book .

Пожалуйста, ознакомьтесь с нашей статьей Начало работы с формами в Spring MVC для получения более подробной информации о том, как обрабатывать отправку форм.

7. Обработка ошибок

Из-за существующих ограничений на использование Spring Boot с JSP мы не можем предоставить собственный файл error.html для настройки сопоставления по умолчанию /error . Вместо этого нам нужно создать пользовательские страницы ошибок для обработки различных ошибок.

7.1. Статические страницы ошибок

Мы можем предоставить статическую страницу ошибок, если хотим отображать пользовательскую страницу ошибок для разных ошибок HTTP.

Допустим, нам нужно предоставить страницу ошибок для всех ошибок 4xx, выдаваемых нашим приложением. Мы можем просто поместить файл с именем 4xx.html в каталог ${project.basedir}/main/resources/static/error/ .

Если наше приложение выдает ошибку HTTP 4xx, Spring устранит эту ошибку и вернет предоставленную страницу 4xx.html .

7.2. Динамические страницы ошибок

Существует несколько способов обработки исключений, чтобы предоставить настраиваемую страницу ошибок вместе с контекстной информацией. Давайте посмотрим, как Spring MVC предоставляет нам эту поддержку, используя аннотации @ControllerAdvice и @ExceptionHandler .

Допустим, наше приложение определяет DuplicateBookException :

public class DuplicateBookException extends RuntimeException {
private final Book book;

public DuplicateBookException(Book book) {
this.book = book;
}

// getter methods
}

Кроме того, предположим, что наш класс BookServiceImpl выдаст указанное выше исключение DuplicateBookException , если мы попытаемся добавить две книги с одинаковым ISBN:

@Service
public class BookServiceImpl implements BookService {

private final BookRepository bookRepository;

// constructors, other override methods

@Override
public Book addBook(Book book) {
final Optional<BookData> existingBook = bookRepository.findById(book.getIsbn());
if (existingBook.isPresent()) {
throw new DuplicateBookException(book);
}

final BookData savedBook = bookRepository.add(convertBook(book));
return convertBookData(savedBook);
}

// conversion logic
}

Затем наш класс LibraryControllerAdvice определит, какие ошибки мы хотим обрабатывать, а также как мы собираемся обрабатывать каждую ошибку:

@ControllerAdvice
public class LibraryControllerAdvice {

@ExceptionHandler(value = DuplicateBookException.class)
public ModelAndView duplicateBookException(DuplicateBookException e) {
final ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("ref", e.getBook().getIsbn());
modelAndView.addObject("object", e.getBook());
modelAndView.addObject("message", "Cannot add an already existing book");
modelAndView.setViewName("error-book");
return modelAndView;
}
}

Нам нужно определить файл error-book.jsp , чтобы вышеуказанная ошибка была устранена здесь. Обязательно поместите его в каталог ${project.basedir}/main/webapp/WEB-INF/jsp/ , так как это уже не статический HTML, а шаблон JSP, который необходимо скомпилировать.

8. Создание исполняемого файла

Если мы планируем развернуть наше приложение в веб-контейнере, таком как Tomcat, выбор прост, и для этого мы будем использовать военную упаковку .

Однако мы должны помнить, что мы не можем использовать упаковку jar , если мы используем JSP и Spring Boot со встроенным контейнером сервлетов. Таким образом, наш единственный вариант — это военная упаковка, если она работает как отдельное приложение.

Наш pom.xml в любом случае должен иметь директиву упаковки, установленную в war :

<packaging>war</packaging>

Если мы не использовали родительский POM Spring Boot для управления зависимостями, нам нужно будет включить spring-boot-maven-plugin , чтобы убедиться, что полученный файл войны может работать как автономное приложение.

Теперь мы можем запустить наше автономное приложение со встроенным контейнером сервлетов или просто поместить полученный военный файл в Tomcat и позволить ему обслуживать наше приложение.

9. Заключение

В этом уроке мы затронули различные темы. Давайте вспомним некоторые ключевые соображения:

  • JSP содержит некоторые неотъемлемые ограничения. Вместо этого рассмотрите Thymeleaf или FreeMarker.
  • Не забудьте пометить необходимые зависимости как предоставленные при развертывании в веб-контейнере.
  • Undertow не будет поддерживать JSP, если он используется в качестве встроенного контейнера сервлетов.
  • При развертывании в веб-контейнере наш аннотированный класс @SpringBootApplication должен расширять SpringBootServletInitializer и предоставлять необходимые параметры конфигурации.
  • Мы не можем переопределить страницу /error по умолчанию с помощью JSP. Вместо этого нам нужно предоставить настраиваемые страницы ошибок.
  • Упаковка JAR не подходит, если мы используем JSP с Spring Boot.

Как всегда, полный исходный код с нашими примерами доступен на GitHub .