Servlet(一)

      Servlet接口定义所有servlet必须实现的方法。一个servlet是运行在web服务器中的一个小java程序,servlet通过HTTP协议接收并响应web客户端发来的请求。这个接口中定义的方法包括初始化servlet,服务请求,以及remove servlet,这些方法即servlet的生命周期方法,调用顺序如下:

           1)某个创建servlet对象的时候,调用初始化方法: void init(ServletConfig config)

           2)客户端发送请求的时候,service方法被执行:void service(ServletRequest req, ServletResponse res)

           3)某个servlet对象被摧毁的时候,调用destroy方法: void destroy()
     sun公司定义Servlet接口的两个默认实现类,分别为javax.servlet.GenericServlet和javax.servlet.http.HttpServlet

     HttpServlet指能够处理HTTP请求的servlet,在原有的Servlet接口上添加一些与HTTP协议处理方法,比Servlet接口的功能更为强大,因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法

    

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

注意点:

        客户端是通过URL地址访问服务器中的资源,所以servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成

<web-app>
    <servlet>
    <servlet-name>AnyName</servlet-name>
    <servlet-class>HelloServlet</servlet-class> //设置Servlet的完整类名
    </servlet>
    <servlet-mapping>
    <servlet-name>AnyName</servlet-name>
    <url-pattern>/demo/hello.html</url-pattern>//用于指定Servlet的对外访问路径
    </servlet-mapping>
