介于Servlet是Java Web开发的基础,因此好好看了一下Servlet3.0.1的源码,于是有了这篇记录。
Servlet架构图
Servlet和JSP是众多java EE定义的技术当中的两种,其他还有JMS,EJB等等,运行JEE程序需要一个JEE容器,如GlassFish、JBOSS、WebLogic等,Servlet\JSP也可以部署在JEE容器中,不过用Servlet/JSP容器已经足够了,而且比JEE容器更加轻量化,Tomcat和Jetty不属于JEE容器,不能运行EJB或JMS。
首先,下载Servlet-api源码,可以使用IDE,maven等方式,本文使用maven下载,命令如下(或者自行搜索下载源码):
maven dependency:sources
servlet API中有4个Java包,包括:
javax.servlet 定义Servlet和Servlet容器之间的七月类和接口。
javax.servlet.http 定义HTTP Servlet与Serlvet容器之间的契约和接口
javax.servlet.annotation 包含对Servlet、Filter和Listener进行标注的注解。还为标注元件指定元数据。
javax.servlet.descriptor 包含为Web应用程序的配置星系提供编程式访问的类型。
不完整截图:
javax.servlet方法一览
Servlet包中的主要成员:
Servlet主要成员
Servlet接口是核心接口,是所有Serlvet都必须直接或间接实现的一个接口,Servlet接口定义了Servlet与Servlet容器之间的约定,总的来说就是Servlet容器会把Servlet类加载到内存中,并在Servlet实例中调用特定方法,在一个应用程序中,每个Servlet类型只能有一个实例。当用户的请求引发service方法,并给这个方法传入一个ServletRequest实例和一个ServletResponse实例。ServletRequest封装当前的HTTP请求,让开发者不必去解析和操作原始的HTTP数据,同理,ServletResponse表示当前用户的HTTP响应,它的作用是使得将响应回传给用户更容易。Servlet容器还为每个应用程序创建一个ServletContext实例。这个对象封装context(应用程序)的环境细节,而每个context只有一个ServletContext。每个Servlet实例还有一个封装Servlet配置信息的ServletConfig。
下面先看看Servlet接口,(如果别人问你什么是Servlet,你可以告诉他,就是一个java接口,分分钟定义出来给你看,不过人们常说的Servlet应该是指任何实现了Servlet接口的类)看源码发现有234行(3.0.1版),仔细一看也就5个抽象方法,其他的全是注释,所以兄弟们,看源码不要虚,这就是传说中的和Servlet容器之间的约定(有没有很熟悉的感觉):
Servlet接口
init 第一次请求我们编写的Serlvet时,Servlet容器调用此方法,后续不在调用,可以利用这个方法做一些初始化的工作。在调用这个方法时,Servlet容器会传递一个ServletConfig。一般会将这个ServletConfig赋给一个类级变量,以方便其他方法也可以使用这个对象。
service 每次用户请求service时,servlet容器都会调用这个方法,我们对请求的处理就是在这里完成的。
destroy 要销毁Servlet时,Servlet容器就会调用这个方法,它通常发生在卸载应用程序,或者关闭Servlet容器的时候,这里一般我们会写一些资源清理相关的代码。
Servlet中另外另个费生命周期的方法:getServletInfo和getServletConfig
getServletInfo 就是字面意思,返回Servlet的描述
getServletConfig 这个方法返回由Servlet容器传给init方法的ServletConfig,上面说了,一般在init方法中将ServletConfig赋给一个类级变量,免得本方法返回null。
PS:由于Servlet不是线程安全的,一个应用程序中所有的用户公用一个Servlet实例,因此不建议使用类级别的变量(只使用局部变量最好),除非是只读的或者java.utilconcurrent.atomic包中的成员。
现在来写一个Servlet应用程序,写起来很简单,只要创建一个目录,并将Servlet类放在指定目录中就可以了,同时,如果要运行这个应用程序,你还需要安装一个Servlet容器,如Tomcat或者Jetty(安装方法自行搜索)。
编写Servlet应用
Servlet应用目录结构
以上为Servlet的目录结构,要编译servlet,类路径中还要有servlet API,tomcat容器中已经自带了servlet-api.jar。另外珍爱生命,还是用IDE来创建吧,博主试过,自己去一步一步创建配置虽说也可以,但是确实会花费不少时间,如果你就是要自己一步一步创建,觉得这样能学到更多东西,我只能说加油骚年!
应用程序中一般会有JSP、HTML、图像等其他资源,这些都应该放在应用程序的目录下面,并且经常放在子目录下,如上图,html放在html文件下,jsp放在jsp目录下。放在应用程序目录下的任何资源,用户可以通过资源的URL直接访问(放在应用程序目录下当然要可以访问了),如果希望某个资源可以被Servlet访问,但不能被用户访问,那就应该放在WEB-INF目录下(是不是找到该目录的作用了)。
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
//name可选,提供servlet名,关键urlPatterns指定匹配当前servlet的模式
@WebServlet(name="/FirstServlet",urlPatterns={ "/myapp" })
public class FirstServlet implements Servlet {
private transient ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
//将config给类变量,扩大使用范围
this.config=config;
}
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return "My Servlet";
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
String servletName=config.getServletName();
response.setContentType("text/html");
PrintWriter pw=response.getWriter();
pw.write("hello from "+servletName);
}
@Override
public void destroy() {}
}
以上程序部署完成,启动Servlet容器,就可以通过url在浏览器中访问了:
通过访问 http://localhost:8080/App01/FirstServlet
效果图
对于每一个HTTP请求,servlet容器会创建一个封装了HTTP请求的ServletRequest实例传递给servlet的service方法,ServletResponse则表示一个Servlet响应,其影藏了将响应发给浏览器的复杂性。通过ServletRequest的方法你可以获取一些请求相关的参数,而ServletResponse则可以将设置一些返回参数信息,并且设置返回内容。返回内容之前一般会调用setContentType方法设置响应的内容类型,如果没有设置,大多数浏览器会默认以html的形式响应,不过为了避免出问题,我们一般都设置该项。
值得注意的是ServletResponse中定义的getWriter方法,它返回可以将文本传给客户端的java.io.PrintWriter。在默认的情况下,PrintWriter对象使用ISO-8859-1编码,这有可能引起乱码。
以下为ServletRequest和ServletResponse的大部分方法:
ServletRequest
ServletResponse
ServletConfig封装可以通过@WebServlet或者web.xml传给一个Servlet的配置信息,以这种方式传递的每一条信息都称做初始化信息,初始化信息就是一个个K-V键值对。为了从一个Servlet内部获取某个初始参数的值,init方法中调用ServletConfig的getinitParameter方法或getinitParameterNames方法获取,除此之外,还可以通过getServletContext获取ServletContext对象。方法签名:
ServletConfig
通过WebServlet传递配置信息示例:
WebServlet初始化参数
ServletContext是代表了Servlet应用程序。每个Web应用程序只有一个context。在分布式环境中,一个应用程序同时部署到多个容器中,并且每台Java虚拟机都有一个ServletContext对象。有了ServletContext对象后,就可以共享能通过应用程序的所有资源访问的信息,促进Web对象的动态注册,共享的信息通过一个内部Map中的对象保存在ServiceContext中来实现。保存在ServletContext中的对象称作属性。操作属性的方法:
ServletContext
前面编写的Servlet应用中通过实现Servlet接口来编写Servlet,但是我们每次都必须为Servlet中的所有方法都提供实现,还需要将ServletConfig对象保存到一个类级别的变量中,GenericServlet抽象类就是为了为我们省略一些模板代码,实现了Servlet和ServletConfig,完成了一下几个工作:
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
同时为避免覆盖init方法后在子类中必须调用super.init(servletConfig),GenericServlet还提供了一个不带参数的init方法,当ServletConfig赋值完成就会被第带参数的init方法调用。这样就可以通过覆盖不带参数的init方法编写初始化代码,而ServletConfig实例依然得以保存(这难道不是适配器模式吗?)
为Servlet接口中的所有方法提供默认实现。
提供方法来包装ServletConfig中的方法。
用GenericServlet实现Servlet应用
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
@WebServlet(name="SecondServlet",
urlPatterns={"/generic"},
initParams={
@WebInitParam(name="user",value="xiaobai"),
@WebInitParam(name="email",value="[email protected]")
}
)
public class SecondServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
ServletConfig config=getServletConfig();
String user=config.getInitParameter("user");
String email=config.getInitParameter("email");
response.setContentType("text/html");
PrintWriter pw=response.getWriter();
pw.write("User:"+user+"
email:"+email);
}
}
在编写Servlet应用程序时,大多数都要用到HTTP,也就是说可以利用HTTP提供的特性,javax.servlet.http包含了编写Servlet应用程序的类和接口,其中很多覆盖了javax.servlet中的类型,我们自己在编写应用时大多时候也是继承的HttpServlet,以下为其中的重要成员:
HttpServelt主要成员
从上图看,HttpServlet继承了GenericServlet,HttpServletRequest/Response继承了覆盖了ServletRequest/Response,成为了新的Servlet请求和响应的代表。在HttpServlet中覆盖了GenericServlet的service方法,并用新的Servlet请求和响应代表作为参数添加了一个service方法:
//覆盖GenereicServlet中的service
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
}
//新service方法签名
protected void service(HttpServletRequest req, HttpServletResponse resp)
原始的service方法将请求和响应进行向下转换,分别为HttpServletRequest和HttpServletResponse,并调用新的service方法。看了下2.5版本中的实现,发现没有加以上代码是中的instanceof判断,恩,看来2.5中直接向下转型确实暴力了点,考虑容器不一定总是把请求当做HTTP请求,这样做看起来稳妥了些。新的service方法会查寻HTTP请求的方法从而调用do{Method}来处理请求。
总之HttpServlet中有两项特性是GenericServlet中没有的:
不覆盖service方法,而是覆盖doGet、doPost等。
用HttpServletRequest\Response 替代ServletRequest\Response
HttpServletRequest,HttpServletResponse由于带有了HTTP的特性,因此除了ServletRequest,ServletResponse中的方法之外还增加了几个可以获取HTTP特性信息的方法。
//获取context的请求URI部分
java.lang.String getContextPath()
//获取Cookie对象数组
Cookie [] getCookies()
//返回指定HTTP标头的值
java.lang.String getHeader(java.lang.String name)
//返回发出这条请求的HTTP方法的名称
java.lang.String getMethod()
//返回请求URL中的查询字符串
java.lang.String getQueryString()
//获取session对象,没找到就新创建
HttpSession getSession()
//返回与这个请求相关的session对象,如果没有,并且create参数为true,创建新的session对象
//响应对象添加cookie
void addCookie(Cookie cookie)
//添加标头
void addheader(String name,String value)
//重定向
void sendRedirect(String location)
在Servlet3.x中可以使用@WebServlet来部署应用,可以不必在WEB-INF目录下放一个web.xml配置文件,当然也可以同时使用,前者优先级更高,这是annotation为我们带来的好处,介于使用web.xml来配置Servlet也有其有点,就简单介绍下,使用web.xml配置优点:
不用更改代码,也就意味着不需要重新编译
可包含@WebServlet中没有的元素,如load-on-startup元素,init方法比较费时间这个就很有帮助了。
SimpleServlet
app01.SimpleServlet
2
SimpleServlet
/simple
现在再来看下我们我们通常写的Servlet,看完上文的客官,看到下面Servlet是不是感觉自己看到了更多的东西呢?反正我是看到了。讲真,如果想要缕下Servlet的话,真的可以试一试下载Servlet-api的源码看看,结合本文,或许风味更佳哦!!
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/normal")
public class NormalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
Servlet技术是Java EE技术的重要组成,Servlet容器中运行的所有Servlet,以及容器与Servlet之间的约定,都采用了javax.servlet.Servlet接口的形式。javax.servlet包也提供了实现Servlet接口的GenericServlet抽象类。这是一个比较方便的类,可以通过扩展它来创建Servlet。但是大多数的现代Servlet都在HTTP环境中处理请求,因此提供了javax.servlet.http.HttpServlet来继承GenericServlet并且加入HTTP特性。
作者:BigfaceMonster
链接:https://www.jianshu.com/p/e9f31c783ff1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。