【Java Web编程 八】深入理解Servlet常用对象

简介: 【Java Web编程 八】深入理解Servlet常用对象

Servlet中有几个常用的对象,如果大家还记得之前在JSP的内置对象中介绍过的内容那么应该会对这几个对象大致有个印象JSP内置对象,分别是下图中的这几个红圈内容:

我们知道JSP编译后就是Servlet,这也再次证明了这一点。

常用对象使用

接下来我们详细介绍下Servlet中使用最频繁的这四个常用对象。

HttpServletRequest接口

HttpServletRequest 接口代表客户端的请求,它包含了客户端提交过来的请求数据:

  • HttpServletRequest 接口来自于Servlet规范。
  • HttpServletRequest 接口实现类由Http服务器厂商提供。
  • HttpServletRequest 接口读取请求协议包中的内容。
  • 一般习惯将 HttpServletRequest 接口修饰的对象称为 请求对象。

然后我们探索下主要功能。

request主要功能

主要功能如下:

  • HttpServletRequest 接口读取请求包中的请求行中的信息(url、method、uri、scheme)
  • HttpServletRequest 接口读取请求包中请求头或者请求体中参数的信息。
  • HttpServletRequest 接口代替浏览器向Tomcat索要资源文件【请求转发】。

实现以上功能的方法如下

request常用方法

常用的方法如下,例如获取请求的参数等:

请求头和请求体中的一些数据

request使用示例

一个获取请求中内容的示例如下:

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //读取请求行中的url属性信息
        String url=request.getRequestURL().toString();
        //读取请求行中method的属性信息
        String method=request.getMethod();
        //读取请求行中的uri属性信息
        //uri是从url中截取的一段字符串,格式:"/网站名/资源文件名",可以帮助tomcat进行资源定位
        String uri=request.getRequestURI().toString();
        //获取请求行中的协议信息
        String scheme=request.getScheme();
        PrintWriter out = response.getWriter();
        out.println("url===" + url);
        out.println("uri===" + uri);
        out.println("method===" + method);
        out.println("scheme===" + scheme);
    }
//请求转发的写法
 RequestDispatcher requestDispatcher=request.getRequestDispatcher("/five.html");
        requestDispatcher.forward(request,response);

打印结果如下:

HttpServletResponse接口

HttpServletResponse 接口代表向客户端发送的响应,利用response可以向客户 端响应信息或跳转界面:

  • HttpServletResponse 接口来自于Servlet规范。
  • HttpServletResponse 接口实现类由Http服务器厂商提供。
  • HttpServletResponse 接口可以将Servlet中的运行结果写入到响应包。
  • 一般习惯将 HttpServletResponse 接口修饰的对象称为 响应对象

然后我们探索下主要功能。

response主要功能

主要功能如下:

  • HttpServletResponse 接口负责将Servlet运行结果以二进制形式写入到响应包中的响应体。
  • HttpServletResponse 接口负责设置响应包中响应头的content-type属性,控制浏览器采用对应的解析器和编译器对响应体中的二进制数据进行处理。
  • HttpServletResponse 接口负责将一个请求地址写入到响应头中的location属性中,来控制浏览器下一次请求的方式【重定向】。

实现这样功能的方法如下

response常用方法

HttpServletResponse常用方法如下:

response使用示例

一个重定向的使用的示例如下:

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String url="http://www.baidu.com";
        //通过响应对象将地址写入到响应头中的location
        response.sendRedirect(url);
    }

这样我们可以重定向到百度页面:

ServletConfig对象

在 Web 容器初始化一个 Servlet 实例时,会为当前的 Servlet 准备一个唯一的 ServletConfig 实例配置对象。ServletConfig 对象能读取配置在 web.xml 文件中对应Servlet 配置的初始化参数。

ServletConfig用于封装servlet的配置信息。从一个servlet被实例化后,对任何客户端在任何时候访问有效,但仅对servlet自身有效,一个servlet的ServletConfig对象不能被另一个servlet访问

ServletConfig只能针对当前配置的Servlet有效,例如我们在注解中定义和通过配置获取代码如下:

