天天看点

Servlet详解(四): Servlet3.0新特性详解基于注解的配置文件上传API异步处理组件可插性动态注册组件ServletContainerInitializer

Servlet2.5是JavaEE5.0规范,最低运行环境为JDK5.0以及Tomcat5.0。而Servlet3.0是JavaEE6.0规范,最低环境为JDK6.0以及Tomcat7.0。Servlet3.0的新特性主要分为以下几个点:

  1. 基于注解配置三大组件
  2. 文件上传API的优化
  3. 异步处理
  4. 组件可插性
  5. 动态注册三大组件(ServletContext)
  6. SPI - ServletContainerInitializer 机制
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>
           

基于注解的配置

基于web.xml的配置与基于注解的配置,在Servlet3.0中,可以混合使用

@WebServlet

用来注册servlet组件,在servlet2.x中,需要在web.xml中配置servlet标签:

<servlet>
    <!--servlet描述-->
    <description>Servlet描述</description>
    <!--servlet展示名称-->
    <display-name>TestServlet</display-name>
    <!--servlet名称-->
    <servlet-name>TestServlet</servlet-name>
    <!--servlet class-->
    <servlet-class>com.yangsx95.demo.TestServlet</servlet-class>
    <!--servlet 初始化参数-->
    <init-param>
      <param-name>param1</param-name>
      <param-value>value1</param-value>
    </init-param>
    <init-param>
      <param-name>param2</param-name>
      <param-value>value2</param-value>
    </init-param>
    <!--servlet加载顺序-->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <!--servlet url 映射-->
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
           

在3.0 可以使用如下方式注册一个Servlet:

//@WebServlet("test") // value 就是 urlPatterns,是默认属性,二者不能同时使用
@WebServlet(
        description = "Servlet描述",
        displayName = "TestServlet",
        name = "TestServlet",
        initParams = {@WebInitParam(name="param1", value = "value1"), @WebInitParam(name="param2", value = "value2")},
        loadOnStartup = 1,
        urlPatterns = {"test", "aaa"}
)
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取servlet名称
        this.getServletName();
        // 获取servlet初始化参数
        Enumeration<String> initParameterNames = this.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            System.out.println(initParameterNames.nextElement());
        }
        resp.getWriter().append("servlet test");
    }
}
           

@WebFilter

xml配置:

<filter>
    <description>TestFilter描述</description>
    <display-name>TestFilter</display-name>
    <!--Filter名称-->
    <filter-name>TestFilter</filter-name>
    <!--Filter class-->
    <filter-class>com.yangsx95.demo.TestFilter</filter-class>
    <!--初始化参数-->
    <init-param>
        <param-name>param1</param-name>
        <param-value>value1</param-value>
    </init-param>
    <init-param>
        <param-name>param2</param-name>
        <param-value>value2</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>TestFilter</filter-name>
    <!--filter 过滤规则-->
    <!--过滤路径-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--过滤指定的servlet-->
    <servlet-name>TestServlet</servlet-name>
</filter-mapping>
           

对应的Servlet3.0 注解配置:

@WebFilter(
        displayName = "TestFilter",
        filterName = "TestFilter",
        initParams = {@WebInitParam(name="param1", value = "value1"), @WebInitParam(name="param2", value = "value2")},
//        urlPatterns = {"/*"},  // 过滤路径
        servletNames = {"TestServlet"} // 过滤指定的servlet
)
public class TestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取初始化参数
        // filterConfig.getInitParameter();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("-------------start-----------");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("--------------end------------");
    }

    @Override
    public void destroy() {

    }
}
           

@WebListener

<listener>
    <listener-class>com.yangsx95.demo.TestListener</listener-class>
  </listener>
           

对应的Servlet3.0注解方式也很简单:

@WebListener
public class TestListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("应用程序启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("应用程序关闭");
    }
}

           

文件上传API

