2023年3月

因为疫情和工作的原因,2022年整整一年我基本没有深度参与过网络安全和渗透测试相关的工作。

背景:之前因为使用习惯,一直使用的是ThinkPad X1 Extreme,可联想的品控实在拉胯,奈何ThinkPad使用了不到两年就出现了键盘失灵、间歇卡顿等等问题。后续购入了HP的暗影精灵8 Plus,可由于这类游戏本的重量实在是感人……遂翻出了一直在手边吃灰的2019款Macbook Pro。
自2019年下半年开始兼职从事网络安全相关工作开始,还是接触CTF、AWD竞赛更多一点,除了2019年至2021年间参加了几次各级单位组织的实战攻防演习以外几乎对渗透测试、漏洞挖掘工作没有涉及。
2023年初,因为所在单位派我到北京某单位开展网络安全的相关工作,为期半年,故准备重新学习、整理渗透测试和其他网络安全领域的工作内容。综合考虑还是搬出了吃灰的Macbook来开展相关工作。

那么就从布置Mac下的渗透测试环境开始吧!!!


1、虚拟机的布置

虚拟机环境:Vmare Fusion + Kali

在Mac上我使用了Vmare Fusion作为虚拟机软件
image

Kali镜像下载地址:
https://www.kali.org/get-kali/#kali-platforms
image

因为是在虚拟机中安装Kali的镜像,所以直接选择右边的Virtual Machines选项就可以,点击即可跳转到对应的下载链接,选择Vmare直接下载即可。完成下载后,使用解压工具解压到文件夹后,双击打开即可开启虚拟机。


2、漏洞扫描程序Nessus

Nessus主要分为三个版本:Expert、Professional、Essentials。
其中,Essentials为家用版,可以在Nessus官网直接注册获取激活密钥。
Nessus官网链接:
https://www.tenable.com/products/nessus
image

因为Nessus在Web界面安装过程中,需要用到激活密钥,可以直接到该链接:
http://www.tenable.com/products/nessus/nessus-plugins/obtain-an-activation-code
按照说明一步一步操作将激活密钥发送到你的邮箱中。
image

下载玩安装包后,使用命令:
sudo dpkg -i {安装包文件名}
完成Nessus的安装,安装成功后可看到如下提示:
image

注意一下倒数第二行的提示,这是你后续运行启动Nessus的命令。然后使用该命令:
/bin/systemctl start nessusd.service
启动Nessus的服务,直接在虚拟机本机浏览器中输入:
https://kali:8834/
或在其他可联通的计算机浏览器输入: https://{Kali虚拟机主机ip地址}:8834 即可访问Nessus的Web管理界面。


(后续待更新……)


TS 是结构类型系统(structural type system),基于结构/形状检查类型,而非类型的名字。

TS 中的兼容性,主要看
结构是否兼容
。(核心是考虑安全性),结构化的类型系统(又称鸭子类型检查),如两个类型名字不一样但是无法区分
类型兼容性是基于结构子类型的。 结构类型是一种只使用其成员来描述类型的方式。

如果
x
要兼容
y
,那么
y
至少具有与
x
相同的属性。
这里要检查
y
是否能赋值给
x
,编译器检查
x
中的每个属性,看是否能在
y
中也找到对应属性。
X 兼容 Y:X(目标类型)= Y(源类型)
简单一句话概括兼容性: 重新赋值不报错(类型自动转化)

一.基本数据类型的兼容性

let temp: string | number;
let num!: number;
temp = num;
let obj: {
  toString(): string;
};
let str: string = "yya";
obj = str; // 字符串中具备toString()方法,所以可以进行兼容
obj.toString(); // 安全, 保证使用的时候不会发生异常

二.接口兼容性

接口的兼容性,只要满足接口中所需要的类型即可!(保证你要的,我都有,就行,多了也没关系)

interface IAnimal {
  name: string;
  age: number;
}
interface IPerson {
  name: string;
  age: number;
  address: string;
}
let animal: IAnimal;
let person: IPerson = {
  name: "yya",
  age: 18,
  address: "beijing",
};

type T2 = IPerson extends IAnimal ? true : false; // true
animal = person; // 子类赋予给父类 兼容

三.函数的兼容性

函数的兼容性主要是比较参数和返回值

参数
:赋值函数的参数要少于等于被赋值的函数:也就是说,对应函数的参数来讲,
少的参数可以赋予给多的
,因为内部实现传了多个可以少用或不用(忽略额外的参数在 JavaScript 里是很常见的)

sum2
的每个参数必须能在
sum1
里找到对应类型的参数。 注意的是参数的名字相同与否无所谓,
只看它们的类型

sum2
的每个参数在
sum1
中都能找到对应的参数,所以允许赋值。

let sum1 = (a: string, b: string) => a + b;
let sum2 = (a: string) => a;
sum1 = sum2;

举例:
Array#forEach
给回调函数传 3 个参数:item,index 和 array。 尽管如此,传入一个只使用第一个参数的回调函数也是可以的

type Func<T> = (item: T, index: number, array: any[]) => void;
function forEach<T>(arr: T[], cb: Func<T>) {
  for (let i = 0; i < arr.length; i++) {
    cb(arr[i], i, arr);
  }
}
forEach([1, 2, 3], (item) => {
  console.log(item);
});

返回值
:

type sum1 = () => string | number;
type sum2 = () => string;

let fn1: sum1;
let fn2!: sum2;
fn1 = fn2;

四.类的兼容性

类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。 比较两个类类型的对象时,只有实例的成员会被比较。 静态成员和构造函数不在比较的范围内。

class Animal {
  feet!: number;
  constructor(name: string, numFeet: number) {}
}

class Size {
  feet!: number;
  constructor(numFeet: number) {}
}

let a!: Animal;
let s!: Size;

a = s; // OK
s = a; // OK

类的私有成员和受保护成员

只要有 private 或者 protected 关键字会影响兼容性, 当检查类实例的兼容时,如果目标类型包含一个 private 私有成员,那么源类型
必须包含来自同一个类的这个私有成员
。 这条规则也适用于包含 protected 受保护成员实例的类型检查。 允许子类赋值给父类,但是不能赋值给其它有同样类型的类。

class A {
  private name!: string;
  age!: number;
}

class B {
  private name!: string;
  age!: number;
}

// let a: A = new B();  // error

class Parent {
  protected name: string = "zf";
  age: number = 11;
}
class Child extends Parent {}
let child: Parent = new Child(); // ok

五.泛型的兼容性

泛型比较的是最终的结果 比较的不是泛型传递的参数
例一:

interface Empty<T> {}

let x: Empty<number>;
let y!: Empty<string>;

type xx = Empty<number> extends Empty<string> ? true : false; // true

x = y; // OK 因为 y 匹配 x 的结构

在例一中,x 和 y 是兼容的,因为它们的结构使用类型参数时并没有什么不同。 把这个例子改变一下,增加一个成员,就能看出是如何工作的了:
例二:

interface NotEmpty<T> {
  data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

type xx = NotEmpty<number> extends NotEmpty<string> ? true : false; // false

x = y; // Error,  不兼容

对于没指定泛型类型的泛型参数时,会把所有泛型参数当成 any 比较。 然后用结果类型进行比较,就像例一:

let identity = function <T>(x: T): T {};

let reverse = function <U>(y: U): U {};

identity = reverse; // OK,  (x: any) => any 匹配 (y: any) => any

六.枚举的兼容性

枚举类型与数字类型兼容,并且数字类型与枚举类型兼容

enum Status {
  Pending,
  Resolved,
  Rejected,
}

let current = Status.Pending;
let num = 0;

current = num;
num = current;

不同枚举类型之间是不兼容的。

enum Status {
  Pending,
  Resolved,
  Rejected,
}
enum Color {
  Red,
  Blue,
  Green,
}

let current = Status.Pending;
let color = Color.Red;

current = color; // 不能将类型“Color.Red”分配给类型“Status”

标称类型简短介绍

类型分为两种 结构化类型(structural type system) 、标称类型(nominal type system)
标称类型: 虽然 BTC,USDT 都是 number 类型,但还是想要用不同的类型表示,且不能互换,数据的值本身没什么区别,安上不同名字就是不同类型,也就是说,标称类型系统中,两个变量是否类型兼容(可以交换赋值)取决于这两个变量显式声明的类型名字是否相同。

class AddType<S> {
  private _type!: S;
}
type NewType<T, S extends string> = T & AddType<S>;

type BTC = NewType<number, "btc">; // number + BTC
type USDT = NewType<number, "usdt">; // number + USDT
let btc = 100 as BTC;
let usdt = 100 as USDT;

function getCount(count: USDT) {
  return count;
}
getCount(usdt);

关于message

消息分片

消息分片的发送

消息分片允许将多个消息封装成一条消息。在发送自定义协议数据时,我们经常需要在消息前“填充”一个包头。如下代码,在发送的时候加上
zmq::send_flags::sndmore
标识(对应 zeromq
ZMQ_SNDMORE
),表示后面还有消息。这样 zeromq 会将
ZMQ_SNDMORE
的消息和最后一段消息拼装成一条完整的消息发送。

int SendData(char* pMsg, int iMsgLen)
{
    tagMsgHead stHead;
    bzero(&stHead, sizeof(stHead));
    ...
    stHead.Len = iMsgLen;
    stHead.Crc = 0;

    try
    {
        m_socket.send(zmq::const_buffer((const void*)(&stHead), sizeof(stHead)),zmq::send_flags::dontwait|zmq::send_flags::sndmore);
        m_socket.send(zmq::const_buffer((const void*)(pMsg), static_cast<size_t>(iMsgLen)), zmq::send_flags::dontwait);
    }
    catch (...)
    {
			  ....
    }

	return 0;
}

消息分片的接收

需要注意的是,如果发送使用了
ZMQ_SNDMORE
分片,那么在接收时也需要分多次
recv
接收数据(这点比较麻烦)。开始的时候以为
recv
接收的是一个完成的包,后面才知道
recv
接收的其实是“帧”数据,多个“帧”拼装成一个消息。具体接收方法如下:

Buffer buffer;
while (1) {
    // 接收消息
    zmq_msg_t identify;
    zmq_msg_t message;
    zmq_msg_init(&identify);
    zmq_msg_init(&message);
    zmq_recvmsg(socket, &identify, 0);
    zmq_recvmsg(socket, &message, 0);

    buffer.Append(zmq_msg_data(&message), zmq_msg_size(&message));
    // 检查是否还有更多消息可读
    while(zmq_msg_more(&message)) {
        zmq_recvmsg(socket, &message, 0);
        buffer.Append(zmq_msg_data(&message), zmq_msg_size(&message));
    }
    zmq_msg_close(&identify);
    zmq_msg_close(&message);
}

使用 cppzmq 的话,代码如下:

Buffer buffer;
while (1) {
    // 接收消息
    zmq::message_t identity;
    zmq::message_t message;
    socket.recv(identity, zmq::recv_flags::none);
    socket.recv(message, zmq::recv_flags::none);

    buffer.Append(message.data(), message.size());
    // 检查是否还有更多消息可读
    while(message.more()) {
        socket.recv(message, zmq::recv_flags::none);
        buffer.Append(message.data(), message.size());
    }
}

使用
ZMQ_SNDMORE
后接收也需要分片接收,这个确实是比较麻烦的地方。个人觉得如果改成一次接收会更好,因为这样更符合使用的“直觉”。

后面会不断更新这部分,有新的问题会加进来。

解读 Servlet 源码:GenericServlet,ServletConfig,ServletContext

在这里插入图片描述

每博一文案

人活着,就得随时准备经受磨难。他已经看过一些书,知道不论是普通人还是了不起的人,都要在自己的一生中经历许多磨难。磨难使人坚强。
人和社会、一切斗争的总结局也许都是中庸而已。与其认真、不如随便、采菊东篱下、悠然见南山。有钱就寻一醉、无钱就寻一睡、与过无争、随遇而安。
hellip;…人哪,活着是这么的苦!一旦你从幸福的彼岸被抛到苦难的此岸,你真是处处走头无路;而现在你才知道,在天堂与地狱之间原来也只有一步之遥!
倘若流落在他乡异地,生活中的一切都将失去保障,得靠自己一个人去对付冷酷而严峻的现实了
青年,青年,无论受怎样的挫折和打击,都要咬着牙关挺住,因为你们完全有机会重建生活;只要不灰心丧气,每一次挫折就只不过是通往新境界的一块普通的绊脚石,而绝不会置人于死命。
你永远要宽恕众生,不论他有多坏,甚至他伤害过你,你一定要放下,才能得到真正的快乐。
也许人生仅有那么一两个辉煌的瞬间————甚至一生都可能在平淡无奇中度过......不过,每个人的生活同样也是一个世界,即是最平凡的人,也得要为他那个世界的存在而战斗。这个意义上来说,在这些平凡的世界里,也没有一天是平静的。因此,大多数
普通人不会像飘飘欲仙的老庄,时常把自己看作是一粒尘埃————尽管地球在浩渺的宇宙中也只不过是一粒尘埃罢了。
                                       ——————《平凡的世界》

@

1. Servlet对象的生命周期

什么是Servlet 对象的生命周期 ?

Servlet 对象的生命周期表示:一个Servlet对象从出生在最后的死亡,整个过程是怎样的 。

Servlet对象是由谁来维护的?

  • Servlet 对象的创建,Servlet对象上方法的调用,以及Servlet 对象的销毁,JavaWeb程序员都无权干预的。
  • Servlet 对象的生命周期是由
    Tomcat
    服务器(web Server)全权负责的
  • Tomcat
    服务器通常又被我们称之为
    WEB容器 (WEB Container)
    。WEB 容器来管理 Servlet对象的死活

我们自己new的Servlet对象受WEB容器的管理吗?

我们自己 new 的 Servlet 对象是不受
WEB容器
管理的。

因为:
WEB容器
创建的 Servelt 对象,这些Servlet 对象都会被放到一个集合当中
(HashMap)
,只有放到这个 HashMap 集合中的 Servlet 才能够被 WEB容器管理,自己 new 的 Servlet 对象不会被 WEB容器管理,因为我们自己 new 的Servlet对象并不没有存放到 WEB 容器当中。

WEB容器底层应该有一个 HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系。
在这里插入图片描述

1.1 Servlet 五 个方法的调用周期

Servlet 必须重写的五方法分别为:
init(),service(ServletRequest request, ServletResponse response),getServletConfig(),getServletInfo(),destroy(),还有一个无参构造器
什么时候创建的,什么时候调用的,什么时候销毁的。

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class AServlet implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}






