Java Servlet

Tomcat调优

一、Servlet 简介

Servlet是sun公司提供的一门用于开发动态web资源的技术。Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1、编写一个Java类,实现servlet接口。
2、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常也把实现了servlet接口的Java程序,称之为Servlet。

Servlet 是 Java Web 开发的起点,几乎所有的 Java Web 框架都是基于 Servlet 的封装,其中最主要的就是 Servlet 和 Filter 接口。从 API 可以看出,Servlet 本质是一套接口(Interface)。那么接口的本质是什么?是规范、是协议,所以常说要面向接口编程,而不是面向实现。接口是连接 Servlet 和 Servlet 容器(Tomcat、Jetty 等)的关键。

Servlet 接口定义了一套处理网络请求的规范,所有实现 Servlet 的类都需要实现它的五个方法,其中最主要的是两个生命周期方法 init 和 destroy,还有一个处理请求的 service 方法。也就是说,所有实现 Servlet 接口的类,或者说,所有想要处理网络请求的类,都需要回答这三个问题:

• 初始化时要做什么
• 接收到请求时要做什么
• 销毁时要做什么

这是 Java EE 给的一种规范!看一下 Servlet 的接口定义,即 Servlet 和 Servlet 容器的规范。开发者最需要关注的就是 service 方法,在这里处理请求。

package javax.servlet;
import java.io.IOException;

public interface Servlet {
   public void init(ServletConfig config) throws ServletException;   
   public ServletConfig getServletConfig();
   public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
   public String getServletInfo();
   public void destroy();
}

二、Servlet 如何工作

Servlet 想要工作离不开 Servlet 容器,比如最常用的 Tomcat。它监听了某个端口,http 请求过来后,容器根据 url 等信息,确定要将请求交给哪个 Servlet 去处理,然后调用 Servlet 的 service 方法,service 方法返回一个 response 对象,容器将 respose 对象解析之后封装成一个 http 响应返回客户端。
Servlet 工作原理

具体运行过程:
Servlet程序由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步;否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

三、Servlet 体系结构

Servlet 规范类如下:

• Servlet
• ServletContext
• ServletConfig
• ServletRequest
• ServletResponse

Servlet 运行模式是典型的「握手型」交互。举个买早点的例子:
找到一家早点铺(SerletContext 开始),看到牌面上写着可以加肉松(ServletConfig),就告诉老板要加肉松的煎饼果子,拿出手机扫码支付了五块钱(ServletRequest)。老板摊好饼子,然后递给我(ServletResponse),我就离开了(ServletContext 结束)。

为什么强调 HttpServletRequest 和 HttpServletResponse 这两个接口?因为 Web 开发是离不开 HTTP 协议的,而 Servlet 规范其实就是对 HTTP 协议做面向对象的封装,HTTP协议中的请求和响应就是对应了 HttpServletRequest 和 HttpServletResponse 这两个接口。

可以通过 HttpServletRequest 来获取所有请求相关的信息,包括 URI、Cookie、Header、请求参数等等,别无它路。因此当使用某个框架时,想获取 HTTP 请求的相关信息,只要拿到 HttpServletRequest 实例即可。而 HttpServletResponse接口是用来生产 HTTP 回应,包含 Cookie、Header 以及回应的内容等等。

四、Servlet 实践

写一个简单的 Servlet,在 doGet 方法打印所有请求的信息:

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.Enumeration;

