什么是Servlet

Servlet

什么是Servlet

Servlet是JavaWeb三大组件之一,它属于动态资源。接收浏览器请求,并作出响应。

Servlet由我们自己编写,必须实现javax.servlet.Servlet接口

public interface Servlet {
    /** Called by the servlet container to indicate to a servlet that the servlet is being placed into service. 生命周期方法,调用一次,注意传入的参数 ServletConfig,我们可以在init中将其保存下来。*/
    void init(ServletConfig var1) throws ServletException;
	
    /** Returns a ServletConfig object, which contains initialization and startup parameters for this servlet. */
    ServletConfig getServletConfig();
	
    /** Called by the servlet container to allow the servlet to respond to a request.  具体的处理浏览器的请求的方法。 */
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
	
    /**  Returns information about the servlet, such as author, version, and copyright. */
    String getServletInfo();
	
    /** Called by the servlet container to indicate to a servlet that the servlet is being taken out of service. 生命周期方法,销毁时调用,可做一些资源释放操作。*/
    void destroy();
}

Servlet实现

完全手动实现

  1. 新建AServlet实现Servlet interface

    public class AServlet implements Servlet {
    
        /**
         * 生命周期之内,只会执行一次。此方法并不是服务器启动即开始调用,默认只有在第一次访问时创建。
         * 但可以通过在web.xml中配置达到一启动服务就加载此Servlet的方法
         */
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            System.out.println("Init success");
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    		PrintWriter writer = servletResponse.getWriter();
            writer.println("<h1> Hello My Servlet <h1>");
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        /**
         * 临死之前,执行一次。可以用来做一些资源释放的操作。
         */
        @Override
        public void destroy() {
            System.out.println("I am done.");
        }
    }
    
  2. 在web.xml中注册

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <servlet>
            <servlet-name>AServlet</servlet-name>
            <servlet-class>cn.edu.ustc.AServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>AServlet</servlet-name>
            <url-pattern>/AServlet</url-pattern>
        </servlet-mapping>
    </web-app>
    
  3. 访问

    Servlet的生命周期

    • 出生:一个Servlet类型,服务器只创建一个实例对象。 例如在我们首次访问时,服务器通过“/AServlet”找到了绑定的Servlet名称为AServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建AServlet的实例。当我们再次访问时,服务器就不会再次创建AServlet实例了,而是直接使用上次创建的实例
    • 服务:服务器接收到一次请求,就会调用service() 方法一次
    • 销毁:Servlet是不会轻易离去的,通常都是在服务器关闭时Servlet才会离去!在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法

    ServletConfig

    ServletConfig对应web.xml中的<servlet>元素,由服务器创建。我们可以在init方法中,像GenericServlet那样保存下来。

    public interface ServletConfig {
        /** Returns the name of this servlet instance. */
        String getServletName();
    	
        /** Returns a reference to the ServletContext in which the caller is executing. */
        ServletContext getServletContext();
    	
        /** Gets the value of the initialization parameter with the given name. */
        String getInitParameter(String var1);
    	
        /** 
        * Returns the names of the servlet's initialization parameters as an Enumeration 	 * of String objects, or an empty Enumeration if the servlet has no initialization 	   * parameters.
        */
        Enumeration<String> getInitParameterNames();
    }
    

    如果在web.xml的servlet标签下面我们添加了额外的参数,我们就可以通过getInitParameter()方法获得

    <servlet>
            <servlet-name>AServlet</servlet-name>
            <servlet-class>cn.edu.ustc.AServlet</servlet-class>
            <init-param>
                <param-name>param-name</param-name>
                <param-value>param-value</param-value>
            </init-param>
        </servlet>
    

JavaEE自带实现

GenericServlet

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;
	
    /**
    * 保存ServletConfig
    */
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
	
    /**
    * 子类继承时如果需要重写init方法,最好覆盖此方法。覆盖上面init也可以,但一定要有super,否则成员变量
    * config不会被赋值,其他的config相关方法也无法正确使用。
    */
    public void init() throws ServletException {
    }
    
    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public String getServletName() {
        return this.config.getServletName();
    }
    
    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

}