@WebServlet(name = "Servlet", value = "/myFirstServlet",initParams = { @WebInitParam(name = "name", value = "tml"),
        @WebInitParam(name = "age", value = "27") })
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=gbk");
        PrintWriter out = response.getWriter();
        // 获取 ServletConfig 实例
        ServletConfig config = this.getServletConfig();
        // 获取指定名称的初始化参数的字符串值
        String name = config.getInitParameter("name");
        String age = config.getInitParameter("age");
        out.println("servlet 初始化参数 name 的值是:" + name + "<br/>");
        out.println("servlet 初始化参数 age 的值是:" + age + "<br/>");
    }

浏览器的打印结果为:

ServletContext对象

ServletContext可以实现多个Servlet获取相同的初始化参数值,它不属于某一个Servlet所有,而是Web 应用程序的上下文环境的参数。

servlet容器在启动时会加载web应用,并为每个web应用创建唯一的servlet context对象,可以把ServletContext看成是一个Web应用的服务器端组件的共享内存,在ServletContext中可以存放共享数据。ServletContext对象是真正的一个全局对象,凡是web容器中的Servlet都可以访问。 整个web应用只有唯一的一个ServletContext对象

我们可以做个实验,分别在两个Servlet里都加入同样的计数器代码,在HelloServlet中:

package com.example.myfirstweb;
import java.io.*;
import javax.servlet.ServletContext;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
/**
 * @author tianmaolin
 */
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    private String message;
    @Override
    public void init() {
        message = "Hello World!";
    }
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=gbk");
        PrintWriter out = response.getWriter();
        // 获取 ServletContext 对象
        ServletContext context = this.getServletContext();
        // 获取指定名称的 Web 应用程序的上下文初始参数的字符串值
        if(null==context.getAttribute("counter")){
            context.setAttribute("counter", 1);
        }else{
            int counter=(Integer)context.getAttribute("counter");
            context.setAttribute("counter", counter+1);
            out.println("当前计数器数值为"+counter);
        }
    }
    @Override
    public void destroy() {
    }
}

在FirstServlet中:

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 = "/myFirstServlet")
public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=gbk");
        PrintWriter out = response.getWriter();
        // 获取 ServletContext 对象
        ServletContext context = this.getServletContext();
        // 获取指定名称的 Web 应用程序的上下文初始参数的字符串值
        if(null==context.getAttribute("counter")){
            context.setAttribute("counter", 1);
        }else{
            int counter=(Integer)context.getAttribute("counter");
            context.setAttribute("counter", counter+1);
            out.println("当前计数器数值为"+counter);
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
}

每个Servlet中都可以读取上下文初始化的参数,分别请求两个页面:

他们没有各自累加,而是总体在累加

请求转发和重定向

其实在JSP中的这篇Blog:JSP内置对象中就详细介绍了请求转发和重定向,当时并没有引入Servlet的概念,当我们将Servlet作为请求的目标时这两个概念依然存在:

二者的请求流程

整个执行流程比较好理解,一句话,转发是服务器行为,重定向是客户端行为。为什么这样说呢,这就要看两个动作的工作流程:

  • 转发过程:客户浏览器发送http请求----》web服务器接受此请求–》调用内部的一个方法在容器内部完成请求处理和转发动作----》将目标资源 发送给客户;

在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

  • 重定向过程:客户浏览器发送http请求----》web服务器接受后发送302状态码响应及对应新的location给客户浏览器–》客户浏览器发现 是302响应,则自动再发送一个新的http请求,请求url是新的location地址----》服务器根据此请求寻找资源并发送给客户。

在这里 location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的 路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的

二者的主要区别

从以下几个浅层的角度分别来看下二者的区别:

  1. 从地址栏显示的角度来看
  • forward:是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址;
  • redirect:是服务端根据逻辑发送一个状态码告诉浏览器重新去请求那个地址,所以地址栏显示的是新的URL.
  1. 从数据共享的角度来看
  • forward:转发页面和转发到的页面可以共享request里面的数据.
  • redirect:请求页面和重定向页面不可以共享request里面的数据
  1. 从使用场景的角度来看
  • forward: 一般用于用户登陆的时候,根据角色转发到相应的模块.
  • redirect: 一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
  1. 从请求效率的角度来看
  • forward: 高,因为forwar全程依然在一个请求里,所以效率较高
  • redirect: 低,因为至少发生了两次请求,所以效率一般较低
  1. 从使用优先级角度来看:
  • 优先选择转发,因为转发效率更高
  • 在同一个 Web 应用程序的两个请求间传递数据时,采用转发
  • 如果需要跳转到其他服务器上的资源,则必须使用重定向

