关于Servlet一些东西

---- 概念

   Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。 Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。

  Servlet 容器是 web server 或 application server 的一部分,提供基于请求/响应发送模型的网络服务,解码基于 MIME 的请求,并且格式化基于 MIME 的响应。Servlet 容器也包含了管理 Servlet 生命周期。 

   附加文中参考代码

---- 生命周期

说明:

    init():
在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
   service():
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
   destroy():
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

Servlet在Tomcat的工作流程

说明:

   1)web client向Tomcat发起Http请求

   2)Servlet容器接收到该Http请求

   3)Servelt容器创建一个HttpRequest对象,将web client的请求信息封装到这个对象中去

   4)Servelt容器创建一个HttpResponse对象

   5)Servelt容器调用HttpServlet对象中的service()方法,将HttpRequest对象和HttpResponse对象作为参数传给HttpServlet对象

   6)HttpServlet对象调用HttpRequest对象的相关方法,获取http请求信息

   7)HttpServlet对象调用HttpResponse对象的相关方法,返回响应数据

   8)Servlet容器把HttpServlet的响应结果传给Web Client

---- Servlet的对象

 HttpServletRequest 请求对象:获取请求信息

 HttpServletResponse 响应对象: 设置响应对象

 ServletConfig对象: servlet配置对象

 ServletContext对象: servlet的上下文对象

--- ServletConfig对象

1) 作用

   主要是用于加载servlet的初始化参数。在一个web应用可以存在多个ServletConfig对象(一个Servlet对应一个ServletConfig对象)

2)创建

  在创建完servlet对象之后,在调用init方法之前创建,直接从有参数的init方法中得到

3)举个栗子

 servlet文件

//ServletConfig用于封装servlet的配置
public class ServletDemo1 extends HttpServlet {

    private ServletConfig config;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //返回此上下文中支持servlet API级别的最大和最小版本号
        int m = this.getServletContext().getMinorVersion();
        int n = this.getServletContext().getMajorVersion();
        System.out.println("m="+m+",n="+n);
        
        System.out.println(this.getServletConfig().getServletName());
        System.out.println(this.getServletConfig().getInitParameter("data"));
        
        //得到所有的值
        Enumeration<String> initParameterNames = getServletConfig().getInitParameterNames();
        while(initParameterNames.hasMoreElements()){
            String name = initParameterNames.nextElement();
            System.out.println(name);
            System.out.println(this.getServletConfig().getInitParameter(name));
        }
    }
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

web.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>ServletTest</display-name> 
  <servlet>
  <servlet-name>ServletDemo1</servlet-name>
  <servlet-class>com.xxg.web.ServletDemo1</servlet-class>
  <init-param>
        <param-name>data</param-name>
        <param-value>xxxx</param-value>
    </init-param>
    <init-param>
        <param-name>data2</param-name>
        <param-value>yyyy</param-value>
    </init-param>
  </servlet>
  
  <servlet-mapping>
  <servlet-name>ServletDemo1</servlet-name>
  <url-pattern>/ServletDemo1</url-pattern>
  </servlet-mapping>  
</web-app>

运行结果:

说明:其实可以不用先获得ServletConfig,然后在获取其各种参数,可以直接使用其方法,比如上面我们用的ServletConfig().getServletName();可以直接写成getServletName();而不用在先获取ServletConfig();了,原因就是在GenericServlet中,已经帮我们获取了这些数据,我们只需要直接拿就行。

 

--- ServletContext对象

1)作用

   ServletContext对象 ,叫做Servlet的上下文对象。tomcat为每个web项目都创建一个ServletContext实例,tomcat在启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等,通俗点讲,就是一个web项目,就存在一个ServletContext实例,每个Servlet读可以访问到它。一个web应用中只有一 个ServletContext对象

2)创建

  加载web应用时创建ServletContext对象,加载web应用时创建ServletContext对象

3)举个栗子

 在上边的ServletDemo1文件中

    //在公共区域存放了一个key值为key1,value值为xiaomi的值
        getServletContext().setAttribute("key1", "xiaomi");
        String value = (String) this.getServletContext().getAttribute("key1");
        System.out.println("key1: "+value);

在里面的一个ServletDemo2文件中

/*
ServletContext域:1,是一个容器 2。作用范围是应用程序范围
*/
/*@WebServlet("/ServletDemo2")*/
public class ServletDemo2 extends HttpServlet {
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //在ServletDemo2通过ServletContext获取在ServletDemo1中设置的值
        String value = (String) this.getServletContext().getAttribute("key1");
        System.out.println("key1: "+value);
    }    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