执行如下代码,启动 Tomcat 服务器:并访问该AServlet ,通过在浏览器其当中输入:
http://127.0.0.1:8080/blog01/A

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class AServlet implements Servlet {

    public AServlet(){
        System.out.println("AServlet 的无参构造器的调用");
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("AServlet 中的 init 的调用执行");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("AServlet 中的 service 的方法的调用执行");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("AServlet 中的 destroy 方法的执行调用");
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <servlet>
<!--        两个 name 值要保持一致-->
        <servlet-name>AServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AServlet</servlet-name>
<!--        注意: / 开始-->
        <url-pattern>/A</url-pattern>
    </servlet-mapping>
</web-app>

执行结果如下:

在这里插入图片描述

重点:

  • 当我们在默认普通配置的情况下,启动Tomcat 服务器的时候,我们编写的
    AServlet
    对象并没有被实例化(因为并没有调用我们 AServlet 构造器创建对象)。
  • 当用户访问该资源,第一次向我们的 AServlet 发送请求的时候。我们的 Servlet对象就被实例化了(这里是 AServlet 的构造器被执行了,并且执行的是该 AServlet 无参构造器。 )
  • 之后,当 Servlet 对象被创建出来了,也就是这里的 AServlet 对象,Tomcat 服务器马上就调用了 AServlet 对象当中的
    init()对象方法
    ,(注意:init()方法在执行的时候,我们的 Servlet(这里的 AServlet)对象就已经存在了,已经被创建出来了,因为 init() 是对象方法,需要通过创建对象才能被调用(特殊的 :反射除外))。
  • 用户发送第一次请求的时候,init()方法执行完,Tomcat 服务器马上调用了 Servlet (这里的AServlet 对象)当中的
    servelt()
    方法。
  • 注意:当用户继续发送第二次请求(刷新该,页面的资源,重新发送新的请求)。或者第三次,或者第四次请求的时候,Servlet对象并没有新建,还是使用之前创建好的Servlet对象,直接调用该Servlet对象的service方法
    结果如下:

在这里插入图片描述

  • 根据上述结果:我们可以知道如下信息:
    • Servlet
      对象使单例的 (单实例的。但是要注意:Servlet 对象使单实例的,但是 Servlet 类并不符合
      单例模式
      的设计。因为
      真正的单例模式的构造器private私有的
      ,所以我们称之为
      假单例
      ,之所以单例是因为 Servelt 对象的创建,我们 javaWeb程序员是管不着的,这个对象的创建只能是
      Tomcat
      服务器或其他服务器来说的算的。Tomcat 只会创建一个,所以导致了单例,但是属于假单例。)
    • 无参构造器,只会执行一次,该无参构造器是通过反射机制调用的(通过从 web.xml 中的配置信息获取到其中对应类的:
      全限定类名
      创建对象),
    • init()
      方法只在第一次用户发送请求的时候执行,init对象方法也只被 Tomcat 服务器调用一次。
    • 只要用户发送一次请求:
      service()
      方法必然会被
      Tomcat
      服务器调用一次,发送 100 次请求,servlect() 方法就会被调用 100次。
  • 我们关闭 Tomcat 服务器,控制台上显示如下结果

在这里插入图片描述

从上述控制台上显示的结果,我们可以知道:
当我们关闭服务器的时候 destroy()方法被调用了

  • destroy()方法是在什么时候被调用的 ?
  • 在服务器关闭的时候。

  • 因为服务器关闭的时候:要销毁 AServlet 对象的内存信息。

  • 所以服务器在销毁 AServlet 对象内存之前,Tomcat 服务器会自动调用 AServlet 对象中的
    destroy()
    方法。

问题:destroy() 方法调用的时候,Servlet 对象销毁了还是没有销毁 ???

答:
destroy()
方法执行的时候,AServlet 对象还在,没有被销毁,因为
destroy()
方法是对象方法。调用该方法的话需要通过对象才能调用的(反射除外),destroy()方法执行结束之后,AServlet 对象的内存才会被 Tomcat 释放。

注意点:
对应 Servlet 类我们不要编写构造器。

当我们Servlet 类中编写了一个有参数的构造器,那么如果我们没有手动再编写一个无参构造器的话,无参构造器就会消失。

如果一个 Servlet 无参构造器消失的会,如何:结果如下:

在这里插入图片描述

报错:500 错误。500 是一个 HTTP 协议的错误状态码。 500 一般情况下是因为服务器端的 java 程序出现了异常 。(服务器端的错误基本上都是 500 错误,服务器内部错误)

  • 如果一个 Servlet 类没有无参构造器的话,会导致 500 错误,反射机制无法通过
    无参构造器创建对象
    。导致 无法实例化 Servlet 对象。
  • 所以,一定要注意:在 Servlet 开发当中,不建议 程序员来定义构造器,因为定义不当,一不小心就会导致无法实例化 Servlet对象了。

思考:Servlet 的无参数构造器是在对象第一次创建的时候执行的,并且只会执行一次。而
init()
方法也是在对象第一次拆创建的时候执行的,并且也只会执行一次。那么这个无参构造器可以代替 init()方法吗 ?

答:不能。

Servlet 规范中有要求,作为 javaWeb 程序员,编写 Servlet 类的时候,不建议手动编写构造器,因为编写构造器,很容易让无参数构造器消失。这个操作可能会导致 Servlet 对象无法实例化,所以
init()
方法是有存在的必要的。

1.2 Servlet 常用的三个方法使用总结

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class AServlet implements Servlet {
    // init 被翻译为初始化
    // init 方法只会被执行一次,基本上和 Servlet构造器的调用同时执行,在Servlet 对象第一次被创建只会执行
    // init 方法通常是完成初始化操作的。
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("AServlet is init method execute!");
    }


    // service 方法:是处理用户请求的核心方法
    // 只要用户发送一次请求,service 方法必然会执行一次
    // 发送100次请求,service方法执行100次
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("AServlet is service method execute");
    }

    // destroy ()方法也是只执行一次
    // Tomcat 服务器在销毁AServlet 对象之前会调用一次destroy 方法
    // destroy()方法在执行的时候,AServlet 对象的内存还没有被销毁,即将被销毁,因为destroy 是对象方法,需要通过对象调用
    // destroy 方法中可以编写销毁前的准备
    // 比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接
    // 那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()
    @Override
    public void destroy() {
        System.out.println("AServlet is destroy method execute");
    }
}

