05.Servlet
一、Servlet入门
1、什么是Servlet
Servlet是JavaWeb的三大组件(Servlet、Filter、Listener)之一,它属于动态资源。Servlet的作用是处理请求,服务器(Tomcat)会把接收到的请求交给Servlet来处理,Servlet的任务有:
- 接收请求数据;
- 处理请求;
- 完成响应。
补充:
Filter(过滤器)的用法与Servlet极其相似,但是Servlet主要负责处理请求,而Filter主要负责拦截请求和放行。
Listener(监听器),我们在JavaSE开发或者Android开发时,经常会给按钮加监听器,当点击这个按钮就会触发监听事件,调用onClick方法,本质是方法回调。在JavaWeb的Listener也是这么个原理,但是它监听的内容不同,它可以监听Application、Session、Request域对象,当这些对象发生变化就会调用对应的监听方法。
Tomcat是Web应用服务器,是一个Servlet/JSP容器。Tomcat作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而Servlet是一种运行在支持Java语言的服务器上的组件。
2、实现Servlet的方式(由我们自己来写!)
Servlet技术的核心是Servlet接口,它是所有Servlet类必须直接或者间接实现的一个接口,它规定了每个Servlet所必须实现的方法。
实现Servlet有三种方式:
- 实现javax.servlet.Servlet接口;
- 继承javax.servlet.GenericServlet抽象类;
- 继承javax.servlet.http.HttpServlet抽象类;
我们看一下Servlet结构体系的UML图。
其中,Servlet、ServletConfig和Serializable是三个接口,Servlet和ServletConfig是javax.servlet包中的接口,Serializable是java.io包中的序列化接口;GenericServlet是部分实现上面三个接口的抽象类,位于javax.servlet包中;HttpServlet是继承了GenericServlet的抽象类,位于javax.servlet.http包中。实际应用时我们需要从GenericServlet和HttpServlet两个抽象类继承出自己的Servlet类,并实现所需的功能。
通常我们会去继承HttpServlet类来完成我们的Servlet,但学习Servlet还要从javax.servlet.Servlet接口开始学习。
另外,Servlet依赖于ServletRequest和ServletResponse接口,这两个接口负责为Servlet接收和发送信息。 HttpServlet也类似,需要依赖于HttpServletRequest和HttpServletResponse接口。
3、创建HelloServlet应用
完成Servlet需要分为两步:
①编写Servlet类;
②在web.xml文件中配置Servlet或者使用注解的形式@WebServlet()。
下面我们按照这两步来创建Servlet。
开始我们的第一个Servlet应用,项目名称为HelloServlet,选择Eclipse工具栏的File > New > Other选项,进入新建工程向导界面,选择Web分类下的Dynamic Web Project(动态网页项目)。
在创建项目时,我们要勾选“Generate web.xml deployment descriptor”,才会创建web.xml文件。如果创建项目时没有勾选自动创建web.xml文件,我们后期也可以在创建好的项目上点击右键,选择Java EE Tools > Generate Deployment Descriptor Stup选中后,项目名下即出现web.xml。(如果已经存在web.xml文件,Generate Deployment Descriptor Stup变为灰色)。
在src目录下创建包com.sdbi.servlet,在包下创建AServlet类,去实现javax.servlet.Servlet接口,并实现其抽象方法。
我们暂时忽略Servlet中其他四个方法,只关心service()方法,因为它是用来处理请求的方法。我们在该方法内给出一条输出语句。
public class AServlet implements Servlet { public void init(ServletConfig config) throws ServletException {} public ServletConfig getServletConfig() { return null; } public void destroy() {} public String getServletInfo() { return null; } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("Hello AServlet!"); } }
将项目编译生成的class文件指定到/WebContent/WEB-INF/classes文件夹下,项目上右键 > Build path > Configure Build Path > Source,Default output folder目录为“项目名/WebContent/WEB-INF/classes”。
在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>HelloServlet</display-name> <servlet> <servlet-name>aaa</servlet-name> <servlet-class>com.sdbi.servlet.AServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>aaa</servlet-name> <url-pattern>/AServlet</url-pattern> </servlet-mapping> </web-app>
在web.xml中配置Servlet的目的其实只有一个,就是把访问路径与一个Servlet绑定到一起,上面配置是把访问路径:“/AServlet”与“com.sdbi.servlet.AServlet”绑定到一起。
- <servlet>:指定AServlet这个Servlet的名称为aaa;
- <servlet-mapping>:指定/AServlet访问路径所访问的Servlet名为aaa。
<servlet>和<servlet-mapping>通过<servlet-name>这个元素关联在一起了!
部署发布Web应用,项目上右键 > Run As > Run on Server,打开浏览器,地址栏里输入http://localhost:8080/HelloServlet/AServlet(http://主机名:端口号/项目名称/Servlet路径名)
查看控制台,发现输出一条语句,说明Servlet运行成功。
二、Servlet接口
1、Servlet接口中的方法
javax.servlet.Servlet接口包含以下5个方法:
(1)init() 方法
用于初始化,在Servlet启动时调用。该方法接收一个ServletConfig类型的参数,Servlet容器通过这个参数向Servlet传递初始化配置信息。
public void init(ServletConfig config) throws ServletException;
(2)service() 方法
Servlet通过这个方法,从req获得客户端请求,处理并生成结果,再通过res发送给客户端。
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
(3)destroy()方法
Servlet销毁时执行的方法,释放Servlet对象占用的资源。
public void destroy();
(4)getServletConfig() 方法
获取包含Servlet各种信息的ServletConfig对象,返回值是Servlet容器调用init(ServletConfig config)方法时传递的ServletConfig对象。
public ServletConfig getServletConfig();
(5)getServletInfo()方法
将Servlet的信息作为字符串返回。
public String getServletInfo();
我们在这5个方法中分别添加输出语句。
public class AServlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException { System.out.println("init()..."); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("service()..."); } @Override public void destroy() { System.out.println("destroy()..."); } @Override public ServletConfig getServletConfig() { System.out.println("getServletConfig()..."); return null; } @Override public String getServletInfo() { System.out.println("getServletInfo()..."); return null; } }
我们不用重启Tomcat,在浏览器地址栏里重新请求之前的网址即可,http://localhost:8080/HelloServlet/AServlet,你会在命令行窗口看到。
当某个Servlet第一次被请求时,服务器(Servlet容器)会生成该Servlet对象并调用它的init()方法,再调用service()方法处理请求。处理结束后,该Servlet会常驻于容器中,下一个请求则不再重新生成Servlet对象,而是直接调用常驻的Servlet对象的service()方法。服务器停止时,会调用该Servlet对象的destroy()方法。(关闭浏览器,Servlet对象不会被销毁!)
因此,在Servlet的一个生命周期中,init()和destroy()仅会被调用一次,而service()则会被调用多次。
2、Servlet的生命周期
所谓生命周期,就是从出生到死亡的过程。Servlet生命周期也是这样,一共分为三个阶段,分别对应三个生命周期方法。
(1)初始化阶段
void init(ServletConfig config)
服务器会在Servlet第一次被访问时创建Servlet,或者是在服务器启动时创建Servlet。如果服务器启动时就创建Servlet,那么还需要在web.xml文件中配置。也就是说默认情况下,Servlet是在第一次被访问时由服务器创建的。
而且一个Servlet类型,服务器只创建一个实例对象,例如在我们首次访问http://localhost:8080/HelloServlet/AServlet时,服务器通过“/AServlet”找到了绑定的Servlet名称为com.sdbi.servlet.AServlet,然后服务器查看这个类型的Servlet是否已经创建过,如果没有创建过,那么服务器才会通过反射来创建AServlet的实例。当我们再次访问http://localhost:8080/HelloServlet/AServlet时,服务器就不会再次创建AServlet实例了,而是直接使用上次创建的实例。
在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法。请记住,Servlet创建后马上就会调用init()方法,而且在一个Servlet的生命周期内,这个方法只会被调用一次。
因此,我们可以把一些对Servlet的初始化工作放到init方法中!
(2)运行阶段
void service(ServletRequest req, ServletResponse res)
当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求。服务器接收到一次请求,就会调用service() 方法一次,所以service()方法是会被调用多次的。正因为如此,所以我们才需要把处理请求的代码写到service()方法中!
(3)销毁阶段
void destroy()
Servlet是不会轻易销毁的,通常都是在服务器关闭时Servlet才会销毁。在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法,我们可以把对某些资源的释放等代码放到destroy()方法中。
以上三个方法为Servlet的生命周期方法,Servlet接口中另外两个抽象方法不是生命周期方法,那他们是干什么用的呢?
- ServletConfig getServletConfig():获取包含Servlet各种信息的ServletConfig对象,返回值是Servlet容器调用init(ServletConfig config)方法时传递的ServletConfig对象,我们可以在init()方法中通过全局的成员变量将它保存起来,然后作为getServletConfig()方法的返回值返回。
- String getServletInfo():将Servlet的信息作为字符串返回。我们可以自定义一个Servlet的描述信息作为返回值,也可以ServletConfig对象的某些属性作为返回值,例如,ServletConfig对象.getServletName()。
3、Servlet接口相关类型
在Servlet接口中还存在三个我们不熟悉的类型:
- ServletRequest:service()方法的参数,它表示请求对象,它封装了所有与请求相关的数据,它是由服务器创建的;
- ServletResponse:service()方法的参数,它表示响应对象,在service()方法中完成对客户端的响应需要使用这个对象;
- ServletConfig:init()方法的参数,它表示Servlet配置对象,它对应Servlet的配置信息,它对应web.xml文件中的<servlet>元素。
(1)ServletRequest和ServletResponse(我们在之后的请求和响应章节中会详细讲解这两个)
ServletRequest和ServletResponse是Servlet#service() 方法的两个参数,一个是请求对象,一个是响应对象,可以从ServletRequest对象中获取请求数据,可以使用ServletResponse对象完成响应。ServletRequest和ServletResponse的实例由服务器创建,然后传递给service()方法。你以后会发现,这两个对象总是成对出现。
(2)ServletConfig
ServletConfig对象对应web.xml文件中的<servlet>元素。例如你想获取当前Servlet在web.xml文件中的配置名,那么可以使用ServletConfig.getServletName()方法获取。
三、GenericServlet抽象类
由于javax.servlet.Servlet接口中的5个方法都是抽象方法,我们在使用时非常麻烦,需要把这5个方法都实现。
所以,这里又提供了javax.servlet.GenericServlet抽象类,它与任何网络应用层协议无关。
GenericServlet抽象类为我们实现了Servlet接口的大部分方法,除了service()方法之外。
因此,我们在创建自己的Servlet时,只需要继承GenericServlet并重写service()方法即可。
1、init()方法
在GenericServlet中,有两个init()方法,一个是无参的,一个是有参数的。GenericServlet中定义了一个ServletConfig config成员变量(全局变量),并在有参数的init(ServletConfig)方法中把参数ServletConfig赋给了成员变量config。然后在该类的很多方法中使用了成员变量config。
注意,我们创建Servlet继承GenericServlet时,如果子类覆盖(重写)了GenericServlet的有参的init(ServletConfig)方法,那么this.config=config这一条语句就会被覆盖了,也就是说GenericServlet的成员变量config的值为null,那么所有依赖config的方法都不能使用了。如果真的希望完成一些初始化操作,我们要去覆盖GenericServlet提供的无参的init()方法,它会在init(ServletConfig)方法中被调用,也能够得到执行,也能够完成初始化。
2、实现了ServletConfig接口
GenericServlet还实现了ServletConfig接口,所以可以直接调用getInitParameter()、getInitParameterNames()、getServletContext()、getServletName()等ServletConfig的方法。
我们在HelloServlet项目中,创建一个名为BServlet的类,让它去继承GenericServlet类。
这时我们只需重写service()方法即可,并且在GenericServlet类定义了一个log()方法可以方便的用于日志的输出,我们来调用试一下。
BServlet.java代码如下:
BServlet.java代码如下: public class BServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { log("service()..."); } }
web.xml文件配置如下:
<servlet> <servlet-name>bbb</servlet-name> <servlet-class>com.sdbi.servlet.BServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>bbb</servlet-name> <url-pattern>/BServlet</url-pattern> </servlet-mapping>
部署运行Tomcat,浏览器打开http://localhost:8080/HelloServlet/BServlet地址。
详细参见GenericServlet源代码:
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; // transient不需要序列化的属性 public GenericServlet() { } @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } // 子类继承时,如果需要初始化,要覆盖这个自定义的无参init()方法 // 如果覆盖那个有参init()方法,就会导致全局变量config为null,其他方法使用它时报错 public void init() throws ServletException { } @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; @Override public void destroy() { } @Override public ServletConfig getServletConfig() { return config; } @Override public String getServletInfo() { return ""; } @Override public String getInitParameter(String name) { return getServletConfig().getInitParameter(name); } @Override public Enumeration<String> getInitParameterNames() { return getServletConfig().getInitParameterNames(); } @Override public ServletContext getServletContext() { return getServletConfig().getServletContext(); } @Override public String getServletName() { return config.getServletName(); } public void log(String msg) { getServletContext().log(getServletName() + ": " + msg); } public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } }
四、HttpServlet抽象类
HttpServlet抽象类是GenericServlet抽象类的子类,HttpServlet类为Servlet接口提供了与HTTP协议相关的通用实现,对GenericServlet类进行了泛化,实现了service()方法。其实HttpServlet中没有什么抽象方法,但是它还是一个抽象类,因为要求我们不能直接使用HttpServlet,而是要自己创建Servlet使用,另外HttpServlet还提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。
1、HttpServlet中的两个service()方法
我们查看HttpServlet的源代码,发现这里面定义了两个service()方法。大家注意,这两个service()方法的参数列表是不一样的。其中,service(ServletRequest req, ServletResponse res)是重写了父类GenericServlet中的抽象方法service()方法,是Servlet的生命周期方法。service(HttpServletRequest, HttpServletResponse)方法,是HttpServlet自己的方法,不是覆盖GenericServlet继承来的。
- void service(HttpServletRequest req, HttpServletResponse resp)
- void service(ServletRequest req, ServletResponse res)
HttpServlet.java的部分源码如下:
public abstract class HttpServlet extends GenericServlet { …… protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); …... } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException("non-HTTP request or response"); } // 调用HttpServlet中的自定义service(HttpServletRequest, HttpServletResponse) service(request, response); } }
两个service()方法,其中void service(ServletRequest req, ServletResponse res)方法是由Tomcat自动调用,它将接收的客户端请求和响应强制类型转换为HttpServletRequest和HttpServletResponse类型,然后转交给HttpServlet中的另一个protected void service(HttpServletRequest req,HttpServletResponse resp)方法,此保护类型的service方法再把将请求和响应分发给doPost()、doGet()方法进行下一步处理。
2、doGet()和doPost()方法
在HttpServlet的service(HttpServletRequest req,HttpServletResponse res)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。
我们在HelloServlet项目中,创建一个名为CServlet的类,让它去继承HttpServlet类。重写其中的init()、doGet(HttpServletRequest req, HttpServletResponse resp)、doPost(HttpServletRequest req, HttpServletResponse resp)三个方法。
public class CServlet extends HttpServlet { @Override public void init() throws ServletException { System.out.println("init()..."); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doGet()..."); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doPost()..."); } }
在这三个方法中去掉父类方法的调用super.xxx(),使用输出语句输出一段文字,表示方法被执行。
我们在web.xml文件中配置好CServlet,代码如下:
<servlet> <servlet-name>ccc</servlet-name> <servlet-class>com.sdbi.servlet.CServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ccc</servlet-name> <url-pattern>/CServlet</url-pattern> </servlet-mapping>
我们重新部署应用程序。浏览器地址栏里输入http://localhost:8080/HelloServlet/CServlet,回车。
发现出现空白页面,但是控制台输出了“init()…”和“doGet()...”。说明CServlet正确执行了。
我们再来测试一下POST请求,这时需要有一个表单,我们在WebContent目录下创建一个HTML页面,名称为login.html,在<body>里面定义一个<form>标签,其中定义一个提交按钮,表单的提交方式为"POST"。运行这个HTML页面,点击“提交”按钮。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> This is my HTML page. <br> <form action="/HelloServlet/CServlet" method="POST"> <input type="submit" value="提交" /> </form> </body> </html>
控制台输出“init()…”和“doPost()...”,说明doPost()方法被执行了。
五、使用向导创建Servlet
我们之前是创建一个Java类,让它继承HttpServlet抽象类。另外,Eclipse提供了专门创建Servlet的向导,我们下面来操作一下看看。
1、使用向导创建Servlet
在src文件夹下的包名上点击右键,选择New > Servlet,进入Servlet创建向导。
指定Servlet所在包名称为“com.sdbi.servlet”,Servlet类名为“DServlet”,父类为“javax.servlet.http.HttpServlet”。
选择需要向导帮我们自动创建的方法。
如果出现HttpServlet抽象类找不到的情况,我们可以选择Eclipse给我们提供的修复方法。
我们不用配置web.xml文件,部署运行DServlet程序,你会发现我们的DServlet可以运行,而web.xml文件中没有关于DServlet的配置信息。这是因为,从Servlet 3.0之后(含3.0),Servlet的配置直接在Java代码中通过注解进行了配置。我们来看一下Java代码中的注解。
这样可以省去我们配置web.xml的麻烦,但是需要服务器应该是Tomcat 7以上才支持。
六、Servlet高级
1、自动加载Servlet
默认情况下,服务器会在某个Servlet第一次收到请求时创建它。也可以在web.xml中对Servlet进行配置,使服务器启动时就创建Servlet。
<?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>HelloServlet</display-name> <servlet> <servlet-name>aaa</servlet-name> <servlet-class>com.sdbi.servlet.AServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aaa</servlet-name> <url-pattern>/AServlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>bbb</servlet-name> <servlet-class>com.sdbi.servlet.BServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>bbb</servlet-name> <url-pattern>/BServlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>ccc</servlet-name> <servlet-class>com.sdbi.servlet.CServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ccc</servlet-name> <url-pattern>/CServlet</url-pattern> </servlet-mapping> </web-app>
在<servlet>元素中配置<load-on-startup>元素可以让服务器在启动时就创建该Servlet,其中<load-on-startup>元素的值必须是大于等于0的整数,代表服务器启动时创建Servlet的顺序,值越小优先级越高。上例中,根据<load-on-startup>的值可以得知服务器创建Servlet的顺序为BServlet、CServlet、AServlet。
如果该Servlet类使用@WebServlet注解的形式配置,我们可以在注解中增加属性。
@WebServlet(urlPatterns = { "/DServlet" }, loadOnStartup = 0)
参见博客园:《Servlet3.0中@WebServlet注解》
2、多重映射
(1)配置多个<servlet-mapping>元素
在web.xml文件中对于AServlet配置多个<servlet-mapping>元素,代码如下:
<servlet> <servlet-name>aaa</servlet-name> <servlet-class>com.sdbi.servlet.AServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aaa</servlet-name> <url-pattern>/AServlet</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>aaa</servlet-name> <url-pattern>/EServlet</url-pattern> </servlet-mapping>
重新部署运行程序,我们通过下面两个地址http://localhost:8080/HelloServlet/AServlet和http://localhost:8080/HelloServlet/EServlet都可以运行AServlet。
(2)在一个<servlet-mapping>中配置多个<url-pattern>元素
在web.xml文件中对于AServlet的<servlet-mapping>元素中配置多个<url-pattern>,代码如下:
<servlet> <servlet-name>aaa</servlet-name> <servlet-class>com.sdbi.servlet.AServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aaa</servlet-name> <url-pattern>/AServlet</url-pattern> <url-pattern>/EServlet</url-pattern> </servlet-mapping>
在以上代码中,说明<url-pattern>是<servlet-mapping>的子元素,用来指定Servlet的访问路径,即URL。它必须是以“/”开头!
如果该Servlet类使用@WebServlet注解的形式配置,我们可以在注解中增加属性。
@WebServlet(urlPatterns = { "/DServlet", "/FServlet" })
或者 @WebServlet(value = { "/DServlet", "/FServlet" })
参见博客园:《Servlet3.0中@WebServlet注解》
3、使用通配符
我们还可以在<url-pattern>中使用通配符,所谓通配符就是星号“*”,星号可以匹配任何URL前缀或后缀,使用通配符可以命名一个Servlet绑定一组URL,例如:
- <url-pattern>/servlet/*<url-patter>:/servlet/a、/servlet/b,都匹配/servlet/*;
- <url-pattern>*.do</url-pattern>:/abc/def/ghi.do、/a.do,都匹配*.do;
- <url-pattern>/*<url-pattern>:匹配所有URL;
请注意,通配符要么为前缀,要么为后缀,不能出现在URL中间位置,也不能只有通配符。例如:/*.do就是错误的,因为星号出现在URL的中间位置上了。
通配符是一种模糊匹配URL的方式,如果存在更具体的<url-pattern>,那么访问路径会优先去匹配具体的<url-pattern>。
4、默认Servlet
说到上面的<url-pattern>/*<url-pattern>(匹配所有URL),这就是一种默认Servlet的匹配方式。其实,对于默认Servlet,我们可以直接给他匹配<url-pattern>/<url-pattern>即可,也就是我们通过这个路径http://localhost:8080/HelloServlet/访问,就可以方法到默认的Servlet。
七、ServletConfig接口
ServletConfig是一个接口(Interface),这个接口对象在之前的Servlet接口和GenericServlet抽象类中都作为了init()方法的参数和getServletConfig()方法的返回值。当Servlet配置了初始化参数之后,Web容器(Tomcat)在创建Servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用Servlet的init方法时,将ServletConfig对象传递给Servlet。进而,我们可以通过ServletConfig对象得到当前Servlet的初始化参数信息。简而言之,ServletConfig对象对应web.xml文件中的<servlet>元素。
例如,你想获取当前Servlet在web.xml文件中的配置名(别名),那么可以使用ServletConfig.getServletName()方法获取。
ServletConfig接口中定义的抽象方法有4个。这个接口的实现类是由Tomcat给我们实现提供的,不用我们自己去实现,我们只需要知道这4个方法的作用和用法就可以。
1、String getServletName()
获取当前Servlet在web.xml中配置的名字。对应的<servlet-name>元素的数据。
2、String getInitParameter(String name)
获取当前Servlet指定名称的初始化参数的值。对应的<init-param>元素中的数据。
3、Enumeration<String> getInitParameterNames()
获取当前Servlet所有初始化参数的名字组成的枚举。对应的<param-name>元素的数据。
4、ServletContext getServletContext()
获取代表当前web应用的ServletContext对象(ServletContext上下文,接口)。
一个Servlet对应一个ServletConfig,一个Web应用对应一个ServletContext;
同一个Web应用中的多个ServletConfig对应同一个ServletContext !!!
我们以之前的HelloServlet应用为例,学习一下ServletConfig的使用。找到之前的web.xml文件,在原来名的AServlet的<servlet>元素中添加初始化参数,代码如下:
<servlet> <servlet-name>aaa</servlet-name> <servlet-class>com.sdbi.servlet.AServlet</servlet-class> <init-param> <param-name>p1</param-name> <param-value>v1</param-value> </init-param> <init-param> <param-name>p2</param-name> <param-value>v2</param-value> </init-param> </servlet>
在AServlet.java代码的init()方法中,添加如下代码:
@Override public void init(ServletConfig config) throws ServletException { System.out.println("AServlet:init()..."); // 获取初始化参数 System.out.println("p1 = " + config.getInitParameter("p1")); System.out.println("p2 = " + config.getInitParameter("p2")); // 获取所有初始化参数名称 Enumeration e = config.getInitParameterNames(); while (e.hasMoreElements()) { System.out.println(e.nextElement()); } }
运行程序,访问http://localhost:8080/HelloServlet/AServlet,输出如下:
如果这个Servlet是用注解的形式配置的,那么初始化参数应该怎么添加呢?
其实我们之前见到的注解@WebServlet("/DServlet")是省略了urlPatterns属性名的,完整的写法是:@WebServlet(urlPatterns = { "/DServlet" })。
博客园:https://www.cnblogs.com/lihuawei/p/14776188.html
如果添加参数,我们可以在注解中增加属性:
@WebServlet(urlPatterns = { "/DServlet" }, initParams = { @WebInitParam(name = "p1", value = "v1"), @WebInitParam(name = "p2", value = "v2") })
如下图所示:
另外,我们在init()方法中可以获取初始化参数,如果我们想在其他方法中获得初始化参数呢?
我们在CServlet(继承于HttpServlet)中测试一下,先在web.xml中添加如下代码:
<servlet> <servlet-name>ccc</servlet-name> <servlet-class>com.sdbi.servlet.CServlet</servlet-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>ccc</servlet-name> <url-pattern>/CServlet</url-pattern> </servlet-mapping>
然后在CServlet.java的doGet()方法中添加如下代码:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doGet()..."); // 获取ServletConfig对象 ServletConfig config = getServletConfig(); // 获取初始化参数encoding的值 String param = config.getInitParameter("encoding"); System.out.println("encoding = " + param); }
运行程序,访问http://localhost:8080/HelloServlet/CServlet,输出如下:
由此可以看出,通过ServletConfig对象,我们可以获得web.xml文件中的参数信息。
这个接口中的4个方法,其中最常用的方法是getServletContext(),用于获得一个ServletContext对象。
八、ServletContext接口
当Tomcat(Servlet容器)启动时,会为每个Web应用创建一个唯一的ServletContext对象,代表当前Web应用,这个对象不仅封装了当前Web应用的所有信息,而且实现了多个Servlet之间的数据共享(域对象,对应Application,该Web应用程序)。
- 一个Web应用只有一个ServletContext对象;
- 可以在多个Servlet中获取这个唯一的ServletContext对象,可以实现Servlet间数据传递;
- 生存时间长,Tomcat启动时,就创建该对象,Tomcat关闭时才销毁。
1、ServletContext作用
在整个Web应用的动态资源之间共享数据!例如在AServlet中向ServletContext对象中保存一个值,然后在BServlet中就可以获取这个值,这就是共享数据了。
ServletContext是JavaWeb四大域对象之一:(范围由小到大)
- PageContext
- ServletRequest
- HttpSession
- ServletContext
所有域对象都有存取数据的功能,因为域对象内部都有一个Map,用来存储数据。
2、获取ServletContext的方法
- ServletConfig#getServletContext();
- GenericServlet#getServletContext();
- HttpSession#getServletContext()
- ServletContextEvent#getServletContext()
(1)在Servlet中获取ServletContext对象:
在Servlet接口中没有定义ServletContext对象的获取,所以我们需要在自己的实现Servlet接口的子类中,从ServletConfig对象中获取ServletContext对象。在init()方法中会传入ServletConfig对象,所以我们可以在void init(ServletConfig config)中通过ServletConfig类的getServletContext()方法可以用来获取ServletContext对象:
ServletContext context = config.getServletContext();
(2)在GenericeServlet(或HttpServlet)中获取ServletContext对象:
在GenericeServlet抽象类中定义了一个getServletContext()方法,具体实现代码如下:
@Override public ServletContext getServletContext() { return getServletConfig().getServletContext(); }
另外由于HttpServlet继承于GenericeServlet,所以HttpServlet中也有getServletContext()方法。因此,我们可以在GenericeServlet或HttpServlet中,直接使用this.getServletContext()来获取ServletContext对象。
ServletContext context = this.getServletContext();
(3)使用HttpSession和ServletContextEvent获取ServletContext对象:以后用到再讲。
3、获取应用初始化参数
我们之前使用ServletConfig也可以获取初始化参数,但它是局部的参数,一个Servlet只能获取自己的初始化参数,不能获取别人的,我们要是想为所有Servlet配置初始化参数,这时就需要使用ServletContext了。修改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"> …… <context-param> <param-name>school</param-name> <param-value>SDBI</param-value> </context-param> <context-param> <param-name>author</param-name> <param-value>lihuawei</param-value> </context-param> …… </web-app>
<context-param>元素位于<web-app>根元素中。要想获取这些信息,我们需要使用ServletContext对象的getInitParameterNames()和getInitParameter(String name)方法分别获取参数名列表和参数值。
在CServlet.java的doGet()方法中添加如下代码:
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("doGet()..."); // 获取ServletContext对象 ServletContext context = getServletContext(); Enumeration<String> paramNames = context.getInitParameterNames(); // 遍历所有的参数名列表,得到相应的参数值 while (paramNames.hasMoreElements()) { String name = (String) paramNames.nextElement(); System.out.println(name + " = " + context.getInitParameter(name)); } }
运行程序,访问http://localhost:8080/HelloServlet/CServlet,输出如下:
4、实现多个Servlet对象共享数据
ServletContext中用来操作数据的方法有:
- void setAttribute(String name, Object value):用来存储一个对象,也可以称之为存储一个域属性,例如:servletContext.setAttribute(“xxx”, “XXX”),在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX。请注意,如果多次调用该方法,并且使用相同的name,那么会覆盖上一次的值,这一特性与Map相同;
- Object getAttribute(String name):用来获取ServletContext中的数据,当前在获取之前需要先去存储才行,例如:String value = (String)servletContext.getAttribute(“xxx”);,获取名为xxx的域属性;
- void removeAttribute(String name):用来移除ServletContext中的域属性,如果参数name指定的域属性不存在,那么本方法什么都不做;
- Enumeration<String> getAttributeNames():获取所有域属性的名称枚举;
我们新建两个Servlet:EServlet和FServlet,让它们分别去继承HttpServlet。注意,使用@WebServlet()注解的方式,或者使用<servlet>和<servlet-mapping>元素在web.xml中注册的方式都可以。
<servlet> <servlet-name>EServlet</servlet-name> <servlet-class>com.sdbi.servlet.EServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>EServlet</servlet-name> <url-pattern>/EServlet</url-pattern> </servlet-mapping> <servlet> <servlet-name>FServlet</servlet-name> <servlet-class>com.sdbi.servlet.FServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FServlet</servlet-name> <url-pattern>/FServlet</url-pattern> </servlet-mapping>
我们在EServlet的doGet()方法中保存数据,代码如下:
public class EServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext context = getServletContext(); context.setAttribute("book", "JavaWeb"); context.setAttribute("price", "56"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
我们在FServlet的doGet()方法中获取数据,代码如下:
public class FServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext context = getServletContext(); String book = (String) context.getAttribute("book"); System.out.println("book = " + book); System.out.println("price = " + context.getAttribute("price")); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
在浏览器地址栏里面先输入http://localhost:8080/HelloServlet/EServlet,访问EServlet,存入数据,再输入http://localhost:8080/HelloServlet/FServlet,访问FServlet,获取数据,结果显示如下:
5、读取Web应用下的资源文件
(1)获取真实路径:getRealPath()
可以使用ServletContext对象的getRealPath()方法来获取Web应用下的资源的真实路径,参数必须以“/”开头,表示Web应用程序根目录。例如,在HelloServlet应用的根目录下有个login.html文件。
- 获取login.html的真实路径:
ServletContext context = getServletContext();
String realPath = context.getRealPath("/login.html");
realPath的值为login.html文件的绝对路径:
D:\eclipse-jee-kepler-SR2-win32-x86_64\apache-tomcat-7.0.93\webapps\HelloServlet\login.html
- 获取/WEB-INF/web.xml的真实路径:
String realPath = context.getRealPath("/WEB-INF/web.xml");
(2)获取资源数据流:getResourceAsStream()
不只可以获取资源的路径,还可以通过ServletContext的getResourceAsStream()方法获取资源数据流,即把资源以输入流的方式获取,参数必须以“/”开头,表示Web应用程序根目录。
ServletContext context = getServletContext(); InputStream input = context.getResourceAsStream("/login.html"); System.out.println("length = " + input.available()); //获取数据流里有多少个字节 int len = 0; byte[] bys = new byte[1024]; while ((len = input.read(bys)) != -1) { System.out.println(new String(bys, 0, len, "utf-8")); }
如果在Web应用的/WEB-INF/files目录下,创建一个资源文件sdbi.properties,资源文件的内容为:
School = SDBI
Author = lihuawei
我们如果想读取这个文件的内容,可以结合使用Properties类来读取。属性映射(Properties):是一种存储键/值对的数据结构,属性映射经常被用来存放配置信息。
ServletContext context = getServletContext(); InputStream input = context.getResourceAsStream("/WEB-INF/files/sdbi.properties"); Properties properties = new Properties(); properties.load(input); System.out.println(properties.size()); Enumeration<String> names = (Enumeration<String>) properties.propertyNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); System.out.println(name + " = " + properties.getProperty(name)); }
(3)获取指定目录下所有资源路径:getResourcePaths()
使用ServletContext的getResourcePaths()方法获取指定目录下所有资源路径,参数必须以“/”开头,例如,获取/WEB-INF下所有资源的路径:
ServletContext context = getServletContext(); Set set = context.getResourcePaths("/WEB-INF"); System.out.println(set);
(4)获取资源的URL:getResource()
我们可以通过ServletContext的getResource()方法获取某个资源文件的URL对象,参数必须以“/”开头,例如,获取/login.html的URL对象:
ServletContext context = getServletContext(); URL url = context.getResource("/login.html"); System.out.println(url.toString());
6、应用案例:访问量统计
大家一定见过很多访问量统计的网站,即“本页面被访问过XXX次”。
因为无论是哪个用户访问指定页面,都会累计访问量,所以这个访问量统计应该是整个项目共享的!
很明显,这需要使用ServletContext来保存访问量。
思路:创建一个Integer类型的变量,用来保存访问量,然后把它保存到ServletContext的域中,这样可以保证所有的Servlet都可以访问到!
步骤:
最初时,ServletContext中没有保存访问量相关的属性;
当本站第一次被访问时,创建一个变量,设置其值为1,保存到ServletContext中;
当以后的访问(第2~N次)时,就可以从ServletContext中获取这个变量,然后在其基础之上加1后,再保存到ServletContext中。
ServletContext context = getServletContext(); Integer count = (Integer) context.getAttribute("count"); // 获取当前数值 if (count == null) { count = 1; // 第一次访问 } else { count++; // 加1 } response.setContentType("text/html;charset=utf-8"); response.getWriter().print("<h1>本项目一共被访问" + count + "次!</h1>"); context.setAttribute("count", count); // 保存最新数值