Servlet简介
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
![](https://img-blog.csdn.net/20140407094812156?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdG9wX2NvZGU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
同一个Servlet可以被映射到多个URL上,即多个<servlet-mapping>元素的<servlet-name>子元素的设置值可以是同一个Servlet的注册名。
在Servlet映射到的URL中也可以使用*通配符,但是只能有两种固定的格式:一种格式是“*.扩展名”,另一种格式是以正斜杠(/)开头并以“/*”结尾。
例如:
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
解决并发问题
解决并发出现的问题,可以采用以下方式:
ServletContext
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
请求重定向和请求转发
请求转发:
发出一次请求.
共享同一个请求和响应对象
请求地址不变
读取资源文件(三种方法)
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
- 编写一个Java类,实现servlet接口。
- 把开发好的Java类部署到web服务器中。
JavaEE API文档中关于Servlet的介绍如下:
Servlet是一个接口,方法定义如下:
void destroy()
Called by the servlet container to indicate to a servlet that the servlet is being taken out of service.
ServletConfig getServletConfig()
Returns a ServletConfig object, which contains initialization and startup parameters for this servlet.
String getServletInfo()
Returns information about the servlet, such as author, version, and copyright.
void init(ServletConfig config)
Called by the servlet container to indicate to a servlet that the servlet is being placed into service.
void service(ServletRequest req, ServletResponse res)
Called by the servlet container to allow the servlet to respond to a request.
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。 WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
Called by the servlet container to indicate to a servlet that the servlet is being taken out of service.
ServletConfig getServletConfig()
Returns a ServletConfig object, which contains initialization and startup parameters for this servlet.
String getServletInfo()
Returns information about the servlet, such as author, version, and copyright.
void init(ServletConfig config)
Called by the servlet container to indicate to a servlet that the servlet is being placed into service.
void service(ServletRequest req, ServletResponse res)
Called by the servlet container to allow the servlet to respond to a request.
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。 WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
它有两个直接的子类,实际开发中我们通常使用Servlet的子类HttpServlet
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
Servlet的运行过程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
- Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
- 装载并创建该Servlet的一个实例对象。
- 调用Servlet实例对象的init()方法。
- 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
- WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
在Eclipse/MyEclipse中开发Servlet
在Eclipse/MyEclipse中新建一个web project工程,eclipse会自动创建下图所示目录结构:
在Eclipse/MyEclipse中新建一个web project工程,eclipse会自动创建下图所示目录结构:
Servlet映射细节
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
<servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。例如:
<web-app>
<servlet>
<servlet-name>AnyName</servlet-name>
<servlet-class>HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AnyName</servlet-name>
<url-pattern>/demo/hello.html</url-pattern>
</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的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
通配符匹配的优先级
对于如下的一些映射关系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题:
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题:
当请求URL为“/abc/a.html”,“/abc/*”和“/*”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc/*”和“/abc”都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/*”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当两个映射都能响应时,Servlet引擎将调用更具体的Url映射。
Servlet的生命周期
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
- Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
- 针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
- 在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
- 如果在<servlet>元素中配置了一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>
org.apache.catalina.servlets.InvokerServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
Servlet的线程安全
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
SingleThreadModel接口中没有定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
SingleThreadModel接口中没有定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
解决并发问题
解决并发出现的问题,可以采用以下方式:
- 使用Java同步机制对多线程同步:运行效率低
- 使用SingleThreadModel接口
- 合理决定在Servlet中定义的变量的作用域
ServletConfig对象
在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,我们可以通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
在Servlet的配置文件中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,我们可以通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
例如,获取字符编码信息:
<servlet>
<servlet-name>ServletDemo</servlet-name>
<servlet-class>com.fxsky.servlet.ServletDemo</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</servlet>
public class ServletDemo extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
//1、在init方法中
@Override
public void init(ServletConfig config) throws ServletException {
String pValue = config.getInitParameter("encoding");
System.out.println("encoding="+pValue);
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//2、通过getServletConfig()方法
ServletConfig config = getServletConfig();
String pValue = config.getInitParameter("encoding");
System.out.println("encoding="+pValue);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
ServletContext
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
ServletContext应用
- 多个Servlet通过ServletContext对象实现数据共享。
- 获取WEB应用的初始化参数。
- 实现Servlet的转发。
- 利用ServletContext对象读取资源文件。
示例代码:
public class ServletDemo2 extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1、通过ServletContext对象实现数据共享
// ServletContext sc = getServletConfig().getServletContext();
ServletContext sc = getServletContext();
sc.setAttribute("aa", new Person("01", "zhangsan"));
Person p = (Person) sc.getAttribute("aa");
//2、获取WEB应用的初始化参数
String encoding = sc.getInitParameter("encoding");
System.out.println("encoding="+encoding);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
请求重定向和请求转发
两者区别:
请求重定向:
发出两次请求。
两次请求的请求对象不是同一个
请求地址会变
发出两次请求。
两次请求的请求对象不是同一个
请求地址会变
请求转发:
发出一次请求.
共享同一个请求和响应对象
请求地址不变
示例代码如下:
/**
* 请求转发和重定向的区别
* 请求重定向:
发出两次请求。
两次请求的请求对象不是同一个
请求地址会变
请求转发:
发出一次请求.
共享同一个请求和响应对象
请求地址不变
* @author FX_SKY
*
*/
public class ServletDemo3 extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1、请求重定向
// response.sendRedirect("/day04/a.html");
//2、请求转发
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher("/servlet/ServletDemo4");
rd.forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
利用ServletContext获取文件真实路径,示例代码如下:
public class ServletDemo4 extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
//文件下载
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 得到文件的输入流:文件到底存在了哪里("/" 就代表JavaWEb应用的根路径)
ServletContext sc = getServletContext();
String realpath = sc.getRealPath("/images/21.jpg");// 该文件存放的真实的绝对路径
System.out.println(realpath);
InputStream in = new FileInputStream(realpath);
// 通知客户端以下载的方式打开
response.setHeader("Content-Disposition", "attachment;filename=21.jpg");
// 得到response的输出流
OutputStream out = response.getOutputStream();
int len = -1;
byte b[] = new byte[1024];
while ((len = in.read(b)) != -1) {
out.write(b, 0, len);
}
in.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
读取资源文件(三种方法)
1、通过ServletContext读取配置文件
ServletContext的getRealpath(String path):参数path必须以"/"开头,代表当前JavaWeb应用的根目录。
ServletContext context = this.getServletContext();
String path = context.getRealPath("/WEB-INF/classes/cfg2.properties");
这种方式可以加载任何位置上的配置文件。
2、通过类加载器读取配置文件 (只能读取classes目录下的配置文件)
ClassLoader loader = ServletDemo10.class.getClassLoader();
//配置文件在classes根目录下
InputStream in = loader.getResourceAsStream("cfg2.properties");
//配置文件在classes目录的某个包中
InputStream in = loader.getResourceAsStream("cn/itcast/servlet/cfg3.properties");
3、利用ResourceBundle进行加载(国际化时的一个类)(只能读取classes目录下的配置文件)
//配置文件在classes根目录下
ResourceBundle rb = ResourceBundle.getBundle("cfg2");//基名(不带扩展名的名称)
//配置文件在classes目录的某个包中
ResourceBundle rb = ResourceBundle.getBundle("cn.itcast.servlet.cfg3");
ServletContext的getRealpath(String path):参数path必须以"/"开头,代表当前JavaWeb应用的根目录。
ServletContext context = this.getServletContext();
String path = context.getRealPath("/WEB-INF/classes/cfg2.properties");
这种方式可以加载任何位置上的配置文件。
2、通过类加载器读取配置文件 (只能读取classes目录下的配置文件)
ClassLoader loader = ServletDemo10.class.getClassLoader();
//配置文件在classes根目录下
InputStream in = loader.getResourceAsStream("cfg2.properties");
//配置文件在classes目录的某个包中
InputStream in = loader.getResourceAsStream("cn/itcast/servlet/cfg3.properties");
3、利用ResourceBundle进行加载(国际化时的一个类)(只能读取classes目录下的配置文件)
//配置文件在classes根目录下
ResourceBundle rb = ResourceBundle.getBundle("cfg2");//基名(不带扩展名的名称)
//配置文件在classes目录的某个包中
ResourceBundle rb = ResourceBundle.getBundle("cn.itcast.servlet.cfg3");
示例代码如下:
//三种读取配置文件的方式
public class ServletDemo10 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
test4(request,response);
}
//利用ResourceBundle加载*.properties的配置文件。只能把这样的文件放在classes目录
private void test4(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ResourceBundle rb = ResourceBundle.getBundle("cfg2");//基名
ResourceBundle rb = ResourceBundle.getBundle("com.itheima.cfg3");//基名
System.out.println(rb.getString("username"));
}
//专业做法:利用类的类加载器(只能读取classes目录下的配置文件)
private void test3(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ClassLoader cl = ServletDemo10.class.getClassLoader();
// InputStream in = cl.getResourceAsStream("cfg2.properties");
InputStream in = cl.getResourceAsStream("com/itheima/cfg3.properties");
Properties props = new Properties();
props.load(in);
System.out.println("-------获取指定的某个参数的取值-------");
System.out.println(props.getProperty("username"));
System.out.println(props.getProperty("password"));
}
//利用ServletContext读取配置文件
private void test2(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext sc = getServletContext();
// String path = sc.getRealPath("/WEB-INF/classes/cfg2.properties");
String path = sc.getRealPath("/WEB-INF/classes/com/itheima/cfg3.properties");
InputStream in = new FileInputStream(path);
Properties props = new Properties();
props.load(in);
System.out.println("-------获取指定的某个参数的取值-------");
System.out.println(props.getProperty("username"));
System.out.println(props.getProperty("password"));
}
//利用ServletContext读取WEB-INF下的配置文件
private void test1(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ServletContext sc = getServletContext();
String path = sc.getRealPath("/WEB-INF/cfg1.properties");
InputStream in = new FileInputStream(path);
Properties props = new Properties();
props.load(in);
System.out.println("-------获取指定的某个参数的取值-------");
System.out.println(props.getProperty("username"));
System.out.println(props.getProperty("password"));
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}