Java Web之Servlet入门

Java Web之Servlet入门

​ 上世纪90年代,随着Internet和浏览器的飞速发展,基于浏览器的B/S模式变得火爆起来。 最初,用户使用浏览器向WEB服务器发送的请求都是请求静态的资源,比如html、css等。 但是静态网页很难满足不同用户的需求,因此根据用户请求的不同动态的处理并返回资源是理所当然必须的要求。

​ 首先CGI(后文有介绍)出现了,但是其过于“笨重”的设计,且限制过多,因此很难同时处理大量的客户端请求,而Java在探索动态网站技术的过程中,首先推出了applet(Java编写的插件,需要浏览器端安装),其后,推出了Servlet,其命名可以理解为(Server + applet),将Java代码的执行都放在后端服务器执行,而不需要依赖浏览器端。Servlet的出现对动态网站的发展作出了重要的贡献,到现在,Servlet技术也是一些web框架的基础,比如现在流行的SpringMVC框架。因此,Servlet仍是一项必须要掌握的技术。

​ 下面,让我们一起来了解下Servlet,一起与Servlet来次"亲密"接触。

1.Servlet的基本概念

​ Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

​ 大家想一想,在我们学习Java的时候,编写的代码,是如何调用的,我想大多都是在编辑中通过执行main函数或者是单元测试来进行执行,在进一步,就是将代码打包成jar包,通过指定主函数来提供代码的执行入口。但是如果想一想,这样指定主函数入口的话,其实一个jar包只可实现一个单一的功能。如果我们要开发一款应用,就仅以登录模块来将,我们可能需要使用注册、修改密码、登录等等的调用,如果每个接口都要开一个应用,那程序员也就不用做别的事了。

​ 因此,Servlet的作用就凸显出来了。比如用户在浏览器中要访问这么地址:http://localhost:8080/xxx/loginServlet,我们就可以在xxx项目中创建一个Servlet,并为其配置路径映射,那么从浏览器中到达服务器中的请求,就可以通过xxx/logonServlet来找到对应项目中的对应的Servlet容器,也就可以执行对应的代码了。使用 Servlet,既可以通过其收集来自网页表单的用户输入,并呈现来自数据库或者其他源的数据,还可以动态创建网页

资源分配图

​ 讲Servlet就不得不提到CGI(Common Gateway Interface,公共网关接口),两者可以实现相同的效果,不过相比CGI,Servlet优势如下:

  • Servlet 在 Web 服务器的地址空间内执行,这样对于每个客户端的请求,就可以通过线程来处理,也因此会有更好的性能(线程更轻量级);
  • Servlet 是用 Java 编写的,可以很好的跨平台;
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源,更安全;
  • Java 类库的全部功能对 Servlet 来说都是可用的,不仅可以调用本项目中的资源,还可以通过网络调用处于别处服务器中的Servlet。

2.Servlet包

​ 为了支持Servlet的开发,或者说方便我们创建Servlet,jdk提供了一个jar包----servlet-api.jar,这个包中有几个比较重要接口和类,其中包括:javax.servlet 包中的Servlet接口和GenericServlet抽象类,javax.servlet.http中的HttpServlet类;这几个类的关系为GenericServlet抽象类实现了Servlet接口,并提供了部分实现,HttpServlet继承了Genericrvlet抽象类,并实现了其所有方法。其关系如下图所示,对于其中的ServletRequest接口、ServletResponse接口分别是来自客户端的请求和对客户端的响应,HttpServletRequset、HttpServletResponse分别继承了上面两个接口。

资源分配图

​ 一般我们创建Servlet,是通过继承HttpServlet,这样我们只需重写doPost和doGet方法即可。因为Servlet接口是源头,我们来看下其中的主要抽象方法:

资源分配图

3.Servlet生命周期

​ Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程,也对应三个阶段:

  • 初始化阶段:Servlet 通过调用 init () 方法进行初始化;
  • 运行阶段:Servlet 调用 service() 方法来处理客户端的请求;
  • 销毁阶段:Servlet 通过调用 destroy() 方法终止(结束),并由 JVM 的垃圾回收器进行垃圾回收的。

