servlet总结

servlet总结

servlet总结

1 Servlet的概念

要理解Servlet,首先得从最早的网页开始讲起。最早时,网页是静态的,我们通过url定位到一个静态的html网页,然后,服务器把这个网页传输回去,这样就完成了一次访问。但是,这时的网页是静态的,甚至是单文件的。后来,为了把相同类型的元素之间的属性归类,只需要定义一次,所以,这种类型的元素可以共享属性,于是就发明了css,把元素的属性归类集中定义。但是,html语言是固定的。它本身没有任何控制语句,没有if判断,也没有循环。不能动态的生成网页。比如,根据输入的行数改变表格的行数等,这个只通过html是无法实现的,它必须借助于另外一门有控制语句的语言。这些语言有很多,比如JavaScript、Python、Tcl等等。通过这样语言动态的输出html的元素,可以动态的生成网页。我们说到的这些都还是前端的,就是我们访问一个网页的时候,把html、css、还有js以及一些多媒体文件一起返回。其实从服务器的角度来说还是静态的,虽然,我们的网页确实是动态生成的了,但这是在客户端,通过浏览器执行js等前端语言动态生成的html。
那么,从服务器的角度来看,怎样才算是动态的呢?那就是,当我们在url中使用不同的参数提交给服务器时,服务器可以根据参数,动态的生成html。那才能算是从服务器端实现了动态网页。
那这个是如何实现的呢?最早的实现叫CGI(common gateway interface),这些CGI的实现语言有很多种,早期Sun公司Tcl做CGI很流行,后来,Sun公司收购了Java。逐渐改为使用Java作为CGI的实现语言。
而Servlet是使用Java实现的一个CGI程序的封装。它把一些常见的处理流程封装起来,使用者只需继承它,修改几个重要的处理函数就可以了。所以,Servlet就是一个Java版的后台处理程序,它能理解客户发送过来的请求。并能根据请求进行处理,然后,返回处理结果。而我们平时使用的最多的,应该是Servlet基于http的协议的一个实现,叫HttpServlet。就是说客户发送http协议的请求,HttpServlet能够理解,并响应请求。

2 HttpServlet的使用

Java现在流行的构建工具是gradle,所以,我们这里也使用gradle来进行展示。关于各种构建工具的比较参见:
http://www.cnblogs.com/yangwen0228/p/6534655.html

2.1 程序结构如下:

Directory tree
==============
[-] src
 `--[-] main
     |--[-] java
     |   `--[-] cn
     |       `--[-] shinysw
     |           `--[-] servlet
     |               `----- Servlet1.java
     `--[-] webapp
         `--[-] WEB-INF
             `----- web.xml

2.2 build.gradle配置文件如下:

apply plugin: 'war'
apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin'

dependencies {
  compile 'javax.servlet:javax.servlet-api:3.1'
}

2.3 Servlet1.java文件如下:

package cn.shinysw.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet1 extends HttpServlet {
    private String message;

    public void init() throws ServletException {
        message = "Hello World";
    }

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        out.println("<h1>" + message + "</h1>");
    }

    public void destroy() {
        // do nothing.
    }
}

我们这里的Servlet1就是继承于HttpServlet的,只要有这个HttpServlet在,那么任何向它的请求就能由它的定义函数来进行处理了。它的几个主要处理函数分别是:

  • init()
    用于初始化变量或者状态。
  • doGet()
    用于处理http中的get请求。
  • doPost()
    用于处理http中的post请求。
  • destroy()
    用于结束时的状态改变或者资源回收等。

2.4 web.xml文件如下:

<web-app>
    <servlet>
        <servlet-name>servletdemo</servlet-name>
        <servlet-class>cn.shinysw.servlet.Servlet1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>servletdemo</servlet-name>
        <url-pattern>/haha</url-pattern>
    </servlet-mapping>
</web-app>

这个文件,是实现url到servlet的具体类之间的映射关系的关键。我们通过<servlet>定义一个具体的处理容器。这个容器一般包括两部分:

  • 具体类 -> servlet 之间的关联
    <servlet>
        <servlet-name>servletdemo</servlet-name>
        <servlet-class>cn.shinysw.servlet.Servlet1</servlet-class>
    </servlet>
    
  • url -> servlet 之间的关联
    <servlet-mapping>
        <servlet-name>servletdemo</servlet-name>
        <url-pattern>/haha</url-pattern>
    </servlet-mapping>
    