关于Servlet类中方法的说明:

  • 无参构造器
    :只会执行调用一次。
  • init()
    方法:init 被翻译为初始化,init ()方法只会被执行一次;基本上和 Servlet构造器的调用同时执行;在Servlet 对象第一次被创建只会执行。init 方法通常是完成初始化操作的。
  • service()
    方法:
    service() 方法是处理用户请求的核心方法。
    只要用户发送一次请求,service 方法必然会执行一次;发送100次请求,service方法执行100次。
  • destroy()
    方法:destroy 方法中可以编写销毁前的准备;比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接;那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()。

init、service、destroy方法中使用最多的是哪个方法?

  • 使用最多就是service()方法,service方法是一定要实现的,因为service方法是处理用户请求的核心方法。

  • 什么时候使用init方法呢?

    init方法很少用;通常在init方法当中做初始化操作,并且这个初始化操作只需要执行一次。例如:初始化数据库连接池,初始化线程池....

  • 什么时候使用destroy方法呢?

    destroy方法也很少用;通常在destroy方法当中,进行资源的关闭。马上对象要被销毁了,还有什么没有关闭的,抓紧时间关闭资源。还有什么资源没保存的,抓紧时间保存一下。

Servlet对象更像一个人的一生:

  • Servlet的无参数构造方法执行:标志着你出生了。
  • Servlet对象的init方法的执行:标志着你正在接受教育。
  • Servlet对象的service方法的执行:标志着你已经开始工作了,已经开始为人类提供服务了。
  • Servlet对象的destroy方法的执行:标志着临终。有什么遗言,抓紧的。要不然,来不及了。

1.3 补充:让启动Tomcat 服务器的时候会调用构造器

从上述内容我们知道了,当 Tomcat 服务器启动的时候,我们的构造器实际上还没有调用,也就是对象还没有被实例化创建出来。但是我们想在启动 Tomcat 服务器的时候就调用构造器。可以使用如下方式:

在我们想让Tomcat 服务器启动的时候调用哪个类的构造器,就在该类当中的
web.xml
加上如下:标签
<load-on-startup>(填写数值)</load-on-startup>
: 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建

<!--<load-on-startup> 的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
<load-on-startup>2</load-on-startup>

举例:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <servlet>
        <!--        两个 name 值要保持一致-->
        <servlet-name>AServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
        <!--        的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>AServlet</servlet-name>
        <!--        注意: / 开始-->
        <url-pattern>/A</url-pattern>
    </servlet-mapping>


    <servlet>
        <!--        两个 name 保持一致-->
        <servlet-name>BServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
        <!--        的作用就是启动服务器的时候就会创建该对象:数值越小的越先被创建-->
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>BServlet</servlet-name>
        <!--        注意 / 开始-->
        <url-pattern>/B</url-pattern>
    </servlet-mapping>
</web-app>

在这里插入图片描述

在这里插入图片描述

2. GenericServlet

2.1 适配器

我们编写一个Servlet 类直接实现了 这个
Servlet 接口
存在什么缺点没有:?
有的:我们编写的 Servlet 类实现了该 Servlet 接口中所有的方法。但是我们其实只需要其中的
service()方法
,其他方法大部分情况下是不需要使用的。简单粗暴的实现了我们所有的方法,但是其中大部分的方法我们是不需要的,这样就显的我们的代码很丑陋了:

在这里插入图片描述

所以:我们不想要重写所以的方法,而是重写我们需要用的方法。这样该怎么做呢?

我们可以使用 23种设计模式中的一种:
适配器模式(Adapter)

适配器模式的理解:举例

比如:如果我们的手机充电时直接插入到 220V 的电压上,手机会直接报废的,因为手机无法接受如此高的电压,怎么办呢?
我们可以该手机配备一个电源适配器——充电器,手机连接充电器(适配器),适配器连接 220V的电压,这样就问题解决了。同理的还有很多:电脑的充当器。充电热水壶等等。

编写一个 GenericServlet (abstract )抽象类适配器。
这个适配器是一个
abstract
抽象类。:我们该类实现了 Servlet 接口中所有的方法,但是其中我们常用的方法
service()
定义为是抽象方法,因为如果不想实现的方法,可以定义将该方法定义为抽象方法就不用重写了,而是交给
继承
的子类重写就好了。因为重写方法只能存在于抽象类,接口当中,所以我们这个
适配器就为了一个抽象类
。这样我们以后编写的所以 Servlet 类就只需要继承 GenericServlet 抽象类,并且只要重写其中的
service()
抽象方法即可。但是我们又可以使用 Servlet 接口中的方法,因为我们的父类GenericServlet 抽象类 实现了 Servlet 接口中的所有方法。如有必要,我们编写的Servlet 类也可以重写GenericServlet 抽象类中的方法,该重写的方法也是重写Servlet 接口中的方法。

具体编写如下:

abstract class GenericServlet 抽象类适配器

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


/**
 * 编写一个标准通用的Servlet
 * 以后所有的Servlet 类都不要直接实现Servlet 接口了。
 * 以后所有的Servlet 类都继承 GenericServlet 类。
 * GenericServlet 就是一个适配器
 */
public abstract class GenericServlet implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 抽象方法:这个方法最常用,所以要求子类必须实现Service 方法。
     */
    @Override
    public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

以后编写的Servlet 都继承该 GenericServlet 抽象类适配器

package com.RainbowSea.servlet;


import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class AServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("AServlet 处理请求中....");
    }
}

package com.RainbowSea.servlet;


import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class BServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("BServlet 处理请求中....");
    }
}



在这里插入图片描述

问题:提供了 GenericServlet 类,init()方法还好执行吗?

答:还会执行,执行 GenericServlet 中被重写的 init()方法。

问题:init()方法是谁调用的 ?

Tomcat 服务器调用的。

问题:init()方法中的 ServletConfig 对象是谁创建的 ? 有是谁传过来的?

是由 Tomcat服务器 创建的,也是由 Tomcat 服务器传过来的。都是Tomcat 服务器干的。

Tomcat 服务器先创建了 ServleConfig 对象,然后调用 init()方法,将ServleConfig 对象传给了 init()方法

Tomcat 执行init()方法的伪代码

@Override
    public void init(ServletConfig config) throws ServletException {

    }
public class Tomcat {
    public static void main(String[] args){
        // .....
        // Tomcat服务器伪代码
        // 创建LoginServlet对象(通过反射机制,调用无参数构造方法来实例化LoginServlet对象)
        Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
        Object obj = clazz.newInstance();
        
        // 向下转型
        Servlet servlet = (Servlet)obj;
        
        // 创建ServletConfig对象
        // Tomcat服务器负责将ServletConfig对象实例化出来。
        // 多态(Tomcat服务器完全实现了Servlet规范)
        ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
        
        // 调用Servlet的init方法
        servlet.init(servletConfig);
        
        // 调用Servlet的service方法
        // ....
        
    }
}

2.2 模板方法设计模式

思考:
init 方法中的ServileConfig 对象是小猫咪创建好的,这个ServletConfig对象目前在init 方法的参数上,属于局部变量。那么ServletConfig 对象肯定以后要在Service 方法中使用,怎么才能保证ServletConfig 对象在Service方法中能够使用呢?

