介绍
曾几何时,Java 由于选择 Web 应用程序作为优先事项而巩固了自己的地位。从早期开始,Java 就一直在努力寻找自己的出路。首先,我建议使用小程序。这为开发人员在静态 HTML 页面上创建动态内容提供了许多机会。然而,由于安全、开销等多种原因,小程序并没有达到预期。于是Java语言的开发者提出了一种替代方案——Servlet
API。事实证明这是一个正确的决定。
Servlet API是构建任何 Java Web 应用程序的规范,无论是基于 Web 的应用程序还是根据请求返回信息的 Web 服务。因此,了解 Java Web 应用程序如何工作的途径始于了解 Servlet API。
服务程序接口
因此,
Servlet API是语言开发人员提供给 Java 开发人员的。Servlet API 是一个应该回答我们主要问题的规范。您可以在这里找到它:“
JSR-000340 JavaTM Servlet 3.1 Final Release for Assessment ”。
“ 1.1 什么是Servlet? ”一章说
Servlet是基于Java技术的创建动态内容(即内容)的Web组件。“基于Java”意味着
servlet 是编译成字节码的Java 类。Servlet 由 servlet 容器(有时称为 Servlet 引擎)管理。servlet 容器是提供 servlet 功能的
Web 服务器扩展。反过来,servlet 在请求/响应范例中提供与客户端的交互,这是由 servlet 容器实现的。
在“ 1.2什么是Servlet容器? ”一章中,提到
Servlet容器是Web服务器或应用程序服务器的一部分,它提供网络服务,通过它发送请求和响应,生成和处理基于MIME的请求和响应。此外,servlet 容器管理 servlet 的生命周期(即决定何时创建它们、删除它们等)。所有servlet容器都必须支持HTTP协议来接收请求和发送响应。在这里我想补充一点,MIME是一个标准,一个规范,它告诉信息应该如何编码和消息格式化,以便它们可以通过互联网发送。
网络服务器
Web 服务器是接受来自客户端的 HTTP 请求并向其提供 HTTP 响应(通常连同 HTML 页面、图像、文件或其他数据)的服务器。请求的资源由 URL 标识。
Apache Tomcat是最流行的支持 Servlet API 的 Web 服务器之一。大多数网络服务器都是由各种组件组成的复杂机器,每个组件执行特定的功能。例如:
连接器
— 在输入处,我们有连接器(即连接器),用于接受来自客户端的传入请求。Tomcat 中的 HTTP 连接器是使用“Coyote”组件实现的。连接器从客户端接收数据并将其传递到 Tomcat 引擎。
Servlet 容器- Tomcat 引擎反过来使用“Catalina”组件(一个 Servlet 容器)处理从客户端接收到的请求。更多详细信息请参阅 Tomcat 文档:“
架构概述”。还有其他支持 Servlet API 规范的 Web 服务器。例如,“
Jetty ”或“
Undertow ”。它们的架构相似,因此了解使用一个 servlet 容器的原理后,您可以切换到使用另一个容器。
Web应用程序
因此,为了让我们运行一个 Web 应用程序,我们需要一个支持 Servlet API 的 Web 服务器(即具有为 Web 服务器实现 Servlet API 支持的扩展组件)。美好的。
到底什么是网络应用程序? 根据Servlet API 规范的“
10 Web 应用程序”一章, Web 应用程序是 servlet、HTML 页面、类和其他资源的集合,它们构成 Web 服务器上的最终应用程序。根据“
10.6 Web应用程序存档文件”一章,可以将Web应用程序打包在Web ARchive(带有WAR扩展名的存档)中。
如“ Glossary-219 ”页面所述:
也就是说,制作WAR而不是JAR来表明这是一个Web应用程序。下一个重要事实:我们的 WAR 存档中必须有一定的目录结构。在Servlet API规范中的“
10.5目录结构”一章中。本章说有一个特殊的目录叫“WEB-INF”。该目录的特殊之处在于它对客户端不可见,也不会直接显示给客户端,但 servlet 代码可以访问它。它还说明了 WEB-INF 目录可以包含哪些内容:
从整个列表中,我们现在不知道也不理解有关某些称为
部署描述符的web.xml文件的项目。它是什么?
“ 14.部署描述符”一章专门介绍部署描述符。简而言之,
部署描述符是一个 xml 文件,描述如何在 Web 服务器上部署(即运行)我们的 Web 应用程序。例如,部署描述符指示应使用哪些 URL 来访问我们的应用程序、指示与我们的应用程序相关的安全设置等。
“ 14.2 处理部署的规则”一章说,在配置和启动我们的应用程序之前,web.xml 将进行模式验证(即,将根据模式检查 web.xml 的内容是否正确写入) 。
在“ 14.3 部署描述符”一章中指出该图在这里: 如果我们查看文件的内容,我们可以看到:
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
XML 文件使用什么架构?模式指示如何正确填写 XML 文档:可以使用哪些元素、可以在元素中指定什么类型的数据、元素应按什么顺序排列、需要哪些元素等。您可以将 XML 文档的模式与 Java 中的接口进行比较,因为 Java 中的模式还指定应如何编写满足给定接口(即实现给定接口)的类。因此,我们已经掌握了秘密知识,并准备好创建我们的第一个 Web 应用程序!
创建 Web 应用程序
很难想象在不使用自动项目构建系统的情况下使用现代 Java 应用程序。一些最流行的系统是
Maven 和 Gradle。我们将使用 Gradle 进行本次审查。
Gradle的安装在官方网站上有描述。要创建一个新应用程序,我们需要一个内置于 Gradle 中的插件:“
Build Init Plugin ”。要创建 Java 应用程序,您需要运行以下命令:
gradle init --type java-application
创建项目后,我们需要编辑
build.gradle文件。这就是所谓的Build Script(更多详细信息,请参见Gradle文档:“
编写构建脚本”)。该文件描述了如何组装项目以及使用 Java 项目的其他方面。插件块描述了当前 Gradle 项目应该使用哪些“
Gradle 插件”。插件扩展了我们项目的功能。例如,默认插件是“
java ”。如果我们需要 Java 支持,总是会使用这个插件。但我们不需要“
应用程序”插件,因为...... 它的描述表明它用于创建“可执行的 JVM 应用程序”,即 运行 JVM 应用程序。我们需要以 WAR 存档的形式创建一个 Web 应用程序。如果我们在 Gradle 文档中查找 WAR 一词,我们会找到“
War Plugin ”。因此,我们将指定以下插件:
plugins {
id 'java'
id 'war'
}
另外,在“
War Plugin Default Settings ”中,表示包含Web应用程序所有内容的目录应该是“src/main/webapp”,应该有与web.xml相同的WEB-INF目录位于。让我们创建一个这样的文件。我们稍后会填写它,因为...... 我们对此还没有足够的信息。在“依赖项”块中,我们指示项目的依赖项,即那些我们的应用程序无法运行的库/框架。在本例中,我们正在编写一个 Web 应用程序,这意味着没有 Servlet API 就无法工作:
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
providedCompile 意味着依赖项不需要包含在我们的 Web 应用程序的 WAR 存档中:仅在编译时需要它。而执行时,这个依赖将由其他人(即Web服务器)提供。好吧,我们在构建脚本中留下有关我们要使用哪个依赖项存储库的信息 - 所有指定的依赖项都将从它下载:
repositories {
jcenter()
}
我们从构建脚本文件中删除其他所有内容。现在让我们编辑类 src\main\java\App.java。让我们用它来创建一个 servlet。
“第 2 章 Servlet 接口”一章中的 Servlet API 规范指出,Servlet 接口有一个
HttpServlet 的基本实现,这在大多数情况下应该足够了,开发人员只需要继承它即可。
在“ 2.1.1 HTTP 特定请求处理方法”一章中指出了处理传入请求的主要方法。因此,让我们重写 App.java 类:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.IOException;
public class App extends HttpServlet {
public String getGreeting() {
return "Hello world.";
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
out.println(this.getGreeting());
out.close();
}
}
所以,我们似乎已经万事俱备了。剩下的就是正确编写部署描述符。从图中,将以下文本复制到 web.xml 中:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="..."
version="3.1">
...
</web-app>
还有其中指示的架构路径:
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
现在让我们看一个示例,说明 web.xml 在 Servlet API 规范中应是什么样子。
该示例在“ 14.5.1 基本示例”一章中给出。让我们将图中所示的内容与规范中所示的示例结合起来。我们得到以下信息:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>A Simple Web Application</display-name>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>App</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>
如您所见,我们使用了之前指定的 schema 和 schemaLocation。元素本身的描述取自第 14.5.1 章的示例。如果一切正确,我们将正确执行 gradle war 任务:
启动网络应用程序
Web 应用程序如何启动?让我们首先处理更复杂的选项。我们之前说过有一个支持 Servlet API 的 Apache Tomcat Web 服务器。这意味着我们可以在该服务器上部署我们收集的战争档案(他们也说“部署”)。在“
下载 Tomcat ”页面上,从“二进制发行版”部分下载 zip 格式的“核心”交付类型。并将下载的存档解压到某个目录,例如 C:\apache-tomcat-9.0.14。在启动服务器之前,让我们打开文件进行编辑
conf\tomcat-users.xml
并向其中添加以下行:
<user username="tomcat" password="tomcat" roles="tomcat,manager-gui,admin-gui"/>
现在,在命令行上,转到 bin 目录并执行
catalina.bat start
. 默认情况下,服务器控制台将在 处可用
http://localhost:8080/manager
。登录名和密码与我们在 tomcat-users.xml 中指定的相同。Tomcat 有一个“webapps”目录,其中包含Web 应用程序。如果我们想部署我们自己的,我们必须将我们的战争档案复制到那里。当我们之前运行 gradle war 命令时,
\build\libs\
会在目录中创建一个 war 存档。这就是我们需要复制的。复制完成后刷新页面
http://localhost:8080/manager
即可看到:
完成后
http://localhost:8080/javaweb/app
,我们将转向我们的 servlet,因为 我们之前将 /app 请求“映射”(即映射)到 App servlet。有一种更快的方法来检查应用程序的工作情况。装配系统再次帮助我们解决了这个问题。在 Gradle 项目的构建脚本中,我们可以将一个新插件“
Gretty ”添加到插件部分:
id "org.gretty" version "2.3.1"
现在我们可以执行 gradle 任务来运行我们的应用程序:
gradle appRun
详细信息 请参阅“
添加 gretty 插件并运行应用程序”。
Spring 和 Servlet API
Servlet 是一切的基础。即使是现在流行的 Spring 框架也只不过是 Servlet API 的一个附加组件。首先,
Spring 框架是我们项目的新依赖项。因此,让我们将其添加到依赖项块中的构建脚本中:
compile 'org.springframework:spring-webmvc:5.1.3.RELEASE'
在 Spring 框架文档中有一章“
1.1.DispatcherServlet ”。它说 Spring 框架是建立在“前端控制器”模式之上的 - 这是当有一个名为“
DispatcherServlet ”的中央 servlet 时。所有请求都到达此 servlet,并将调用委托给必要的组件。您看,即使这里也有 servlet。您需要将侦听器添加到部署描述符:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
这是一个 servlet 上下文事件监听器。也就是说,当Servlet Context启动时,Spring上下文(WebApplicationContext)也启动。
什么是 Servlet 上下文?它在 Servle API 规范的“
第 4 章 Servlet 上下文”一章中进行了描述。Servlet 上下文是 Servlet 运行的 Web 应用程序的“视图”。每个 Web 应用程序都有自己的 Servlet 上下文。接下来,要启用 Spring 框架,您需要指定 context-param - servlet 上下文的初始化参数。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
DispatcherServlet
定义完成配置:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
现在我们只需要填写 contextConfigLocation 中指定的文件即可。Spring框架文档的“1.3.1.声明”一章描述了如何做到这一点:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="ru.javarush.javaweb"/>
<mvc:annotation-driven/>
</beans>
这里重要的是不仅要指示要扫描哪个包,而且我们要注解驱动,即控制 Spring 将如何工作的注解。剩下的就是创建 ru.javarush.javaweb 包并将 Spring 控制器类放入其中:
package ru.javarush.javaweb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SpringController {
@GetMapping("/app")
@ResponseBody
public String getGreeting() {
return "Hello world.";
}
}
现在运行 gradle appRun 并转到该地址,
http://127.0.0.1:8080/javaweb/app
我们将得到相同的 Hello World。正如您所看到的,Spring 框架与 Servlet API 紧密相连,并使用它在其之上工作。
注释
正如我们所见,注释很方便。我们并不是唯一这么认为的人。因此,在Servlet API规范中,从3.0版本开始,出现了“
CHAPTER 8 Annotations and Pluggability ”一章,规定Servlet容器必须支持通过注解指定之前在Deployment Descriptor中指定的内容的能力。这样,web.xml就可以从项目中完全删除,并且在servlet类之上您可以指定
@WebServlet注释并指示将servlet映射到哪个路径。这里一切似乎都很清楚。但是如果我们把Spring连接到项目中,需要更复杂的设置呢?这里一切都有点复杂。首先,Spring 文档说要在没有 web.xml 的情况下配置 Spring,您需要使用自己的类来实现 WebApplicationInitializer。
更多详细信息,请参见“ 1.1.DispatcherServlet ”章节。原来这是一个Spring类。那么这里的Servlet API是如何使用的呢?事实上,
ServletContainerInitializer已添加到 Servlet API 3.0 中。使用 Java 中的特殊机制(称为
SPI),Spring 指定了名为 的 servlet 容器初始值设定项
SpringServletContainerInitializer
。反过来,它已经查找 WebApplicationInitializer 的实现并调用必要的方法并执行必要的设置。有关更多详细信息,请参阅“
servlet 容器如何查找 WebApplicationInitializer 实现”。上述设置可以这样完成:
package ru.javarush.javaweb.config;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(AppConfig.class);
servletContext.addListener(new ContextLoaderListener(ctx));
ctx.setServletContext(servletContext);
ServletRegistration.Dynamic servlet =
servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}
现在,使用“
基于 Java 的配置”,我们将指示要扫描哪个包+启用注释:
package ru.javarush.javaweb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "ru.javarush.javaweb.controllers")
public class AppConfig {
}
并且SpringController本身被移到了
ru.javarush.javaweb.controllers
,这样在扫描的时候,配置就不会找到它自己,而只会寻找控制器。
总结
我希望这篇概述能让您对 Web 应用程序如何在 Java 中工作有所了解。这只是冰山一角,但如果不了解基础知识,就很难理解基于此基础的技术是如何工作的。Servlet API 是任何 Java Web 应用程序的核心部分,我们已经研究了其他框架如何融入其中。要继续,您可以查看以下材料:
#维亚切斯拉夫
GO TO FULL VERSION