Servlet 学习(一)

1.Servlet简介
sun公司制定的一种用来扩展web服务器功能的组件规范.

(1)扩展web服务器功能
早期的很多web服务器(apache http server,iis等等)只能够处理静态资源的请求,不能够处理动态资源的请求.
静态资源:指的是需要事先将网页写好.
动态资源:通过计算,生成网页.  
(了解)CGI(Common Gateway Interace通用网关接口):可以使用perl,c/c++等语言来开发(比如复杂,并且可移植性差),现在用的少了.

(2)组件规范
组件:符合规范,实现部分功能,并且需要部署到相应的容器里面才能运行的软件模块.
容器:符合规范,提供组件运行环境的程序.
Servlet就是一个组件,需要部署到相应的Servlet容器当中才能运行(比如部署到Tomcat上运行)

 

案例:

2.Serlvet Hello World

 

(1).Servlet是Oralce(SUN)定义的开发规范:

webapp
  |-- WEB-INF
  |   |-- web.xml  (部署描述文件)配置请求与Serlvet的映射关系 
  |   |              /hello -> cn.tedu.day01.HelloServlet
  |   |-- lib      放置第三方的库 如:数据库驱动程序等
  |   |-- classes  放置自己写的,编译后的类
  |   |    |-- cn.tedu.day01.HelloServlet.class
  |-- index.html
  |-- logo.png

 

(2).固定的接口名

  1. Servlet 接口
  2. HelloServlet 类必须实现Servlet接口(还可以继承HttpServlet)

(3).固定的配置文件规则 web.xml

<servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>cn.tedu.day01.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>  

 

使用Maven项目创建Web项目步骤:

  1. 创建Maven项目,选择war方式
  2. 利用右键菜单生成部署描述文件
  3. 导入 Targeted Runtimes
  4. 创建Servlet类
  5. 部署测试

案例: 使用Servlet接口创建Servlet

     1.创建类

public class HelloServlet implements Servlet{
    public void init(ServletConfig cfg)
            throws ServletException {
    }
    public void destroy() {
    }
    public ServletConfig getServletConfig() {
        return null;
    }
    public String getServletInfo() {
        return null;
    }
    public void service(ServletRequest req,ServletResponse res) throws ServletException, IOException {
        res.setContentType("text/html");
        PrintWriter out=res.getWriter();
        out.println(
        "<html><body><h1>Hello World</h1></body></html>");
    }
}

 

2.配置web.xml

 <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>cn.tedu.day01.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>

 

3.部署测试

http://localhost:8080/Servlet01/hello

三.Servlet的运行过程

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

 

运行流程图:

 

 

 

step1. 浏览器根据ip和port建立与服务器之间连接.
step2. 浏览器将相关数据打包成请求数据包,然后发送给服务器.
step3. 服务器解析请求数据包,并将解析到的数据存放到request对象里面,同时创建一个respose对象.
step4. 服务器创建Servlet对象,然后调用该对象的service方法来处理请求.
注:在调用service方法时,会将request对象和response对象作为参数传过来.所以,可以在service方法里面,调用request对象来获取请求数据包中的数据,并且将处理结果写到response对象里面.
step5. web服务器会将response对象中的数据取出来,打包(即按照 http协议创建响应数据包),然后发送给浏览器。

step6浏览器拆包(按照http协议要求,将响应数据包中的数据解析 出来),然后生成相应的页面。 

 

 

四.常见的问题及解决方式
(1)404
404是一个状态码,表示服务器依据请求路径找不到对应的资源.
状态码:是一个三位数字,由http协议规定,表示服务器处理请求的一种状态.
(2)解决方式:
a.检查请求路径是否正确
b.检查是否正确部署该应用

 (2)500
500也是一个状态码,表示服务器处理出错.
解决方式:
a.代码错误
b.代码不严谨
c.配置出错

 (3)405
405也是一个状态码,表示服务器找不到处理方法
解决方法:
检查service方法
(包括方法名,返回类型,参数类型,异常类型)

 

 

HttpServlet

实现Servlet接口:

  1. 直接Servlet接口编程繁琐
  2. Servlet 接口有两个实现类实现 HttpServlet 更加简便
    1. GenericServlet
    2. HttpServlet
  3. HttpServlet区分了Http请求类型
    1. get 请求 被 doGet方法处理
    2. post 请求被 doPost处理
    3. doGet方法中调用一下doPost,就可以一起处理get和Post

案例2 :GenericServlet

1.编写类

/**
 * 相对于实现 Servlet接口,继承 GenericServlet 更简单
 */