答: 可以定义一个类的属性,再通过 init 方法的,将该ServleConfig 赋值上去

具体代码如下:

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


/**
 * 编写一个标准通用的Servlet
 * 以后所有的Servlet 类都不要直接实现Servlet 接口了。
 * 以后所有的Servlet 类都继承 GenericServlet 类。
 * GenericServlet 就是一个适配器
 */
public abstract class GenericServlet implements Servlet {
    private ServletConfig config ;

    // 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
    // ServletConfig 对象了
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }

    // 获取该 config 对象值
    public ServletConfig getConfig() {
        return config;
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 抽象方法:这个方法最常用,所以要求子类必须实现Service 方法。
     */
    @Override
    public abstract void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

举例:Servale 类在 service ()方法当中获取到该 ServletConfig 对象值,并使用

package com.RainbowSea.servlet;


import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class AServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置 response 在浏览器页面当中显示的格式类型
        // 注意需要在显示之前,设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print("AServlet 处理请求中...." + "<br>");
        ServletConfig config = super.getConfig();
        out.print("AServlet 当中 service 获取到 Tomcat 服务器创建的ServletConfig对象: " + config);
        
    }
}

在这里插入图片描述

重点:
我们通过上述定义了一个
private ServletConfig config
成员属性,可以获取到Tomcat 创建的 ServletConfig 对象了,并在
service()
方法当中使用。但是存在一个问题:如果我们的子类要是重写了,我们父类适配器
GenericServlet
类当中的
init()
方法的话。因为多态性的缘故:在实际的运行过程中,调用的会是我们子类重写的的
init()
方法,这样导致的结果就,我们父类中的
private ServletConfig config
成员属性的值无法赋值上为
null
了。因为我们父类当中的 config 成员属性是通过父类当中的 init()方法赋值的,现在我们的子类重写了父类的 init()方法。不会调用我们父类的 init()进行赋值操作了,从而导致为了 null 值。

解决方式:将我们父类当中的 init()方法被
final
修饰,这样我们的子类就无法重写 init()方法了。

public abstract class GenericServlet implements Servlet {
    private ServletConfig config ;

    // 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
    // ServletConfig 对象了
    @Override
    public final void init(ServletConfig config) throws ServletException {
        this.config = config;
    }
}

上述的解决方式仍然存在一个问题:就是如果我们的子类一定要重写 init()方法该怎么办,现在你父类当中的 init()方法被 final 修饰了无法,被 子类重写。

解决方式:
我们使用 23中设计模式当中的 模板方法设计模式:

因为子类想要重写我们父类当中的 init()方法,那么我们就在父类当中,再创建一个 无参数的init()方法(方法的重载),这个方法让子类去重写,但是我们父类当中还有一个 带参数的 init(ServletConfig config) 方法,在该带有参数的init()方法当中调用我们这个提供该子类重写的 init()方法。具体代码如下:

在这里插入图片描述

package com.RainbowSea.servlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


/**
 * 编写一个标准通用的Servlet
 * 以后所有的Servlet 类都不要直接实现Servlet 接口了。
 * 以后所有的Servlet 类都继承 GenericServlet 类。
 * GenericServlet 就是一个适配器
 */
public abstract class GenericServlet implements Servlet {
    private ServletConfig config ;

    // 这样我们在我们的子类当中的 service()方法当中也可以使用 Tomcat 服务器创建的
    // ServletConfig 对象了,final 修饰的方法无法重写
    @Override
    public final void init(ServletConfig config) throws ServletException {
        // 赋值依旧存在,config 不会为 null
        this.config = config;
        // 调用子类重写后的 init()方法
        this.init();
    }
    
    // 用于提供给子类重写
    public void init() {
        
    }

    public ServletConfig getConfig() {
        return config;
    }
}

举例:在AServlet 类当中重写 init()方法,并且执行该重写的方法:

package com.RainbowSea.servlet;


import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class AServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置 response 在浏览器页面当中显示的格式类型
        // 注意需要在显示之前,设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print("AServlet 处理请求中...." + "<br>");
        ServletConfig config = super.getConfig();
        out.print("AServlet 当中 service 获取到 Tomcat 服务器创建的ServletConfig对象: " + config);

    }

    // 重写的父类当中的 init()无参方法
    @Override
    public void init() {
        System.out.println("AServlet 重写的 init()方法执行了");
    }
}

在这里插入图片描述

2.3 补充:GenericServlet 不用我们自己编写

注意:
GenericServlet
抽象类适配器是不需要我们自己编写的,Servlet 已经编写好了,我们只需要使用就可以了。

上述是为了理解 GenericServlet 源码,从而自己编写的 GenericServlet 适配器的。如下是
Servlet 为我们编写好的 GenericServlet 源码
,和我们上面自己编写的设计结构是一样的。

在这里插入图片描述

package javax.servlet;

import java.io.IOException;
import java.util.Enumeration;

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {

    private static final long serialVersionUID = 1L;

    private transient ServletConfig config;


    public GenericServlet() {
        // NOOP
    }


    @Override
    public void destroy() {
        // NOOP by default
    }

    @Override
    public String getInitParameter(String name) {
        return getServletConfig().getInitParameter(name);
    }

   
    @Override
    public Enumeration<String> getInitParameterNames() {
        return getServletConfig().getInitParameterNames();
    }

    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    
    @Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }

   
    @Override
    public String getServletInfo() {
        return "";
    }

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

    
    public void init() throws ServletException {
        // NOOP by default
    }

    
    public void log(String message) {
        getServletContext().log(getServletName() + ": " + message);
    }

    
    public void log(String message, Throwable t) {
        getServletContext().log(getServletName() + ": " + message, t);
    }

    
    @Override
    public abstract void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    
    @Override
    public String getServletName() {
        return config.getServletName();
    }
}

3. ServletConfig

从上面的知识我们知道了 : Tomcat 服务器先创建了 ServletConfig 对象,然后调用init()方法,将ServletConfig对象传给了init()方法。

先来一波,自问自答:

ServletConfig 是什么?

package javax.servlet; 显然 ServletConfig 是一个接口。

在这里插入图片描述

谁去实现了这个接口呢 ?

在这里插入图片描述

org.apache.catalina.core.StandardWrapperFacade
这个类实现了这个 ServletConfig 接口

StandardWrapperFacade
源码如下:

package org.apache.catalina.core;


import java.util.Enumeration;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;


/**
 * Facade for the <b>StandardWrapper</b> object.
 *
 * @author Remy Maucherat
 */
public final class StandardWrapperFacade
    implements ServletConfig {


    // ----------------------------------------------------------- Constructors


    /**
     * Create a new facade around a StandardWrapper.
     * @param config the associated wrapper
     */
    public StandardWrapperFacade(StandardWrapper config) {

        super();
        this.config = config;

    }


    // ----------------------------------------------------- Instance Variables


    /**
     * Wrapped config.
     */
    private final ServletConfig config;


    /**
     * Wrapped context (facade).
     */
    private ServletContext context = null;


    // -------------------------------------------------- ServletConfig Methods


    @Override
    public String getServletName() {
        return config.getServletName();
    }


    @Override
    public ServletContext getServletContext() {
        if (context == null) {
            context = config.getServletContext();
            if (context instanceof ApplicationContext) {
                context = ((ApplicationContext) context).getFacade();
            }
        }
        return context;
    }


    @Override
    public String getInitParameter(String name) {
        return config.getInitParameter(name);
    }


    @Override
    public Enumeration<String> getInitParameterNames() {
        return config.getInitParameterNames();
    }


}

