servlet3.x 续集,进一步了解

servlet3.x 续集,进一步了解

​ 今天对servlet3.0做一点点的补充,主要是针对注解式 和原有的配置式的一个对比,让从以前的配置式同学更好的到现在的注解式。

1 异步请求支持

​ 在将异步请求之前,让我们先了解一下,为什么要支持异步,及以前servlet 工作的过程

正常的servlet请求流程

1530689064390

在这种阻塞式的请求下,当请求数据过多,并且后服务处理逻辑复杂,需要的相应时间比较就,比如大数据量的导出, 导致Tomcat 假死的情况,无法处理请求。

异步请求

1530690084199

从对比中可以发现,异步时,servlet主线程在接收请求之后,创建子线程,然后主线程就结束了,子线程的处理过程就与主线程无关,这是,servlet就可以在很短的时间内接收一个新的请求。当然,我们这里只是将servlet的异步处理,在特别大的并发访问下, 后台服务对请求的接收也是有限的,我们不讨论。

1.1 如何实现

​ 下面让我们看看 如何实现servlet 的异步请求。

​ AsyncContext 对象, servlet 提供的异步容器对象,当需要此对象时,必须告知容器此Servlet支持异步处理,

  1. ​ 如果使用@WebServlet来标注,则可以设置其asyncSupported为true

  2. ​ 如果使用web.xml设置Servlet,则可以在中设置标签为true:

    ​ AsyncContext 是一个接口,他可以通过request 的 startAsync

  3. ​ AsyncContext 在调用了startAsync()方法取得AsyncContext对象之后,此次请求的响应会被延后,并释放容器分配的线程

  4. ​ AsyncContext 是一个容器,可以getRequest()、getResponse()方法取得请求、响应对象 。

  5. ​ AsyncContext 还提供了complete()或dispatch()方法,对子线程进行终止 或者跳转到指定路径,同时释放所有的 资源信息 request 、response

  6. ​ AsyncContext 还提供了超时设置setTimeOut(), 当超时时间到达时,会自动停止异步子线程,返回请求,同时释放所有的 资源信息 request 、response。

    所以AsyncContext 是异步的关键,既可以在主线程启动子线程,同时设置超时时间,又可以在子线程中,对请求对象进行共享传递,还可以对子线程进行终止,返回response。


//无参方法,直接利用原有的请求与响应对象来创建AsyncContext
public AsyncContext startAsync() throws IllegalStateException {
    return request.startAsync();
}

//可以传入自行创建的请求、响应封装对象
public AsyncContext startAsync(ServletRequest servletRequest,
                               ServletResponse servletResponse)
        throws IllegalStateException {
    return request.startAsync(servletRequest, servletResponse);
}

1.2 一起看代码

主线程

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

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    resp.setContentType("text/html;charset=UTF-8");
    PrintWriter out = resp.getWriter();
    out.write("this is a asyncServelt <br>");
    System.out.println(Thread.currentThread().getName()+ "主线程开始....");
    out.write( "主线程开始运行.....<br>");

//经过测试 发现req.getAsyncContext() 一直是null, 可能需要进行特殊设置
//    req.getAsyncContext().start(new MyAsyncThread(out));
    AsyncContext ac = req.startAsync();
    MyAsyncThread th = new MyAsyncThread(ac);
    ac.start(th);

    System.out.println(Thread.currentThread().getName()+ "主线程结束....");
    out.write("主线程运行结束.....<br>");
  }
}

子线程

public class MyAsyncThread implements  Runnable {
  private AsyncContext ac;

   //将AsyncContext 对象通过构造器传入子线程,便可以恭喜请求内容
  public MyAsyncThread(AsyncContext ac) {
    this.ac = ac;
  }