public class DemoServlet
    extends GenericServlet{

    public void service(ServletRequest req, 
            ServletResponse res)
            throws ServletException, IOException {
        res.setContentType("text/html");
        PrintWriter out = res.getWriter();
        out.print("HI GenericServlet"); 
    }
}

2.配置 web.xml

  <servlet>
    <servlet-name>demo1</servlet-name>
    <servlet-class>cn.tedu.day01.DemoServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>demo1</servlet-name>
    <url-pattern>/demo1</url-pattern>
  </servlet-mapping>

 

实例3:使用 HttpServlet创建Servlet

1.创建类

/**
 * 继承 HttpServlet,比实现Servlet接口简单 相比 GenericServlet 可以区别 get、post请求重写 doGet 处理 get 请求
 *   get请求: 浏览器地址栏直接请求是get请求
 *            a标签连接请求是get请求
 *            img标签中的src是get请求
 * 重写 doPost处理  post 请求
 *   post请求: 表单method=post时候的请求
 */
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req,
            HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out=resp.getWriter();
        out.println("HI HttpServlet doGet()"); 

    }
}

2.配置 web.xml

  <servlet>
    <servlet-name>demo2</servlet-name>
    <servlet-class>cn.tedu.day01.TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>demo2</servlet-name>
    <url-pattern>/demo2</url-pattern>
  </servlet-mapping>

 

案例4 :利用网页处理get请求

1.编写网页 webapp/demo.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>测试get请求</h1>
    <a href="demo2">test</a>

    <h1>测试post请求</h1>
    <p>客户端向服务器发起post请求,但是服务器只能处理
     get请求,此时服务器端会出现 405错误!</p>
    <form action="demo2" method="post">
        <input type="submit" value="GO">  
    </form>     

</body>
</html>

 

案例5 利用HttpServlet处理post请求

1.编写Servlet类

/**
 * 处理Post请求,就需要重写doPost方法
 */
public class DoPostServlet extends HttpServlet{

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("Hi doPost"); 
    }

}

2.配置

  <servlet>
    <servlet-name>demo3</servlet-name>
    <servlet-class>cn.tedu.day01.DoPostServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>demo3</servlet-name>
    <url-pattern>/demo3</url-pattern>
  </servlet-mapping>

3.编写网页

<p>正确处理post请求案例</p>
<form action="demo3" method="post">
    <input type="submit" value="GO">  
</form>

 

案例6: 既能处理get也能处理post请求的Servlet

1.编写Servlet

/**
 * 既能处理get请求也能处理post请求 
 * 1. get请求-> doGet() -> doPost()
 * 2. post请求 -> doPost() 
 */
public class GetPostServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp); 
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out=resp.getWriter();
        out.print("get & post"); 
    }
}

2.配置

  <servlet>
    <servlet-name>demo4</servlet-name>
    <servlet-class>cn.tedu.day01.GetPostServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>demo4</servlet-name>
    <url-pattern>/demo4</url-pattern>
  </servlet-mapping>

3.编写 html

<p>即能处理get也能处理post请求</p>
<a href="demo4">test</a>
<form action="demo4" method="post">
    <input type="submit" value="GO">  
</form>

 

 

HttpServletRequest

客户端浏览器发起的请求消息,经过Tomcat解析以后,封装到Request对象中。

利用Request提供API可以获取请求中的信息。

案例7 利用Request获取请求信息

  1. 编写Servlet

/**
 * 演示 Request 对象的功能
 * Request 代表用户浏览器发送的请求信息
 */
public class RequestDemoServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //读取请求行上的 Method 信息
        String method=request.getMethod();
        //读取请求行上的 Request-URI
        String uri = request.getRequestURI();
        //读取请求行上的协议版本
        String protocol = request.getProtocol();


        //读取请求头信息 
        // User-Agent 用户代理,就是浏览器的信息, 类型版本等 
        // 获取请求头中的用户浏览器相关信息
        String ua=request.getHeader("User-Agent");
        String host = request.getHeader("Host");

        //设置服务器发送端的编码规则
        //response.setCharacterEncoding("UTF-8");
        //设置浏览器接收时候 的解码规则
        response.setContentType(
                "text/html; charset=UTF-8");

        //设置 contentType 时候,response会自动
        //设置CharacterEncoding

        PrintWriter out = response.getWriter();
        out.print("<html>");
        out.print("<body>");
        out.print("<ul>");
        out.print("<li>"+method+"</li>");
        out.print("<li>"+uri+"</li>");
        out.print("<li>"+protocol+"</li>");
        out.print("<li>"+ua+"</li>");
        out.print("<li>"+host+"</li>");
        out.print("<li>试试</li>");
        out.print("</ul>");
        out.print("</body>");
        out.print("</html>");
    }

}