public class HelloWorld extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        System.out.println("init helloworld: " + config);
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("********doGet********");
        resp.setContentType("text/html;charset=UTF-8");
        resp.setCharacterEncoding("UTF-8");
        System.out.println("method: " + req.getMethod());
        System.out.println("charsetEncoding: " + req.getCharacterEncoding());
        System.out.println("contentType: " + req.getContentType());
        System.out.println("contentLength: " + req.getContentLength());
        System.out.println("requestUrl: " + req.getRequestURL());
        System.out.println("servletPath: " + req.getServletPath());
        System.out.println("contextPath: " + req.getContextPath());
        System.out.println("requestUri: " + req.getRequestURI());
        System.out.println("locale: " + req.getLocale());
        System.out.println("authType: " + req.getAuthType());
        System.out.println("scheme: " + req.getScheme());
        System.out.println("protocol: " + req.getProtocol());
        System.out.println("serverPort: " + req.getServerPort());
        System.out.println("remoteHost: " + req.getRemoteHost());
        System.out.println("remoteAddr: " + req.getRemoteAddr());
        System.out.println("remoteUser: " + req.getRemoteUser());
        System.out.println("requestedSessionId: " + req.getRequestedSessionId());
        System.out.println("pathInfo: " + req.getPathInfo());
        System.out.println("isSecure: " + req.isSecure());
        System.out.println("servletName: " + req.getServerName());
        System.out.println("pathTranslated: " + req.getPathTranslated());
        System.out.println("++headers++");
        Enumeration headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String paramName = (String) headerNames.nextElement();
            String paramValue = req.getHeader(paramName);
            System.out.println("name: " + paramName + ", value: " + paramValue);
        }
        System.out.println("--headers--");
        System.out.println("++parameters++");
        Enumeration<String> parameterNames = req.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = parameterNames.nextElement();
            String value = req.getParameter(name);
            System.out.println("name: " + name + ", value: " + value);
        }
        System.out.println("--parameters--");
        System.out.println("++attributes++");
        Enumeration<String> attributeNames = req.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String name = attributeNames.nextElement();
            Object value = req.getAttribute(name);
            System.out.println("name: " + name + ", value: " + value);
        }
        System.out.println("--attributes--");
        System.out.println("++cookies++");
        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            System.out.println("name: " + cookie.getName() + ", value: " + 
                            URLDecoder.decode(cookie.getValue(), "utf-8"));
        }
        System.out.println("--cookies--");
        PrintWriter writer = resp.getWriter();
        try {
            writer.println("<h1>Hello world!</h1>");
            writer.flush();
        } finally {
            writer.close();
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("--------doPost--------");
        doGet(req, resp);
    }
    @Override
    public void destroy() {
        super.destroy();
        System.out.println("destroy helloworld");
    }
}

在 web.xml 配置上面的 Servlet。

<?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>HelloWorld</servlet-name>
  <servlet-class>com.richie.servlet.HelloWorld</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>HelloWorld</servlet-name>
  <url-pattern>/helloworld</url-pattern>
</servlet-mapping>

</web-app>

配置好 Tomcat,然后使用 postman 发送 post 请求http://localhost:8080/helloworld,并加上参数username=admin&password=123。输出如下:

--------doPost--------
********doGet********
method: POST
charsetEncoding: null
contentType: application/x-www-form-urlencoded
contentLength: 23
requestUrl: http://localhost:8080/helloworld
servletPath: /helloworld
contextPath: 
requestUri: /helloworld
locale: zh_CN
authType: null
scheme: http
protocol: HTTP/1.1
serverPort: 8080
remoteHost: 0:0:0:0:0:0:0:1
remoteAddr: 0:0:0:0:0:0:0:1
remoteUser: null
requestedSessionId: F17A359B0544082FC6A6C5F62E672E8A
pathInfo: null
isSecure: false
servletName: localhost
pathTranslated: null++headers++
name: host, value: localhost:8080
name: connection, value: keep-alive
name: content-length, value: 23
name: cache-control, value: max-age=0name: origin, value: http://localhost:8080name: upgrade-insecure-requests, value: 1name: content-type, value: application/x-www-form-urlencoded

name: user-agent, value: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36name: accept, value: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

name: referer, value: http://localhost:8080/

name: accept-encoding, value: gzip, deflate, br

name: accept-language, value: zh-CN,zh;q=0.9,en;q=0.8

