JavaRush /Java 博客 /Random-ZH /从 Hello World 到 Spring Web MVC 以及 servlet 与它有什么关系
Viacheslav
第 3 级

从 Hello World 到 Spring Web MVC 以及 servlet 与它有什么关系

已在 Random-ZH 群组中发布
从 Hello World 到 Spring Web MVC 以及 servlet 与之有何关系 - 1

介绍

众所周知,Java 的成功正是得益于致力于连接网络的软件的发展。因此,我们将以通常的控制台应用程序“ Hello World ”为基础,了解它从控制台应用程序变成网络应用程序需要什么。因此,首先您需要创建一个 Java 项目。程序员都是懒惰的人。在史前时代,当一些人在狩猎猛犸象时,另一些人则坐着努力避免对各种各样的 Java 库和目录结构感到困惑。为了让开发人员可以控制创建应用程序的过程,以便他可以简单地写“我想要一个这样那样的版本2的库”,他们想出了特殊的工具——构建系统。最著名的两个是MavenGradle。在本文中,我们将使用 Gradle。如果早些时候我们必须自己创建目录结构,现在 Gradle 使用Gradle Init 插件,允许我们在一个命令中创建一个具有目录结构和基 Main 类的 Java 项目: gradle init --type java-application 此命令执行初始化 (init)我们是一个带有控制台 Hello World 的 Java 应用程序 (java-application )。完成后,目录中会出现一个文件——build.gradle。这是我们的构建脚本- 即用于创建应用程序的特定脚本,并描述了为此需要执行哪些操作。让我们打开它并向其中添加以下行:jar.baseName = 'webproject' Gradle 允许您对项目执行各种操作,这些操作称为任务。通过执行命令(任务),将gradle build/build/libs目录中创建 JAR 文件。而且,正如您所猜测的,它的名称现在将是webproject.jar。但如果我们执行java -jar ./build/libs/webproject.jar,我们会得到一个错误:no main manifest attribute。这是因为对于 java 应用程序,您需要附加一个清单 - 这是关于如何使用该应用程序、如何感知它的描述。然后,将执行java应用程序的JVM将知道哪个类是程序的入口点以及其他信息(例如,类路径)。如果我们仔细查看构建脚本的内容,我们将看到正在连接的插件。例如:apply plugin: 'java' 如果我们进入Gradle Java Plugin 页面,我们可以看到我们可以配置清单:
jar {
    manifest {
        attributes 'Main-Class': 'App'
    }
}
主类,即程序的入口点,是由 Gradle Init Plugin 为我们生成的。它甚至在 mainClassName 参数中指定。但这不适合我们,因为...... 此设置引用另一个插件Gradle Application Plugin。因此,我们有一个在屏幕上显示 Hello World 的 Java 应用程序。该 Java 应用程序打包在 JAR (Java ARchive) 中。它很简单,基于控制台,不是最新的。如何将其变成Web应用程序?
От Hello World до Spring Web MVC и при чём тут сервлеты - 2

服务程序接口

