上一篇介绍了在Web项目中web.xml文件的配置信息,本篇主要介绍里面非常重要的配置——Servlet配置,重点介绍与Servlet相关的几个接口和类,包括Servlet接口、ServletConfig接口、ServletContext接口、GenericServlet类、HttpServlet类。
1、Servlet介绍
什么是Servlet:
百度的解释是“小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容”。Servlet是Java语言里面的一个接口,任何直接或间接实现了Servlet接口的类都可称为Servlet类。
Servlet与Tomcat的关系:
Tomcat 是Web应用服务器,是Servlet的容器。Tomcat 作为Servlet容器,在启动时可以创建Servlet类的实例,调用init()方法对Servlet进行初始化,同时负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。可以说,Servlet是一种运行在支持Java语言的服务器上的组件。在web.xml文件中,通过<servlet>标签配置Servlet类的相关信息,详细配置请见 http://www.cnblogs.com/Y-oung/p/8401549.html 。
2、Servlet接口
先看源码:
package javax.servlet; import java.io.IOException; public abstract interface Servlet { public abstract void init(ServletConfig paramServletConfig) throws ServletException; public abstract ServletConfig getServletConfig(); public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse) throws ServletException, IOException; public abstract String getServletInfo(); public abstract void destroy(); }
init():初始化方法,在Tomcat对Servlet实例化后,Servlet容器会调用init()方法来初始化该对象,让Servlet对象在处理客户请求前可以完成一些初始化工作,例如:建立数据库的连接,获取配置信息等。在Servlet的整个生命周期中,init()方法自始至终只会被调用一次。init()方法有一个类型为ServletConfig的参数,Servlet容器通过这个参数向Servlet传递配置信息。Servlet使用ServletConfig对象从web.xml文件中获取以名-值对形式提供的初始化参数。另外,在Servlet中,还可以通过ServletConfig对象获取描述Servlet运行环境的ServletContext对象,使用该对象,Servlet可以和它的Servlet容器进行通信。
如图中红圈里面的配置信息可以在MainServlet类的init()方法里面解析。
getServletConfig():该方法返回容器调用init()方法时传递给Servlet对象的ServletConfig对象,ServletConfig对象包含了Servlet的初始化参数(如上图红圈里的配置信息)。关于ServletConfig接口下面会介绍。
service():该方法是用来处理客户端请求(在service()方法被容器调用之前,必须确保init()方法正确完成)的核心方法。容器会构造一个表示客户端请求信息的请求对象(类型为ServletRequest)和一个用于对客户端进行响应的响应对象(类型为ServletResponse)作为参数传递给service()。在service()方法中,Servlet对象通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。
getServletInfo():该方法返回一个包括了关于Servlet的信息的字符串(如作者、版本和版权等信息),返回的应该是纯文本字符串,而不是任何类型的标记。
destroy():该方法用来销毁Servlet对象。当容器检测到一个Servlet对象应该从服务中被移除的时候,容器会调用该对象的destroy()方法,以便让Servlet对象可以释放它所使用的资源,该方法同样只会执行一次。当需要释放内存或者容器关闭时,容器就会调用Servlet对象的destroy()方法,在Servlet容器调用destroy()方法前,如果还有其他的线程正在service()方法中执行容器会等待这些线程执行完毕或者等待服务器设定的超时值到达。一旦Servlet对象的destroy()方法被调用,容器不回再把请求发送给该对象。如果需要改Servlet再次为客户端服务,容器将会重新产生一个Servlet对象来处理客户端的请求。在destroy()方法调用之后,容器会释放这个Servlet对象,在随后的时间内,该对象会被java的垃圾收集器所回收。
可以看到,Servlet接口里面只有五个方法:init()、getServletConfig()、service()、getServletInfo()、destroy()。其中init()、service()、destroy()三个方法是和servlet生命周期相关的方法,在生命周期的不同时间段内会执行不同的方法,其顺序是init()->service()->destroy()。其中init()、destroy()方法在servlet生命周期中都只执行一次,而service()方法可以多次执行。Servlet接口是Servlet类最底层、最基础、最重要的接口。
3、ServletConfig接口
先看源码:
package javax.servlet; import java.util.Enumeration; public abstract interface ServletConfig { public abstract String getServletName(); public abstract ServletContext getServletContext(); public abstract String getInitParameter(String paramString); public abstract Enumeration<String> getInitParameterNames(); }
getServletName():获取web.xml文件里面的servlet的名字(即<servlet-name>标签配置的名字)。
getServletContext():获取ServletContext对象,下面会介绍ServletContext接口。
getInitParameter():获取web.xml文件在servlet标签中配置的参数值(注意是servlet标签,而不是其他标签配置的参数值)。
getInitParameterNames():获取在Servlet中所有初始化参数的名字,也就是key值,可以通过key值,来找到各个初始化参数的value值(注意返回的是枚举类型)。
可以看到,ServletConfig接口里面的方法主要是获取web.xml文件里和servlet相关的配置信息。
4、ServletContext接口
先看源码:
package javax.servlet; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.EventListener; import java.util.Map; import java.util.Set; import javax.servlet.descriptor.JspConfigDescriptor; public abstract interface ServletContext { public static final String TEMPDIR = "javax.servlet.context.tempdir"; public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs"; public abstract ServletContext getContext(String paramString); public abstract String getContextPath(); public abstract int getMajorVersion(); public abstract int getMinorVersion(); public abstract int getEffectiveMajorVersion(); public abstract int getEffectiveMinorVersion(); public abstract String getMimeType(String paramString); public abstract Set<String> getResourcePaths(String paramString); public abstract URL getResource(String paramString) throws MalformedURLException; public abstract InputStream getResourceAsStream(String paramString); public abstract RequestDispatcher getRequestDispatcher(String paramString); public abstract RequestDispatcher getNamedDispatcher(String paramString); public abstract Servlet getServlet(String paramString) throws ServletException; public abstract Enumeration<Servlet> getServlets(); public abstract Enumeration<String> getServletNames(); public abstract void log(String paramString); public abstract void log(Exception paramException, String paramString); public abstract void log(String paramString, Throwable paramThrowable); public abstract String getRealPath(String paramString); public abstract String getServerInfo(); public abstract String getInitParameter(String paramString); public abstract Enumeration<String> getInitParameterNames(); public abstract boolean setInitParameter(String paramString1, String paramString2); public abstract Object getAttribute(String paramString); public abstract Enumeration<String> getAttributeNames(); public abstract void setAttribute(String paramString, Object paramObject); public abstract void removeAttribute(String paramString); public abstract String getServletContextName(); public abstract ServletRegistration.Dynamic addServlet(String paramString1, String paramString2); public abstract ServletRegistration.Dynamic addServlet(String paramString, Servlet paramServlet); public abstract ServletRegistration.Dynamic addServlet(String paramString, Class<? extends Servlet> paramClass); public abstract <T extends Servlet> T createServlet(Class<T> paramClass) throws ServletException; public abstract ServletRegistration getServletRegistration( String paramString); public abstract Map<String, ? extends ServletRegistration> getServletRegistrations(); public abstract FilterRegistration.Dynamic addFilter(String paramString1, String paramString2); public abstract FilterRegistration.Dynamic addFilter(String paramString, Filter paramFilter); public abstract FilterRegistration.Dynamic addFilter(String paramString, Class<? extends Filter> paramClass); public abstract <T extends Filter> T createFilter(Class<T> paramClass) throws ServletException; public abstract FilterRegistration getFilterRegistration(String paramString); public abstract Map<String, ? extends FilterRegistration> getFilterRegistrations(); public abstract SessionCookieConfig getSessionCookieConfig(); public abstract void setSessionTrackingModes( Set<SessionTrackingMode> paramSet) throws IllegalStateException, IllegalArgumentException; public abstract Set<SessionTrackingMode> getDefaultSessionTrackingModes(); public abstract Set<SessionTrackingMode> getEffectiveSessionTrackingModes(); public abstract void addListener(String paramString); public abstract <T extends EventListener> void addListener(T paramT); public abstract void addListener(Class<? extends EventListener> paramClass); public abstract <T extends EventListener> T createListener( Class<T> paramClass) throws ServletException; public abstract void declareRoles(String[] paramArrayOfString); public abstract ClassLoader getClassLoader(); public abstract JspConfigDescriptor getJspConfigDescriptor(); }
Tomcat为每个web项目都创建一个ServletContext实例,在Tomcat启动时创建,服务器关闭时销毁,在一个web项目中共享数据,管理web项目资源,为整个web配置公共信息等。每一个web项目都存在一个ServletContext实例,每个Servlet都可以访问它。可以看到,ServletContext接口里面的方法很多,下面根据功能介绍其中的方法。
在Web应用范围内存取共享数据的方法:
setAttribute(String paramString, Object paramObject):把一个Java对象与一个属性名绑定,并把它存入到ServletContext中。paramString参数指定属性名,paramObject参数标识共享的数据。
getAttribute(String name):根据参数给定的属性名,返回一个Object类型的对象,它表示ServletContext中与属性名匹配的属性值。
getAttributeNames():返回一个Enumeration对象,该对象包含了所有存放在ServletContext中的属性名。
removeAttribute(String name):根据参数指定的属性名,从ServletContext中删除匹配的属性。
访问当前Web应用的资源:
getContextPath():返回当前Web应用的URL入口。
getInitParameter(String name):根据给定的参数名,返回Web应用范围内初始化参数值。在web.xml文件中,直接在<web-app>跟元素下定义的<context-param>元素表示应用范围内的初始化参数。
getInitParameterNames():返回一个Enumeration对象,它包含了Web应用范围内的所有初始化参数。
getServletContextName():返回Web应用的名字,即web.xml文件中<display-name>元素的值。
getRequestDispatcher(String path):返回一个用于向其他Web组件转发请求的RequestDispatcher对象。
访问Servlet容器中的其他Web应用:
getContext(String uripath):根据参数指定的URI,返回当前Servlet容器中其他Web应用的ServletContext对象。
访问Servlet容器的相关信息:
getMajorVersion():返回Servlet容器支持的Java Servlet API的主版本号。
getMinorVersion();返回Servlet容器支持的Java Servlet API的次版本号。
getServletInfo():返回Servlet容器的名字和版本。
访问服务器端的文件系统资源:
getRealPath(String path):根据参数指定的虚拟路径,返回文件系统中的一个真实路径。
getResource(String path):返回一个映射到参数指定的路径的URL。
getResourceAsStream(String path):返回一个用于读取参数指定的文件的输入流。
getMimeType(String file):返回参数指定的文件的MIME类型。
输出日志:
log(String msg)向Servlet的日志文件中写日志。
log(String message , java.lang.Throwable throwable):向Servlet的日志中写错误日志,以及异常的堆栈信息。
5、GenericServlet类
先看源码:
package javax.servlet; import java.io.IOException; import java.io.Serializable; import java.util.Enumeration; public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; public void destroy() { } public String getInitParameter(String name) { return getServletConfig().getInitParameter(name); } public Enumeration<String> getInitParameterNames() { return getServletConfig().getInitParameterNames(); } public ServletConfig getServletConfig() { return this.config; } public ServletContext getServletContext() { return getServletConfig().getServletContext(); } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; init(); } public void init() throws ServletException { } public void log(String msg) { getServletContext().log(getServletName() + ": " + msg); } public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } public abstract void service(ServletRequest paramServletRequest, ServletResponse paramServletResponse) throws ServletException, IOException; public String getServletName() { return this.config.getServletName(); } }
可以看到,GenericServlet类是一个抽象的(abstract)类,所以它无法被实例化。它继承了三个接口,其中Servlet和ServletConfig接口是前面介绍过的,可以看出GenericServlet类里面大多数方法都是这两个接口里面的方法,所以这个类其实是整合了这两个接口,给出了设计servlet的一些骨架,定义了servlet生命周期,还有一些得到名字、配置、初始化参数的方法,使得编写servlet类时更方便。GenericServlet类里面所有继承了这两个接口的方法的功能,在本文前面已经详细介绍,这里只说明一下这个类里面新增的方法:log(String msg)、log(String message, Throwable t)、init()。
两个log()方法:通过ServletContext对象将Servlet的类名和给定的信息写入log文件中。
两个init()方法:init(ServletConfig config)方法是实现了Servlet接口中的方法,作用是可以通过getServletConfig()方法获取ServletConfig对象。如果你覆盖了这个方法,你必须调用super.init(config),这样GenericServlet类的其他方法才能正常工作。而它下面还调用了无参的init()方法,也就是GenericServlet类自定义的无参方法。为什么还要定义这个无参数的init()方法呢?其实这个无参数的init()方法是专门留给开发者使用的,开发者只需要覆盖这个方法就可以很方便的在方法里面扩充一些功能,比如对自己写的Servlet类初始化时可以在这个方法里面进行,解析web.xml文件里面关于servlet配置的信息时可以在这个方法里面。这样就不需要覆盖init(ServletConfig config)方法了,不需要存储config对象,也不需要调用super.init(config)。
尽管GenericServlet类实现了Servlet和ServletConfig接口的功能,但它的service方法中的参数还是ServletRequest,ServletResponse,它处理的还只一般的Servlet请求,并没有跟http相关对象挂钩。service方法是和应用层协议无关的,也就是说你有可能用非http协议实现它。而我们平时通过浏览器访问网站时,基本上都是HTTP请求(当然还有FTP请求),请求方式包括GET、POST等,显然如果直接使用service方法会很繁琐,所以提供了一个HttpServlet类来解决这些问题,这个类继承了GenericServlet类,除了能实现其所有方法,还根据HTTP请求的特性进行了扩充,使得其更能有效地处理基于HTTP协议的请求。
6、HttpServlet类
这个类的源码有点长,不再详细展示,有兴趣可以去看源码,这里通过类的结构图说明。
其中HTTP协议的请求类型与其对应的实现方法见下表:
该类继承了GenericServlet类,用于接收基于Http协议的请求,并对请求进行处理,然后做出响应。一般在web开发中,开发者自己编写的Servlet类只需要继承HttpServlet类即可。这里我们重点介绍两个service()方法。
service(ServletRequest req, ServletResponse res):通过源码可以发现,此方法是GenericServlet类的service()方法的实现,他把ServletRequest和ServletResponse对象分别强制转化为HttpServletRequest和HttpServletResponse对象,并把强制转化后的对象作为参数,调用另一个重载的service(HttpServletRequest req, HttpServletResponse resp)方法。
service(HttpServletRequest req, HttpServletResponse resp):通过源码可以看出,这个方法会根据不同的HTTP请求类型(GET、POST或其他),调用不同的实现方法(doGet()、doPost()或其他)。这样一来,当开发者自己编写Servlet类时(继承HttpServlet类),我们只需要覆盖(重写)doGet()、doPost()或其他doXXX()方法就可以了,而不需要覆盖(重写)service()方法,这样就简化了许多。实际上笔者在开发中遇到的几个项目的servlet类中都是覆盖(重写)了doGet()、doPost()方法,而没有覆盖(重写)service()方法。
7、总结
本篇着重介绍了和servlet相关的几个比较重要的接口和类,下面通过图片说明它们之间的继承关系:
在实际的web项目中,如果需要开发者自己写servlet类,一般直接继承HttpServlet类即可。在用Tomcat加载servlet类时,如果需要初始化一些信息(比如解析web.xml文件中<servlet>标签下的配置信息),可以覆盖(重写)无参的init()方法;如果要处理GET请求,可以覆盖(重写)doGet()方法;如果要处理POST请求,可以覆盖(重写)doPost()方法。init()、doGet()、doPost()这三个方法是笔者在开发web项目中遇到最多的,基本上自己写的servlet类里面,都是继承了HttpServlet类,覆盖(重写)了这三个方法。