Tomcat 服务器实现了ServletConfig 接口。

思考:
如果 Tomcat 服务器换成 了其他的服务器,比如换成是 Jetty 服务器,输出 ServletConfig 对象的时候,还是这个结果吗?

不一定是一样的结果了,包含的类名可能和Tomcat 不一样了,但是他们都实现了 ServletConfig 这个规范。

问题:ServletConfig对象是谁创建的,在什么时候创建的?

Tomcat 服务器(WEB服务器) 创建了ServletConfig 对象,在创建Servlet 对象的时候,同时创建ServletConfig 对象.

问题:ServletConfig 接口到底是干什么的? 有什么用?

Config 是 Configuration单词的缩写:n. 布局,构造;配置

  •  ServletConfig 对象翻译为: Servlet 对象的配置信息对象。
    
  •  一个Servlet对象就有一个配置信息对象:`web.xml <servlet></servlet>`
    
  •  两个Servlet 对象就有两个配置信息对象。
    

ServletConfig对象中创建包装了
web.xml <servlet></servlet> 标签配置信息
:
Tomcat 小喵咪解析web.xml 文件,将web.xml文件中
<servlet><init-param></init-param></servlet>
标签中的配置信息自动包装到ServletConfig 对象当中

如下:
注意:一个Servlet 对象就会有一个
<servlet></servlet>
配置信息标签

。如下是
AServlet对象的
xml 配置信息。

<--以上是 <servlet-name>ConfigTestServlet</servlet-name> <init-param></init-param>
<--是初始化参数,这个初始化参数信息被小喵咪封装到 ServletConfig 对象当中 -->
<servlet>
        <servlet-name>AServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
        <!-- 这里是可以配置一个Servlet对象的初始化信息的 注意:这里的配置对应的是上述<servlet-name>
        为AServlet-->
        <init-param>
            <param-name>Driver</param-name>
            <param-value>com.mysql.cj.jdbc.Driver</param-value>
        </init-param>
    
        <init-param>
            <param-name>url</param-name>
            <param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
        </init-param>
    
        <init-param>
            <param-name>user</param-name>
            <param-value>root</param-value>
        </init-param>
</servlet>

3.1 ServletConfig 当中常用的四个方法

public String getServletName();  // 获取该Servlet对象的名称
public String getInitParameter(String name); // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应  <param-value>root</param-value> 标签当中的 value 值
public Enumeration<String> getInitParameterNames();  // 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
public ServletContext getServletContext(); // 获取到ServletContext对象
//  以上的4个方法: 在自己的编写的Servlet表当中也可以使用this,/super去调用(这个Servlet继承了GenericServet)

3.1.2 通过ServletConfig 对象获取到 web.xml 配置文件

标签当中的配置信息

举例:获取到 web.xml 配置文件当中 :
<servlet><init-param></init-param></servlet>
标签中的配置信息。因为该配置信息是会被动包装到ServletConfig 对象当中

。这里我们分别获取到 AServlet 对象和 BServlet 对象当中的配置信息。

对应web.xml 信息如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <servlet>
        <servlet-name>AServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
        <!-- 这里是可以配置一个Servlet对象的初始化信息的 注意:这里的配置对应的是上述<servlet-name>
        为AServlet-->
        <init-param>
            <param-name>Driver</param-name>
            <param-value>com.mysql.cj.jdbc.Driver</param-value>
        </init-param>
        <init-param>
            <param-name>url</param-name>
            <param-value>jdbc:mysql://localhost:3306/dbtest9</param-value>
        </init-param>
        <init-param>
            <param-name>user</param-name>
            <param-value>root</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>AServlet</servlet-name>
        <url-pattern>/A</url-pattern>
    </servlet-mapping>


    <servlet>
        <servlet-name>BServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
        <init-param>
            <param-name>password</param-name>
            <param-value>root123</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>BServlet</servlet-name>
        <url-pattern>/B</url-pattern>
    </servlet-mapping>
</web-app>

获取BServlet 对象当中的 web.xml 标签


的配置信息

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class BServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到 GenericServlet 父类适配器当中的  ServletConfig  config成员属性
        ServletConfig servletConfig = super.getServletConfig();
        // 获取该Servlet对象的名称
        String ServletName = servletConfig.getServletName();
        writer.print(ServletName + "<br>");  // 输出 Servlet 的对象的名称

        String name = "password"; // 这里如果我们直接知道name 为 pawword 
        
        // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应  <param-value>root</param-value> 标签当中的 value 值
        String value = servletConfig.getInitParameter(name);  
        writer.print(name + "--->" + value);  // 页面输出
    }
}



在这里插入图片描述

获取AServlet 对象当中的 web.xml 标签


的配置信息

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class AServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到 GenericServlet 父类适配器当中的  ServletConfig  config成员属性
        ServletConfig servletConfig = super.getServletConfig();
        // 获取该Servlet对象的名称
        String ServletName = servletConfig.getServletName();
        writer.print(ServletName + "<br>");  // 输出 Servlet 的对象的名称

        // 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
        Enumeration<String> names = servletConfig.getInitParameterNames();

        while(names.hasMoreElements()) {  // 判断是否还有 元素,有返回 true,没有返回false。和集合当中的迭代器类似
            String name = names.nextElement(); // 取出name 值

            // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应  <param-value>root</param-value> 标签当中的 value 值
            String value = servletConfig.getInitParameter(name);
            writer.print(name + "--->" + value);  // 页面输出
            writer.print("<br>");  // 页面换行

        }

    }

}

在这里插入图片描述

3.1.3 获取到 ServletContext对象

方式一:
通过先获取到 ServletConfig 对象,再通过 ServletConfig 对象获取到 ServletContext 对象

使用 ServletConfig 当中的 public ServletContext getServletContext(); // 获取到ServletContext对象

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class BServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 获取到 GenericServlet父类适配器当中的 private transient ServletConfig config; 成员属性
        ServletConfig servletConfig = super.getServletConfig();
        // 获取到 servletContext 对象
        ServletContext servletContext = servletConfig.getServletContext();
        System.out.println("servletContext的值: " + servletContext);

    }
}



在这里插入图片描述

方式二:
直接通过 父类 GenericServlet 适配器当中的。this,super.getServletContext() 方法 也可以获取到ServletContext对象

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class BServlet extends GenericServlet {

    /**
     * 只需要重写我们常用的 service 父类中的抽象方法即可
     */
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        System.out.println("servletContext的值: " + servletContext);

    }
}



在这里插入图片描述

4. ServletContext

