1.2 Java Web开发的进化史
Web应用最初是指通过Web浏览器提供用户界面的软件系统,例如博客、网上购物、搜索引擎等。后来随着RESTful Services、微服务等概念的兴起,各种API也被包含在Web应用的范畴。
Web应用程序很简单,仅由静态或动态交互式的页面组成。静态页面存储在Web服务器的文件系统中,向所有访问者显示固定的内容。而动态页面是由动态生成HTML的程序构建的。这种类型的Web应用程序可以根据不同用户的请求提供各种不同的信息,目前主流的Web应用都是动态应用。
那么Web应用是如何工作的呢?通常,访问一个静态Web应用的步骤如下:
(1)用户在浏览器中输入URL。
(2)浏览器通过互联网向Web服务器发送请求。
(3)Web服务器检查请求,并基于请求服务器查找其本地存在的静态页面。
(4)Web服务器将响应发送到浏览器。
(5)浏览器获取HTML及相关资源,最终渲染为完整的用户可视化界面。
静态Web应用的访问过程如图1-9所示。
图1-9 静态Web应用访问过程示例
动态Web应用的内容根据用户请求的差异而有所不同。在Java生态中,最初是以Servlet规范和JSP规范来支持动态Web开发的。对于这种类型的应用程序,Web服务器通过Servlet插件构建动态页面,Servlet插件也称为Servlet容器。动态Web应用的访问过程如图1-10所示。
图1-10 动态Web应用的访问过程
1.2.1 应用服务器
1.2.1.1 什么是应用服务器
在Java生态中,构建动态Web应用需要借助Servlet容器。提供Servlet容器功能的软件就是应用服务器,而只能提供静态资源访问能力的软件则被称为Web服务器。Servlet容器也被称为Servlet引擎,它为Java Servlet组件提供了非常高效的运行环境。
主流的应用服务器既可以作为Servlet容器,也可以充当Web服务器,这种将Servlet容器和Web服务器合二为一的方式在开发阶段比较常见,便于开发团队调试应用程序。但我们不建议在正式的生产环境中使用,因为两种服务器的核心功能是完全不同的,应当各司其职。
目前,主流的部署方式是使用Web服务器处理所有的静态请求,并通过特定的连接方式与应用服务器相连,将动态请求交由应用服务器处理,然后由Web服务器一并返回客户端。
在当前的Java生态中,主流的Java应用服务器有Apache Tomcat、GlassFish、Jetty、JBoss等,下面将以Apache Tomcat为例,演示如何将Java Web应用部署到应用服务器,以供客户端访问。
1.2.1.2 Apache Tomcat下载和安装
我们可以直接从官网下载Apache Tomcat,根据应用程序所使用的各种规范版本(比如JDK版本)的不同,其所需的Apache Tomcat版本也不一样,请读者参考Apache Tomcat的官方规范兼容文档选择正确的Apache Tomcat版本。在本书中笔者使用JDK 8进行开发,因此选择了能支持JDK 8的最低兼容版本Apache Tomcat 9。
从官网下载压缩包apache-tomcat-9.0.41.zip,并解压缩,其目录结构如图1-11所示。
图1-11 Apache Tomcat目录结构
图1-11中目录的具体功能如下:
• bin:它用于存放批处理和shell脚本,例如Apache Tomcat的开始和停止的相关脚本,其内容如图1-12所示。
图1-12 Apache Tomcat bin目录
• conf:它用于存放Apache Tomcat相关配置文件,其内容如图1-13所示。
图1-13 Apache Tomcat conf目录
• lib:Apache Tomcat服务器依赖项目库目录,其中包含Apache Tomcat服务器运行环境所需的jar包,其内容如图1-14所示。
图1-14 Apache Tomcat lib目录
• webapps:Apache Tomcat的默认Web应用程序部署目录,即打包出的Java Web应用将会被放在该目录,如图1-15所示。
图1-15 Apache Tomcat webapps目录
在了解了各个目录的基本功能以后,接下来我们验证解压后的Apache Tomcat是否可以正常工作。在命令行界面进入bin目录运行startup.sh文件,对使用macOS的用户来说,如果命令行界面出现类似“Cannot find ./catalina.sh”的错误,说明当前系统用户对该目录下的脚本没有执行权限,需要运行以下命令对操作用户赋权:
成功启动Apache Tomcat的日志如图1-16所示。
图1-16 Tomcat启动成功日志
如果Apache Tomcat以默认配置启动,打开任意浏览器并输入http://localhost:8080,就可以访问Apache Tomcat的管理界面,如图1-17所示。
图1-17 Apache Tomcat的管理界面
1.2.2 青铜Servlet
从Java Web应用的发展历程来看,Servlet技术是Java对Web应用的早期支持方式,当我们回过头来看当年的Servlet和Java Web技术,就如同回顾人类历史上的青铜时代。
1.2.2.1 什么是Servlet
Servlet是一个新造词,是由server(服务器)和let(表示很小的东西的词根)而来,因此,Servlet顾名思义表示一种小型的服务。
一个Servlet就是一个Java类,它主要用于扩展处理请求——响应应用服务器处理请求的能力。对于此类应用程序,Java Servlet技术定义了特定于HTTP的Servlet类。
我们可以将Servlet看成是在服务器上运行的Java应用程序编程接口(API),它拦截客户端发出的请求并生成与之对应的响应。在Servlet规范中,javax.servlet包和javax.servlet.http包提供用于编写Servlet的接口和类。
在Servlet容器中,一个Servlet是何时被创建又在何时被销毁的呢?在Servlet规范中,诸如Servlet创建和Servlet销毁之类的行为被称为Servlet的生命周期。在了解Servlet生命周期之前,需要先简单了解Servlet接口。
javax.servlet.servlet接口的定义如表1-1所示(未全部列出)。
表1-1 Servlet接口定义
Servlet的生命周期如图1-18所示。
图1-18 Servlet生命周期
下面介绍生命周期的5个步骤。
(1)加载Servlet类
当容器收到对Servlet的第一个请求时,类加载器会加载Servlet类。
(2)创建Servlet实例
容器在加载Servlet类后创建Servlet实例,Servlet实例在Servlet生命周期中只创建一次。
(3)调用init()方法
init()方法用于初始化Servlet。它是javax.servlet.Servlet接口的生命周期方法。容器在创建Servlet实例后只调用init()方法一次。
(4)调用Service()方法
Servlet容器在收到用户请求时,会尝试调用Servlet实例的service()方法。如果Servlet尚未被实例化,则执行步骤(1)~(3),再调用service()方法。
(5)调用destroy()方法
Servlet容器在删除Servlet实例之前将调用该实例的destroy()方法。destroy()方法为服务器提供了清理资源(例如内存、线程等)的机会。
1.2.2.2 使用Servlet实现HelloWorld
首先,创建一个Servlet的项目,目前Maven的archetype已经不再支持最新版的Servlet规范,所以需要有一些前提准备。
创建新的项目可以通过Maven或IDE来直接创建,本节将演示如何使用IntelliJ IDEA来创建一个Servlet项目。
(1)在IntelliJ IDEA中选择新建项目,其界面如图1-19所示。
图1-19 IntelliJ IDEA创建项目
(2)选择从archetype创建项目,所选archetype类型为maven-archetype-webapp,然后单击Next按钮,其界面如图1-20所示。
图1-20 IntelliJ利用archetype创建项目
(3)输入项目名servlet-demo,填写项目路径、GroupId、ArtifactId,再单击Next按钮即可,指定项目信息如图1-21所示。
图1-21 指定项目信息
项目创建成功,其结构如图1-22所示。
图1-22 Servlet项目结构
目前,Maven和Servlet的最新版存在兼容性问题,需要webapp/WEB-INF/web.xml文件做一些修改,由于其默认支持Servlet 2.3,所以读者需要手动添加依赖,具体代码如下:
基于Servlet 4规范创建Servlet程序有两种方式,第一种是传统的使用web.xml注册的方式,第二种是新型的使用WebServlet注释的方式,第一种方式的执行步骤如下。
第一步:实现Servlet类。实现Servlet有三种方式:实现Servlet接口、继承GenericServlet类、继承HttpServlet类。
本示例程序以继承HttpServlet类的方式实现,示例代码如下:
第二步:注册Servlet。
编辑src/main/webapp/WEB-INF/web.xml文件即可注册Servlet,代码如下:
如上代码所示,Servlet注册信息主要有两项内容:
• 通过<Servlet-name>标签注册com.broadview.servlet.OldServlet,并将其命名为oldDemo。
• 通过<url-pattern>标签注册访问oldDemo Servlet的URL,即/old。
由上可知,注册信息的主要目的是定义访问URL与Servlet之间的映射关系。
第三步:编译打包。
进入项目的根目录,并运行打包命令:
命令运行完毕,项目的target目录会产生一个名为servlet-demo.war的文件,将此文件复制到1.2.1.2节所安装的Tomcat的webapps目录下。
第四步:添加Tomcat的管理权限。
在启动Tomcat之前还需要为Tomcat设置管理员权限,进入Tomcat的config目录,打开tomcat-users.xml文件,在该文件中添加以下代码:
第五步:启动Tomcat。
进入Tomcat的bin目录运行以下命令:
如果Tomcat安装正常,Servlet项目编写、编译和打包无误,则会出现Tomcat started的启动日志。
下面验证Servlet应用是否能够正常启动,打开浏览器输入http://localhost:8080/,进入Tomcat的管理界面,如图1-23所示,此界面有管理Web应用的功能,单击Manager App按钮,输入在tomcat-users.xml中配置的用户名和密码,进入Tomcat Web应用的管理界面,如图1-24所示。
图1-23 Tomcat管理界面
图1-24 Tomcat Web应用管理界面
从图1-24可见servlet-demo项目显示在应用程序列表中,单击该项目链接,会跳转到该项目的主页界面,如图1-25所示。
图1-25 项目主页界面
由于Servlet实现了doGet()方法,所以用户可以通过http://localhost:8080/servlet-demo/old来访问此Servlet,URL中的servlet-demo部分为context path,访问servlet-demo项目下的所有API都需要包含此路径。URL中结尾处的“old”的作用是,将当前请求转发到指定的Servlet实例。
在浏览器中输入http://localhost:8080/servlet-demo/old,就可以查看对应Servlet的输出,如图1-26所示。
图1-26 Servlet的输出
上面这种需要在web.xml中注册Servlet的方式是早期的Servlet开发方式,下面是使用WebServlet注释的方式创建Servlet的示例代码。
再重新打包编译并复制war文件到Tomcat的webapps目录。通过http://localhost:8080/servlet-demo/new访问新的Servlet,其效果如图1-27所示。
图1-27 新式Servlet输出
这种新方式无需在web.xml中注册Servlet,编程体验更加简洁优雅。但是无论如何Servlet的开发方式都是相对原始的,其开发效率很难满足当前多变的复杂的业务需求。因此,业界发明了更多的新技术以满足日益增长的用户需求。
1.2.3 铂金Spring MVC
正如1.2.2节中所述,Servlet技术无法满足日益复杂的业务需求,市场自然就会出现可以填补这种需求空白的技术,因此Spring MVC应运而生。
1.2.3.1 什么是Spring MVC
在了解Spring MVC之前,先简单介绍一下MVC。MVC是一种实现应用程序表现层的常用模式,此模式定义了不同的组件、组件的职责及不同组件之间的关系。MVC模式有三个概念:
• 模型(Model):模型表示业务数据及用户上下文中应用程序的“状态”,例如电商网站中的订单、用户、物流信息等都属于模型的范畴。
• 视图(View):以某种特定的方式向用户展示数据,可以简单地理解为用户界面,也支持与用户的交互。
• 控制器(Controller):控制器处理前端用户发送的操作请求、与服务层交互、更新模型,并根据执行结果将用户定向到相应的视图。
Spring MVC模块为MVC模式提供了全面支持,它结合MVC模式的所有优点和Spring的便利性,使用DispatcherServlet实现了前端控制器模式(Front Controller Pattern)。
图1-28描述了Spring MVC的主要组件和请求处理的流程。其中,通用服务为各类组件提供各种工具,包括i18n、主题和文件上传。它们的配置定义在DispatcherServlet的WebApplicationContext中。
图1-28 Spring MVC处理流程
图1-28中的关键组件介绍如下:
• Filter:过滤器适用于每个请求,下一节将介绍几种常用的过滤器及其用途。
• DispatcherServlet:DispatcherServlet分析请求并调度到相应的控制器进行处理。
• Handler Mapping:将传入的请求映射到相应的处理程序(Spring MVC控制器类中的方法)。自Spring 2.5以来,在大多数情况下不需要配置,因为Spring MVC将自动注册一个处理程序映射的实现,该实现是基于class或方法级别的@RequestMapping注解。
• Handler Interceptor:在Spring MVC中,可以注册处理程序的拦截器,以实现常见的检查逻辑。
• Handler Exception Resolver:在Spring MVC中,程序异常解析器接口(在org.springframe.Web.servlet中定义)旨在处理程序在处理请求期间抛出的异常。解析程序通过设置特定的响应状态代码处理某些标准的Spring MVC异常。此外也可通过@ExceptionHandler注解来处理异常程序。
• View解析器:Spring MVC控制器方法通常会返回逻辑视图,想要定位到真正的页面,可以通过View解析器来实现。
• View:View可以是物理视图也可以是模板,Spring MVC会解析逻辑视图配置,返回一种Freemarker模板或thymelea模板,该模板用于将数据模型中的数据合并到模板中,从而生成标准的输出文本,包括HTML、XML、Java源码等。
1.2.3.2 使用Spring MVC实现HelloWorld
使用Spring MVC实现HelloWorld,与直接使用Servlet构建HelloWorld类似,需要先创建一个Web应用项目,具体步骤请参照1.2.2.2节。
然后,在pom.xml文件中添加相关依赖项,具体代码如下:
与定义传统Servlet一样,Spring MVC需要在web.xml中定义Servlet映射,具体代码如下:
根据上面代码中定义的servlet-name,按照Spring MVC的规范创建相应的Spring Bean的定义文件springmvcDemo-servlet.xml,具体代码如下:
下面,我们来创建一个DemoController类,并定义一个方法来处理用户请求,具体代码如下:
在上述代码中,@RestController注解表明DemoController类是被Spring MVC托管的controller类,@RequestMapping注解定义了访问Service的方式。参照1.2.2节中的步骤使用mvn命令将项目打包,再将编译后的springmvc-demo.war文件复制到Tomcat的webapps目录下,重新启动Tomcat(如果Tomcat是一直运行的,且本身是可以热加载的,则无需重启),再从浏览器中输入http://localhost:8080/springmvc-demo/mvcdemo访问Spring MVC应用,运行效果如图1-29所示。
图1-29 Spring MVC的运行效果
相较于传统的Servlet,Spring MVC代码简洁了许多,只是额外多了DispatchServlet的Spring Bean配置文件。Spring MVC的功能十分丰富,本章只是做了基本演示,其他细节知识,请读者通过Spring官网探索。
1.2.4 王者Spring Boot
1.2.4.1 什么是Spring Boot
虽然Spring MVC相较于传统Servlet已经相对简化,但仍需要web.xml和相应的Spring Bean的配置,这些文件烦琐而又不可缺,从广大开发者的角度出发,如果可以进一步省略这些文件,不但可以减少代码量,而且可以使代码更加优雅整洁。所以Spring Boot正是秉承约定大于配置(Convention Over Configuration)的精神出现了。
Spring Boot的主要目标是简化传统Spring应用的开发过程。使用Spring Boot可以减少大量的配置文件,其内置的Servlet容器可以简化应用的部署过程,简洁易用的工具可以使开发者从重复性工作中得到解放。简而言之,Spring Boot并非要替代Spring框架,而是要简化基于Spring框架的开发工作。
本章只是简要地介绍Spring Boot应用开发的便利性,第2章我们将借助实战项目深入了解Spring Boot。
1.2.4.2 使用Spring Boot实现HelloWorld
首先,我们通过Spring Boot Initializr官网来创建一个简单的Spring Boot项目。打开Spring Boot Initializr官网选择合适的Spring Boot版本及所需要的依赖,就可以创建一个Spring Boot项目,Spring Boot项目创建界面如图1-30所示。
图1-30 Spring Boot项目创建界面
然后,将产生的项目文件导入IDE中,Spring Boot项目默认有一个Spring Boot启动类,在本例中启动类是SpringbootdemoApplication,该类的定义代码如下:
再创建一个DemoController类,实现一个HelloWorld,具体代码如下:
只需以上两个简单的类,一个基于Spring Boot的简版Web Service就创建成功了,此外,该项目在开发阶段可以直接从IDE运行,无需额外的打包和部署操作。因为Spring Boot内置了Servlet容器(默认为Tomcat),所以Spring Boot项目可以直接运行。在本例中直接运行SpringbootdemoApplication类的main()方法即可(此类为应用启动类),在IDE中运行方式如图1-31所示。
图1-31 在IDE中运行Spring Boot项目
如果一切运行正常,那么该应用会默认监听8080端口。在浏览器中输入http://localhost:8080/bootdemo,验证应用是否正常启动,Spring Boot运行效果如图1-32所示。
图1-32 Spring Boot运行效果
相对于Servlet和Spring MVC,基于Spring Boot构建的应用从代码到配置都做了极大的简化。
本章逐一使用不同时期的主流技术进行开发,理解这些技术之间的差异性是成为一名合格程序员的基本功,深入研究技术背后的工作原理是程序员进阶的必备手段。在本书的后续章节,我们将深入了解各个技术的实现细节,来理解其背后的工作原理。就全书而言,本章只是一场简单的热身运动,后续的精彩内容将为各位读者一一呈现。