2.配置

 <servlet>
    <description></description>
    <display-name>RequestDemoServlet</display-name>
    <servlet-name>RequestDemoServlet</servlet-name>
    <servlet-class>cn.tedu.day01.RequestDemoServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>RequestDemoServlet</servlet-name>
    <url-pattern>/reqdemo</url-pattern>
  </servlet-mapping>

 

HttpServletResponse:

Response对象代表服务器向客户端发送的信息

案例8 利于Response对象向客户端发送信息

  1. Servlet类

/**
 * 演示Response对象的功能
 */
public class ResponseDemoServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应行状态码
        response.setStatus(404); 
        //添加一个自定义的响应消息头:
        response.addHeader("Demo", "Hello World!"); 
        response.setContentType(
                "text/html; charset=UTF-8");
        //一定在设置编码以后,获取out对象!!否则有编码错误
        PrintWriter out = response.getWriter();
        //设置响应的实体Body
        out.println("<html>");
        out.println("<body>");
        out.println("<h1>404,没有找到妹子呀!</h1>");
        out.println("</body>");
        out.println("</html>");
    }

}

2.配置

 <servlet>
    <description></description>
    <display-name>ResponseDemoServlet</display-name>
    <servlet-name>ResponseDemoServlet</servlet-name>
    <servlet-class>cn.tedu.day01.ResponseDemoServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ResponseDemoServlet</servlet-name>
    <url-pattern>/respdemo</url-pattern>
  </servlet-mapping>

 

五.http协议(了解)
1.什么是http协议?
是w3c制定的一个网络应用层协议,规定了浏览器如何与web服务器之间进行通信以及相应的数据包格式.

# 2.如何通信?
ste1.建立连接
step2.发送请求
step3.发送响应
step4.关闭连接
注:如果要再次发送请求,需要重新建立新的连接.即"一次请求,一次连接."这样做,优点是,服务器可以利用有限的连接为尽可能多的请求服务.

# 3.数据格式
1.请求数据包
请求行(请求类型 请求资源路径 协议类型和版本)
若干消息头:
消息头是一些键值对,由w3c定义,浏览器和服务器可以利用消息头传递一些特定的信息,比如浏览器可以通过
"user-agent"消息头告诉浏览器,浏览器的类型和版本.
实体内容 :
如果请求类型是get,则内容为空
如果是请求类型是post,则内容为请求参数.

GET /servlet-day01/date HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3724.8 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


2.响应数据包
状态行(协议类型和版本 状态码 状态描述)
若干消息头:
服务器也可以发送一些消息头给浏览器,比如发送content-type消息头,告诉浏览器,服务器返回的
数据的类型和编码
实体内容:
程序的处理结果,浏览器解析之后,生成相应的页面

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 10
Date: Wed, 18 Sep 2019 13:55:20 GMT

3.get请求
特点:
a.会将请求参数添加到请求行,只能提交少量的数据给服务器 请求行只有一行,大约能存放4k左右的数据.
b.会将请求参数显示在浏览器地址栏,不安全.

4.post请求
a.会将请求参数添加到实体类型里面,可以提交大量的数据给服务器.
b.不会将请求参数显示 在浏览器地址栏,相对安全(注意,并不会加密,对于敏感数据 需要加密处理)


6.Servlet输出中文,要注意什么?
1.为什么会有乱码?
out.println方法 默认会使用ISO-8859-1来编码.
如何解决?
response.setContentType("text/html;charset=utf-8");
注:这句话有两个作用
a.设置content-type消息头的值(告诉浏览器,服务器返回的数据类型和编码)
b.设置out.println方法使用哪种字符集

2.表单包含有中文参数值,如何处理?
为什么会有乱码?
表单提交时,浏览器会对中文参数值进行编码(比如使用utf-8来编码),服务器端,默认会使用iso-8859-1来解码,所以会产生编码.
注:使用打开该表单所在的页面时的字符集来编码.
如何解决?
a.post请求
request.setCharacterEncoding("utf-8");
b.get请求
在server.xml添加代码 URIEncoding="utf-8":
<Connector connectionTimeout="20000" URIEncoding="utf-8"port="8080" protocol="HTTP/1.1" redirectPort="8443"/>

 

