Spring Boot使用具有JSP模板機制
- Spring Boot
- java
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.
這會將我們的應用程序的Servlet
, Filter,
和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打包不是一個選擇