Servlet3.0新特性

tomcat 7以上的版本都支持Servlet 3.0。

Servlet3.0新特性

  • 注解支持;Servlet、Filter、Listener无需在web.xml中进行配置(使用时将web.xml删除即可),可以通过对应注解进行配置;
  • 支持Web模块;
  • Servlet异步处理;
  • 文件上传API简化;

Servlet3.0的注解

  • @WebServlet :修饰Servlet类,用于部署该Servlet类。
  • @WebFilter:修饰Filter类,用于部署该Filter类
  • @WebInitParam:与@WebServlet或@WebFilter注解连用,为它们配置参数
  • @MultipartConfig:修饰Servlet类,指定该Servlet类负责处理multipart/form-data类型的请求(主要用于处理上传文件)
  • @ServletSecurity:修饰Servlet类,与JAAS(Java验证和授权API)有关的注解
  • @HttpConstrait:与@ServletSecurity连用
  • @HttpMethodConstrait:与@ServletSecurity连用

ServletContainerInitializer

在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。

一般伴随着ServletContainerInitializer一起使用的还有HandlesTypes注解,通过HandlesTypes可以将感兴趣的一些类注入到ServletContainerInitializerde的onStartup方法作为参数传入。

Tomcat容器的ServletContainerInitializer机制的实现,主要交由Context容器和ContextConfig监听器共同实现,ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中,最后Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法,并将感兴趣的类作为参数传入。

基本的实现机制如图,首先通过ContextConfig监听器遍历每个jar包或web根目录的META-INF/services/javax.servlet.ServletContainerInitializer文件,根据读到的类路径实例化每个ServletContainerInitializer;然后再分别将实例化好的ServletContainerInitializer设置进Context容器中;最后Context容器启动时分别调用所有ServletContainerInitializer对象的onStartup方法。

假如读出来的内容为com.seaboat.mytomcat.CustomServletContainerInitializer,则通过反射实例化一个CustomServletContainerInitializer对象,这里涉及到一个@HandlesTypes注解的处理,被它标明的类需要作为参数值传入到onStartup方法。如下例子:

@HandlesTypes({ HttpServlet.class,Filter.class }) 
public class CustomServletContainerInitializer implements 
    ServletContainerInitializer { 
  public void onStartup(Set<Class<?>> classes, ServletContext servletContext) 
      throws ServletException {
      for(Class c : classes) 
         System.out.println(c.getName());
  } 
}

其中@HandlesTypes标明的HttpServlet和Filter两个class被注入到了onStartup方法。所以这个注解也是需要在ContextConfig监听器中处理。由于有了编译器的协助,我们可以方便地通过ServletContainerInitializer的class对象中获取到HandlesTypes对象,进而再获取到注解声明的类数组,如

HandlesTypes ht =servletContainerInitializer.getClass().getAnnotation(HandlesTypes.class);
Class<?>[] types = ht.value();

即可获取到HttpServlet和Filter的class对象数组,后面Context容器调用CustomServletContainerInitializer对象的onStartup方法时作为参数传入。至此,即完成了servlet规范的ServletContainerInitializer初始化器机制。

@WebServlet

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/AServlet", initParams = {
        @WebInitParam(name = "p1", value = "v1"),
        @WebInitParam(name = "p2", value = "v2")
}, loadOnStartup = 1)
public class AServlet extends HttpServlet {

    @Override
    public void init() throws ServletException {
        System.out.println("Servlet3.0 初始化...");
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().print("hello servlet3.0!!");
    }
}

@WebServlet属性:

  • name:servlet-name,如果没有显示指定,该Servlet的取值为全限定名。
  • value:String[],等价于 urlPatterns 属性,与urlPatterns不能同时使用。
  • urlPatterns:String[],指定Servlet url的匹配模式,等价于value。
  • loadOnStartup:int,指定Servlet的加载顺序。值代表优先级,正数的值越小,优先级越高,启动时就越先加载(init方法)。
  • initParams:webInitParam[],指定初始化参数。
  • asyncSupported:boolean,是否支持异步操作。
  • displayName:servlet显示名。

@WebFilter

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class AFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        System.out.println("过滤器。。。。。。。。。。。。");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }
}

@WebFilter属性说明:

@WebListener

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class AListener implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        System.out.println("死掉了");
    }

    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        System.out.println("出生了");
    }
}

说明:Servlet、Listener、Filter的执行顺序。

启动的顺序为listener->Filter->servlet

简单记为:理(Listener)发(Filter)师(servlet)

Servlet异步处理

Servlet异步处理就是让Servlet在处理费时的请求时不要阻塞,而是一部分一部分的显示。也就是说,在使用Servlet异步处理之后,页面可以一部分一部分的显示数据,而不是一直卡,等到请求响应结束后一起显示。

