基于动态Web资源开发,Sun公司提供了Servlet和JSP两种技术。
针对Servlet技术的开发,Sun公司提供了一系列接口和类,其中最重要的是javax.servlet.Servlet接口。Servlet就是一种实现了Servlet接口的类,它由Web容器负责创建并调用,用于接收和响应用户的请求。
Servlet接口
接口中的方法,其中提到的Servlet容器指的是Web服务器。
- void init(ServletConfig config):负责Servlet初始化工作,容器在创建好Servlet对象后,就会调用此方法。该方法接收一个ServletConfig类型的参数,Servlet容器通过这个参数向Servlet传递初始化配置信息。
- ServletConfig getServletConfig():返回容器调用init(ServletConfig config)方法时传递给Servlet的ServletConfig对象。
- String getServletInfo():返回一个字符串,其中包含关于Servlet的信息,例如,作者、版本和版权等信息。
- void service(ServletRequest req, ServletResponse res):负责响应用户的请求,当容器接受到客户端访问Servlet对象的请求时,就会调用此方法。容器会构造一个表示客户端请求信息的ServletRequest对象和一个用于响应客户端ServletResponse对象作为参数传递给service()方法。在service()方法中,可以通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求信息进行处理后,调用ServletResponse对象的方法设置响应信息。
- void destroy():负责释放Servlet对象占用的资源。当Servlet对象被销毁时,容器会调用此方法。
第一个Servlet程序
创建一个Web项目HelloWeb,创建包com.eaglezsx.servlet,在里边创建类ServletDemo1,让其实现Servlet接口,然后重写里边的方法,找到service这个方法,输出一句话hello servlet。
package com.eaglezsx.servlet;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletDemo1 implements Servlet{
public void destroy() {
// TODO Auto-generated method stub
}
public ServletConfig getServletConfig() {
// TODO Auto-generated method stub
return null;
}
public String getServletInfo() {
// TODO Auto-generated method stub
return null;
}
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
}
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
System.out.println("hello servlet");
}
}
现在编写了.java,其.class会编译到WEB-INF的classes目录下,但WEB-INF这个目录下的文件不能被外部直接访问,如何让外界可以访问?
在web.xml文件的<web-app>
元素下填写
<!-- 创建一个Servlet实例 -->
<servlet>
<servlet-name>servletDemo1</servlet-name>
<servlet-class>com.eaglezsx.servlet.ServletDemo1</servlet-class>
</servlet>
<!-- 给Servlet提供(映射)一个可供客户端访问的URI -->
<servlet-mapping>
<servlet-name>servletDemo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
启动服务器,在浏览器中访问http://localhost:8080/HelloWeb/demo1,之后会在MyEclipse的控制台中会打印出hello servlet。
元素<servlet>
用于注册Servlet,它的两个子元素<servlet-name>
和<servlet-class>
分别用来指定Servlet名称及其完整类名(找到那个类,右键–>Copy QualifiedName就可以获得全类名)。元素<servlet-mapping>
用于映射Servlet对外访问的虚拟路径,它的子元素<servlet-name>
的值必须和<servlet>
元素中<servlet-name>
相同,子元素<url-pattern>
则是用于指定访问该Servlet的虚拟路径,该路径以正斜线(/)开头,代表当前Web应用程序的根目录。
Servlet的生命周期
在Java中,任何对象都有声明周期,Servlet也不例外。创建了当前应用的类后,会找lib下的jar,然后找Tomcat中lib目录的所有jar。
按照功能的不同,大致可以将Servlet的生命周期分为三个阶段,分别是初始化阶段、运行阶段和销毁阶段。
1.初始化阶段
当客户端向Servlet容器发出HTTP请求要求访问Servlet时,Servlet容器首先会解析请求,检查内存中是否已经有了该Serlvet对象,如果有直接使用该Serlvet对象,如果没有就创建Servlet实例对象,然后通过调用init()方法实现Servlet的初始化工作。在Servlet的整个生命周期内,它的init()方法只被调用一次。
2.运行阶段
在这个阶段,Servlet容器会为这个请求创建代表HTTP请求的ServletRequest对象和代表HTTP响应的ServletResponse对象,然后将它们作为参数传递给Servlet的service()方法。service()从ServletRequest对象中获得客户请求信息并处理该请求,通过ServletResponse对象生成响应结果。在Servlet的整个生命周期内,对于Servlet的每一次访问请求,Servlet容器都会调用一次Servlet的service()方法,并且创建新的ServletRequest和ServletResponse对象,也就是说,service()方法在Servlet的整个生命周期中会被调用多次。
3.销毁阶段
当服务器关闭或Web应用被移除容器时,Serlvet随着Web应用的销毁而销毁。在销毁Servlet之前,Servlet容器会调用Servlet的destroy()方法,以便让Servlet对象释放它所占用的资源。在Servlet的整个生命周期中,destroy()方法也只被调用一次。Servlet对象一旦创建就会驻留在内存中等待客户端的访问,直到服务器关闭,或Web应用被移除容器时Servlet对象才会销毁。
自动加载Servlet程序
有时,我们希望某些Servlet程序可以在Tomcat启动时随即启动(其构造方法和init方法会被调用 )。例如,当启动一个Web项目时,首先需要对数据库信息进行初始化。
<servlet>
<servlet-name>servletDemo1</servlet-name>
<servlet-class>com.eaglezsx.servlet.ServletDemo1</servlet-class>
<!-- 设置Servlet在Web应用启动时初始化 -->
<load-on-startup>1</load-on-startup>
</servlet>
在<servlet>
元素中添加元素<load-on-startup>
,用于指定Servlet被加载的时机和顺序,其值必须是一个整数。如果这个值是一个负数,或者没有设定这个元素,Servlet容器将在客户端首次请求这个Servlet时加载它;如果这个值为正整数或0,Servlet容器将在Web应用启动时加载并初始化Servlet,并且<load-on-startup>
的值越小,它对应的Servlet就越先被加载。这个元素应该放在最后一个。
可以在其构造方法和init方法中分别输出一句话,在Tomcat启动的时候,观察控制台,会发现这两句话被打印出来了。
Servlet的三种创建方式
1.实现javax.servlet.Servlet接口
2.继承javax.servet.GenericServlet类(适配器模式 )
实现Servlet接口的话,需要重写里边的所有方法。现在继承GenericServlet类(这个类实现了Servlet接口,除了 service方法为抽象的别的方法都已实现),只需要重写service方法就可以了。
查看GenericServlet的源码,这时候会提示没有找到文件。因为服务器已经实现了JavEE规范,这是由服务器创建的,服务器源码中会有,找到之前下载的apache-tomcat-7.0.75-src.zip,添加进来,就可以看到起源码了。
GenericServlet中实现了Servlet和ServletConfig接口,里边的方法基本都实现了,现在GenericServlet中有一堆方法,用的时候时候直接用就行了。
3.继承javax.servlet.http.HttpServlet类(模板方法)
因为大多数Web应用都是通过HTTP和客户端进行交互,因此,在Servlet接口中,提供了一个抽象类HttpServlet,它是GenericServlet的子类,专门用于创建应用于HTTP的Servlet。
查看HttpServlet中的源码
public abstract class HttpServlet extends GenericServlet {
...
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
...
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
...
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
...
@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");
}
service(request, response);
}
}
最主要看这四个方法
- doPost
- doGet
- service(ServletRequest req, ServletResponse res)
- service(HttpServletRequest req, HttpServletResponse resp)
HttpServlet主要有两大功能,第一是根据用户请求方式的不同,定义相应的doXxx()方法处理用户请求。例如,与Get请求方式对应的doGet()方法。第二是通过service()方法将HTTP请求和响应分别转为HttpServletRequest和HttpServletResponse。
由于HttpServlet类在重写的service()方法中,为每一种HTTP请求方式都定义了对应的doXxx方法,因此,当定义的类继承HttpServlet后,只需要根据请求方式,重写对应的doXxx方法即可,而不需要重写service()方法。
通过get请求或post请求提交出现乱码情况时的解决方法是不同的,现在将这两种请求方式分开,到时候分别编写对应的解决方法。
自动创建Servlet
以前是New–>Class,现在改成New–>Servlet
然后填写类名ServletDemo2,选择自动创建的方法(只选doGet和doPost),然后下一步,Display Name和Description里面的内容都删了(只是一些说明,没有必要显示),Servlet的名字和Mapping URL自己看情况设置。
然后点击完成,那些配置信息会自动填写在web.xml中。但通过默认Servlet模板生成的很乱,一堆注释和out.println。
在MyEclipse的安装目录\Common\plugins中找到com.genuitec.eclipse.wizards这个jar文件,用压缩工具打开,注意是打开不是解压这个jar包 ,进入templates文件夹,可以看到里面有一个Servlet.java文件,把相关内容改为
<aw:method name="doGet">
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
</aw:method>
<aw:method name="doPost">
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
</aw:method>
然后保存,重新启动MyEclipse,再生成的Servlet就没有那么多没用的东西。更详细的可以查看修改MyEclipse默认生成的Servlet以及JSP页面
Servlet虚拟路径的映射
1.Servlet的多重映射
Servlet的多重映射指的是同一个Servlet可以被映射成多个虚拟路径。也就是说,客户端可以通过多个路径实现对同一个Servlet的访问。
<servlet-mapping>
<servlet-name>servletDemo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>servletDemo1</servlet-name>
<url-pattern>/demo2</url-pattern>
</servlet-mapping>
这样访问demo2或demo1的时候都会映射到servletDemo1
或者写成
<servlet-mapping>
<servlet-name>servletDemo1</servlet-name>
<url-pattern>/demo1</url-pattern>
<url-pattern>/demo2</url-pattern>
</servlet-mapping>
2.Servlet映射路径中使用通配符
(1)格式为“*.扩展名”,例如“*.do”匹配以“*.do”结尾的所有URL地址
(2)格式“/*”,例如“/abc/*”匹配以“/abc”开始的所有URL地址。
这两种通配符的格式不能混合使用,例如,/abc/*.do就是不合法的映射路径。另外,当客户端访问一个Servlet时,如果请求的URL地址能够匹配多个虚拟路径,那么Tomcat将采取最具体匹配原则查找与请求URL最接近的虚拟映射路径。
3.默认Servlet
如果某个Servlet的映射路径仅仅是一个正斜线(/),那么这个Servlet就是当前Web应用的默认Servlet。
Servlet服务器在接收访问请求时,如果在web.xml文件中找不到匹配的<servlet-mapping>
元素的URL,就会将访问请求交给默认Servlet处理,也就是说,默认Servlet用于处理其他Servlet都不处理的访问请求。
在Tomcat安装目录下的web.xml文件中也配置了一个默认的Servlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
...
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
当Tomcat服务器中的某个Web应用没有默认Servlet时,都会将DefaultServlet作为默认的Servlet。当客户端访问Tomcat服务器中的某个静态HTML文件时,DefaultServlet会判断HTML是否存在,如果存在,就会将数据以流的形式会送给客户端,否则会报告404错误。
Servlet的线程安全
单实例:每次访问多线程
解决线程安全的最佳办法,不要写全局变量,而写局部变量。
ServletConfig接口
这个接口用来获取配置信息,在web.xml中的servlet元素中填写,现在我要在程序中获取到这些信息。有三种方式
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
第一种
public class ServletDemo1 extends HttpServlet{
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config=config;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String value=config.getInitParameter("encoding");
System.out.println(value);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("Post");
}
}
重写其父类的父类GenericServlet中的init(ServletConfig config)方法,得到ServletConfig,通过其getInitParameter方法获得。
第二种
String value=this.getServletConfig().getInitParameter("encoding");
System.out.println(value);
通过其父类GenericServlet中的getServletConfig()得到ServletConfig,然后通过其getInitParameter方法获得。
第三种
String value=this.getInitParameter("encoding");
System.out.println(value);
直接通过GenericServlet中的getInitParameter方法获得,因为GenericServlet实现了ServletConfig接口。
ServletContext接口
当Servlet容器启动时,会为每个Web应用创建一个唯一的ServletContext对象代表当前Web应用,该对象不仅封装了当前Web应用的所有信息,而且实现了多个Servlet之间数据的共享。
1.获取Web应用程序的初始化参数
在web.xml文件中,不仅可以配置Servlet的初始化信息,还可以配置整个Web应用的初始化信息。
在根元素<web-app>
中填写
<context-param>
<param-name>address</param-name>
<param-value>beijing</param-value>
</context-param>
在Servlet中填写代码
String value=this.getServletContext().getInitParameter("address");
System.out.println(value);
2.实现多个Servlet对象共享数据
由于一个Web应用中的所有Servlet共享一个ServletContext对象,因此Servlet-Context对象的域属性可以被该Web应用中的所有Servlet访问。在ServletContext中维护了一个Map集合。
- Enumeration getAttributeNames():返回一个Enumeration对象,该对象包含所有存放在ServletContext中的所有域属性名。
- Object getAttibute(String name):根据参数指定的域属性名返回一个与之匹配的域属性值。
- void removeAttribute(String name):根据参数指定的域属性名,从ServletContext中删除匹配的域属性。
- void setAttribute(String name,Object obj):设置ServletContext的域属性,其中name是域属性名,obj是域属性值。
在第一个Servlet的doGet方法中填写代码
ServletContext application=this.getServletContext();
application.setAttribute("name", "Tom");
在第二个Servlet的doGet中填写
String name=(String)this.getServletContext().getAttribute("name");
System.out.println(name);
先放我第一个,再访问第二个,就会在控制台打印出Tom。
3.读取Web应用下的资源文件
- String getRealPath(String path):返回资源文件在服务器文件系统上的真实路径(文件的绝对路径)。参数path代表资源文件的虚拟路径,它应该以正斜线(/)开始,“/”表示当前Web应用的根目录,如果Servlet容器不能将虚拟路径转换为文件系统的真实路径,则返回null。
1.在MyEclipse的src目录下,创建一个文件test.properties,里边填写Address=Beijing
MyEclipse中src目录下创建的资源文件在Tomcat服务器启动时会被复制到WEB-INF/classes目录下
2.在Servlet中的doGet中填写代码
ServletContext sc=this.getServletContext();
String path=sc.getRealPath("/WEB-INF/classes/test.properties");
Properties prop=new Properties();
prop.load(new FileInputStream(path));
System.out.println(prop.getProperty("Address"));
Servlet的转发
在ServletDemo1中填写代码
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ServletContext sc=this.getServletContext();
RequestDispatcher rd=sc.getRequestDispatcher("/ServletDemo2");
rd.forward(req, resp);
System.out.println("转发回来了");
}
在ServletDemo2中填写代码
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("转发过来了");
}
在浏览器访问ServletDemo1的时候,控制台显示为
转发过来了
转发回来了