@WebServlet("/upload")
@MultipartConfig   // 代表该servlet可以处理multipart请求
public class UploadServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取服务器本地路径
        String txtPath = this.getServletContext().getRealPath("/txts");

        Part file = req.getPart("file");// 文件表单对应的input name
        // 获取指定的头部属性
//        file.getHeader("Content");
        file.write(txtPath + File.separator + "111.txt"); // 将文件写入到磁盘
    }
}
           

异步处理

部分servlet需要做一些很长的耗时操作,这时,就需要servlet提前响应前端用户,自己后台继续执行逻辑,比如邮件发送、短信通知、复杂耗时任务提前返回处理中。这就是servlet的异步处理。

下面是代码展示:

@WebServlet(value = "/register", asyncSupported = true) // 开启异步支持
public class RegisterServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取异步上下文对象,包含request信息,response信息
        AsyncContext ac = req.getAsyncContext();
        // 创建异步Runnable对象
        ComputeRunnable computeRunnable = new ComputeRunnable(ac);
        // 异步处理超时时间
        ac.setTimeout(30 * 1000);
        // 设置异步监听器
        ac.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                System.out.println("异步任务完成");
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                System.out.println("异步任务超时");
            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
                System.out.println("异步任务出错");
            }

            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
                System.out.println("异步任务开始执行");
            }
        });
        // 异步执行runnable对象
        ac.start(computeRunnable);

        // 立即响应
        resp.getWriter().print("成功,稍后会给您发送邮件");
    }
}

public class ComputeRunnable implements Runnable {

    private AsyncContext asyncContext;

    public ComputeRunnable(AsyncContext context) {
        this.asyncContext = context;
    }

    @Override
    public void run() {
        System.out.println(asyncContext.getRequest());
        System.out.println(asyncContext.getResponse());
    }
}

           

组件可插性

JavaEE6.0项目支持将打为Jar包的Servlet、Filter、Listener直接插入到运行的web项目中,这些jar中包含相应的配置文件,这些jar也称为web片断项目。

定义一个web-fragment项目很容易,只需要在web-inf下创建文件web-fragment即可:

<?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" 
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee  
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"  
     version="3.0"> 
    <name>WebFragment1</name> 
    <servlet> 
        <servlet-name>hi</servlet-name> 
        <servlet-class>com.yangsx95.HiServlet</servlet-class> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>hi</servlet-name> 
        <url-pattern>/hi.view</url-pattern> 
    </servlet-mapping> 
</web-fragment> 
           

将此项目打包为jar,然后放入web项目的lib目录下,就可以访问web片段项目中的servlet了。

动态注册组件

也就是在代码中,通过一些api,比如addServlet动态添加组件,处于安全考虑,组件的动态注册,只能在应用启动时执行。

@WebListener
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        // 获取ServletContext
        ServletContext servletContext = servletContextEvent.getServletContext();
        ServletRegistration.Dynamic servletName = servletContext.addServlet("servletName", TestServlet.class);
        servletName.addMapping("/test");

        FilterRegistration.Dynamic filterName = servletContext.addFilter("filterName", TestFilter.class);
        filterName.setInitParameter("param2", "value2");
        filterName.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        servletContext.addListener( TestListener.class);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

           

ServletContainerInitializer

Serlvet容器在启动容器的时候,会扫描当前应用中的每个jar中的

META-INF/services/javax.servlet.ServletContainerInitializer

中指定的实现类,启动并运行这个实现类的方法。这是一种SPI机制。

示例代码:

@HandlesTypes({Person.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {

    // 参数1是 HandlesTypes 指定的类型集合。 将系统中所有HandlesType定义的类型的类,也就是子类/实现类(这里是Person类的子类/实现类),都传递给这个接口;
    // 参数2是ServletContext对象,用于在启动时向应用程序动态注册组件
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        servletContext.addListener(MyServletContextListener.class);
    }

}

           

配置

META-INF/services/javax.servlet.ServletContainerInitializer

com.yangsx95.demo.MyServletContainerInitializer