  @Override
  public void run() {
    int sum = 0;
    try {
      PrintWriter out = ac.getResponse().getWriter();
      out.print("子线程开始运行.....<br>");
      out.print("sum = " + sum);
      for (int i = 0; i < 10; i++) {
        System.out.println("i = " + i );
        Thread.sleep(1000);
        sum += i;
      }
      out.print("sum = " + sum + "<br>");
      out.print("子线程运行结束.....<br>");

     // 结束子线程,进行数据返回
    //ac.complete();
     //结束子线程同时,将数据返回至指定的跳转页面,相当与 RequestDispathcher的include() 
     ac.dispatch("/index.jsp");
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

==总结:==

​ 对上面的整个测试结果来看,我们主要使用异步的目的是为了尽快释放主线程,释放servlet资源,以增加访问能力。

​ 那在代码中,==我们是否能够或者说需要调用多个ac.start(th); 方法,启动多个线程呢?==我认为是不需要的,因为servlet异步是为了释放资源,而不是使用多个线程去完成业务规则。而且同一个request的 AsyncContext是相同的,只要调用complete方法,资源都会被释放,所以多个start 多个线程没有意义。

​ 所以我觉得,当时设计AsyncContext 初衷就是为了封装子线程资源,达到资源共享,资源销毁的目的,然后将请求做分离。 有点类似FutureTask 封装Callable实现,获取返回值的目的一样。

​ 在测试中,我们可以知道,如果子线程中没有释放response资源,用户页面就一直在等待,所以,如果有场景需要及时返还,在使用AsyncContext 时,就不要使用response ,而是让资源在主线程中直接释放掉。

2 当web.xml 与配置同时存在

​ 在servlet3.0 中,编程式方式可以取代之前的配置式,但是在3.0中,其实文件与注解可以同时存在,我们来比较下如果两个同时存在会有哪些问题

​ web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">   
  <!-- version 需要是3.x以上,  描述符需要是web-app_3_x.xsd 以上版本 -->
</web-app>

Servlet 共存

Web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
  <!--注册servlet-->
  <!--servlet 名称 MySerlvet-->
  <!--url /MySerlvet-->
  <servlet>
    <servlet-name>MySerlvet</servlet-name>
    <servlet-class>com.Henry.servlet.MySerlvet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MySerlvet</servlet-name>
    <url-pattern>/MySerlvet</url-pattern>
  </servlet-mapping>

</web-app>

代码注册

//相同servlet名称  MySerlvet 
//Url  /MySerlvet

//写法1 
@WebServlet("/MySerlvet")
//写法2
@WebServlet(name = "MySerlvet" ,urlPatterns = "/MySerlvet")
public class MySerlvet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    PrintWriter out = resp.getWriter();
    out.write(" this is a servlet ");
  }
}

==结论==

​ 当@WebServlet(“/MySerlvet”)使用这种写法的时候, 如果web.xml 中,存在一个相同的 url-pattern 却不指定servlet-name时,系统启动时报错

​ 当@WebServlet(name = “MySerlvet” ,urlPatterns = “/MySerlvet”)使用 这种写法时,制定了servlet-name时, 如果web.xml中存在相同的servlet名称,则以web.xml为主

​ 原因其实是因为,两个servlet 不同的servlet,虽然路径可以包含,但url-pattern 是不允许使用一样的,

当@WebServlet(“/MySerlvet”) 其实是没有指定名称,系统会默认指定一个名称,此时就出现了两个servlet名称不同但是 url-pattern却相同的情况,所以报错

Filter共存

Web.xml

<filter>
  <filter-name>myFilter</filter-name>
  <filter-class>com.Henry.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>myFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

代码

@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {

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

  }

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

  @Override
  public void destroy() {

  }
}

==结论==

​ 其实与servlet是相同的, 当 @WebFilter(filterName = “myFilter”,urlPatterns = “/*”) 指定了名称,如果web.xml中存在相同名称的filter, 则以web.xml为主。 如果是@WebFilter(“/”) 这种,那两个filter是可以同时存在的。因为Filter 本么有URL的冲突

Listener 共存

这个没什么好进行测试的, 直接可以获取到结论, @WebListener 只有一个属性,所以如果当@WebListener 和web.xml 存在相同的Listener 配置,以web.xml内容生效

3 文件上传

在servlet3.0中,request增加了对文件上传处理MulitPart请求的支持。

在定义的servlet中增加@MultipartConfig 注解,同时form的enctype=”multipart/form-data” method=”post”

<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="${pageContext.request.contextPath}/uploadFile" method="post" enctype="multipart/form-data">
      文件<input type="file" name="text"/>
      <input type="submit" value="上传"/>
  </form>
  </body>
</html>
@WebServlet("/uploadFile")
@MultipartConfig    //表示支持文件上传 处理MulitPart请求
public class fileServlet extends HttpServlet{

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    String path = this.getServletContext().getRealPath("/text");
    Part part = req.getPart("text");
    part.write(path + "/xxx.txt");
  }
}

这样就可以文件上传了,通过获取request.getPart 或者getParts方法。

唯一不好的地方就是无法获取到对应的文件实际名称,需要更加hearder 去截取文件名称,不算方便

猜你喜欢

转载自blog.csdn.net/whw174660897/article/details/80919591