在这里插入图片描述

  • ServletContext
    是接口,是Servlet规范中的一员。Tomcat服务器(WEB服务器) 实现了ServletContext 接口。
  • ServletContext
    是一个接口,Tomcat 服务器对 ServletContext 接口进行了实现。ServletContext 对象的创建也是 Tomcat 服务器来完成的。启动
    webapp
    的时候创建的。
  • ServletContext
    对象在服务器启动阶段创建的,在服务器关闭的时候销毁。这就是 ServletContext 对象的生命周期。
  • ServletContext 对象的环境对象,(Servlet对象的上下文对象)。

重点:

一个ServletContext 表示的就是一个 webapp 当中的
web.xml
文件当中下配置信息。而一个 webapp 一般只有一个 web.xml 文件。所以只要在同一个 webapp 当中,只要在同一个应用当中,所以的Servlet 对象都是共享同一个
ServletContext对象的
。举例:Tomcat 服务器中有一个 webapps ,这个 webapps 下存放了多个 webapp 。假设有 100 个 webapp ,那么就会有 100 个 ServeltContext 对象,因为一个 webapp 对应一个 web.xml 文件。总之,一个应用,一个 webapp 只有一个 web.xml文件,则只有一个 ServletContext 对象,所有该应用下/webapp下的 Servlet 对象共享。

如下:我们一个 webapp 下有两个 Servlet 对象,分别为 BServlet 和 AServlet 对象,但是他们两个的同时在同一个 webapp下的,只有一个 web.xml 文件。所以这个两个 Servlet 对象共享 一个 ServletContext 对象:

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class BServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        writer.print("BServlet 下的 servletContext的值: " + servletContext);

    }
}



package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class AServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        writer.print("AServlet 下的 servletContext的值: " + servletContext);

    }

}

在这里插入图片描述

ServletContext对应显示生活中的什么例子呢?

一个教室里有多个学生,那么每一个学生就是一个Servlet,这些学生都在同一个教室当中,那么我们可以把这个教室叫做ServletContext对象。那么也就是说放在这个ServletContext对象(环境)当中的数据,在同一个教室当中,物品都是共享的。比如:教室中有一个空调,所有的学生都可以操作。可见,空调是共享的。因为空调放在教室当中。教室就是ServletContext对象。

4.1 ServletContext 获取web.xml 配置文件当中的 <context-param>标签当中的配置信息

我们想要获取到web.xml 配置文件大当中的<context-param>标签当中编写的的配置信息,需要使用到如下两个方法:

public String getInitParameter(String name); // 通过初始化参数的name获取value
public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name
<!--以上两个方法是ServletContext对象的方法,这个方法获取的是什么信息?是以下的配置信息-->
<context-param>
    <param-name>pageSize</param-name>
    <param-value>10</param-value>
</context-param>
<context-param>
    <param-name>startIndex</param-name>
    <param-value>0</param-value>
</context-param>
<!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。-->
<!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。-->

举例:

如下是 Servle 对象通过,ServletContext对象的上述两个方法,获取到

标签当中编写的配置信息:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--如下是: context-param 的编写的配置信息,该配置信息,被所有的Servlet共享
可以使用ServletContext对象获取到 -->
    <context-param>
        <param-name>pageSize</param-name>
        <param-value>10</param-value>
    </context-param>

    <context-param>
        <param-name>startIndex</param-name>
        <param-value>0</param-value>
    </context-param>

    <servlet>
        <servlet-name>AServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AServlet</servlet-name>
        <url-pattern>/A</url-pattern>
    </servlet-mapping>
    
     <servlet>
        <servlet-name>BServlet</servlet-name>
        <servlet-class>com.RainbowSea.servlet.BServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>BServlet</servlet-name>
        <url-pattern>/B</url-pattern>
    </servlet-mapping>
</web-app>
package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class AServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        writer.print("AServlet 下的 servletContext的值: " + servletContext + "<br>");

        //  一次性获取到 <context-param> </context-param>标签当中所有的 name 值
        Enumeration<String> names = servletContext.getInitParameterNames();

        while(names.hasMoreElements()) { // 判断该集合是否还有元素,有返回true,没有返回false
            // 获取到元素当中的 name值
            String name = names.nextElement();
            // 通过对象 <context-param> </context-param>标签下的name获取到对应的value值
            String value = servletContext.getInitParameter(name);
            writer.print(name + "--->" + value);
            writer.print("<br>");
        }

    }

}

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class BServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        writer.print("BServlet 下的 servletContext的值: " + servletContext + "<br>");

        //  一次性获取到 <context-param> </context-param>标签当中所有的 name 值
        Enumeration<String> names = servletContext.getInitParameterNames();

        while(names.hasMoreElements()) { // 判断该集合是否还有元素,有返回true,没有返回false
            // 获取到元素当中的 name值
            String name = names.nextElement();
            // 通过对象 <context-param> </context-param>标签下的name获取到对应的value值
            String value = servletContext.getInitParameter(name);
            writer.print(name + "--->" + value);
            writer.print("<br>");
        }

    }
}



在这里插入图片描述

在这里插入图片描述

4.2 ServletContext 获取文件的绝对路径

获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径。 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,会给你的另外取起一个什么名字。所以我们需要动态获取。

public String getContextPath(); // 获取到该项目在 url 上输入的项目名
public String getRealPath(String path);// 获取文件的绝对路径(真实路径)
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
String realPath = servletContext.getRealPath("/index.html");

举例:

在这里插入图片描述

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class AServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        // 获取到这个项目名。
        String contextPath = servletContext.getContextPath();
        writer.print(contextPath + "<br>");
        // 获取到这个 index.html 文件的绝对路径
        // "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
        // 就算你不写也是,默认从 web目录下开始找的。
        String realPath = servletContext.getRealPath("/index.html");
        writer.print(realPath);


    }

}

在这里插入图片描述

注意:如果想要获取到web目录下面的子目录下的,文件的绝对路径的话,需要写明该文件是在 web目录下的哪个子目录。不然无法找到的
。因为
getRealPath()
方法是从 web目录下开始寻找的。

在这里插入图片描述

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class AServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        // 获取到这个项目名。
        String contextPath = servletContext.getContextPath();
        writer.print(contextPath + "<br>");
        // 获取到这个 index.html 文件的绝对路径
        // "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
        // 就算你不写也是,默认从 web目录下开始找的。
        // 如果是web目录下的子目录的文件,需要写明对应的子目录
        String realPath = servletContext.getRealPath("/test/index.html");
        writer.print(realPath);


    }

}

在这里插入图片描述

4.3 ServletContext对象"应用域" 存放数据量小,不易修改的数据信息

ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)
如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中存储起来。存储起来后,可以被其他的 Servlet 对象获取到。

为什么是所有用户共享的数据? 不是共享的没有意义。

因为ServletContext这个对象只有一个。只有共享的数据放进去才有意义。

为什么数据量要小?

因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。

为什么这些共享数据很少的修改,或者说几乎不修改?

所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的。

数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率。

对于ServletContext对象应用域,的存,取,删的方法如下:

