Servlet3.0新特性(从注解配置到websocket编程)

     Servlet3.0的出现是servlet史上最大的变革,其中的许多新特性大大的简化了web应用的开发,为广大劳苦的程序员减轻了压力,提高了web开发的效率。主要新特性有以下几个:

  • 引入注解配置
  • 支持web模块化开发
  • 程序异步处理
  • 改进文件上传API
  • 非阻塞式IO读取流
  • Websocket实时通信

一、注解配置      Servlet3.0新规范顺应了时代的潮流,使用注解配置,取代混乱的web.xml全局配置。在这之前我们在创建servlet,filter,listener时,都是在web.xml中配置。

//创建一个servlet需要在web.xml中配置如下内容
<servlet>
        <servlet-name>myFirstServlet</servlet-name>
        <servlet-class>Test.myServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>myFirstServlet</servlet-name>
        <url-pattern>/aaa</url-pattern>
    </servlet-mapping>
//我们只使用一行代码完成servlet的配置
@WebServlet(name = "myFirstServlet",urlPatterns = {"/aaaa"})

public class myServlet extends HttpServlet {

    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        RequestDispatcher rd =  req.getRequestDispatcher("/default.jsp");
        rd.forward(req,resp);
    }
}

关于filter和listener的注解配置方法和上述形式一样,在3.0新规范中主要提供了以下一些注解用于配置:

  • Websocket :用于配置socket
  • WebInitParam :用于配置初始化参数,往往和servlet和filter结合使用
  • WebListener :用于配置Listener
  • WebFilter :用于配置Filter
  • MultipartConfig :用于文件上传(后面会详细介绍)
  • 还有一些,暂时没有涉及,就不列举了

二、Servlet3.0 Web模块化开发      在这之前我们对于web应用中的各个Servlet,Filter,Listener都是需要在web.xml中进行配置,如果只是本项目中的各个点的配置,那倒还好,但是如果我们引入框架,是不是每个框架中的各种配置也是需要在我们的web.xml中配置?这无疑会导致我们唯一的web.xml中内容混乱。Servlet3.0新规范提出了模块化开发,也就是每个Servlet,Filter,Listener都可以有属于自己的配置文件,功能和web.xml一样,它只负责配置当前的servlet。然后我们只需要将配置文件和自己写的Servlet等内容打包成jar,引入到具体项目中即可。(就像我们想要使用了某个功能,引入了从网上下载的jar包到项目中)下面我们看如何使用,由于Servlet,Filter,Listener的配置类似,此处以Servlet为例作为演示:      首先我们写一个servlet类:

public class MyServlet extends HttpServlet {
    
    @Override
    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        RequestDispatcher rd = req.getRequestDispatcher("/default.jsp");
        rd.forward(req,resp);
    }
}

然后我们创建一个web-fragment.xml文件,这就是属于此Servlet自己的配置文件,功能类似于Web.xml,只是这个是私有的。键入以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<web-fragment 
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
    metadata-complete="false">
    
    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>Test.MyServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>myServlet</servlet-name>
        <url-pattern>/index</url-pattern>
    </servlet-mapping> 
</web-fragment>

我们可以对比看出,web.xml文件和web-fragment.xml文件除了头部的不一样,一个是web-app,一个是web-fragment,别处几乎一样。我们创建的这个servlet主要负责拦截URL为index的请求,并转向default.jsp页面。

接下来我们看如何打包jar,然后再次为我们项目使用。第一步,无论你是用javac命令还是用IDE编译,首先我们需要将此.java文件编译成class文件。在你的电脑的任意位置创建一个空文件夹,将编译后的class文件及其包复制进去,因为我们MyServlet在Test包下,此处我就是将Test文件夹复制进去(你们需要根据自己建立的文件进行操作)

然后创建一个空文件夹,命名为META-INF,一定要这样命名,因为等我们把jar包引入到项目中之后,一旦web应用启动时,就会去我们引入的jar包的此文件夹下查找web-fragment.xml文件并加载,如果没有找到就不会加载,我们的配置也就不会生效。此时我们文件夹中的内容如下:

将刚刚写完的web-fragment.xml文件复制到META-INF下,然后我们将这两个文件夹压缩成zip格式,然后修改zip为jar即可(因为jar和zip的区别就在于jar中多了一个META-INF文件夹,如果我们已经手动添加了,那他们这两种格式就是一样了)