在使用异步处理之前,一定要在@WebServlet注解中给出asyncSupported=true,不然默认Servlet是不支持异步处理的。如果存在过滤器,也要设置@WebFilter的asyncSupportedt=true。注意,响应类型必须是text/html,所以要设置:response.setContentType(“text/html;charset=utf-8”);

使用异步处理大致可以分为两步:Servlet正常响应数据,Servlet异常响应数据。

在Servlet正常响应数据时,没什么可说的,可通知response.getWriter().print()来向客户端输出,但输出后要使用response.getWriter().flush()刷新,不然数据只是在缓冲区中,不能向客户端发送数据的。

异步响应数据需要使用request.startAsync()方法获取AsyncContext对象。然后调用AsyncContext对象的start()方法启动异步响应,start()方法需要一个Runnable类型的参数。在Runnable的run()方法中给出异步响应的代码。注意在异步处理线程中使用response做响应后,要使用response.getWriter().flush()来刷新流,不然数据是不能响应到客户端浏览器的。

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(urlPatterns = "/AServlet", asyncSupported = true)
public class AServlet extends HttpServlet {

    public void doGet(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        //兼容IE!如果输出不足512B,IE没有异步效果!
        for (int i = 0; i <= 512; i++) {
            resp.getWriter().print("a");
        }
        resp.getWriter().flush();
        //得到异步上下文对象
        final AsyncContext ac = req.startAsync(req, resp);
        /**
         * 设置超时时间为10秒,Tomcat需要知道异步响应是否结束,如果响应不结束,虽然客户端浏览器会看到响应的数据,但是鼠标上只是有个圈圈的不行的转啊转的,表示还没有结束响应。Tomcat会等待到超时为止,这个超时的时间可以通过AsyncContext类的getTimeout()方法获取,Tomcat默认为20000毫秒。当然也可以通过此方法方法设置
         */
        ac.setTimeout(1000 * 10);
        //给上下文对象一个Runnable对象,让它执行这个任务
        ac.start(new Runnable() {
            public void run() {
                println("现在马上开始<br/>", resp);
                sleep(2000);
                // 2s后,页面逐渐显示字母
                for (char c = 'A'; c <= 'Z'; c++) {
                    println(c + "", resp);
                    sleep(250);
                }
                ac.complete();// 通知Tomcat我们已经执行结束了!
            }
        });
    }

    public void println(String text, HttpServletResponse resp) {
        try {
            resp.getWriter().print(text);
            resp.getWriter().flush();
        } catch (IOException e) {
        }
    }

    public void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
        }
    }
}

文件上传

Servlet3.0提供了文件上传的处理方案。只需要在Servlet上添加@MultipartConfig注解即可。

当然也可以为@MultipartConfig注解指定属性值,它有四个属性:

  • int filesizeThreshold:指定缓存的大小,当超出这个大小后,文件会保存到磁盘上;
  • String location:指定临时文件的目录;
  • long maxFilesize:指定上传单个文件的大小限制,如果上传的谁的超出了这个大小,那么就会抛出异常;
  • long maxRequestSize:指定整个表单的大小限制。

当在Servlet上使用了@MultipartConfig注解后,那么就可以使用request.getPart(“fieldName”)来获取<input:file>的内容,其中Part表示一个文件表单项。

index.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>测试</title>
</head>
<body>
<form action="/AServlet" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username"/><br/>
    简 历:<input type="file" name="resume"/><br/>
        <input type="submit" value="注册"/>
</form>
</body>
</html>

上传文件的Servlet:

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;

@WebServlet(urlPatterns = "/AServlet")
@MultipartConfig(maxFileSize = 1024 * 1024)
public class AServlet extends HttpServlet {

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        //getParameter()方法可以使用了!!!
        String username = req.getParameter("username");//可以使用了!!!
        //获取文件表单字段,对应的Part对象
        Part part = req.getPart("resume");
        //从Part中获取需要的数据
        System.out.println(part.getContentType());//获取上传文件的MIME类型
        System.out.println(part.getSize());// 获取上传文件的字节数
        System.out.println(part.getName());// 获取文件字段名称
        System.out.println(part.getHeader("content-disposition"));// 获取头,这个头中包含了上传文件的名称
        part.write("C:/xxx.jpg");// 保存上传文件
        // 截取上传文件名称
        String filename = part.getHeader("content-disposition");
        int start = filename.lastIndexOf("filename=\"") + 10;
        int end = filename.length() - 1;
        filename = filename.substring(start, end);
        System.out.println(filename);
    }
}

 

posted @ 2022-09-29 17:52  残城碎梦  阅读(145)  评论(0编辑  收藏  举报