// 存(怎么向ServletContext应用域中存数据)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取(怎么从ServletContext应用域中取数据)
public Object getAttribute(String name); // Object v = map.get(k)
// 删(怎么删除ServletContext应用域中的数据)
public void removeAttribute(String name); // map.remove(k)

举例:如下我们定义了一个 User 类,将该 User类的实例化对象存储到 ServletContext 对象当中去,并在不同的Servlet 对象当中取出来。

package com.RainbowSea.servlet;

public class User {
    private String name;
    private String password;
    
    public User() {
        
    }
    
    public User(String name,String password) {
        this.name = name;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

AServlet 对象,存,取

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


public class AServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();

        // 创建一个 User 实例对象
        User user = new User("Tom","123456");

        // 存
        servletContext.setAttribute("userObj",user); // map<K,V>
        // 取 注意参数是我们 ,setAttribute设置的 K 值
        //Object userObj = servletContext.getAttribute("userObj");
        // 因为我们这里知道我们存储的是什么类型的数据所以可以直接强制类型转换
        User userObj = (User) servletContext.getAttribute("userObj");

        writer.print(userObj); // 浏览器页面输出

    }

}

BServlet 对象的取

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class BServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        // 设置在浏览器页面显示的格式类型,必须在输出前设置
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();

        // 获取到GenericServlet父类适配器当中的 servletContext 对象
        ServletContext servletContext = super.getServletContext();
        // 取 注意参数是我们 ,setAttribute设置的 K 值
        //Object userObj = servletContext.getAttribute("userObj");
        // 因为我们这里知道我们存储的是什么类型的数据所以可以直接强制类型转换
        User userObj = (User) servletContext.getAttribute("userObj");

        writer.print(userObj); // 浏览器页面输出
    }
}



两者浏览器显示的结果:注意要先访问 AServlet (先将数据存储到 ServletContext对象当去),不然BServlet 访问的话就是 null了

在这里插入图片描述

4.4 ServletContext 日志信息

我们可以通过 ServletContext 生成日志信息:

// 通过ServletContext对象也是可以记录日志的
public void log(String message);
public void log(String message, Throwable t);
// 这些日志信息记录到哪里了?
// localhost.2021-11-05.log
// Tomcat服务器的logs目录下都有哪些日志文件?
//catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
//localhost.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
//localhost_access_log.2021-11-05.txt 访问日志

举例:

package com.RainbowSea.servlet;


import javax.servlet.GenericServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class BServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = super.getServletContext();

        servletContext.log("你好世界");  // 添加日志信息

        // 达到某个条件,计入日志信息:异常
        int age = 17;
        if( age < 17) {
            servletContext.log("对不起,您未成年,请绕行",new RuntimeException("小屁孩,快走开,不适合你"));
        }
    }
}



在这里插入图片描述

在这里插入图片描述

5. 总结:

  1. Servlet 对象的生命周期:
    1. 无参构造器
      :只会执行调用一次。
    2. init()
      方法:init 被翻译为初始化,init ()方法只会被执行一次;基本上和 Servlet构造器的调用同时执行;在Servlet 对象第一次被创建只会执行。init 方法通常是完成初始化操作的。
    3. service()
      方法:
      service() 方法是处理用户请求的核心方法。
      只要用户发送一次请求,service 方法必然会执行一次;发送100次请求,service方法执行100次。
    4. destroy()
      方法:destroy 方法中可以编写销毁前的准备;比如:服务器关闭的时候,AServelt 对象开启了一些资源,这些资源可能是流,也可能是数据库连接;那么关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码旧可以写到destroy()。
  2. GenericServlet 抽象类适配器思想以及模板方法设计思想
  3. ServletConfig对象中创建包装了
    web.xml <servlet></servlet> 标签配置信息
    :
    Tomcat 小喵咪解析web.xml 文件,将web.xml文件中
    <servlet><init-param></init-param></servlet>
    标签中的配置信息自动包装到ServletConfig 对象当中

    。这个是 :一个 Servlet 对象就一 个 ServletConfig 对象,有 100 个 Servlet 对象就有 100 个 ServletConfig 对象。
public String getServletName();  // 获取该Servlet对象的名称
public String getInitParameter(String name); // 通过 <param-name>user</param-name> 标签当中的name 值获取到对应  <param-value>root</param-value> 标签当中的 value 值
public Enumeration<String> getInitParameterNames();  // 一次性获取到该Servlet对象<init-param>标签当中配置信息的 name 值
public ServletContext getServletContext(); // 获取到ServletContext对象
//  以上的4个方法: 在自己的编写的Servlet表当中也可以使用this,/super去调用(这个Servlet继承了GenericServet)
  1. ServletContext 获取web.xml 配置文件当中的 <context-param>标签当中的配置信息。一个 webapp 就有一个ServleContext 对象,因为一个 webapp 就只有一个 web.xml 配置文件,所有的 Servlet 对象共享 一个 ServletContest 对象。有 100 个 webapp 就有 100 个 web.xml配置文件,就有 100 个 ServletContext 对象。简单的来说 一个 ServletContext 对象就对应着一个 web.xml 配置文件。
  2. ServletContext 获取文件的绝对路径。
public String getContextPath(); // 获取到该项目在 url 上输入的项目名
public String getRealPath(String path);// 获取文件的绝对路径(真实路径)
// 获取到这个 index.html 文件的绝对路径
// "/" 表示的是 web 目录,该方法默认是从web目录下开始找的。
// 就算你不写也是,默认从 web目录下开始找的。
String realPath = servletContext.getRealPath("/index.html");
  1. ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中存储起来。存储起来后,可以被其他的 Servlet 对象获取到。
// 存(怎么向ServletContext应用域中存数据)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取(怎么从ServletContext应用域中取数据)
public Object getAttribute(String name); // Object v = map.get(k)
// 删(怎么删除ServletContext应用域中的数据)
public void removeAttribute(String name); // map.remove(k)
  1. Servlet 接口的结构图

在这里插入图片描述

jakarta.servlet.Servlet(接口)【爷爷】
jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
我们以后编写的Servlet要继承HttpServlet类。

6. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后会有期!!!

在这里插入图片描述

前一段时间因为要将一些生活照片传给别人,由于是相机拍的单张图片普遍在10M以上,于是就想着把这些大尺寸图片简单进行下压缩

上百度找了一圈图片压缩软件,发现要不是不好用,要不是就得付费批量压缩又或者付费去除水印。功能付费我支持,只是一张图片收费一次,图片多了,感觉不合适吧。本着自己动手丰衣足食原则。花了两天时间简单搞了一个压缩工具网站。

目前由于功能比较简单,没有进行前后端拆分,主要技术点:SpringBoot、WebSocket、webjars、Vue、Element-UI、axios、jszip,核心压缩库是
Thumbnailator
提供压缩支持。源代码已经在
码云
star
公开。欢迎各位大佬批评。

如果熟悉Docker容器,

第一步:拉取镜像

docker pull songyinzeng/image-compress

第二步:运行容器

docker run -d -p 9999:9999 --name image-compress songyinzeng/image-compress

第三步:查看服务界面 http://ip:9999

压缩工具腾讯云
在线地址