【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实例并接管它的全生命周期,而我们需要做的就是重写业务逻辑达成我们的目标

相关文章
|
12天前
|
SQL Java 数据库
2025 年 Java 从零基础小白到编程高手的详细学习路线攻略
2025年Java学习路线涵盖基础语法、面向对象、数据库、JavaWeb、Spring全家桶、分布式、云原生与高并发技术,结合实战项目与源码分析,助力零基础学员系统掌握Java开发技能,从入门到精通,全面提升竞争力,顺利进阶编程高手。
194 1
|
12天前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
290 100
|
15天前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
141 0
|
16天前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
196 1
|
23天前
|
NoSQL Java 关系型数据库
超全 Java 学习路线,帮你系统掌握编程的超详细 Java 学习路线
本文为超全Java学习路线,涵盖基础语法、面向对象编程、数据结构与算法、多线程、JVM原理、主流框架(如Spring Boot)、数据库(MySQL、Redis)及项目实战等内容,助力从零基础到企业级开发高手的进阶之路。
121 1
|
29天前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
66 16
|
2月前
|
安全 Java Shell
Java模块化编程(JPMS)简介与实践
本文全面解析Java 9模块化系统(JPMS),帮助开发者解决JAR地狱、类路径冲突等常见问题,提升代码的封装性、性能与可维护性。内容涵盖模块化核心概念、module-info语法、模块声明、实战迁移、多模块项目构建、高级特性及最佳实践,同时提供常见问题和面试高频题解析,助你掌握Java模块化编程精髓,打造更健壮的应用。
|
16天前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
49 0
|
2月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。

热门文章

最新文章