• 关于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 est01WebRoot..

             运行时:D:java omcatapache-tomcat-7.0.53webapps est01..

       web站点根:

          运行时:D:java omcatapache-tomcat-7.0.53webapps..

      从这里可以看出,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 生命周期、工作原理

  • 相关阅读:
    《HTTP权威指南》之HTTP连接管理及对TCP性能的考虑
    TCP/ip
    java scoket http TCP udp
    java ==和equals区别
    Java 流
    android ViewPager,ViewFlipper,ViewFlow实现左右滑动
    java反射详解
    退出清理所有Activity方法
    Android UI 使用Merge,include和 ViewStub--优化(1)
    android resources使用总结
  • 原文地址:https://www.cnblogs.com/liangyueyuan/p/10150105.html
Copyright © 2020-2023  润新知