IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Servlet原理解析 -> 正文阅读

[系统运维]Servlet原理解析

1 Servlet介绍

1.1 简介

Servlet是一种运行于服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面,它担当客户请求与服务器响应的中间层 ,有特殊的技术规范:必须继承某个特定父类;必须配置之后才能执行;有特定的生命周期
Servlet是在Java代码中嵌入页面代码,JSP是在页面代码中嵌入Java代码
JSP不能执行,必须转译成Servlet并编译成class后才能执行,Servlet是学好JSP的基础,能了解JSP的底层运作方式

1.2 Servlet生命周期

1.2.1 生命周期

每个servlet实例的生命周期中只有三种类型的事情,分别对应于由servlet容器所调用的三个方法:

  • init() 初始化时期:
    servlet第一次被装载的时候由servlet容器调用init(),且只调用一次,默认情况下调用超类的init()方法。
  • service()运行时期:
    接受客户请求并决定调用何种doXXX方法,并将处理结果返回到客户端。
  • destroy()结束时期:
    为可选方法,释放不用的servlet实例所占内存和资源。

Servlet的生命周期可以归纳为以下几步:

  1. Servlet创建一个Servlet实例;
  2. Servlet调用Servletinit()方法;
  3. 客户发送请求到Servlet
  4. Servlet创建一个请求对象和一个响应对象
  5. Servlet调用service()方法,传递请求和响应对象作为参数;
  6. service()方法获得请求对象的参数,处理请求,访问其他资源。
  7. service()方法通过响应对象将结果传递给Server,最终到达客户端。
  8. 对于更多的相同客户端请求, Servlet将创建新的请求和响应对象,仍然激活此Servletservice方法,传递新的参数对象,不需在初始化一次。
  9. Servlet不再需要Servlet时(关闭时)调用destory()方法。

1.2.2 servlet配置

<servlet>
	<servlet-name>Servlet的名称</servlet-name>
    <servlet-class>该servlet类的路径</servlet-class>
    <init-param>
          <param-name>参数名</param-name>
          <param-value>参数值</param-value>
     </init-param>
     <jsp-file>/路径/XXX.JSP </jsp-file>
	<load-on-startup>表示web站台被启动时,自动加载该servlet的优先级别,越小越优先加载</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>对应的servlet-name名称</servlet-name>
    <url-pattern>在项目运行时用以访问的URL </url-pattern>
</servlet-mapping>