为了让Java能够与网络一起工作,古代就出现了一种叫做Servlet API的规范。该规范描述了客户端-服务器交互、从客户端(例如浏览器)接收消息并发送响应(例如,使用页面文本)。当然,从那时起,发生了很多变化,但重点是,为了使 Java 应用程序成为 Web 应用程序,需要使用 Servlet API。为了避免毫无根据的推测,让我们选择这个规范:JSR-000340 JavaTM Servlet 3.1。首先,我们感兴趣的是“第一章:概述”。它描述了我们必须理解的基本概念。首先,什么是servlet?“ 1.1 什么是 Servlet? ”一章说,Servlet是一个由容器管理并生成动态内容的 Java 组件。与其他 Java 组件一样,servlet 是一个 Java 类,它被编译为字节码,并且可以使用 Java 技术加载到 Web 服务器中。Servlet 在请求/响应范例的框架内与 Web 客户端(例如浏览器)进行交互非常重要,该范例由 Servlet 容器实现。事实证明,Servlet 存在于某种 Servlet 容器中。这是什么?在“ 1.2 什么是 Servlet 容器? ”一章中,提到Servlet 容器是 Web 服务器或应用程序服务器的一部分,它提供网络服务,通过这些服务发送请求和发送响应。这个 Servlet 容器管理 servlet 的生命周期。所有 Servlet 容器都必须至少支持 HTTP 协议,但也可以支持其他协议。例如,HTTPS。同样重要的是,Servlet 容器可以对执行 servlet 的环境施加任何与安全相关的限制。同样重要的是,根据“ 10.6 Web 应用程序存档文件”,Web 应用程序必须打包在 WAR(Web 存档)文件中。也就是说,现在我们需要删除 jar 和应用程序插件以进行其他操作。这就是Gradle WAR 插件。并指定 war.baseName 而不是 jar.baseName 因为 由于我们不再使用 jar 插件,因此我们也删除了清单设置。当我们启动 JAR 时,需要通过清单告诉 Java 虚拟机 (JVM) 如何使用我们的应用程序。因为 JVM 正在运行它。显然,Web 应用程序是由某种 Web 服务器执行的。事实证明,他需要以某种方式告诉他如何使用我们的 Web 应用程序?事实证明是的。Web 应用程序有自己特殊的宣言。它称为部署描述符。有一整节专门介绍它:“ 14.部署描述符”。有一个重要的章节:“第10章:”。它从Servlet API的角度讲了什么是Web应用程序。例如,在“ 10.5目录结构”一章中指出了部署描述符应该在哪里:/WEB-INF/web.xmlWEB-INF放在哪里?正如 Gradle WAR 插件中所述,它添加了一个新的布局src/main/webapp。因此,让我们创建一个这样的目录,在里面我们将创建一个 WEB-INF 目录,在里面我们将创建一个 web.xml 文件。重要的是该目录称为 WEB-INF,而不是 META-INF!让我们从“ 14.5.1 基本示例”XML 示例中复制它:
От Hello World до Spring Web MVC и при чём тут сервлеты - 3
正如我们所看到的,XML 文档用于配置。一个 XML 文档,为了被认为是有效的(Valid),必须符合一些“模式”。您可以将其视为 XML 文档的一种接口。模式指定 XML 文档中可以包含哪些元素、可以定义元素的数据类型、顺序、要求以及其他方面。从文档复制的示例指示版本 2.5,但我们想使用版本 3.1。当然,规范会随着版本的变化而变化,并添加新功能。因此,您需要使用 2.5 版 (web-app_2_5.xsd) 所用架构以外的架构。3.1版本应该使用什么方案?文档将帮助我们解决这个问题,章节“ 14.3部署描述符”,其中指出specification is available at http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd ,也就是说,我们需要在各处用指定的xsd替换模式的链接,不要忘记将其更改version="2.5"为3.1,并且还要更改各处的命名空间( xmlns 和 xsi:schemaLocation 中)。它们指示我们将在哪个命名空间内工作(简单地说,我们可以使用哪些元素名称)。如果打开架构文件,targetNamespace 将包含我们应指定的相同命名空间:
От Hello World до Spring Web MVC и при чём тут сервлеты - 4
我们记得,在 Jar 文件的清单中,我们编写了要使用的类。在这里做什么?这里我们需要指定当我们收到来自 Web 客户端的请求时要使用哪个 servlet 类。详细说明可以参见“ 14.4 部署描述符图”一章。它看起来像这样:
От Hello World до Spring Web MVC и при чём тут сервлеты - 5
这里一切都很简单。声明serverlet,然后将其映射到某个模板。在本例中,在 /app 上。当模板执行时,servlet方法将被执行。为了美观,应该将App类转移到包中,同时不要忘记更正xml配置。但这还不是全部。应用程序必须是 servlet。成为 servlet 意味着什么?这意味着我们必须继承自HttpServlet一个例子可以在“ 8.1.1 @WebServlet ”章节中看到。根据它,我们的 App 类将如下所示:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class App extends HttpServlet {
    public String getGreeting() {
        return "Hello world.";
    }

	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		response.setContentType("text/html");
		try {
			response.getWriter().println(getGreeting());
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}
}
但我们的项目还没有准备好。因为我们现在依赖Servlet API 3.1版本。这意味着在我们的构建脚本中,我们需要指示对 Servlet API 的依赖。JVM需要知道你在代码中编写的内容是否正确以及如何使用它。我们记得,规范本质上只是描述其如何工作的接口。实现位于网络服务器端。因此,如果没有 Servlet API,就会在 Maven Central 上找到所需的库:javax.servlet-api。并向依赖项块添加一个条目。在 Maven 存储库中,正如您所看到的,它显示“已提供”。在使用依赖项之前,您必须指定范围。Gradle 没有名为“provided”的作用域,但它有一个“仅编译”作用域。因此,我们会表示:providedCompile 'javax.servlet:javax.servlet-api:3.1.0' 呃,好像一切都好吧?Gradle Build 会将我们的项目构建成 WAR 文件。接下来我们应该用它做什么?首先,我们需要一个 Web 服务器。在 Google 中,我们编写“ Web 服务器 Java 列表”并查看 Web 服务器列表。让我们从此列表中进行选择,例如TomCat。转至Apache Tomcat网站,下载最新版本(当前版本 9)作为 zip 存档(如果适用于 Windows)。将其解压到某个目录中。万岁,我们有一个网络服务器。从 Web 服务器目录的bin子目录中,我们从命令行执行catalina并查看可用选项。让我们做:catalina start。每个 Web 服务器都有一个该 Web 服务器监视的目录。如果其中出现 Web 应用程序文件,Web 服务器就会开始安装它。这种安装称为部署或部署。是的,这就是为什么“部署描述符”。即如何正确部署应用程序。在 Tomcat 中,该目录是webapps。让我们复制使用 gradle build 进行的战争。之后,在日志中我们将看到类似以下内容: Deployment of web application archive [tomcat\webapps\webproject.war] has finished in [время] ms 为了更好地理解,在 tomcat 目录中,我们将编辑该文件\conf\tomcat-users.xml,添加以下行:
От Hello World до Spring Web MVC и при чём тут сервлеты - 6
现在我们重新启动服务器(catalina stop,catalina start)并转到该地址,http://127.0.0.1:8080/manager 这里我们将看到所有应用程序的路径。我们的 web 项目很可能被指定为 /webproject 路径。这条路是什么?“ 10.1 Web 服务器内的 Web 应用程序”一章中的规范指出,Web 应用程序与应用程序内的某个路径相关联(在本例中为 /webproject)。通过此路径的所有请求都将与同一个 ServletContext 关联。该路径也称为contextRoot。并且根据“ 10.2 与 ServletContext 的关系”,Servlet 容器将 Web 应用程序和 ServletContext 一对一关联。也就是说,每个Web应用程序都有自己的ServletContext。什么是ServletContext?正如规范所述,ServletContext是一个对象,它为 Servlet 提供运行它们的“应用程序视图”。Servlet Context在 Servlet API 规范的第 4 章中有更详细的描述。令人惊讶的是,3.1 版中的 Servlet API 不再需要 web.xml。例如,您可以使用注释定义 servlet:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/app2")
public class App2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        response.getWriter().println("app2");
    }
}
另外推荐的主题是:《Java EE 面试 - JEE Servlet API(问答)》。因此,我们有一个 Servlet - 它负责向 Web 客户端提供什么响应。我们有一个 ServletContainer,它接收来自用户的请求,将访问的路径与 Servlet 的路径进行匹配,如果找到匹配,则执行 Servlet。美好的。春天在这幅世界图景中占据什么位置?