​ 其中的init()方法应只调用一次,Servlet创建后,在其运行阶段,便可通过service方法来处理来自客户端的请求,通过,当服务器关闭(tomcat停止运行)或者应用被移除容器(tomcat下webapps目录中的文件被移除)时,调用destory()方法来终止Servlet。应其生命周期如下图所示:

资源分配图

​ 其中和我们相关性最强的service()方法,在客户端请求Servlet时,Servlet容器会为这个请求创建一个代表此次HTTP请求的ServletRequest对象,和对此请求进行响应的ServletResponse对象,并将这两个对象传给service方法,由service方法来处理请求。

​ 我们来看下HttpServlet中的service()方法的源码(HttpServlet我们用的比较多):

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  String method = req.getMethod();
  long lastModified;
  if (method.equals("GET")) {
    //...
  } else if (method.equals("HEAD")) {
    lastModified = this.getLastModified(req);
    this.maybeSetLastModified(resp, lastModified);
    this.doHead(req, resp);
  } else if (method.equals("POST")) {
    this.doPost(req, resp);
  } else if (method.equals("PUT")) {
    this.doPut(req, resp);
  } else if (method.equals("DELETE")) {
    this.doDelete(req, resp);
  } else if (method.equals("OPTIONS")) {
    this.doOptions(req, resp);
  } else if (method.equals("TRACE")) {
    this.doTrace(req, resp);
  } else {
    String errMsg = lStrings.getString("http.method_not_implemented");
    Object[] errArgs = new Object[]{method};
    errMsg = MessageFormat.format(errMsg, errArgs);
    resp.sendError(501, errMsg);
  }
}

​ 从上面我们可以看到,service方法会根据客户端的请求类型来做转发,调度不同的方法来处理用户的请求,也因此,我们重写doPost()、doGet()方法,来处理我们的请求,并将响应结果通过HttpServletResponse来返回给客户端。

​ 对于上述service()中的参数列表为何变成HttpServletRequest与HttpServletResponse,主要是下面的函数来做的转换,可以看到方法名同样是service,其实,这个方法才是HttpServlet重写的Genericrvlet中的service方法,在这个方法中,将ServletRequest与ServletResponse向下转型,变成了上个代码块中我们看到的那样。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  HttpServletRequest request;
  HttpServletResponse response;
  try {
		//向下转型
    request = (HttpServletRequest)req;
    response = (HttpServletResponse)res;
  } catch (ClassCastException var6) {
    throw new ServletException("non-HTTP request or response");
  }
	//调用重载的service方法,上个代码块中的
  this.service(request, response);
}

4.创建一个Servlet

​ 理论说了这么多,现在来动手实现一个Servlet,并在浏览器上来进行调用。

​ 在之前的一篇博文中,我们完成了开发环境的大家和第一个web项目的创建,这次,我们就继续在这个项目中来创建我们的第一个servlet。

​ 首先,我们要在项目的Java Resources目录的src下创建package(包),一般我们的Java类都在包中,包优点和特点还请自行回顾,创建之后项目结构如下:

资源分配图

​ 然后右击刚创建的包–>new–>Servlet,可以看到如下页面,从上之下分别为项目名,源代码文件目录,新建Servlet所在包,Servlet名,继承的父类(HttpServlet,也是我们上面着重讲的);

资源分配图

​ 填写好Servlet名称后,点击next,来配置一些重要信息,其中1.类名,上一页面填写;2.Servlet描述信息,可为空;3.初始化参数,可以设置零个、一个或多个;4.映射路径,也是我们最关心的,我们就是通过这个映射来找到对应的Servlet,来处理我们的请求,这里同样可以设置多个,不过一般没有必要;5.是否支持异步,默认不选择,即false,选择支持后可以在servlet中使用线程异步的处理业务,客户端可以早早的接收到响应并且服务器端可以释放资源。

资源分配图

​ 上述配置填写完毕后,点击Finsh即可,这样,一道新鲜的Servlet大菜就出炉了。代码如下所示:

@WebServlet(
      description = "My First Servlet", 
      urlPatterns = { "/HelloServlet" }, 
      initParams = { 
        @WebInitParam(name = "name", value = "lizishu")
      })
public class HelloServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  /**
     * @see HttpServlet#HttpServlet()
     */
  public HelloServlet() {
    super();
    // TODO Auto-generated constructor stub
  }

  /**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
    response.getWriter().append("Served at: ").append(request.getContextPath());
  }

  /**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
    doGet(request, response);
  }
}

​ 从上面代码块中,我们可以看到,顶部是一个@WebServlet注解,这个也是Servlet3.0之后的一个特点,配置注解化了,不用在web.xml中在去一个个配置了,但是实现的功能是相同的,我们可以看到,@WebServlet中的一些属性就是我们在创建的过程中设置的。其中@WebServlet中的每个属性的解释如下:

属性名 类型 含义
name String 指定Servlet 的 name 属性,等价于<servlet-name>标签
value String[] 该属性等价于 urlPatterns 属性。两个属性不能同时使用
urlPatterns String[] 指定一组 Servlet 的 URL 匹配模式。等价于<url-pattern>标签
loadOnStartup int 指定 Servlet 的加载顺序,等价于 <load-on-startup>标签,0或者大于0,项目启动是就加载,大于0时,数字越小,启动优先级越高
initParams WebInitParam[] 一组 Servlet 初始化参数,等价于<init-param>标签
asyncSupported boolean 声明 Servlet 是否支持异步操作模式,等价于<async-supported> 标签,不设置时默认值为false
description String Servlet 的描述信息,等价于 <description>标签
displayName String Servlet 的显示名,等价于<display-name>标签

​ 如果创建后还想在修改Servlet的配置,可以根据上面的属性来进行修改。

​ 其中的HelloServlet()方法为构造函数,我们可以在这里放入一些初始化代码,修改后如下所示:

public HelloServlet() {
  super();
  System.out.println("HelloServlet 被初始化了");
}

​ 还有就是doPost()、doGet()两个方法,我们只需在一个方法中来实现我们的代码逻辑即可,比如我们在doGet()中写在如下代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  //控制台打印客户端请求访问时间和请求方式
  System.out.println(System.currentTimeMillis() + "客户端的请求方式为:" + request.getMethod());
  //得到一个可以将数据返回给客户端的打印对象
  PrintWriter out = response.getWriter();
  //可通过out实例的println方法输出到客户端
  //request.getContextPath()可以获得项目的根路径,一般为项目名,可以用作绝对定位
  out.println("Served at: " + request.getContextPath());
  //获取创建Servlet时的初始化参数,可以通过name获取
  String name = this.getInitParameter("name");
  //将获取的初始化参数输出到前端
  out.println("name: " + name);
}

​ 我们来看下在浏览器中的执行结果,请求的url为项目名(根路径)+Servlet配置的映射路径。

资源分配图

​ 为了对比post和get方法,我们使用ApiPost(或其他工具,如PostMan)来发起测试,首先请求方式为Get,其次请求方式为Post,其返回给过和上图浏览器中的响应一致,结果如下,可以自行修改请求方式尝试:

资源分配图

​ 然后,我们一起来看下项目的控制台输出,从中,我们可以看到,HelloServlet()构造函数是在第一次调用时执行的(可以通过设置loadOnStartup提前加载),后面该servlet再次执行客户端的请求时不会再执行;最后两行为在ApiPost中模拟的get、post请求,从后端服务的处理和响应结果来看,其实get和post两种方式并无本质上的区别。

资源分配图

5.总结

​ 本文主要简单的介绍了Servlet的一些概念,和一些基本的用法,详细的介绍了Servlet创建的过程和@WebServlet注解,其实Servlet能做的事更多,我们后续的博客中再来介绍其更强大的招式。


​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​ Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

​ 有任何疑问,可以评论区留言。

发布了38 篇原创文章 · 获赞 304 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_34666857/article/details/104384194