Servlet与普通Java类的区别  

   Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
  针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
  在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。

  如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
    举例:

  <servlet>
        <servlet-name>invoker</servlet-name>
        <servlet-class>
            org.apache.catalina.servlets.InvokerServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

 

  用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。

 

六.Servlet的线程安全问题

  当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。例如下面的代码:

不存在线程安全问题的代码:

 package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServlet;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletResponse;
 9 
10 public class ServletDemo3 extends HttpServlet {
11 
12     
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         
16         /**
17          * 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗
18          * i变量被多个线程并发访问,但是没有线程安全问题,因为i是doGet方法里面的局部变量,
19          * 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,
20          * 各个线程操作的都是自己的i变量,所以不存在线程安全问题
21          * 多线程并发访问某一个方法的时候,如果在方法内部定义了一些资源(变量,集合等)
22          * 那么每一个线程都有这些东西,所以就不存在线程安全问题了
23          */
24         int i=1;
25         i++;
26         response.getWriter().write(i);
27     }
28 
29     public void doPost(HttpServletRequest request, HttpServletResponse response)
30             throws ServletException, IOException {
31         doGet(request, response);
32     }
33 
34 }

存在线程安全问题的代码:

package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServlet;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletResponse;
 9 
10 public class ServletDemo3 extends HttpServlet {
11 
12     int i=1;
13     public void doGet(HttpServletRequest request, HttpServletResponse response)
14             throws ServletException, IOException {
15         
16         i++;
17         try {
18             Thread.sleep(1000*4);
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22         response.getWriter().write(i+"");
23     }
24 
25     public void doPost(HttpServletRequest request, HttpServletResponse response)
26             throws ServletException, IOException {
27         doGet(request, response);
28     }
29 
30 }

把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了,如下图所示:同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2,而第二个浏览器应该看到3的,结果两个浏览器都看到了3,这就不正常。

 

 

 

线程安全问题只存在多个线程并发操作同一个资源的情况下,所以在编写Servlet的时候,如果并发访问某一个资源(变量,集合等),就会存在线程安全问题,那么该如何解决这个问题呢?

先看看下面的代码:

package gacl.servlet.study;
 2 
 3 import java.io.IOException;
 4 
 5 import javax.servlet.ServletException;
 6 import javax.servlet.http.HttpServlet;
 7 import javax.servlet.http.HttpServletRequest;
 8 import javax.servlet.http.HttpServletResponse;
 9 
10 
11 public class ServletDemo3 extends HttpServlet {
12 
13     int i=1;
14     public void doGet(HttpServletRequest request, HttpServletResponse response)
15             throws ServletException, IOException {
16         /**
17          * 加了synchronized后,并发访问i时就不存在线程安全问题了,
18          * 为什么加了synchronized后就没有线程安全问题了呢?
19          * 假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁
20          * 等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,
21          * 所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了
22          * 
23          */
24         synchronized (this) {//在java中,每一个对象都有一把锁,这里的this指的就是Servlet对象
25             i++;
26             try {
27                 Thread.sleep(1000*4);
28             } catch (InterruptedException e) {
29                 e.printStackTrace();
30             }
31             response.getWriter().write(i+"");
32         }
33         
34     }
35 
36     public void doPost(HttpServletRequest request, HttpServletResponse response)
37             throws ServletException, IOException {
38         doGet(request, response);
39     }
40 
41 }

现在这种做法是给Servlet对象加了一把锁,保证任何时候都只有一个线程在访问该Servlet对象里面的资源,这样就不存在线程安全问题了,如下图所示:

  

  这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有9999个人同时访问这个Servlet,那么这9999个人必须按先后顺序排队轮流访问。

  针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
  查看Sevlet的API可以看到,SingleThreadModel接口中没有定义任何方法和常量,在Java中,把没有定义任何方法和常量的接口称之为标记接口,经常看到的一个最典型的标记接口就是"Serializable",这个接口也是没有定义任何方法和常量的,标记接口在Java中有什么用呢?主要作用就是给某个对象打上一个标志,告诉JVM,这个对象可以做什么,比如实现了"Serializable"接口的类的对象就可以被序列化,还有一个"Cloneable"接口,这个也是一个标记接口,在默认情况下,Java中的对象是不允许被克隆的,就像现实生活中的人一样,不允许克隆,但是只要实现了"Cloneable"接口,那么对象就可以被克隆了。

  让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。  
  对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象
  实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。  

2019-12-25 22:26:33

posted @ 2019-12-25 22:27  一场屠夫的战争  阅读(187)  评论(0编辑  收藏  举报