此处我们使用手动添加META-INF文件夹,然后压缩zip格式的形式来完成打包jar的工作,你也可以使用jdk自带jar命令来完成打包操作,效果是一样的。然后我们将此jar包复制到任意web应用的WEB-INF/lib下,这就是web应用的所有外部引入包所存放的地方。然后我们启动web容器:

结果如上,当我们请求index,拦截器拦截并调向default.jsp页面。这样我们就完成了通过引入外部的jar包而不需要做任何配置,使用了其功能。可能此例并没有很好的展示了这种模块化开发的优势,等到我们学到框架的时候就可以很直观的感受到这种方式的简洁,易于携带。

三、异步处理      在传统的servlet开发中,如果servlet调用了一个耗时很长的逻辑处理方法,那么此servlet必须待在原地等待方法调用结束,这是很低效的一种形式。servlet3.0提出了异步处理的概念,也就是释放了主程序,大大提高了运行效率。      Servlet3.0中异步处理主要是通过接口AsyncContext来实现的,我们可以通过HttpServletRequest对象来过去该接口的实现对象。

AsyncContext getAsyncContext();

在使用异步处理之前,我们还需要配置指定当前的servlet是支持异步处理。有两种方法,第一种是在web.xml中配置

<async-supported>true</async-supported>

或者使用webservlet指定属性asyncSupported=true。下面用一个实例演示如何使用servlet的异步处理机制:

@WebServlet(name = "myservlet",urlPatterns = "/index",asyncSupported = true)
public class MyServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{

        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer =  resp.getWriter();
        writer.println("servlet 开始:"+new Date()+"<br />");
        writer.flush();

        AsyncContext asy = req.startAsync();
        asy.setTimeout(4000);
        asy.start(new MyInfo(asy));

        writer.println("servlet 结束:"+new Date()+"<br />");
        writer.flush();
    }
}

我们可以看到,这个servlet非常简单,截取URL为index的请求,首先打印启动时间,然后通过request的startAsync方法创建AsyncContext 对象,设置过期时间,启动异步处理。这个线程类代码如下:

public class MyInfo extends Thread {

    private AsyncContext asyncContext;

