Spring Boot使用具有JSP模板機制

1.簡介

在構建Web應用程序時, JavaServer Pages(JSP)是一種選項,我們可以將其用作HTML頁面的模板機制。另一方面, Spring Boot是一個流行的框架,我們可以用來引導我們的Web應用程序

在本教程中,我們將了解如何將JSP與Spring Boot一起使用來構建Web應用程序。首先,我們將了解如何設置我們的應用程序以在不同的部署方案中工作。然後,我們將繼續了解JSP的一些常用用法。最後,在打包應用程序時,我們將探討各種選項。

這裡要注意的一點是, JSP本身就有局限性,與Spring Boot結合使用時有更多局限性。因此,我們應該將Thymeleaf或FreeMarker視為JSP的更好替代方案。

2. Maven依賴

讓我們看看使用JSP支持Spring Boot需要哪些依賴項。

我們還將注意到在將我們的應用程序作為獨立應用程序運行與在Web容器中運行之間的微妙之處。

2.1 作為獨立應用程序運行

首先,讓我們包括spring-boot-starter-web依賴項。此依賴關係提供了使Web應用程序連同默認的嵌入式Tomcat Servlet容器一起運行的所有核心要求,這些Web應用程序可與Spring Boot一起運行:

<dependency>

 <groupId>org.springframework.boot</groupId>

 <artifactId>spring-boot-starter-web</artifactId>

 <version>2.4.4</version>

 </dependency>

請查看我們關於在Spring Boot中比較嵌入式Servlet容器的文章,以獲取有關如何配置除Tomcat之外的嵌入式Servlet容器的更多信息。

我們應該特別注意, Undertow在用作嵌入式Servlet容器時不支持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版本。

可以通過使用Spring Boot父POM(如我們的文章Spring Boot教程–引導一個簡單的應用程序)中所示的方式來進行版本管理,也可以通過使用我們的文章Spring Boot Dependency Management with Custom Parent中的依賴項管理來完成此版本管理。

最後,我們需要包括jstl庫,該庫將在我們的JSP頁面中提供所需的JSTL標籤支持:

<dependency>

 <groupId>javax.servlet</groupId>

 <artifactId>jstl</artifactId>

 <version>1.2</version>

 </dependency>

2.2 在Web容器(Tomcat)中運行

在Tomcat Web容器中運行時,我們仍然需要上述依賴項。

但是,為了避免我們的應用程序提供的依賴關係與Tomcat運行時提供的依賴關係發生衝突,我們需要使用provided作用域設置兩個依賴關係:

<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並將其標記為provided範圍。這是因為它已經是spring-boot-starter-web提供的可傳遞依賴項。

3.查看解析器配置

按照約定,我們將JSP文件放置在${project.basedir}/main/webapp/WEB-INF/jsp/目錄中。 application.properties文件中配置兩個屬性來讓Spring知道在哪裡可以找到這些JSP文件:

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

 spring.mvc.view.suffix: .jsp

編譯後,Maven將確保生成的WAR文件將上述jsp目錄放在WEB-INF目錄中,然後由我們的應用程序提供服務。

4.運行我們的應用程序

我們主要的應用程序類別將受到影響,無論是計劃作為獨立應用程序運行還是在Web容器中運行。

當作為一個獨立的應用程序運行時,我們的應用程序類將是一個簡單的@SpringBootApplication註釋的類以及main方法

@SpringBootApplication(scanBasePackages = "com.baeldung.boot.jsp")

 public class SpringBootJspApplication {



 public static void main(String[] args) {

 SpringApplication.run(SpringBootJspApplication.class);

 }

 }

但是,如果需要在Web容器中進行部署,則需要擴展SpringBootServletInitializer.這會將我們的應用程序的ServletFilter,ServletContextInitializer綁定到運行時服務器,這對於我們的應用程序運行是必需的:

@SpringBootApplication(scanBasePackages = "com.baeldung.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標準標記庫(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來將其公開為網頁:

@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將在/WEB-INF/jsp/目錄中view-books.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>標記如何可用於遍歷BookController books model屬性。

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>標籤的結合book中所添加的屬性addBookView()在方法BookController到的形式,其反過來,將提交表單時被填充。

使用此標記的結果是,我們需要單獨定義表單操作URL(因為我們不能將標記放在標記內)。我們還使用在<form:input> path屬性,將每個輸入字段綁定到Book對像中的一個屬性。

有關如何處理表單提交的更多詳細信息,請參閱我們的文章“ Spring MVC中的表單入門”。

7.處理錯誤

由於將Spring Boot與JSP結合使用的現有限制,我們無法提供一個custom error.html來定制默認的/error映射。相反,我們需要創建自定義錯誤頁面來處理不同的錯誤。

7.1 靜態錯誤頁面

如果我們要顯示針對不同HTTP錯誤的自定義錯誤頁面,則可以提供一個靜態錯誤頁面。

假設我們需要為應用程序引發的所有4xx錯誤提供一個錯誤頁面。我們可以簡單地在${project.basedir}/main/resources/static/error/目錄4xx.html

如果我們的應用程序拋出4xx HTTP錯誤,那麼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

 }

另外,假設我們嘗試添加具有相同ISBN的兩本書,則BookServiceImpl類將拋出上面的DuplicateBookException

@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之類的Web容器中,那麼選擇war打包來實現這一目標。

但是,請注意,如果將JSP和Spring Boot與Embedded Servlet Container一起jar因此,如果作為獨立應用程序運行,我們唯一的選擇是war

無論哪種情況,我們的pom.xml都需要將其包裝指令設置為war

<packaging>war</packaging>

如果我們不使用Spring Boot父POM來管理依賴項,則需要包含spring-boot-maven-plugin以確保生成的war文件能夠作為獨立應用程序運行

現在,我們可以使用嵌入式Servlet容器運行我們的獨立應用程序,或者將生成的war文件放入Tomcat中並讓它為我們的應用程序服務。

9.結論

我們在本教程中涉及了各種主題。讓我們回顧一些重要的注意事項:

  • JSP包含一些固有的局限性。考慮改用Thymeleaf或FreeMarker
  • 如果在Web容器上部署, provided必要依賴項標記為已提供
  • 如果用作嵌入式Servlet容器,則Undertow將不支持JSP
  • 如果部署在Web容器中,則@SpringBootApplication註釋的類應擴展SpringBootServletInitializer並提供必要的配置選項
  • 我們無法使用JSP覆蓋默認的/error相反,我們需要提供自定義錯誤頁面
  • 如果我們將JSP與Spring Boot結合使用,則對我們而言,JAR打包不是一個選擇