理解了二者的区别我们来看一下写法:

@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //请求转发的写法
        RequestDispatcher requestDispatcher=request.getRequestDispatcher("/first.jsp");
        requestDispatcher.forward(request,response);
        //重定向的写法
        response.sendRedirect("/first.jsp");
    }

具体的使用区别可以参照:JSP内置对象

Servlet同步问题

Servlet在多线程下其本身并不是线程安全的。首先明白几个前置概念

  • JSP/Servlet默认是以多线程模式执行的。
  • Servlet本身是单实例的,当有多个用户访问某个Servlet会访问该唯一实例中的成员变量。

每个浏览器各自创建的request请求当然是不一样的,都有各自的set和getattribute方法,互不干扰,即使是session也是如此,各自的set和getattribute互不干扰。只有application例外,作用于整个服务器 。但是以上说法皆是基于变量是局部变量,如果是成员变量,由于servlet是单实例的,所以会出现线程紊乱现象

问题示例

例如我们在Servlet里定义一个成员变量:

@WebServlet(name = "Servlet", value = "/myFirstServlet",initParams = { @WebInitParam(name = "name", value = "tml"),
        @WebInitParam(name = "age", value = "27") })
public class FirstServlet extends HttpServlet {
    private String username;  //成员变量
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.username=request.getParameter("username");
        try {
            java.lang.Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        request.setAttribute("username", this.username);
        request.getRequestDispatcher("jsp/result.jsp").forward(request, response);
    }

返回的结果的jsp页面为:

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/14
  Time: 11:32 上午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>跳转的结果页面</title>
</head>
<body>
<a>我是跳转后的结果页面</a>
<%=request.getAttribute("username") %>
</body>
</html>

当有两个请求同时到达时,第一个用户会发现自己设置的值没有生效(因为休眠了5秒,这里模拟线程并发):

最终两个页面都会显示同一个,也就是后来的那个值。

对于request作用域,一个浏览器的两个请求会发生这个问题,对于session作用域,两个浏览器的两个请求也会发生这个问题。需要注意的是无论是request还是session,他们的作用域都没有改变,出现这种现象的原因是他们获取的usernam都来自Servlet实例,而一个Servlet实例从初始化生成之后直到销毁之前(一般是服务器down)一直存在,所以它们才看起来能像application一样在作用域隔离的情况下还能一直获取到相同的值。需要厘清一个概念,作用域与具体的Servlet无关

而application作用域不会有问题,因为application本来就是服务共享的,在我们的预期之中。

解决方法

很简单,当我们不使用成员变量的时候就不会有这个问题:

public class FirstServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username=request.getParameter("username");
        try {
            java.lang.Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        request.setAttribute("username", username);
        PrintWriter out=response.getWriter();
        out.println(request.getAttribute("username"));
    }

两个请求都获取了自己设置的值。

使用规范

如果在类中定义成员变量,而在service中根据不同的线程对该成员变量进行更改,那么在并发的时候就会引起错误。

最好是在方法中,定义局部变量,而不是类变量或者对象的成员变量,由于方法中的局部变量是在栈中,彼此各自都拥有独立的运行空间而不会互相干扰,因此才做到线程安全

总结一下

其实Servlet中的对象和JSP中的类似,重点掌握request和response就行了,而这两个之中再了解了请求转发和重定向的原理即可。当然Servlet是单实例这一点也需要知道,在并发的情况下,可能会有多线程下的并发问题。

相关文章
|
1天前
|
Java 开发者
【Java编程新纪元】JDK 22:超级构造函数来袭,super(...) 前导语句改写编程规则!
【9月更文挑战第6天】JDK 22的超级构造函数特性是Java编程语言发展史上的一个重要里程碑。它不仅简化了代码编写,还提升了代码的可读性和维护性。我们有理由相信,在未来的Java版本中,还将有更多令人兴奋的新特性等待我们去发现和应用。让我们共同期待Java编程新纪元的到来!
|
1天前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
|
3天前
|
Java API Apache
从零到英雄的蜕变:如何用Apache Wicket打造你的第一个Web应用——不仅是教程,更是编程之旅的启航
【9月更文挑战第4天】学习Apache Wicket这一开源Java Web应用框架是一段激动人心的旅程。本文将指导你通过Maven搭建环境,并创建首个“Hello, World!”应用。从配置`pom.xml`到实现`HelloWorldApplication`类,再到`web.xml`的设置,一步步教你构建与部署简单网页。适合初学者快速上手,体验其简洁API与强大组件化设计的魅力。
|
7天前
|
存储 关系型数据库 MySQL
PHP编程基础:构建你的第一个Web应用
【8月更文挑战第31天】 在数字时代的海洋里,每个人都可以成为自己命运的船长。本文将引领初学者启航,用PHP语言搭建起第一个属于自己的网站。我们将从浅入深,逐步探索PHP的世界,最终实现一个简单的个人博客系统。这不仅是一段代码的旅程,更是一次思维和技术的飞跃。
|
4天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第2天】在Java的世界里,对象序列化和反序列化就像是给数据穿上了一件隐形的斗篷。它们让数据能够轻松地穿梭于不同的系统之间,无论是跨越网络还是存储在磁盘上。本文将揭开这层神秘的面纱,带你领略序列化和反序列化的魔法,并展示如何通过代码示例来施展这一魔法。
8 0
|
7天前
|
Java 开发者
Java编程中的异常处理机制探究
【8月更文挑战第31天】在Java的世界中,异常处理是维护程序稳定性的重要工具。它像是一套精密的免疫系统,保护代码免受错误的侵袭,确保程序能够优雅地应对意外情况。本文将带你走进Java的异常处理机制,了解如何捕获和处理异常,以及自定义异常类的创建与应用,让你的代码更加健壮,运行更加顺畅。
|
7天前
|
开发者 C# Windows
WPF与游戏开发:当桌面应用遇见游戏梦想——利用Windows Presentation Foundation打造属于你的2D游戏世界,从环境搭建到代码实践全面解析新兴开发路径
【8月更文挑战第31天】随着游戏开发技术的进步,WPF作为.NET Framework的一部分,凭借其图形渲染能力和灵活的UI设计,成为桌面游戏开发的新选择。本文通过技术综述和示例代码,介绍如何利用WPF进行游戏开发。首先确保安装最新版Visual Studio并创建WPF项目。接着,通过XAML设计游戏界面,并在C#中实现游戏逻辑,如玩家控制和障碍物碰撞检测。示例展示了创建基本2D游戏的过程,包括角色移动和碰撞处理。通过本文,WPF开发者可更好地理解并应用游戏开发技术,创造吸引人的桌面游戏。
15 0
|
7天前
|
开发者 C# 存储
WPF开发者必读:资源字典应用秘籍,轻松实现样式与模板共享,让你的WPF应用更上一层楼!
【8月更文挑战第31天】在WPF开发中,资源字典是一种强大的工具,用于共享样式、模板、图像等资源,提高了应用的可维护性和可扩展性。本文介绍了资源字典的基础知识、创建方法及最佳实践,并通过示例展示了如何在项目中有效利用资源字典,实现资源的重用和动态绑定。
22 0
|
7天前
|
Java 开发者
Java编程中的异常处理机制探究
【8月更文挑战第31天】 在Java的世界中,异常处理是维护程序稳定性的重要工具。它像是一套精密的免疫系统,保护代码免受错误的侵袭,确保程序能够优雅地应对意外情况。本文将带你走进Java的异常处理机制,了解如何捕获和处理异常,以及自定义异常类的创建与应用,让你的代码更加健壮,运行更加顺畅。
|
7天前
|
JavaScript 前端开发 Oracle
下一篇
DDNS