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(&quot;/viewBooks&quot;)
public String viewBooks(Model model) {
    model.addAttribute(&quot;books&quot;, bookService.getBooks());
    return &quot;view-books&quot;;
}

}

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(&quot;/addBook&quot;)
public String addBookView(Model model) {
    model.addAttribute(&quot;book&quot;, new Book());
    return &quot;add-book&quot;;
}

@PostMapping(&quot;/addBook&quot;)
public RedirectView addBook(@ModelAttribute(&quot;book&quot;) Book book, RedirectAttributes redirectAttributes) {
    final RedirectView redirectView = new RedirectView(&quot;/book/addBook&quot;, true);
    Book savedBook = bookService.addBook(book);
    redirectAttributes.addFlashAttribute(&quot;savedBook&quot;, savedBook);
    redirectAttributes.addFlashAttribute(&quot;addBookSuccess&quot;, 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>
    &lt;c:url var=&quot;add_book_url&quot; value=&quot;/book/addBook&quot;/&gt;
    &lt;form:form action=&quot;${add_book_url}&quot; method=&quot;post&quot; modelAttribute=&quot;book&quot;&gt;
        &lt;form:label path=&quot;isbn&quot;&gt;ISBN: &lt;/form:label&gt; &lt;form:input type=&quot;text&quot; path=&quot;isbn&quot;/&gt;
        &lt;form:label path=&quot;name&quot;&gt;Book Name: &lt;/form:label&gt; &lt;form:input type=&quot;text&quot; path=&quot;name&quot;/&gt;
        &lt;form:label path=&quot;author&quot;&gt;Author Name: &lt;/form:label&gt; &lt;form:input path=&quot;author&quot;/&gt;
        &lt;input type=&quot;submit&quot; value=&quot;submit&quot;/&gt;
    &lt;/form:form&gt;
&lt;/body&gt;

</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&lt;BookData&gt; 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(&quot;ref&quot;, e.getBook().getIsbn());
    modelAndView.addObject(&quot;object&quot;, e.getBook());
    modelAndView.addObject(&quot;message&quot;, &quot;Cannot add an already existing book&quot;);
    modelAndView.setViewName(&quot;error-book&quot;);
    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

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