通过分析代码可以发现,GenericServlet主要做了两件事

  • 保存系统生成的ServletConfig,并实现ServletConfig接口,子类无需再获取ServletConfig对象后再调用其中方法,直接调用GenericServlet中的ServletConfig实现接口方法即可。
  • 增加了日志方法。注意:ServletContext.log()方法,是根据tomcat目录下conf中logging.properties写入本地文档,一般来讲可以在tomcat下的logs中找到。但是如果是使用的idea,我们配置的tomcat目录并不会被直接使用,而是复制了一份放在了~/.intelliJIdea/system/tomcat中,所以如果调用了servletContext.log()或者GenericServlet中的log方法,需要注意日志所在的目录。

HttpServlet

HttpServlet是GenericServlet的子类,提供了对Http协议的特殊支持,而这正是我们BS所需要的!所以我们一般会通过继承HttpServlet来完成自定义的Servlet。

public abstract class HttpServlet extends GenericServlet {
    
     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }

        this.service(request, response);
    }
    
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
}

由以上代码可知,HttpServlet核心操作

  1. 重写service方法,首先判断是否是Http层的请求,是则进入本类中的service方法,否则抛出异常
  2. 本类中的service方法,对Http请求方式判别后,调用相应的处理方法

主要关心下get方式所作的工作,可以通过一张流程图理解

ServletContext

一个项目只能有一个ServletContext,不管使用的什么方法获取的ServletContext都是同一个。

作用:在整个Web应用的动态资源中传递数据。

  • ServletContext在项目启动时创建
  • 在项目关闭时销毁

ServletContext获取

  1. ServletConfig # getServletContext();
  2. GenericServlet#getServletContext();
  3. HttpSession#getServletContext();
  4. ServletContextEvent#getServletContext();

域对象

域对象就是在多个Servlet中传递数据,ServletContext即为域对象之一。

四个域对象

  1. PageContext;
  2. ServletRequest;
  3. HttpSession;
  4. ServletContext;

存取方法:

  1. void setAttribute(String name, Object value)
  2. Object getAttribute(String name)
  3. void removeAttribute(String name)
  4. Enumeration getAttributeNames():获取所有域属性的名称

在Servlet中获取资源的方法

在获取指定资源之前,一定要搞清楚资源所在位置,以及资源相对于用来获取资源的对象之间的相对位置关系。

  1. 获取真实路径

    getServletContext().getRealPath("/b.txt");
    getServletContext().getRealPath("/WEB-INF/a.text");
    
  2. 获取资源流

    InputStream in = servletContext.getResourceAsStream(“/a.txt”);
    InputStream in = servletContext.getResourceAsStream(“/WEB-INF/b.txt”);
    
    
  3. 获取指定目录下所有资源路径

    /** 注意此方法必须以斜杠开头 */
    Set set = context.getResourcePaths("/WEB-INF");
    
    
  4. 通过ClassLoad获取资源(注意与通过class获取的区别)

    ClassLoader classLoader = this.getClass().getClassLoader();
     
    // 以斜杠开头:斜杠表示classes目录  
    // 若想过去b.txt a.txt
    classLoader.getResourceAsStream("/../../b.txtt");
    classLoader.getResourceAsStream("/../a.txt");
    
    

Servlet总结

  1. Servlet是线程安全的吗?

    一个类型的Servlet只会有一个实例对象,那么必然会出现一个Servlet对象处理多个请求,请求可能来自多个线程,那就面临并发修改问题。

    所以:不应该在Servlet中便宜创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。那为什么不做成线程安全的?因为效率问题。

  2. 让服务器在启动时就创建Servlet

    默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。

    	<servlet>
            <servlet-name>AServlet</servlet-name>
            <servlet-class>cn.edu.ustc.AServlet</servlet-class>
            <load-on-startup>0</load-on-startup>
        </servlet>
    
        <servlet>
            <servlet-name>BServlet</servlet-name>
            <servlet-class>cn.edu.ustc.BServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
    

    在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0整数,它的使用是服务器启动时创建Servlet的顺序。上例中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为AServlet、bServlet。

  3. url-pattern

    servlet的url-pattern匹配规则

posted @ 2019-04-15 16:23  孟海涛  阅读(454)  评论(0编辑  收藏  举报