Spring Web MVC

太棒了,我们有一个网络应用程序。现在我们需要连接Spring。我们应该怎么做?首先,您需要弄清楚如何将 Spring 正确连接到您的项目。事实证明,早前按照Spring平台项目的文档是可以这样做的,但现在“ The Platform willreached the end of itssupported life on April 9, 2019 ”,即不建议这样做使用它,因为 很快它将不再受支持。唯一的出路是“鼓励平台用户开始使用Spring Boot的依赖管理”。因此,让我们继续了解Spring Boot文档。让我澄清一下,我们不使用 Spring Boot 本身,而仅使用 Spring Boot 的依赖管理。也就是说,Spring Boot 项目可以提供有关使用哪些版本的库(包括 Spring MVC)的知识。在那里我们会找到3.2。隔离使用Spring Boot的依赖管理。根据文档,在构建脚本中添加以下内容:
plugins {
    id 'org.springframework.boot' version '2.0.4.RELEASE' apply false
}
apply plugin: 'io.spring.dependency-management'
dependencyManagement {
    imports {
        mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
    }
}
正如你所看到的,我们指出apply false,即 我们不使用 Spring Boot 本身,但我们使用那里的依赖管理。这种依赖性管理也称为 BOM——“物料清单”。现在我们已准备好连接 Spring Web MVC 项目本身。Spring Web MVC 是Spring Framework项目的一部分,我们对“ Web Servlet ”部分感兴趣。让我们将依赖项添加到构建脚本中compile 'org.springframework:spring-webmvc':正如我们所看到的,我们设置了范围编译,因为 Web 服务器不为我们提供 Spring。我们的项目被迫在其内部包含 Spring 库。接下来,我们必须阅读“ 1.2.DispatcherServlet ”部分,其中提到Spring MVC是围绕“前端控制器”模式构建的,其中有某种中央 servlet 提供对其他组件的配置和委托。Dispatcher可以翻译为调度员。因此,首先,在 web.xml 中我们声明:
От Hello World до Spring Web MVC и при чём тут сервлеты - 7
我们可以看到,这实际上是 Servlet API 规范中定义的常规 Listener。更准确地说,这是一个 ServletContextListener,也就是说,它被触发来初始化我们的 Web 应用程序的 Servlet Context。接下来,您需要指定一个设置,告诉 Spring 带有设置的特殊 xml 配置位于何处:
От Hello World до Spring Web MVC и при чём тут сервлеты - 8
正如您所看到的,这只是存储在 Servlet 上下文级别的常规设置,但 Spring 在初始化应用程序上下文时将使用它。现在您需要声明一个分发所有其他请求的调度程序,而不是所有 Servlet。
От Hello World до Spring Web MVC и при чём тут сервлеты - 9
这里没有魔法。如果我们看一下,它是一个 HttpServlet,Spring 做了很多事情,使其成为一个框架。剩下的就是将特定 URL 模板与 servlet 关联(映射):
От Hello World до Spring Web MVC и при чём тут сервлеты - 10
一切都和我们以前一样。现在让我们创建一些我们的网络服务器应该显示的东西。例如,我们在WEB-INF中创建一个pages子目录,里面会有一个文件hello.jsp。内容可以是最原始的。例如,在 html 标签内有一个 h1 标签,其中包含文本“ Hello World ”。并且不要忘记创建applicationContext.xml我们之前指定的文件。让我们以 Spring 文档为例:“ 1.10.3. 自动检测类并注册 bean 定义”。
От Hello World до Spring Web MVC и при чём тут сервлеты - 11
因为 我们以这种方式启用自动检测,我们现在可以创建 2 个类(由于使用了特殊的 Spring 注释,它们将被视为 Spring Bean),Spring 现在将创建它们自己并在它们的帮助下自定义我们的应用程序:
  1. Web 配置例如 Java 样式配置:

    @Configuration
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void configureViewResolvers(ViewResolverRegistry registry) {
            registry.jsp("/WEB-INF/pages/", ".jsp");
        }
        @Override
        public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
        }
    }

    Spring 框架文档中描述了此示例:“ 1.11.MVC Config ”。

    这里我们注册了一个ViewResolver,它将帮助确定jsp页面所在的位置。第二种方法确保启用“默认 servlet ”。

    您可以在这里阅读更多相关内容:“ default-servlet-handler 的需求和用途是什么”。

  2. HelloController 控制器,用于描述请求到特定 JSP 的映射

    @Controller
    public class HelloController {
        @GetMapping("/hello")
        public String handle(Model model) {
            return "hello";
        }
    }

    这里我们使用了“ 1.4.带注释的控制器”一章文档中描述的@Controller注释。

现在,当部署我们的应用程序时,当我们发送请求/webproject/hello(其中 /webproject 是上下文根)时,将首先处理 DispatcherServlet。他作为主调度程序,会确定我们/*匹配当前请求,这意味着DispatcherServlet必须做一些事情。然后它将遍历它找到的所有映射。它将看到有一个 HelloController ,其句柄方法映射到 /hello 并将执行它。此方法将返回文本“hello”。该文本将由 ViewResolver 接收,它将告诉服务器到哪里查找需要向客户端显示的 jsp 文件。因此,客户最终将收到那个非常珍贵的页面。

结论

我希望从这篇文章中可以清楚地看出“背景”这个词并不可怕。事实证明该规范非常有用。文档是我们的朋友,而不是我们的敌人。我希望大家能够清楚 Spring 是基于什么、它是如何连接的以及 Servlet API 与它有什么关系。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION