【Java Web编程 七】Servlet基本概念和生命周期

简介: 【Java Web编程 七】Servlet基本概念和生命周期

好了,通过四篇blog我们已经充分了解了JSP的基本语法、内置对象、JavaBean以及EL&JSTL了。从今天开始学习下Servlet技术,也就是我们MVC架构中的C,controller层。

Servlet基本概念

Servlet是什么?官方定义:Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。这个中间层就是对请求进行分发和控制。

Servlet解决什么问题

JSP是JavaWeb应用中处理和显示动态网页的技术,而JSP页面中出现了大量逻辑处理并且和HTML元素混在了一起,这使得JSP页面显得混乱、不易维护,职责不清晰。

  • JSP是建立在静态Html基础之上,使得数据显示时具有动态特点,所以JSP的职责就是显示数据,而不是逻辑处理
  • Servlet的出现很好的规避了这一点,将复杂的逻辑处理、请求响应交给Servlet处理,JSP则只负责渲染显示页面

我们之前反复提到JSP优化和发展历史的一切努力就是简化脚本代码,让JSP专注于页面显示,控制逻辑则交给Servlet去做,而事实上,JSP编译以后也就成为了Servlet。

JSP和Servlet区别和用途

分别了解下JSP和Servlet区别和用途,明确它们的使用范围。

JSP和Servlet区别

了解了JSP和Servlet的区别后才能更好的理解它们各自的用途:

  1. JSP经编译后就变成了Servlet,JSP的本质就是Servlet,JVM只能识别Java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的Java类
  2. JSP更擅长表现于页面显示,Servlet更擅长于逻辑控制
  • Servlet能够很好地组织业务逻辑代码,但是在Java源文件中通过字符串拼接的方式生成动态HTML内容会导致代码维护困难、可读性差
  • JSP虽然规避了Servlet在生成HTML内容方面的劣势,但是在HTML中混入大量、复杂的业务逻辑同样也是不可取的
  1. Servlet中没有内置对象,JSP中的内置对象有些是通过Servlet相关对象得到的,例如:HttpServletRequest对象,HttpServletResponse对象

JSP是Servlet的一种简化,使用JSP只需要完成程序员需要输出到客户端的内容,JSP中的Java脚本如何镶嵌到一个类中,由JSP容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应

JSP和Servlet用途

依据它们的不同之处来进行分析,Servlet的短板在展示,如果在Servlet里描述html元素:

@Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        // Hello
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("<h1>" + message + "</h1>");
        out.println("</body></html>");
    }

那么会产生大量的html的输出代码,影响业务逻辑梳理。而JSP的短板在逻辑,如果在JSP里加大量逻辑:

<body>
<%
    String print = "我是脚本语句内容";
    List<String> strList = new ArrayList<>();
    strList.add("脚本语句1");
    strList.add("脚本语句2");
    strList.add("脚本语句3");
    //  strList.add("脚本语句4");  //这是一个Java注释,用来注释Java代码,查看源码时不可见
%>
<a href="hello-servlet">我的第一个JavaWeb项目</a> <!-- 这是一个html注释,可见<%=new Date().getTime() %> -->
<br/>
<%--这是一个JSP注释,查看源码时不可见--%>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>
<jsp:forward page="jsp/result.jsp"></jsp:forward>
跳转语句之后,代码不会被执行:<%="我是一个跳转后的表达式语句"%>
</body>

也会使页面看起来比较杂乱。所以双剑合璧,在加上我们之前提到的JavaBean就构成了简易版的MVC模式:

在这种模式【模型(Model)、视图(View)和控制器(Controller)】下

  1. Web浏览器发送HTTP请求到服务端,被Controller(Servlet)获取并进行处理(例如参数解析、请求转发)
  2. Controller(Servlet)调用核心业务逻辑——Model部分,获得结果
  3. Controller(Servlet)将逻辑处理结果交给View(JSP),动态输出HTML内容

MVC模式在Web开发中的好处是非常明显,它规避了JSP与Servlet各自的短板,Servlet只负责业务逻辑而不会通过out.append()动态生成HTML代码;JSP中也不会充斥着大量的业务代码,如果JSP使用标签去显示甚至不需要业务代码。

创建第一个Servlet

创建和请求第一个Servlet的流程如下,通过IDEA非常简单:

创建Servlet类

通过idea创建Servlet非常简单,只需要创建选项时选择Servlet即可:

重命名Servlet

创建好后重命名为我们包含逻辑的Servlet,例如重命名为FirstServlet,IDEA默认为我们生成了代码:

package com.example.myfirstweb.controller; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/19
 */
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "Servlet", value = "/Servlet")
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

需要注意的是在servlet3.0以后,我们可以不用再web.xml里面配置servlet,只需要加上@WebServlet注解就可以修改该servlet的属性了:

这个也算是注解的一个应用,解放配置化的东西,让参数可配置,关于注解可以参照我这篇Blog注解的基本概念和使用

修改注解并访问

注解的默认属性值如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";
    String[] value() default {};
    String[] urlPatterns() default {};
    int loadOnStartup() default -1;
    WebInitParam[] initParams() default {};
    boolean asyncSupported() default false;
    String smallIcon() default "";
    String largeIcon() default "";
    String description() default "";
    String displayName() default "";
}

调整注解的value值为我们想要的url地址:

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("<h1>" + "这是第一个Servlet页面" + "</h1>");
        out.println("</body></html>");
    }

启动项目后输入该地址查看:

Servlet请求流程

当我们请求一个Servlet的时候发生了什么呢,完整的请求流程如下

  1. 用户在浏览器地址栏中输入请求URL地址:http://localhost:8080/myfirstweb/myFirstServlet
  2. Tomcat 服务器根据请求获取 URL 中最后需要访问的资源,即/myFirstServlet
  3. Tomcat 服务器根据获取的/myFirstServlet的url地址找到注解中url属性为该路径的Servlet
  4. 找到之后服务器实例化Servlet
  5. 执行Servlet对应的请求处理方法,例如doGet,完成给用户响应

以上就是一个完整的查找并响应的Servlet流程。如果没有匹配到对应路径,则界面会出现404异常

Servlet生命周期

Servlet 生命周期是由 Servlet 的容器来控制的,Servlet 生命周期分为 4 个阶段:

  1. 第一阶段:加载并实例化,Servlet 容器负责加载和实例化,在服务器启动或第一次请求时执行,如果需要服务器启动时加载,则需在注解中加入loadOnStartup配置(默认为-1总是启动时加载),数值越小优先级越高,加载后该Servlet实例将常驻内存
  2. 第二阶段:初始化,在 Servlet 实例化之后,容器将调用 init() 方法,并传递实现 ServletConfig 接口的对象,在 Servlet 的整个生命周期内,init()方法只被调用一次,一般根据需要是否添加init方法,如果Servlet需要初始化一些数据,可以在该方法中完成代码
  3. 第三阶段:请求处理,当容器收到对这一 Servlet 的请求,就调用 Servlet 的service() 方法,并把请求和响应对象作为参数传递,service() 方法检查 HTTP 请求类型(GET、POST等),并在适当的时候调用 doGet()、doPost() 等方法,每次请求都会调用对应的doGet或doPost方法
  4. 第四阶段:销毁,一旦 Web服务器停止服务,Servlet会自动执行其 destroy() 方法,以释放资源

我们可以看一下上述提到的一些方法的源码,从类继承关系的角度去看:

我们可以看到其实是从最顶层的Servlet接口来定义生命周期的,它定义了生命周期的几个默认方法:

同时我们可以看到,init方法在GenericServlet有两个重载方法,我认为tomcat使用了带有配合的init方法。

public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    public void init() throws ServletException {
    }

同时我们可以看到service方法是在HttpServlet类中具体实现的,就是来匹配请求方法的:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest)req;
            HttpServletResponse response = (HttpServletResponse)res;
            this.service(request, response);
        } else {
            throw new ServletException("non-HTTP request or response");
        }
    }
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

总结一下

JSP其实就是特殊的Servlet,创建一个Servlet也非常简单,需要注意的是要重新doGet或doPost方法,还有就是Servlet的继承结构和生命周期也需要详细理解下,总之Servlet是tomcat操作的对象,tomcat创建Servlet实例并接管它的全生命周期,而我们需要做的就是重写业务逻辑达成我们的目标

相关文章
|
9月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
790 1
|
9月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
1129 0
|
10月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
1078 64
|
8月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
615 0
|
10月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
598 1
|
11月前
|
缓存 NoSQL Java
Java Web 从入门到精通之苍穹外卖项目实战技巧
本项目为JavaWeb综合实战案例——苍穹外卖系统,涵盖Spring Boot 3、Spring Cloud Alibaba、Vue 3等主流技术栈,涉及用户认证、订单处理、Redis缓存、分布式事务、系统监控及Docker部署等核心功能,助你掌握企业级项目开发全流程。
1087 0
|
8月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
615 4
|
12月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
12月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。