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

相关文章
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
12 2
|
2天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
79 53
|
1天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
18 1
|
5天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
27天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
86 3
|
9天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
85 44
|
4天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
13 1
|
7天前
|
XML 安全 PHP
PHP与SOAP Web服务开发:基础与进阶教程
本文介绍了PHP与SOAP Web服务的基础和进阶知识,涵盖SOAP的基本概念、PHP中的SoapServer和SoapClient类的使用方法,以及服务端和客户端的开发示例。此外,还探讨了安全性、性能优化等高级主题,帮助开发者掌握更高效的Web服务开发技巧。
|
10天前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
26 2
下一篇
无影云桌面