name: cookie, value: __utmz=111872281.1521468435.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Idea-734b2b82=47daaaf7-bc69-41ca-9234-dffa6c217ef8; _ga=GA1.1.2085956305.1521468435; Webstorm-717d1cc9=b6b7f7ea-d8d3-4891-8e20-0dca54d5cbd2; __utmc=111872281; __utma=111872281.2085956305.1521468435.1529898141.1530148517.11; SESSION=12913786-3c46-421d-ac2c-02c9c29ae03d; JSESSIONID=F17A359B0544082FC6A6C5F62E672E8A

--headers--
++parameters++
name: user
name, value: admin
name: password, value: 123
--parameters--
++attributes++
--attributes--
++cookies++
name: __utmz, value: 111872281.1521468435.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
name: Idea-734b2b82, value: 47daaaf7-bc69-41ca-9234-dffa6c217ef8
name: _ga, value: GA1.1.2085956305.1521468435
name: Webstorm-717d1cc9, value: b6b7f7ea-d8d3-4891-8e20-0dca54d5cbd2
name: __utmc, value: 111872281
name: __utma, value: 111872281.2085956305.1521468435.1529898141.1530148517.11
name: SESSION, value: 12913786-3c46-421d-ac2c-02c9c29ae03d
name: JSESSIONID, value: F17A359B0544082FC6A6C5F62E672E8A
--cookies--

可以看出,http 请求的基本信息都能取到,每个方法都有它的含义。

五、Filter 过滤器

Filter 和 Servlet 一样重要,它可以实现许多功能,比如敏感词过滤、用户验证等。它也是一个接口,和 Servlet 类似,有 init 和 destroy 方法,最重要的是 doFilter 方法。
Filter 主要用于对用户请求进行预处理,也可以对 HttpServletResponse 进行后处理。使用 Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给 Servlet 进行处理并生成响应,最后 Filter 再对服务器响应进行后处理。

public interface Filter {
  default void init(FilterConfig filterConfig) throws ServletException {}
  void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) 
                            throws IOException, ServletException;
  default void destroy() {}
}

除此之外,规范类还有 FilterChain、FilterConfig,Filter 使用了责任链的设计模式,传递的对象就 FilterChain。

六、Filter 工作原理

当写好 Filter,并配置对哪个 web 资源进行拦截后,web 服务器每次在调用 Servlet 的 service 方法之前,都会先调用一下 filter 的 doFilter 方法。因此,在该方法内编写代码可达到如下目的:

• 调用目标资源之前,让一段代码执行。
• 是否调用目标资源(即是否让用户访问 web 资源)。
• 调用目标资源之后,让一段代码执行。

web 服务器在调用 doFilter 方法时,会传递一个 FilterChain 对象进来,FilterChain 对象是 Filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发者可以根据需求决定是否调用此方法。

七、Filter 实践

简单实现一个拦截器,打印它的生命周期。

public class LogFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) {
    System.out.println("init logFilter: " + filterConfig);
  }
  @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                           FilterChain filterChain) throws IOException, ServletException {
    System.out.println("log doFilter pre");
    // 一定要调用 filterChain 的 doFilter 方法,继续传递事件
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("log doFilter after");
  }
  @Override
  public void destroy() {
    System.out.println("destroy logFilter");
  }
}

然后配置 web.xml,一般把 filter 配置在所有的 servlet 前面,/* 表示匹配所有的请求。

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.richie.servlet.LogFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

可以对请求和响应进行预处理和后处理,运行后输出如下:

log doFilter pre
Servlet 处理的方法
log doFilter after

客户端和服务端的通信,本质上是一种 IO 操作。用户输入网址后(略去查询 DNS),浏览器从某个端口发出数据包,里面有目标地址、请求参数等等(output)。然后经过网络一层层传递,跨越万水千山,到达服务器被 80 端口捕获到了(input),交给 Servlet 容器处理,根据请求信息拿到数据返回给客户端(output)。这是一对多的数据交换,如果请求数据的人多了,服务端的压力其实蛮大的。更细一点说,客户端和服务端的通信是一种进程间的通信。运行在 A 主机上的 A1 进程和运行在 B 主机上的 B1 进程进行通信,它是基于 Socket 的通信,端口是一个重要的标识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269