Spring Boot 和 JSP(Java Server Pages)
1、概览
在构建 Java Web 应用时,可以使用 Java Server Pages(JSP)作为 HTML 页面模板。
Spring Boot 是一个流行的框架,可以用它来快速开发 Java Web 应用。 但是,在 Spring Boot 中使用 JSP 有一定的局限性,应该考虑用 Thymeleaf 或 FreeMarker 来替代 JSP。
2、Maven 依赖
首先来看看在 Spring Boot 中使用 JSP 需要哪些依赖。
2.1、作为独立应用运行
首先,添加 spring-boot-starter-web
依赖。
该依赖提供了使用 Spring Boot 和默认的嵌入式 Tomcat Servlet 容器来运行 Web 应用的所有核心依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
注意,使用 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 Parent POM 或使用 Dependency Management 来实现。
最后,需要加入 jstl 库,它为 JSP 页面提供 JSTL Tag 支持:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2.2、在 Web 容器(Tomcat)中运行
在 Tomcat Web 容器中运行时,仍然需要上述依赖项。
不过,为了避免应用提供的依赖与 Tomcat 运行时提供的依赖发生冲突,需要设置两个具有 provided
scope 的依赖:
<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
scope 标记它。这是因为 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 文件在 WEB-INF 目录中包含上述 jsp 目录。
4、启动应用
Application 类根据部署方式不同,也有差异。
当作为独立应用程序运行时,Application 类是一个简单的 @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、提供一个简单的 WEB 页面
JSP 页面依靠 Java Server Pages 标准标签库(JSTL)来提供常用的模板功能,如分支、迭代和格式化,甚至还提供了一组预定义函数。
接下来,创建一个简单的网页,显示应用中保存的 Book 列表。
假设已有一个 BookService
,可以检索所有 Book
对象:
public class Book {
private String isbn;
private String name;
private String author;
// get / set / 构造函数省略
}
public interface BookService {
Collection<Book> getBooks();
Book addBook(Book book);
}
编写一个 Spring MVC Controller,将其渲染到 Web 页面。
@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
中提供添加 Book
的端点,处理表单提交:
public class BookController {
// 已存在的代码省略...
@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>
使用 form:form
标签提供的 modelAttribute
参数,将 BookController
中 addBookView()
方法中添加的 book
属性绑定到表单,然后在提交表单时填写该属性。
使用该标签后,需要单独定义表单操作 URL,因为不能把标签放在标签内(不能嵌套)。还使用 form:input
标签中的 path
属性,将每个输入字段与 Book
对象中的属性绑定。
7、错误处理
由于使用 Spring Boot 和 JSP 时的现有限制,无法提供自定义 error.html
来定制默认的 /error
mapping。相反,需要创建自定义错误页面来处理不同的错误。
7.1、静态错误页面
如果想针对不同的 HTTP 错误显示自定义错误页面,可以提供静态错误页面。
比方说,需要为应用抛出的所有 4xx
错误提供一个错误页面。只需在 ${project.basedir}/main/resources/static/error/
目录下放置一个名为 4xx.html
的文件即可。
如果应用出现 4xx
HTTP 错误,Spring 将解析该错误并返回所提供的 4xx.html
页面。
7.2、动态错误页面
我们可以通过多种方法来处理异常,从而提供定制的错误页面和上下文(Context)信息。
例如:使用 @ControllerAdvice
和 @ExceptionHandler
注解。
假设应用定义了一个 DuplicateBookException
:
public class DuplicateBookException extends RuntimeException {
private final Book book;
public DuplicateBookException(Book book) {
this.book = book;
}
// get 方法省略
}
如果我们试图添加两本具有相同 ISBN 的 Book 的话,BookServiceImpl
类将抛出上述 DuplicateBookException
异常
@Service
public class BookServiceImpl implements BookService {
private final BookRepository bookRepository;
// 构造函数和其他方法忽略...
@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);
}
// 转换逻辑
}
然后,使用 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、总结
本教程涉及多个主题,关键点如下:
- JSP 包含一些固有的限制。请考虑使用 Thymeleaf 或 FreeMarker。
- 如果在 Web Container 上部署,请记住将必要的依赖项标记为
provided
。 - 如果作为嵌入式 Servlet 容器使用,Undertow 不支持 JSP。
- 如果在 Web 容器中部署,
@SpringBootApplication
注解类应继承SpringBootServletInitializer
并提供必要的配置选项 - 不能用 JSP 覆盖默认的
/error
页面。相反,需要提供自定义错误页面。
另外,你可以参考 《在 Spring Boot 中使用 JSP》 来了解如何在 Spring Boot 3 中使用 JSP。
Ref:https://www.baeldung.com/spring-boot-jsp