</web-app>

       同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是:“*.扩展名”;另一种格式是:以“/”开头,并以“/*”结尾。例如:

<servlet-mapping>
    <servlet-name>AnyName</servlet-name>
    <url-pattern>*do</url-pattern>
</servlet-mapping>


<servlet-mapping>
    <servlet-name>AnyName</servlet-name>
    <url-pattern>/action/*</url-pattern>
</servlet-mapping>

       Servlet是一个供其他java程序(Servlet引擎)调用的java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才销毁。

        在Servlet的整个生命周期中,Servlet的init方法只被调用一次,而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doGet或doPost方法。
       
如果再<servlet>元素中配置一个<load-on-startup>元素,那么web应用程序在启动时,就会装载并创建Servlet的实例对象,以及调用Servlet实例对象的init()方法。该技术可以用来为web写一个initServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。

<servlet>
    <servlet-name>...</servlet-name>
    <servlet-class>...</servlet-class>
    <load-on-startup>1<load-on-startiup>
</servlet>      

       如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的url的访问请求都将交给缺省Servlet处理,即缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
       在<tomcat安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaulServlet的Servlet,并将这个Servlet设置为了缺省Servlet。当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet
       假设现在在工程名为test的WebRoot目录下新建一个1.html,在web.xml文件中并没有配置<servlet-mapping>,即在工程中没有Servlet映射成1.html,然后访问:http://localhost:8080/test/1.html时,这时候这个请求就交给缺省的Servlet,缺省的Servlet收到请求后,会首先看一下web应用下面有没有这个1.html,如果有则读取并返回到浏览器,如果没有则返回一个错误页面。
       

      

       当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因为Servlet只有一个实例化对象,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。那么如何解决Servlet中的线程安全问题呢?
       1) 如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。该接口没有任何方法,它起到了一个标志的作用。对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线成并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立Servlet实例对象。

       2)使用synchronized 关键字,synchronized能保证一次只有一个线程可以访问被保护的区段,从而可以通过同步块操作来保证线程的安全。

       3)尽量避免在Servlet里使用实例变量,只要在Servlet里面的任何方法里面都不使用共有的实例变量,那么该Servlet就是线程安全的。

       以上三种方式中,实现SingleThreadModel接口可以解决问题,但是并不是真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。另外,创建多个Servlet实例对象也会引起大量的开销。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为过时的了。

        同样,如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码。
       

ServletConfig对象    

       在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数,当Servlet配置初始化参数后,web容器在创建servlet实例对象时,会自动将这些参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。进而,程序员通过ServletConfig对象就可以得到当前Servlet的初始化参数信息。该对象的getInitParameter(String name)用来获得指定参数名的参数值,getInitParameterNames()用来获得所有参数名
        配置文件里进行如下配置:

<servlet>
    <servlet-name>ServletConfigDemo</servlet-name>
    <servlet-class>servletConfig.ServletConfigDemo</servlet-class>
    <init-param>
      <param-name>category</param-name>
      <param-value>book</param-value>
    </init-param>     
    <init-param>
      <param-name>school</param-name>
      <param-value>tongji</param-value>
    </init-param>     
    <init-param>
      <param-name>name</param-name>
      <param-value>java</param-value>
    </init-param>        
</servlet>
<servlet-mapping>
    <servlet-name>ServletConfigDemo</servlet-name>
    <url-pattern>/ServletConfigDemo</url-pattern>
</servlet-mapping>

在ServletConfigDemo.java中的代码如下:

public class ServletConfigDemo1 extends HttpServlet {
    ServletConfig config = null;    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String value = config.getInitParameter("category");//获取指定的初始化参数
        resp.getOutputStream().write((value + "<br/>").getBytes());
        
        Enumeration e = config.getInitParameterNames();//获取所有参数名
        while(e.hasMoreElements()){
            String name = (String) e.nextElement();
            value = config.getInitParameter(name);
            resp.getOutputStream().write((name + "=" + value + "<br/>").getBytes());
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }
    @Override
    public void init(ServletConfig config) throws ServletException {        
        this.config = config; //初始化时会将ServletConfig对象传进来
    }
}

 注:实际开发中,并不需要重写init方法,以上代码中重写init方法是为了说明config对象的传递过程。其实在父类的init方法中已经实现了该config的传递了,我们只要直接调用getServletConfig()就可以得到config对象,即在doGet方法中直接通过下面的调用方式获得ServletConfig对象:

ServletConfig config = this.getServletConfig();

那么ServletConfig对象有什么作用呢?一般主要用于以下情况:

         1)获得字符集编码;

         2)获得数据库连接信息;

         3)获得配置文件,查看struts案例的web.xml文件等。

ServletContext对象

       web容器启动时,它会为每个web应用程序都创建一个对应的ServletContext对象,它代表当前web应用。在ServletConfig接口中有getServletContext方法用来获得ServletContext对象;ServletContext对象中维护ServletContext对象的引用,也可以直接获得ServletContext对象。所以开发人员在编写Servlet时,可以通过下面两种方式获得ServletContext对象:

this.getServletConfig().getServletContext();
this.getServletContext();

       一般直接获得即可。
       一个web应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯,ServletContext对象通常也被称为context域对象。有如下主要方法:

getResource(String path);         //方法获得工程里的某个资源
getResourceAsStream(String path); //通过路径获得跟资源相关联的流
setAttribute(Sring name, Object obj); //方法往ServletContext里存对象,通过MAP集合来保存。
getAttribute(String name); //方法从MAP中取对象
getInitParameter(String name); //获得整个web应用的初始化参数,
//这个跟ServletConfig获取参数不同,这是在<context-param></context-param>中定义的,config对象里的getInitParameter方法获得的是具体某个servlet的初始化参数。
getNamedeDispatcher(String name); //方法用于将请求转给另一个servlet处理,参数表示要转向的servlet。
//调用该方法后,要紧接着调用forward(ServletRequest request, ServletResponse response)方法
getServletContextName(); // 获得web应用的名称。

    ServletContext应用有哪些呢?
         1)多个Servlet通过ServletContext对象实现数据共享(见下面的Demo1和Demo2)
         2)获取web应用的初始化参数(见Demo3)
         3)实现Servlet的转发(见Demo4和Demo5)
         4)利用ServletContext对象读取资源文件(xml或者properties)(见Demo6)


    Demo1:往context域中存入数据

public class ServletContextDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {        
        String data = "adddfdf";
        ServletContext context = this.getServletConfig().getServletContext();
        context.setAttribute("data", data);//将数据写到ServletContext
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }    
}

Demo2:从context域中读取数据

public class ServletContextDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {        
        ServletContext context = this.getServletContext();
        String data = (String) context.getAttribute("data");//通过键值从ServletContext中获取刚才存入的数据
        resp.getOutputStream().write(data.getBytes());
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        
        doGet(req, resp);        
    }    
}

Demo3:获取整个web应用的初始化参数

public class ServletContextDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
 
        ServletContext context = this.getServletContext();
        String url = context.getInitParameter("url");//获取整个web应用的初始化参数,参数是在<context-param></context-param>中定义的
        resp.getOutputStream().write(url.getBytes());
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
 
        doGet(req, resp);        
    }        
}

Demo4:实现转发

public class ServletContextDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        RequestDispatcher rd = context.getRequestDispatcher("/ServletContextDemo5");
        rd.forward(req, resp);//将请求转发给ServletContextDemo5.java处理
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }    
}

Demo5:

public class ServletContextDemo5 extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getOutputStream().write("ServletDemo5".getBytes());//处理ServletDemo4传过来的请求
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }
    
}

Demo6:读取资源文件

public class ServletContextDemo6 extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //test1(resp);
        //test2(resp);
        //test3(resp);
        //test4();
    }
    
    //读取文件,并将文件拷贝到e:\根目录,如果文件太大,只能用servletContext,不能用类装载器
    private void test4() throws FileNotFoundException, IOException {
        String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");
        String filename = path.substring(path.lastIndexOf("\\")+1);
        
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
        byte buffer[] = new byte[1024];
        int len = 0;
        
        FileOutputStream out = new FileOutputStream("e:\\" + filename);
        while((len = in.read(buffer)) > 0){
            out.write(buffer, 0, len);
        }
    }
 
    //使用类装载器读取源文件(不适合装载大文件)
    private void test3(HttpServletResponse resp) throws IOException {
        ClassLoader loader = ServletContextDemo6.class.getClassLoader();
        InputStream in = loader.getResourceAsStream("db.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        resp.getOutputStream().write(driver.getBytes());
    }
 
    private void test2(HttpServletResponse resp) throws FileNotFoundException, IOException {
        String path = this.getServletContext().getRealPath("/WEB-INF/classes/db.properties");//获取绝对路径
        FileInputStream in = new FileInputStream(path);//传统方法,参数为绝对路径
        
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        resp.getOutputStream().write(driver.getBytes());
    }
 
    //读取web工程中资源文件的模板代码(源文件在工程的src目录下)
    private void test1(HttpServletResponse resp) throws IOException {
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
        ////注:源文件若在工程的WebRoot目录下,则上面参数路径直接为"/db.properties",因为WebRoot即代表web应用
        Properties prop = new Properties();
        prop.load(in);//先装载流
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        resp.getOutputStream().write(driver.getBytes());
    }
 
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }    
}

 

参考:

https://blog.csdn.net/eson_15/article/details/51245629

posted on 2018-10-12 20:59  溪水静幽  阅读(174)  评论(0)    收藏  举报