public class MyServlet implements Servlet { private ServletConfig servletConfig; public init(ServletConfig servletConfig) throw ServletException { this.servletConfig = servletConfig; } public ServletConfig getServletConfig() { return this.servletConfig; } ... } 也就是说,实现类中还是要有一个private的ServletConfig变量,而且是必须的。幸好的是,这个变量跟业务无关,并且通常你只是用它来获取信息。而init的第一件任务必然初始化这个ServletConfig变量。 接着分析一下service方法,他有两个参数ServletRequest和ServletResponse,这两个参数显然也是容器传入的。当请求到来时,容器会将请求封装成一个ServletRequest对象,同时生成一个ServletResponse对象,都传递给servlet。后者从ServletRequest对象中获取必要信息,然后把处理结果写入到ServletResponse对象。需要注意的是,ServletRequest和ServletResponse都只是接口,它们的实现由容器完成。 四、ServletConfig接口 这个接口也是由容提负责具体实现,可以看出,凡是涉及底层支持的接口,都是容器负责实现;而涉及业务的就是自己实现,我觉得这个设计很棒。先看接口的架构: public interface ServletConfig { public String getServletName(); public ServletContext getServletContext(); public String getInitParameter(String name); public Enumeration<String> getInitParameterNames(); } 只有四个方法,最重要的当然是后两个,用于获取配置文件中的参数。但是分析它们之前,先要知道配置文件到底是啥,在哪儿?这就要从servlet规范规定的Web应用目录结构说起。一个动态的Web应用包含servlet编译后的class文件,html静态文件,jsp文件,js脚本,各种配置文件和图像文件等其他多媒体资源文件。这些文件显然不能混杂的扔到一个目录中。不管使用Tomcat还是Jetty做容器,首先要有自己的工程目录存放上述文件。比如工程目录起名叫MyWeb,那么servlet规范规定了MyWeb下必须要有一个WEB-INF目录。WEB-INF目录下面要有两个子目录classes和lib,前者存放编译后的class文件,后者存放用到的jar包。WEB-INF下还要有一个最重要的配置文件,这就是我们熟悉的web.xml:
<web-app> <servlet> <servlet-name>CometServlet</servlet-name> <servlet-class>comet.CometServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CometServlet</servlet-name> <url-pattern>/test/comet</url-pattern> </servlet-mapping> </web-app> 最顶层的标签是web-app,它下面有很多子标签,但是现在我们先关注<servlet>和<servlet-mapping>,其他标签用到的时候再慢慢加进来。 我们看到<servlet>标签中有两个元素:<servlet-name>和<servlet-class>,后者是servlet实现类的全限定类名,容器会自动从WEB-INF/classes目录下去查找;而前者是我们给它起的名字——可以任意起名。getServletName方法一般也就是返回的这个名字。这个名字是为<servlet-mapping>服务的,这个标签的的主要作用将一个url映射到一个servlet上,当浏览器访问这个url时,容器就会调用这个servlet的service方法,将其产生的html标签返回给浏览器。 在此有必要提一下url:配置文件中的url显然不是一个完整的url,它是有前缀的,前缀包括三部分,IP,端口号,web应用名。其中IP和端口号之间用冒号分割,但是web应用名是可以为空的。当在浏览器中直接访问IP:端口号,比如localhost:8080时,浏览器实际访问的路径是localhost:8080/,也就是会默认加上一个斜线。这个斜线代表根目录,至于根目录到底指向哪个web应用,不同的容器有不同的方案,比如Tomcat会访问ROOT目录。为了区别根目录的默认值,我们最好还是给自己的web应用起一个有意义的名字。对于Tomcat而言,应用的名字是在server.xml文件中配置的,具体细节先参考这里,等我解读Tomcat源码时会仔细分析。 应用名后面一般不再有斜线,所以<url-pattern>标签的值往往以斜线开头,斜线后面的部分可以是一个正则表达式,比如/*代表任何路径,也就是所有访问这个web应用的请求都会由对应的servlet处理。当然也可以把值写的非常具体,那么对应的servlet就只负责这一个url的请求。但是这里有一点需要非常注意:具体url的优先级要高于泛用的url。比如servlet1对应的url是/*,而servlet2对应的url是/test,那么浏览器访问/test时,容器将优先调用servlet2。 好了,扯了半天都没有说到ServletConfig。servlet初始化参数是<servlet>标签内定义的,如下: <servlet> <servlet-name>CometServlet</servlet-name> <servlet-class>comet.CometServlet</servlet-class> <init-param> <param-name>color</param-name> <param-value>red</param-value> </init-param> </servlet> <param-name>标签定义参数名,<param-value>给出了参数值,它们两个要用<init-param>包围起来。当定义多个初始化参数时,就需要多个<init-param>标签。配置文件中定义的参数取出来都是String类型的,唯一的类型。而getInitParameterNames方法返回的Enumeration中,包含了所有的<init-param>。 容器是在什么时候读取配置文件中这些初始化参数呢?首先要明白的一点是,容器启动时不会马上把所有的Servlet都加载到内存中,而是等到有浏览器访问对应的url时才会去加载这个servlet,也就是new这个实现类的对象。读取初始化参数应该是在new完对象、调用init方法之前。 ServletConfig还有一个方法是getServletContext,它返回一个ServletContext对象,我们稍后会分析它。 本篇先写这么多吧。