url-pattern 取值:1、可以与具体的某个Servlet对应。2、也可以用/* 来指定所有的页面,更多关于servlet路径配置可以点此连接查看

1.2.3 servlet中load-on-startup作用

像上面的servlet配置中的标签<load-on-startup>,注意到它里面包含了这段配置:<load-on-startup>1</load-on-startup>,那么这个配置有什么作用呢?

  1. load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)。
  2. 它的值必须是一个整数,表示servlet应该被载入的顺序
  3. 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
  4. 当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载。
  5. 正数的值越小,该servlet优先级越高,应用启动时就越先加载。
  6. 当值相同时,容器就会自己选择顺序来加载。

所以,<load-on-startup>x</load-on-startup>,中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间

1.3 Servlet接口

1.3.1 HttpServlet

HttpServlet类,httpServlet中各种接受请求处理的方法

方法名描述
doGet()处理http的get请求
doPost()处理http的post请求,主要用于发送HTML文本中FORM的内容
doHead()用于处理HEADER请求
doPut()处理http的put请求,模仿 ftp发送
doDelete()处理http的delete请求
doOptions()该操作自动决定支持什么HTTP方法
doTrace()处理HTTP的trace请求

发送请求的三种基本方式
地址栏直接键入地址(默认get方式) 例如:在地址栏输入:http://127.0.0.1/jsp2/index.jsp?a=2
使用表单提交(默认get方式)只有使用表单的method=post才是post提交
点击超链接(默认get方式)

1.3.1.1 service,doGet,doPost区别

HttpServlet 里的三个方法service(HttpServletRequest req, HttpServletResponse resp)doGet(HttpServletRequest req, HttpServletResponse resp)doPost(HttpServletRequest req, HttpServletResponse res)的区别和联系

servlet中默认情况下,无论是get还是post提交过来都会经过service()方法来处理,然后转向到doGet或是doPost方法,可以查看HttpServlet 类的service方法

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

从上面可以看出 这里的service是用来转向的,但是如果在自己的servlet类中覆盖了service方法,比如说你的service是下面这样的,那么这时service就不是用来转向的,而是用来处理业务的,现在不论客户端是用pos还是get来请求此servlet,都会执行service方法也只能执行servlet方法,不会去执行doPost或是doGet方法

public void service(ServletRequest req, ServletResponse res)    
                      throws ServletException, IOException {    
         res.getOutputStream().print(    
          "image is <img src='images/downcoin.gif'></img><br>");    
} 

protected void doGet(HttpServletRequest request,    
   HttpServletResponse response) throws ServletException, IOException {    
   doPost(request,response);    
    
}
protected void doPost(HttpServletRequest request,    
    HttpServletResponse response) throws ServletException, IOException {    
    ServletOutputStream out=response.getOutputStream();    
    String[] args=(String[])request.getParameterValues("fruit");    
   for(int i=0;i<args.length;i++){    
    out.print(args[i]+"<br>");    
   }          
}   

所以,我们在写servlet的时候,一般都是重写doGetdoPost方法,不会管service方法

1.3.2 表单提交中get和post方式区别

1.3.2.1 表单中区别

表单提交中get和post方式的区别有5点

  1. get是从服务器上获取数据,post是向服务器传送数据。
  2. get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一 一对应,在URL中可以看到。post是通过HTTPpost机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
  3. 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
  4. get传送的数据量较小,不能大于2KBpost传送的数据量较大,一般被默认为不受限制
  5. get安全性非常低,post安全性较高。

1.3.2.2 http中区别

HTTP请求:get与post方法的区别:
HTTP定义了与服务器交互的不同方法,最基本的方法是 getpost。事实上 get 适用于多数请求,而保留 post仅用于更新站点。根据 HTTP规范,get 用于信息获取,而且应该是安全的和幂等的。所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,get 请求一般不应产生副作用。幂等的意味着对同一URL的多个请求应该返回同样的结果。完整的定义并不像看起来那样严格。从根本上讲,其目标是当用户打开一个链接时,可以确信从自身的角度来看没有改变资源。比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。反之亦然。但 post请求就不那么轻松了。post表示可能改变服务器上的资源的请求。

FORM提交的时候,如果不指定Method,则默认为get请求,Form中提交的数据将会附加在url之后,以?分开与url分开。字母数字字符原样发送,但空格转换为+号,其它符号转换为%XX, 其中XX为该符号以16进制表示的ASCII(或ISOLatin-1)值。get请求请提交的数据放置在HTTP请求协议头中,而post提交的数据则 放在实体数据中;
get方式提交的数据最多只能有1024字节,而post则没有此限制
在表单里使用postget有什么区别
Form里面,可以使用post也可以使用get。它们都是method的合法取值。但是,postget方法在使用上至少有两点不同:

  1. get方法通过URL请求来传递用户的输入。post方法通过另外的形式。
  2. get方式的提交需要用Request.QueryString来取得变量的值,而post方式提交时,必须通过Request.Form来访问提交的内容。

2 Servlet原理

2.1 工作原理

2.1.1 问题引入

Servlet是如何工作的?Servlet 如何实例化、共享变量、并进行多线程处理?
假设有一个运行了大量 Servletweb 服务器。通过 Servlet 之间传输信息得到 Servlet 上下文,并设置 session 变量。现在,如果有两名或更多使用者向这个服务发送请求,接下来 session 变量会发生什么变化?究竟是所有用户都是用共同的变量?还是不同的用户使用的变量都不一样?如果是后者,服务器如何区分不同用户?
如果有n名用户访问一个特定的 Servlet,那么该 Servlet 是仅在第一个用户首次访问的时候实例化,还是分别为每个用户实例化?

2.1.2 Servlet容器启动

Servlet 容器(比如 Apache Tomcat)启动后,会部署和加载所有 web 应用。当web应用被加载,Servlet 容器会创建一次ServletContext,然后将其保存在服务器的内存中。
web应用的web.xml被解析,找到其中所有 servletfilterListener@WebServlet@WebFilter@WebListener 注解的内容,创建一次并保存到服务器的内存中。对于所有过滤器会立即调用init()。当 Servlet 容器停止,将卸载所有 web 应用,调用所有初始化的 Servlet 和过滤器的destroy() 方法,最后回收 ServletContext 和所有 ServletFilterListener实例。
Servlet 配置的load-on-startup或者 @WebServlet(loadOnStartup) 设置了一个大于 0 的值,则同样会在启动的时候立即调用 init() 方法。
load-on-startup中的值表示那些 Servlet 会以相同顺序初始化。如果配置的值相同,会遵循 web.xml 中指定的顺序或 @WebServlet类加载的顺序。另外,如果不设置 load-on-startup 值,init() 方法只在第一次HTTP请求命中问题中的Servlet时才被调用

2.1.3 HttpServletRequest 与 HttpServletResponse

Servlet 容器附加在一个 web 服务上,这个 web 服务会在某个端口号上监听 HTTP 请求,在开发环境中这个端口通常为 8080,生产环境中通常为 80。当客户端(web 浏览器)发送了一个 HTTP 请求,Servlet 容器会创建新的 HttpServletRequestHttpServletResponse 对象,传递给已创建好并且请求的 URL 匹配 url-patternFilterServlet 实例中的方法,所有工作都在同一个线程中处理。
request 对象可以访问所有该 HTTP 请求中的信息,例如 request headerrequest bodyresponse 对象提供需要的控制和发送 HTTP 响应方法,例如设置 headerbody(通常会带有 JSP 文件中的 HTML 内容)。提交并完成HTTP 响应后,将回收 requestresponse 对象。

2.1.4 HttpSession

当用户第一次访问该 web 应用时,会通过 request.getSession() 第一次获得 HttpSession。之后 Servlet 容器将会创建 HttpSession,生成一个唯一的 ID(可以通过 session.getId() 获取)并储存在服务器内存中。然后 Servlet 容器在该次 HTTP 响应的 Set-Cookie 头部设置一个 Cookie,以JSESSIONID 作为 Cookie 名字,那个唯一的 session ID 作为 Cookie 的值。
按照 HTTP cookie 规则(正常 web 浏览器和 web 服务端必须遵循的标准),当 cookie 有效时,要求客户端(浏览器)在后续请求的 Cookie 头中返回这个 cookie
使用浏览器内置的HTTP流量监控器,Servlet容器将会确定每个进入的 HTTP 请求的 Cookie 头中是否存在名为JSESSIONIDcookie,然后用它的值(session ID)从服务端内存中找到关联的 HttpSession
可以在 web.xml 中设置 session-timeout ,默认值为 30 分钟。超时到达之前 HttpSession 会一直存活。所以当客户端不再访问该 web 应用超过 30 分钟后,Servlet 容器就会回收这个 session。后续每个请求,即使指定 cookie 名称也不能再访问到相同的 sessionServlet 容器会创建一个新的 Cookie
另一方面,客户端上的 session cookie 有一个默认存活时间,该事件和该浏览器实例运行时间一样长。所以,当客户端关闭该浏览器实例(所有标签和窗口)后,这个 session 就会被客户端回收。新浏览器实例不再发送与该 session 关联的 cookie。一个新的 request.getSession() 将会返回新的HttpSession 并设置一个拥有新 session IDcookie

2.1.5 ServletContext

ServletContextweb 应用存活时间一样长。它被所有 session 中的所有请求共享。
只要客户端一直与相同浏览器实例的web应用交互并且没有超时HttpSession就会存在。
HttpServletRequestHttpServletResponse 的存活时间为客户端发送完成到完整的响应(web页面)到达的这段时间。不会被其他地方共享。
所有 ServletFilterListener 对象在web 应用运行时都是活跃的。它们被所有 session 中的请求共享。
设置在 HttpServletRequestHttpServletResponseHttpSession 中的所有属性在问题中的对象存活时都会一直保持存活。

2.1.6 线程安全

即便如此,最关心的可能是线程安全。现在应该学习到 Servletfilter 被所有请求共享。那是 Java 的一个优点,使得多个不同线程(读取 HTTP 请求)可以使用同一个实例。否则为每个请求重新创建线程的开销实在过于昂贵。
但应该也意识到永远不要将任何 requestsession 域中的数据赋值给 servletfilter 的实例变量。它将会被所有其他 session 中的所有请求共享。那是非线程安全的
下面的示例对这种情况进行了展示:

public class ExampleServlet extends HttpServlet {
 
    private Object thisIsNOTThreadSafe;
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object thisIsThreadSafe;
 
        thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests!
        thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe.
    }
}

由于当客户端第一次请求某一个JSP文件时,服务端把该JSP编译成一个CLASS文件,并创建一个该类的实例,然后创建一个线程处理CLIENT端的请求。如果有多个客户端同时请求该JSP文件,则服务端会创建多个线程。每个客户端请求对应一个线程。以多线程方式执行可大大降低对系统的资源需求,提高系统的并发量及响应时间.

JSP中可能用的的变量说明如下:

  • 实例变量: 实例变量是在堆中分配的,并被属于该实例的所有线程共享,所以不是线程安全的. JSP系统提供的8个类变量:JSP中用到的OUT,REQUEST,RESPONSE,CONFIG,PAGE,PAGECONXT是线程安全的(因为每个线程对应的request,respone对象都是不一样的,不存在共享问题), SESSION,APPLICATION在整个系统内被使用,所以不是线程安全的
    ServletContext:(线程是不安全的) ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。所以在Servlet上下文中尽可能少量保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。
    HttpSession:(线程是不安全的) HttpSession对象在用户会话期间存在,只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。 当用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,会出现多次请求,需要多个工作线程来处理请求,可能造成同时多线程读写属性。这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。 ServletRequest:(线程是安全的) 对于每一个请求,由一个工作线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
    注意:ServletRequest对象在service方法的范围内是有效的,不要试图在service方法结束后仍然保存请求对象的引用
  • 局部变量: 局部变量在堆栈中分配,因为每个线程都有它自己的堆栈空间,所以是线程安全的.
  • 静态类: 静态类不用被实例化,就可直接使用,也不是线程安全的.
  • 外部资源: 在程序中可能会有多个线程或进程同时操作同一个资源(如:多个线程或进程同时对一个文件进行写操作).此时也要注意同步问题. 使它以单线程方式执行,这时,仍然只有一个实例,所有客户端的请求以串行方式执行。这样会降低系统的性能

2.2 Servlet单例多线程

2.2.1 Servlet如何处理多个请求访问

Servlet容器默认是采用单实例多线程的方式处理多个请求的:

  1. web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
  2. 容器初始化Servlet主要就是读取配置文件(例如tomcat,可以通过servlet.xml<Connector>设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。
  3. 当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread) 调度它管理下线程池中等待执行的线程(Worker Thread)给请求者;
  4. 线程执行Servletservice方法;
  5. 请求结束,放回线程池,等待被调用; (注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,照成数据的不一致,因此产生线程安全问题)

从上面可以看出:

  1. Servlet单实例,减少了产生servlet的开销;
  2. 通过线程池来响应多个请求,提高了请求的响应时间;
  3. Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servletservice方法将在多线程中并发的执行;
  4. 每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求; Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。

2.2.2 Servlet容器如何采用单实例多线程的方式来处理请求

Java的内存模型JMM(Java Memory Model) JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图所示的模型。

工作者线程Work Thread:执行代码的一组线程。
调度线程Dispatcher Thread:每个线程都具有分配给它的线程优先级,线程是根据优先级调度执行的。
Servlet采用多线程来处理多个请求同时访问。servlet依赖于一个线程池来服务请求。线程池实际上是一系列的工作者线程集合。Servlet使用一个调度线程来管理工作者线程。 当容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servletservice方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servletservice()方法将在多线程中并发执行。 Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat可以在server.xml中通过<Connector>元素设置线程池中线程的数目。
就实现来说,调度者线程类所担负的责任如其名字,该类的责任是调度线程,只需要利用自己的属性完成自己的责任。所以该类是承担了责任的,并且该类的责任又集中到唯一的单体对象中。而其他对象又依赖于该特定对象所承担的责任,我们就需要得到该特定对象。那该类就是一个单例模式的实现了

2.2.3 Servlet多实例

服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。此时,局部变量(字段)也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。 SingleThreadModel接口在servlet规范中已经被废弃了。

Servlet并非只是单例的. 当container开始启动,或是客户端发出请求服务时,Container会按照容器的配置负责加载和实例化一个Servlet(也可以配置为多个,不过一般不这么干).不过一般来说一个servlet只会有一个实例。

  1. Struts2Action是原型,非单实例的;会对每一个请求,产生一个Action的实例来处理。
  2. Struts1Action,SpringIoc容器管理的bean默认是单实例的. Struts1 Action是单实例的,spring mvccontroller也是如此。因此开发时要求必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。 Spring的Ioc容器管理的bean 默认是单实例的。 Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)。 当Spring管理Struts2Action时,bean默认是单实例的,可以通过配置参数将其设置为原型。(scope=prototype )

2.2.4 容器概念理解

Servlet容器,Web容器,应用服务器
Servlet容器的主要任务就是管理Servlet的生命周期;
Web容器也称之为web服务器,主要任务就是管理和部署web应用的; 应用服务器的功能非常强大,不仅可以管理和部署web应用,也可以部署EJB应用,实现容器管理的事务等等。。。
Web服务器就是跟基于HTTP的请求打交道,而EJB容器更多是跟数据库,事务管理等服务接口交互,所以应用服务器的功能是很多的。 常见的web服务器就是Tomcat,但Tomcat同样也是Servlet服务器; 常见的应用服务器有WebLogic,WebSphere,但都是收费的; 没有Servlet容器,可以用Web容器直接访问静态Html页面,比如安装了apache等;如果需要显示Jsp/Servlet,就需要安装一个Servlet容器;但是光有servlet容器也是不够的,它需要被解析为html显示,所以仍需要一个web容器;所以,我们常把web容器和Servlet容器视为一体,因为他们两个容器都有对方的功能实现了,都没有独立的存在了,比如tomcat)

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 22:36:33  更:2022-03-11 22:37:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 2:55:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码