1. Overview
1.概述
When building Web Applications, JavaServer Pages (JSP) is one option we can use as a templating mechanism for our HTML pages.
在构建Web应用程序时,JavaServer Pages (JSP)是我们可以用来作为HTML页面的模板机制的一种选择。
On the other hand, Spring Boot is a popular framework we can use to bootstrap our Web Application.
另一方面,Spring Boot是一个流行的框架,我们可以用来引导我们的Web应用程序。
In this tutorial, we are going to see how we can use JSP together with Spring Boot to build a web application.
在本教程中,我们将看到如何将JSP与Spring Boot一起使用来构建一个Web应用。
First, we’ll see how to set up our application to work in different deployment scenarios. Then we’ll look at some common usages of JSP. Finally, we’ll explore the various options we have when packaging our application.
首先,我们将看到如何设置我们的应用程序以在不同的部署方案中工作。然后我们将看看JSP的一些常见用法。最后,我们将探讨在打包我们的应用程序时有哪些不同的选择。
A quick side note here is that JSP has limitations on its own and even more so when combined with Spring Boot. So, we should consider Thymeleaf or FreeMarker as better alternatives to JSP.
这里有一个简单的附带说明,JSP本身有其局限性,当与Spring Boot结合时更是如此。因此,我们应该考虑Thymeleaf或FreeMarker作为JSP的更好替代品。
2. Maven Dependencies
2.Maven的依赖性
Let’s see what dependencies we need to support Spring Boot with JSP.
让我们看看我们需要哪些依赖性来支持Spring Boot与JSP。
We’ll also note the subtleties between running our application as a standalone application and running in a web container.
我们还将注意到将我们的应用程序作为一个独立的应用程序运行和在一个Web容器中运行之间的微妙关系。
2.1. Running as a Standalone Application
2.1.作为一个独立的应用程序运行
First of all, let’s include the spring-boot-starter-web dependency.
首先,让我们包括spring-boot-starter-web依赖性。
This dependency provides all the core requirements to get a web application running with Spring Boot along with a default Embedded Tomcat Servlet Container:
这个依赖关系提供了所有的核心要求,以使Web应用程序与默认的嵌入式Tomcat Servlet容器一起运行Spring Boot。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.4</version>
</dependency>
Check out our article Comparing Embedded Servlet Containers in Spring Boot for more information on how to configure an Embedded Servlet Container other than Tomcat.
请查看我们的文章比较Spring Boot中的嵌入式Servlet容器,了解有关如何配置Tomcat以外的嵌入式Servlet容器的更多信息。
We should take special note that Undertow does not support JSP when used as an Embedded Servlet Container.
我们应该特别注意,Undertow作为嵌入式Servlet容器使用时不支持JSP。
Next, we need to include the tomcat-embed-jasper dependency to allow our application to compile and render JSP pages:
接下来,我们需要包含tomcat-embed-jasper依赖项,以使我们的应用程序能够编译和渲染JSP页面。
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.44</version>
</dependency>
While the above two dependencies can be provided manually, it’s usually better to let Spring Boot manage these dependency versions while we simply manage the Spring Boot version.
虽然上述两个依赖关系可以手动提供,但通常让Spring Boot管理这些依赖关系的版本,而我们只需管理Spring Boot的版本就好了。
This version management can be done either by using the Spring Boot parent POM, as shown in our article Spring Boot Tutorial – Bootstrap a Simple Application, or by using dependency management as shown in our article Spring Boot Dependency Management With a Custom Parent.
这种版本管理可以通过使用Spring Boot父级POM来完成,如我们的文章Spring Boot教程–Bootstrap一个简单的应用程序中所示,或者通过使用依赖管理,如我们的文章Spring Boot依赖管理与定制父级中所示。
Finally, we need to include the jstl library, which will provide the JSTL tags support required in our JSP pages:
最后,我们需要包括jstl库,它将提供我们的JSP页面中所需要的JSTL标签支持。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2.2. Running in a Web Container (Tomcat)
2.2.在一个网络容器(Tomcat)中运行
We still need the above dependencies when running in a Tomcat web container.
在Tomcat网络容器中运行时,我们仍然需要上述的依赖性。
However, to avoid dependencies provided by our application clashing with the ones provided by the Tomcat runtime, we need to set two dependencies with provided scope:
然而,为了避免我们的应用程序提供的依赖与Tomcat运行时提供的依赖发生冲突,我们需要设置两个具有提供的范围的依赖。
<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>
Note that we had to explicitly define spring-boot-starter-tomcat and mark it with the provided scope. This is because it was already a transitive dependency provided by spring-boot-starter-web.
请注意,我们必须明确地定义spring-boot-starter-tomcat,并在provided范围内标记它。这是因为它已经是由 spring-boot-starter-web 提供的一个横向依赖关系。
3. View Resolver Configuration
3.查看解析器配置
As per convention, we place our JSP files in the ${project.basedir}/main/webapp/WEB-INF/jsp/ directory.
按照惯例,我们将JSP文件放在${project.baseir}/main/webapp/WEB-INF/jsp/目录下。
We need to let Spring know where to locate these JSP files by configuring two properties in the application.properties file:
我们需要通过在application.properties文件中配置两个属性来让Spring知道在哪里找到这些JSP文件。
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
When compiled, Maven will ensure that the resulting WAR file will have the above jsp directory placed inside the WEB-INF directory, which will then be served by our application.
编译时,Maven将确保生成的WAR文件的上述jsp目录放在WEB-INF目录中,然后由我们的应用程序提供服务。
4. Bootstrapping Our Application
4.引导我们的应用程序
Our main application class will be affected by whether we are planning to run as a standalone application or in a web container.
我们的主应用程序类将受到我们是否计划作为独立的应用程序或在Web容器中运行的影响。
When running as a standalone application, our application class will be a simple @SpringBootApplication annotated class along with the main method:
当作为一个独立应用程序运行时,我们的应用程序类将是一个简单的@SpringBootApplication注解类以及main方法。
@SpringBootApplication(scanBasePackages = "com.baeldung.boot.jsp")
public class SpringBootJspApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJspApplication.class);
}
}
However, if we need to deploy in a web container, we need to extend SpringBootServletInitializer.
然而,如果我们需要在Web容器中进行部署,我们需要扩展SpringBootServletInitializer。
This binds our application’s Servlet, Filter and ServletContextInitializer to the runtime server, which is necessary for our application to run:
这将我们的应用程序的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. Serving a Simple Web Page
5.服务一个简单的网页
JSP pages rely on the JavaServer Pages Standard Tag Library (JSTL) to provide common templating features like branching, iterating and formatting, and it even provides a set of predefined functions.
JSP页面依靠JavaServer Pages标准标签库(JSTL)来提供常见的模板功能,如分支、迭代和格式化,它甚至提供了一套预定义的函数。
Let’s create a simple web page that shows a list of books saved in our application.
让我们创建一个简单的网页,显示保存在我们应用程序中的书籍列表。
Say we have a BookService that helps us look up all Book objects:
假设我们有一个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);
}
We can write a Spring MVC Controller to expose this as a web page:
我们可以编写一个Spring MVC控制器,将其作为一个网页来公开。
@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";
}
}
Notice above that the BookController will return a view template called view-books. According to our previous configuration in application.properties, Spring MVC will look for view-books.jsp inside the /WEB-INF/jsp/ directory.
请注意,上面的BookController将返回一个名为view-books的视图模板。根据我们之前在application.properties中的配置,Spring MVC将在/WEB-INF/jsp/目录中寻找view-books.jsp。
We’ll need to create this file in that location:
我们将需要在该位置创建这个文件。
<%@ 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>
The above example shows us how to use the JSTL <c:url> tag to link to external resources such as JavaScript and CSS. We normally place these under the ${project.basedir}/main/resources/static/ directory.
上面的例子向我们展示了如何使用JSTL <c:url>标签来链接到外部资源,如JavaScript和CSS。我们通常将这些资源放在${project.baseir}/main/resources/static/目录下。
We can also see how the JSTL <c:forEach> tag can be used to iterate over the books model attribute provided by our BookController.
我们还可以看到JSTL的<c:forEach>标签如何被用来迭代我们的BookController提供的books模型属性。
6. Handling Form Submissions
6.处理提交的表格
Let’s now see how we can handle form submissions with JSP.
现在让我们看看如何用JSP处理表单提交。
Our BookController will need to provide MVC endpoints to serve the form to add books and to handle the form submission:
我们的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;
}
}
We’ll create the following add-book.jsp file (remember to place it in the proper directory):
我们将创建以下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>
We use the modelAttribute parameter provided by the <form:form> tag to bind the book attribute added in the addBookView() method in BookController to the form, which in turn will be filled when submitting the form.
我们使用modelAttribute参数,由<form:form>标签来绑定book属性,该属性是在BookController的addBookView()方法中添加到表单中的,在提交表单时又会被填写。
As a result of using this tag, we need to define the form action URL separately since we can’t put tags inside tags. We also use the path attribute found in the <form:input> tag to bind each input field to an attribute in the Book object.
由于使用这个标签,我们需要单独定义表单动作URL,因为我们不能把标签放在标签里面。我们还使用了<form:input> tag中的path属性,将每个输入字段绑定到Book对象中的一个属性。
Please see our article Getting Started With Forms in Spring MVC for more details on how to handle form submissions.
请参阅我们的文章Getting Started With Forms in Spring MVC以了解有关如何处理表单提交的详细信息。
7. Handling Errors
7.处理错误
Due to the existing limitations on using Spring Boot with JSP, we can’t provide a custom error.html to customize the default /error mapping. Instead, we need to create custom error pages to handle different errors.
由于使用Spring Boot与JSP的现有限制,我们无法提供自定义的error.html来定制默认的/error映射。相反,我们需要创建自定义错误页面来处理不同的错误。
7.1. Static Error Pages
7.1.静态错误页面
We can provide a static error page if we want to display a custom error page for different HTTP errors.
如果我们想为不同的HTTP错误显示一个自定义的错误页面,我们可以提供一个静态错误页面。
Let’s say we need to provide an error page for all 4xx errors thrown by our application. We can simply place a file called 4xx.html under the ${project.basedir}/main/resources/static/error/ directory.
假设我们需要为我们的应用程序抛出的所有4xx错误提供一个错误页面。我们可以简单地在${project.basedir}/main/resources/static/error/目录下放置一个名为4xx.html的文件。
If our application throws a 4xx HTTP error, Spring will resolve this error and return the provided 4xx.html page.
如果我们的应用程序抛出一个4xx HTTP错误,Spring将解决这个错误并返回所提供的4xx.html页面。
7.2. Dynamic Error Pages
7.2.动态错误页面
There are multiple ways we can handle exceptions to provide a customized error page along with contextualized information. Let’s see how Spring MVC provides this support for us using the @ControllerAdvice and @ExceptionHandler annotations.
我们有多种方法来处理异常,以提供一个定制的错误页面以及上下文信息。让我们看看Spring MVC如何使用@ControllerAdvice和@ExceptionHandler注解为我们提供这种支持。
Let’s say our application defines a DuplicateBookException:
假设我们的应用程序定义了一个DuplicateBookException。
public class DuplicateBookException extends RuntimeException {
private final Book book;
public DuplicateBookException(Book book) {
this.book = book;
}
// getter methods
}
Also, let’s say our BookServiceImpl class will throw the above DuplicateBookException if we attempt to add two books with the same ISBN:
另外,假设我们的BookServiceImpl类将抛出上述DuplicateBookException,如果我们试图添加两本具有相同ISBN的书。
@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
}
Our LibraryControllerAdvice class will then define what errors we want to handle, along with how we’re going to handle each error:
我们的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;
}
}
We need to define the error-book.jsp file so that the above error will be resolved here. Make sure to place this under the ${project.basedir}/main/webapp/WEB-INF/jsp/ directory since this is no longer a static HTML but a JSP template that needs to be compiled.
我们需要定义error-book.jsp文件,这样上述错误就会在这里解决。确保将其放在${project.basedir}/main/webapp/WEB-INF/jsp/目录下,因为这不再是一个静态HTML,而是一个需要编译的JSP模板。
8. Creating an Executable
8.创建一个可执行文件
If we’re planning to deploy our application in a Web Container such as Tomcat, the choice is straightforward, and we’ll use war packaging to achieve this.
如果我们打算在Tomcat等Web容器中部署我们的应用程序,那么选择很简单,我们将使用war打包来实现。
However, we should be mindful that we can’t use jar packaging if we are using JSP and Spring Boot with an Embedded Servlet Container. So, our only option is war packaging if running as a Standalone Application.
但是,我们应该注意的是,如果我们使用JSP和Spring Boot与嵌入式Servlet容器,我们就不能使用jar打包。因此,如果作为独立应用程序运行,我们唯一的选择是war打包。
Our pom.xml will then, in either case, need to have its packaging directive set to war:
我们的pom.xml在这两种情况下,都需要将其打包指令设置为war。
<packaging>war</packaging>
In case we didn’t use the Spring Boot parent POM for managing dependencies, we’ll need to include the spring-boot-maven-plugin to ensure that the resulting war file is capable of running as a Standalone Application.
如果我们没有使用Spring Boot父级POM来管理依赖关系,我们将需要包括spring-boot-maven-plugin以确保生成的war文件能够作为独立应用程序运行。
We can now run our standalone application with an Embedded Servlet Container or simply drop the resulting war file into Tomcat and let it serve our application.
现在我们可以用嵌入式Servlet容器运行我们的独立应用程序,或者简单地将产生的war文件放入Tomcat,让它为我们的应用程序服务。
9. Conclusion
9.结语
We’ve touched on various topics in this tutorial. Let’s recap some key considerations:
在本教程中,我们已经触及了各种主题。让我们回顾一下一些关键的考虑因素。
- JSP contains some inherent limitations. Consider Thymeleaf or FreeMarker instead.
- Remember to mark necessary dependencies as provided if deploying on a Web Container.
- Undertow will not support JSP if used as an Embedded Servlet Container.
- If deploying in a web container, our @SpringBootApplication annotated class should extend SpringBootServletInitializer and provide necessary configuration options.
- We can’t override the default /error page with JSP. Instead, we need to provide custom error pages.
- JAR packaging is not an option if we are using JSP with Spring Boot.
As always, the full source code with our examples is available over on GitHub.
一如既往,带有我们示例的完整源代码可在GitHub上获得over。