51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring Boot 和 JSP(Java Server Pages)

1、概览 {#1概览}

在构建 Java Web 应用时,可以使用 Java Server Pages(JSP)作为 HTML 页面模板。

Spring Boot 是一个流行的框架,可以用它来快速开发 Java Web 应用。 但是,在 Spring Boot 中使用 JSP 有一定的局限性,应该考虑用 ThymeleafFreeMarker 来替代 JSP。

2、Maven 依赖 {#2maven-依赖}

首先来看看在 Spring Boot 中使用 JSP 需要哪些依赖。

2.1、作为独立应用运行 {#21作为独立应用运行}

首先,添加 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)中运行 {#22在-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、视图解析器配置 {#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、启动应用 {#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

这将把应用的 ServletFilterServletContextInitializer 与运行时服务器绑定,这是应用运行所必需的:

@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 页面 {#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、处理表单提交 {#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 参数,将 BookControlleraddBookView() 方法中添加的 book 属性绑定到表单,然后在提交表单时填写该属性。

使用该标签后,需要单独定义表单操作 URL,因为不能把标签放在标签内(不能嵌套)。还使用 form:input 标签中的 path 属性,将每个输入字段与 Book 对象中的属性绑定。

7、错误处理 {#7错误处理}

由于使用 Spring Boot 和 JSP 时的现有限制,无法提供自定义 error.html 来定制默认的 /error mapping。相反,需要创建自定义错误页面来处理不同的错误。

7.1、静态错误页面 {#71静态错误页面}

如果想针对不同的 HTTP 错误显示自定义错误页面,可以提供静态错误页面。

比方说,需要为应用抛出的所有 4xx 错误提供一个错误页面。只需在 ${project.basedir}/main/resources/static/error/ 目录下放置一个名为 4xx.html 的文件即可。

如果应用出现 4xx HTTP 错误,Spring 将解析该错误并返回所提供的 4xx.html 页面。

7.2、动态错误页面 {#72动态错误页面}

我们可以通过多种方法来处理异常,从而提供定制的错误页面和上下文(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、总结 {#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

赞(1)
未经允许不得转载:工具盒子 » Spring Boot 和 JSP(Java Server Pages)