    public MyInfo(AsyncContext as){
        this.asyncContext = as;
    }
    @Override
    public void run(){
        try {
            Thread.sleep(3000);
            PrintWriter pw = asyncContext.getResponse().getWriter();
            pw.println("hello walker:"+new Date()+"<br />");
            asyncContext.complete();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

一个构造方法接受AsyncContext 对象,run方法中,先打印一句话然后结束异步调用。我们看看结果:

通过时间我们可以看到servlet开始和结束几乎同时,而我们的异步处理却相差三秒钟,正是我们sleep的三秒钟。虽然我们实现了在servlet中异步调用别的线程来处理一些逻辑,但是我们还是不能完全控制整个异步处理中的各个过程,比如何时开始,何时结束等。Servlet3.0中的AsyncListener接口提供了以下几个方法帮助我们监控整个过程:

  • onStartAsync(AsyncEvent event) :当异步调用开始时触发
  • onComplete(AsyncEvent event) :当异步完成时触发
  • onError(AsyncEvent event) :当异步调用出错的时候触发
  • onTimeout(AsyncEvent event):当异步调用超时时候触发

想要实现监控异步调用,首先需要编写一个类继承自AsyncListener然后实现如上四个方法,之后这个类就是一个可以监控异步调用的监听器。

public class MyAsyncListener implements AsyncListener {

    public void onComplete(AsyncEvent var1) throws IOException{
        System.out.println("异步调用结束了。。。");
    }

    public void onTimeout(AsyncEvent var1) throws IOException{
        System.out.println("异步调用超时了。。。");
    }

    public void onError(AsyncEvent var1) throws IOException{
        System.out.println("异步调用出错了。。。");
    }

    public void onStartAsync(AsyncEvent var1) throws IOException{
        System.out.println("异步调用开始了。。。");
    }
}

在我们的Servlet主程序中使用以下语句绑定此异步监听器:

asy.addListener(new MyAsyncListener());

此时异步处理的四个结点的动态,我们都是实时掌控的。但是需要注意一点的是:虽然理论上我们是可以监听四个状态的,但是其实异步开始这个事件我们是没法监听的,也就是异步开始的方法永远不会被触发,原因是在注册AsyncContext 的时候,已经开始了异步,然而我们却在注册之后才绑定监听器,自然是不能监听到异步开始这个事件的。

四、文件上传API      对于传统的文件上传,我们是需要借助于外部工具的,例如:common-fileupload等。自从servlet3.0新规范以来,改进了文件上传API。

<body>
    <h1>这是index页面</h1>
    <form method="post" action="/submit" enctype="multipart/form-data">
          姓名:<input type="text" name="name" /><br /><br />
          头像:<input type="file" name="mFile" /><br /><br />
          <input type="submit" value="提交" />
    </form>
  </body>

我们知道,在html中上传文件的表单用type="file"来指定,这是一点,还有一点就是from标签的enctype属性,他指定了表单参数的编码方式,主要有以下三种:

  • application/form-data :这是enctype的默认值,指定了这个值就表名表单只会提交所有input标签中的value值,对于我们的文件,提交的就是文件名。
  • multipart/form-data:这种方式是将参数以二进制存储,上传文件的内容也会被封装成二进制流提交。
  • text/plain:这种方式主要用于发送邮件

对于需要上传文件功能的我们自然选择第二个参数值,正如上述代码展示的一样。下面我们写一个servlet用于处理上传的信息。

@WebServlet(name = "myServlet",urlPatterns = {"/submit"})
@MultipartConfig   //处理文件上传的servlet需要配置此注解
public class FileUpload extends HttpServlet {

    public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        Part part = req.getPart("mFile");
        writer.println("文件类型:"+part.getContentType()+"<br />");
        writer.println("文件名:"+part.getName()+"<br />");
        part.write("C:\\Users\\Administrator\\Desktop\\photo.jpg");
    }
}

在servlet3.0中采用Part接口来处理文件上传,可以通过HtppServletRequest的以下两个方法来获取此接口对象:

Part getPart(String name);
Collection<Part> getParts();

一个part对应于我们一个文件上传域,也就是一个input类型为file的元素。part中有以下一些方法:

    String getContentType();   //返回文件类型,如image/png

    String getName();          //返回文件名

    String getSubmittedFileName();

    long getSize();          //返回文件的大小

    void write(String var1) throws IOException;   //将文件写入到服务器磁盘

    void delete() throws IOException;          //删除此文件

    String getHeader(String var1);            //获取指定文件名的值

    Collection<String> getHeaders(String var1); //获取指定文件名的所有的值

    Collection<String> getHeaderNames();   //获取所有Header 的name集合

在上面的程序中,我们使用了其中一些方法。打印了文件类型,文件名,最后将文件保存到本地桌面上。下面是运行的结果截图:

综上就是关于文件上传API的基本使用情况,还有一些内容留待下篇。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ceph对象存储方案

源码阅读再来一发:解读RGW中request的处理流程

请求处理流程图 ? 以civetweb为例 1. rgw_main.cc为整个radosgw服务的入口,main()函数中根据在ceph.conf的rgw...

3448
来自专栏Java Edge

Cookie与Session基础知识点

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。 Cookie通过在客户端记录信...

43411
来自专栏用户2442861的专栏

【java项目实战】Servlet详解以及Servlet编写登陆页面(二)

http://blog.csdn.net/jiuqiyuliang/article/details/36424981

371
来自专栏Jaycekon

Spring-Boot:6分钟掌握SpringBoot开发

 构建项目 从技术角度来看,我们要用Spring MVC来处理Web请求,用Thymeleaf来定义Web视图,用Spring Data JPA来把阅读列表持久...

3746
来自专栏WindCoder

springBoot初探-创建项目

一边学习公司用到的技术,一边重构小程序后端,从而更好的理解公司搭建的框架。此处记录一个用idea+gradle+springboot的基础实现。

471
来自专栏Web项目聚集地

从零学习Spring Boot-入门起步

Spring Boot是Spring社区较新的一个项目。该项目的目的是帮助开发者更容易的创建基于Spring的应用程序和服务,让更多人的人更快的对Spring进...

613
来自专栏郭少华

Spring boot之Hello World(一)

743
来自专栏xingoo, 一个梦想做发明家的程序员

web中的cookie管理

  本篇是以JSP为背景介绍,但是在web开发中也是相同的原理。   什么是cookie   由于http是一种无状态的协议,因此服务器收到请求后,只会当做一次...

18110
来自专栏Java Web

初学Java Web(3)——第一个Servlet

这学期 Java Web 课程的第一节课就简短复习了一下 Java 的一些基础知识,所以觉得 Java 的基础知识还是很重要的,但当我想要去写一篇 Java ...

2774
来自专栏码匠的流水账

java9 module相关选项解析

由于java.se.ee不在默认的root modules中,因此—add-modules的最常见的用途是用来添加ee中的模块,比如javv.xml.bind

431

扫码关注云+社区