运行结果:(注意需要先运行ServletDemo1,让ServletContext区域存储值,再运行ServletDemo2

 

--- 其他两个对象补充

1)请求转发

      request.getRequestDispatcher(String path).forward(request,response);  //path:转发后跳转的页面,这里不管用不用"/"开头,都是以web项目根开始,因为这是请求转发,请求转发只局限与在同一个web项目下使用,所以这里一直都是从web项目根下开始的,

     web项目根:

      开发:G:\Workspaces\test01\WebRoot\..

         运行时:D:\java\tomcat\apache-tomcat-7.0.53\webapps\test01\..

   web站点根:

      运行时:D:\java\tomcat\apache-tomcat-7.0.53\webapps\..

  从这里可以看出,web项目根就是从该web项目名开始,所以我们请求转发时,只需要接着项目名后面需要访问的路径写就行了,

  特点浏览器中url不会改变,也就是浏览器不知道服务器做了什么,是服务器帮我们跳转页面的,并且在转发后的页面,能够继续使用原先的request,因为是原先的request,所以request域中的属性都可以继续获取到。

 2)重定向

方式一:手动方案

  response.setStatus(302);  //状态码302就代表重定向

  response.setHeader("location","http://www.baidu.com");

方式二:使用封装好的,通过response.sendRedirect("http://www.baidu.com");

  特点服务器告诉浏览器要跳转的页面,是浏览器主动去跳转的页面,浏览器知道,也浏览器的地址栏中url会变,是浏览器重新发起一个请求到另外一个页面,所以request是重新发起的,跟请求转发不一样。

   注意:response.sendRedirect(path);  //

  第一种:response.sendRedirect("/test01/MyServlet01");  //使用了"/"开头,说明是从web站点根开始,所以需要写test01/MyServlet01

  第二种:response.sendRedirect("MyServlet01");  //没有使用"/"开头,说明是从web项目根开始,那么就无需写test01了。

 重定向没有任何局限,可以重定向web项目内的任何路径,也可以访问别的web项目中的路径,并且这里就用"/"区分开来,如果使用了"/"开头,就说明我要重新开始定位了,不访问刚才的web项目,自己写项目名,如果没有使用"/"开始,那么就知道是访问刚才那个web项目下的servlet,就可以省略项目名了。就是这样来区别。

----Servlet与多线程

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

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

    从上面可以看出:

    第一:Servlet单实例,减少了产生servlet的开销;

    第二:通过线程池来响应多个请求,提高了请求的响应时间;

    第三:Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;

   第四:每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;

?-Servlet如何处理多个请求访问

    servlet是默认采用单实例,多线程的方式进行。只要webapp被发布到web容器中的时候,servlet只会在发布的时候实例化一次,servlet在其生命周期中只有在将项目给移除或服务器stop的时候才会销毁,那么一个web项目从发布到运行只存在一个servlet的实例。

    当容器收到一个Servlet请求,调度线程从线程池中选出一个工作者线程,将请求传递给该工作者线程,然后由该线程来执行Servlet的service方法。当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求,容器并不关心这个请求是否访问的是同一个Servlet.当容器同时收到对同一个Servlet的多个请求的时候,那么这个Servlet的service()方法将在多线程中并发执行。

   在Tomcat7.0.3中的server.xml里面的代码:

<!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->

<!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->

 

?-Servlet如何实现线程安全

1、实现 SingleThreadModel 接口 

     该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。(但是Servlet2.4中已不再提倡使用

2、同步对共享数据的操作
使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,但是在JMM内存模型中可以知道,使用synchronized为了保证主内存和工作内存一致性,会来回切换内核线程与用户线程,会造成一定的资源浪费,使得系统性能下降,所以在实际的开发中也应避免或最小化 Servlet 中的同步代码

   3、避免使用实例变量

    局部变量是在堆栈中运行。每个运行的线程都有自己的堆栈。
    别的线程无法访问得到,因此我们说,局部变量是“安全”的。
    全局变量在堆中,堆是对所有的线程都可见的。
   因此在两个以上的线程访问全局变量时,就会出现所谓的“不安全”,a线程访问全局变量,赋值为a,然后中间睡眠了0.001秒,在此期间b进程访问了全局变量,赋值为b了,此时a线程醒来了,抢了处理机制,发现全局变量是b,显然不是我们a线程所要到的值,这时就要加入同步机制或者定义为局部变量,比如如果是方法的话就加同步方法,代码块就加同步代码块。

举个栗子

public class Test1{
    ...
    public void fun1(){
        String s = "";
        System.out.print(s);
  }
}

public class Test2{
    ...
    String s ;
    public void fun1(){
        System.out.print(s);
  }
}

说明:第一种Test1  如果Test1只有一个实例化对象,那么不同的用户访问他的话,每一个用户执行的fun1方法都是由自身的线程单独开辟的空间的。

第二种Test2 如果Test2只有一个实例化对象,那么不同的用户访问他的话,那每一个用户访问的s都是同一个变量,那么线程安全性就很难保证。所以建议第一种方法。

 

参考资料:Servlet其实是多线程

                Servlet中的几个重要对象

               说说Servlet 生命周期、工作原理

posted @ 2018-12-21 14:13  凉月缘  阅读(270)  评论(0编辑  收藏  举报
Live2D //博客园自带,可加可不加