首先,我们通过定义一个<servlet>把具体的HttpServlet的处理类 cn.shinysw.servlet.Servlet1 绑定到 servletdemo 上面,这个名称可以是任意的,只要不与其他的冲突就可以了。然后,我们通过一个<servlet-mapping>把一个url-pattern /haha 绑定到 servletdemo 上面,这里的url-pattern可以是glob形式的匹配模式,比如 /* /test* 等。这样,当我们在浏览器中输入服务器的ip+端口号+项目名称+url-pattern能够匹配到的url,都会发送给绑定的类进行处理了。这里,假设我们使用jetty或者tomcat启动这个war,程序使用默认端口8080,那么当我们访问 http://localhost:8080/servletdemo/haha 时,服务器就会把这个http请求发送给绑定的 cn.shinysw.servlet.Servlet1 这个类进行处理。
注意,这里url-pattern,不管前面是否带有 / ,都是相对于项目url的,所以, /hahahaha 效果一样。对于Jetty容器,可以省略掉/,但是tomcat不能省略,省略掉会报错,无法启动。

3 http GET 请求

当我们在url后面,添加?key1=value1&key2=value2这种形式的请求时,就是发送GET请求。这种请求,由于参数都是可见的,所以,当参数包含密码,以及敏感信息等时,这种方式是不合适的。另外,这种方式,参数最多只能包含1024个字节,所以,长度比较长的参数也不适合用GET请求。相反,像淘宝的item,这种希望能够通过收藏夹收藏链接,下次直接定位到item等,就特别适合于使用GET,将item信息存储在url中。
另一种发送GET请求的方法是使用form,我们在webapp中创建一个get.html,在其中输入:

<!doctype html>
<html>
    <body>
        <form action="para" method="GET">
            first name: <input name="first_name" type="text" value="yang"/>
            <br/>
            <input  type="submit" value="Submit"/>
        </form>
    </body>
</html>

其中的action指向的就是我们在web.xml中定义的url-pattern。
注意:这个action的起始地址是相对于当前项目对应的url,不应带 / 。比如这个例子,是相对于 http://localhost:8080/servletdemo 的。所以,action="para",最终调用的url是 http://localhost:8080/servletdemo/para 。而假设我们使用action="/para",则最终调用的url是 http://localhost:8080/para ,则会出现404错误。
同样,我们创建一个新的Servlet类来处理这个请求:

package cn.shinysw.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet2 extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        System.out.println(request.getParameter("first_name"));

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        String title = "Http GET paramater:";

        out.println("<!doctype html>");
        out.println("<html>");
        out.println("<body bgcolor=\"#241341\">");
        out.println("<h1>" + "" + title + "</h1>");
        out.println("<p>" + request.getParameter("first_name") + "</p>");
        out.println("</body>");
        out.println("</html>");
    }
}

然后,在web.xml中增加一个mapping:

<servlet>
    <servlet-name>s2</servlet-name>
    <servlet-class>cn.shinysw.servlet.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>s2</servlet-name>
    <url-pattern>/para</url-pattern>
</servlet-mapping>

4 http POST 请求

由于GET请求的参数是可见的,所以,为了安全,我们常常需要使用POST请求,POST请求常使用form的形式,和GET一样的。
在webapp下创建post.html文件:

<!doctype html>
<html>
    <body>
        <form action="post" method="POST">
            first name: <input name="first_name" type="text" value="yang"/>
            <br/>
            <input  type="submit" value="Submit"/>
        </form>
    </body>
</html>

同样,我们创建一个新的Servlet类来处理这个请求:

package cn.shinysw.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet3 extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        System.out.println(request.getParameter("first_name"));

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        String title = "Http GET paramater:";

        out.println("<!doctype html>");
        out.println("<html>");
        out.println("<body bgcolor=\"#241341\">");
        out.println("<h1>" + "" + title + "</h1>");
        out.println("<p>" + request.getParameter("first_name") + "</p>");
        out.println("</body>");
        out.println("</html>");
    }
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
        throws ServletException, IOException {
        doGet(request, response);
    }
}

然后,在web.xml中增加一个mapping:

<servlet>
    <servlet-name>s3</servlet-name>
    <servlet-class>cn.shinysw.servlet.Servlet3</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>s3</servlet-name>
    <url-pattern>post</url-pattern>
</servlet-mapping>

5 Filter

Filter顾名思义就是做过滤工作的。实际上,可以将它理解为一种装饰器。当需要的时候,可以在操作之前添加一些操作行为。这些操作完全可以放在每个单独的servlet当中去。之所以弄出一个Filter来,完全是因为这些工作大多数是共用的,如果每个servlet当中去写一个,就会重复。所以,干脆把它单独出来,通过配置的方式,哪些页面需要这些处理的,就加一下。然后,根据处理的内容,还可以分类,filter1,filter2…,哪些页面需要几个filter,自己去组合,组合的顺序,是在web.xml中的定义顺序决定的。

<filter>
    <filter-name>f1</filter-name>
    <filter-class>cn.shinysw.servlet.ServletFilter1</filter-class>
    <init-param>
        <param-name>test_param</param-name>
        <param-value>Initialization Paramter Demo</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>f1</filter-name>
    <url-pattern>*</url-pattern>
</filter-mapping>

6 ErrorHandler

<error-page>
    <expecption-type>java.lang.Throwable</expecption-type>
    <location>/errorpage</location>
</error-page>

其中的location必须带 / ,但这个location不管带不带 / 都是相对于项目url的。

7 Cookie

Cookie是服务器将一些常用的信息保存在浏览器上的一种方法。好比,一家餐馆,它想做活动,发给顾客一张凭证,每吃一次盖一个章,盖满5次送一顿饭。而这张凭证,相当于是餐馆给顾客发的一个cookie。cookie的好处就是,餐馆不需要保管这些凭证,只需要顾客自己保管好就行了。缺点是,顾客可能自己伪造印章,自己盖章,安全性较差。相应的,后面会介绍session,session是与cookie相反的一种存储信息的方式。它相当于,同样是餐馆做活动,这次它不发凭证给顾客,而是给顾客办个会员卡,分配一个会员号。以后,每次来只需要会员号就行了,吃饭的次数保存在餐馆的记录本(数据库/服务器)里面。这种方式的优点是,由于数据是在餐馆里面,所以安全性比较高。缺点是,需要餐馆来进行数据的管理,增加管理成本,特别是涉及到多家连锁餐馆的时候,这些数据是集中保存还是分布式保存,分布式保存时如何同步等,会比较麻烦。

package cn.shinysw.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ServletCookie extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();

        Cookie cookie = null;
        Cookie[] cookies = null;
        Boolean flagExistVisit = false;

        cookies = request.getCookies();
        for (Cookie c : cookies) {
            if (c.getName().equals("visit_times")) {
                c.setValue("" + ((new Integer(c.getValue())).intValue() + 1));
                response.addCookie(c);
                cookie = c;
                flagExistVisit = true;
                break;
            }
        }

        response.addCookie(new Cookie("first_name", "yang"));
        for (Cookie c : cookies) {
            if (c.getName().equals("first_name")) {
                c.setMaxAge(0);
                response.addCookie(c);
                out.println(c.getName() + ": " + c.getValue() + "<br/>");
                break;
            }
        }

        if (!flagExistVisit) {
            cookie = new Cookie("visit_times", "1");
            cookie.setMaxAge(60*60*24);
            response.addCookie(cookie);
        }

        out.println("中文 访问次数 visit times: " + cookie.getValue());
    }

    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
        throws ServletException, IOException {
        doGet(request, response);
    }
}

这里顺便测试了一下中文的输出,由于默认编码是iso-8859的,所以,中文是毫无疑问会乱码的。那么,添加:

response.setCharacterEncoding("utf-8")

之后,是否能输出中文了呢?发现,还是乱码的,并且,检查java源文件的编码也是utf-8。那么,只有可能是javac在编译的时候,用的不是utf-8编码的。一般,编译器都会使用本地系统的编码,我们在windows系统中,当然就是使用gb2312了或者cp936。将文件编码改为gb2312, response.setCharacterEncoding("gb2312") ,然后,编译运行,果然就能正确地输出中文了。当然,为了更通用,我们还是改为utf-8编码。那么,就需要改变javac的编码了。我们在build.gradle添加:

compileJava.options.encoding = 'utf-8'

8 原理总结

  1. Servlet是一种规范,称为Servlet规范,是J2EE规范的一部分。
  2. Servlet规范定义了Servlet相关的一组接口、其实现是由Servlet容器开发商来实现,类似于JDBC驱动。
  3. Servlet的也是类,其对象是通过Servlet容器来创建,Servlet只能在Servlet容器中运行。打个比方说:容器是青山,Servlet是松柏。
  4. 当客户端请求Servlet时,容器会做两件事情:
    a. Servlet容器会将请求自动组装为一个ServletRequest对象,并自动产生一个ServletResponse对象,这两个对象一并传递给Servlet的service(request,response)方法。
    b. 在该Servlet对象上调用service(request,response)方法来处理并响应用户的请求。
  5. 用户无法直接调用Servlet的方法,也无法去创建Servlet的实例。

Date: 2017-05-09 16:01

Created: 2017-05-25 周四 10:25

Emacs 26.0.50.4 (Org mode 8.2.10)

Validate

posted @ 2017-05-25 10:26  yangwen0228  阅读(211)  评论(0编辑  收藏  举报