Servlet 3特性:异步Servlet

本文由 ImportNew - 彭秦进 翻译自 journaldev。欢迎加入翻译小组。转载请见文末要求。

理解异步Servlet之前,让我们试着理解为什么需要它。假设我们有一个Servlet需要很多的时间来处理,类似下面的内容:

LongRunningServlet.java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

package com.journaldev.servlet;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

@WebServlet("/LongRunningServlet")

public class LongRunningServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,

            HttpServletResponse response) throws ServletException, IOException {

        long startTime = System.currentTimeMillis();

        System.out.println("LongRunningServlet Start::Name="

                + Thread.currentThread().getName() + "::ID="

                + Thread.currentThread().getId());

        String time = request.getParameter("time");

        int secs = Integer.valueOf(time);

        // max 10 seconds

        if (secs > 10000)

            secs = 10000;

        longProcessing(secs);

        PrintWriter out = response.getWriter();

        long endTime = System.currentTimeMillis();

        out.write("Processing done for " + secs + " milliseconds!!");

        System.out.println("LongRunningServlet Start::Name="

                + Thread.currentThread().getName() + "::ID="

                + Thread.currentThread().getId() + "::Time Taken="

                + (endTime - startTime) + " ms.");

    }

    private void longProcessing(int secs) {

        // wait for given time before finishing

        try {

            Thread.sleep(secs);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

如果我们的URL是:http://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000

得到响应为“Processing done for 8000 milliseconds! !“。现在,如果你会查看服务器日志,会得到以下记录:


1

2

LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103

LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.

所以Servlet线程实际运行超过 8秒,尽管大多数时间用来处理其它Servlet请求或响应。

这可能导致线程饥饿——因为我们的Servlet线程被阻塞,直到所有的处理完成。如果服务器的请求得到了很多过程,它将达到最大Servlet线程限制和进一步的请求会拒绝连接错误。

Servlet 3.0之前,这些长期运行的线程容器特定的解决方案,我们可以产生一个单独的工作线程完成耗时的任务,然后返回响应客户。Servlet线程返回Servlet池后启动工作线程。Tomcat 的 Comet、WebLogic FutureResponseServlet 和 WebSphere Asynchronous Request Dispatcher都是实现异步处理的很好示例。

容器特定解决方案的问题在于,在不改变应用程序代码时不能移动到其他Servlet容器。这就是为什么在Servlet3.0提供标准的方式异步处理Servlet的同时增加异步Servlet支持。

实现异步Servlet

让我们看看步骤来实现异步Servlet,然后我们将提供异步支持Servlet上面的例子:

  1. 首先Servlet,我们提供异步支持 Annotation @WebServlet  的属性asyncSupported 值为true。
  2. 由于实际实现委托给另一个线程,我们应该有一个线程池实现。我们可以一个通过Executors framework 创建线程池和使用servlet context listener来初始化线程池。
  3. 通过ServletRequest.startAsync方法获取AsyncContext的实例。AsyncContext提供方法让ServletRequest和ServletResponse对象引用。它还提供了使用调度方法将请求转发到另一个 dispatch() 方法。
  4. 编写一个可运行的实现,我们将进行重处理,然后使用AsyncContext对象发送请求到另一个资源或使用ServletResponse编写响应对象。一旦处理完成,我们通过AsyncContext.complete()方法通知容器异步处理完成。
  5. 添加AsyncListener实现AsyncContext对象实现回调方法,我们可以使用它来提供错误响应客户端装进箱的错误或超时,而异步线程处理。在这里我们也可以做一些清理工作。

一旦我们将完成我们的项目对于异步Servlet示例,项目结构看起来会像下面的图片:

在监听中初始化线程池


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

package com.journaldev.servlet.async;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;

import javax.servlet.ServletContextListener;

import javax.servlet.annotation.WebListener;

@WebListener

public class AppContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent servletContextEvent) {

        // create the thread pool

        ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,

                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));

        servletContextEvent.getServletContext().setAttribute("executor",

                executor);

    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

        ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent

                .getServletContext().getAttribute("executor");

        executor.shutdown();

    }

}

实现很直接,如果你不熟悉ThreadPoolExecutor 框架请读线程池的ThreadPoolExecutor 。关于listeners 的更多细节,请阅读教程Servlet Listener

工作线程实现


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

package com.journaldev.servlet.async;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.AsyncContext;

public class AsyncRequestProcessor implements Runnable {

    private AsyncContext asyncContext;

    private int secs;

    public AsyncRequestProcessor() {

    }

    public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {

        this.asyncContext = asyncCtx;

        this.secs = secs;

    }

    @Override

    public void run() {

        System.out.println("Async Supported? "

                + asyncContext.getRequest().isAsyncSupported());

        longProcessing(secs);

        try {

            PrintWriter out = asyncContext.getResponse().getWriter();

            out.write("Processing done for " + secs + " milliseconds!!");

        } catch (IOException e) {

            e.printStackTrace();

        }

        //complete the processing

        asyncContext.complete();

    }

    private void longProcessing(int secs) {

        // wait for given time before finishing

        try {

            Thread.sleep(secs);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

注意:在请求和响应时使用AsyncContext对象,然后在完成时调用 asyncContext.complete() 方法。

AsyncListener 实现


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

package com.journaldev.servlet.async;

import java.io.IOException;

import java.io.PrintWriter;

import javax.servlet.AsyncEvent;

import javax.servlet.AsyncListener;

import javax.servlet.ServletResponse;

import javax.servlet.annotation.WebListener;

@WebListener

public class AppAsyncListener implements AsyncListener {

    @Override

    public void onComplete(AsyncEvent asyncEvent) throws IOException {

        System.out.println("AppAsyncListener onComplete");

        // we can do resource cleanup activity here

    }

    @Override

    public void onError(AsyncEvent asyncEvent) throws IOException {

        System.out.println("AppAsyncListener onError");

        //we can return error response to client

    }

    @Override

    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {

        System.out.println("AppAsyncListener onStartAsync");

        //we can log the event here

    }

    @Override

    public void onTimeout(AsyncEvent asyncEvent) throws IOException {

        System.out.println("AppAsyncListener onTimeout");

        //we can send appropriate response to client

        ServletResponse response = asyncEvent.getAsyncContext().getResponse();

        PrintWriter out = response.getWriter();

        out.write("TimeOut Error in Processing");

    }

}

通知的实现在 Timeout()方法,通过它发送超时响应给客户端。

Async Servlet 实现

这是我们的异步Servlet实现,注意使用AsyncContext和ThreadPoolExecutor进行处理。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

package com.journaldev.servlet.async;

import java.io.IOException;

import java.util.concurrent.ThreadPoolExecutor;

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;

@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)

public class AsyncLongRunningServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,

            HttpServletResponse response) throws ServletException, IOException {

        long startTime = System.currentTimeMillis();

        System.out.println("AsyncLongRunningServlet Start::Name="

                + Thread.currentThread().getName() + "::ID="

                + Thread.currentThread().getId());

        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

        String time = request.getParameter("time");

        int secs = Integer.valueOf(time);

        // max 10 seconds

        if (secs > 10000)

            secs = 10000;

        AsyncContext asyncCtx = request.startAsync();

        asyncCtx.addListener(new AppAsyncListener());

        asyncCtx.setTimeout(9000);

        ThreadPoolExecutor executor = (ThreadPoolExecutor) request

                .getServletContext().getAttribute("executor");

        executor.execute(new AsyncRequestProcessor(asyncCtx, secs));

        long endTime = System.currentTimeMillis();

        System.out.println("AsyncLongRunningServlet End::Name="

                + Thread.currentThread().getName() + "::ID="

                + Thread.currentThread().getId() + "::Time Taken="

                + (endTime - startTime) + " ms.");

    }

}

Run Async Servlet

现在,当我们将上面运行servlet URL:

http://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000

得到响应和日志:


1

2

3

4

AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124

AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms.

Async Supported? true

AppAsyncListener onComplete

如果运行时设置time=9999,在客户端超时以后会得到响应超时错误处理和日志:


1

2

3

4

5

6

7

8

9

10

11

12

13

AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117

AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms.

Async Supported? true

AppAsyncListener onTimeout

AppAsyncListener onError

AppAsyncListener onComplete

Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.

    at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439)

    at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197)

    at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27)

    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)

    at java.lang.Thread.run(Thread.java:680)

注意:Servlet线程执行完,很快就和所有主要的处理工作是发生在其他线程。

这是所有异步Servlet内容,希望你喜欢它。下载 AsyncServletExample 工程

原文链接: journaldev 翻译: ImportNew.com彭秦进
译文链接: http://www.importnew.com/8864.html
转载请保留原文出处、译者和译文链接。]

关于作者: 彭秦进

时间: 2024-12-19 20:36:53

Servlet 3特性:异步Servlet的相关文章

Other - 02 - Servlet学习笔记 - 异步Servlet

异步Servlet Servlet默认情况下都是同步的,但是Servlet可以进行异步的调用. /** * Servlet implementation class MainServlet */ @WebServlet(value = "/MainServlet", asyncSupported = true) public class AsynServlet extends HttpServlet { private static final long serialVersionUI

使用异步servlet提升性能

本文发布之后, 收到了很多的反馈.基于这些反馈,我们更新了文中的示例,使读者更容易理解和掌握, 如果您发现错误和遗漏,希望能给我们提交反馈,帮助我们改进. 本文针对当今 webapp 中一种常碰到的问题,介绍相应的性能优化解决方案.如今的WEB程序不再只是被动地等待浏览器的请求, 他们之间也会互相进行通信. 典型的场景包括 在线聊天, 实时拍卖等 -- 后台程序大部分时间与浏览器的连接处于空闲状态, 并等待某个事件被触发. 这些应用引发了一类新的问题,特别是在负载较高的情况下.引发的状况包括线程

第二章 Servlet 接口(JavaTM Servlet 规范3.1 )

Servlet 接口 Servlet 接口是 Java Servlet API 的核心抽象.所有 servlet 要么直接要么间接地实现该接口,通过扩展一个类实现此接口.在 Java Servlet API 中有两个类 GenericServlet 和 HttpServlet 实现了此 Servlet 接口.为了更多目的,开发者将扩展 HttpServlet 来实现他们的 servlet. 2.1 请求处理方法 为处理客户端请求,基础 Servlet 接口定义了一个 service 方法.ser

Servlet新特性:异步处理

参考: http://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ 异步处理特性可以应用于 Servlet 和过滤器两种组件,由于异步处理的工作模式和普通工作模式在实现上有着本质的区别,因此默认情况下,Servlet 和过滤器并没有开启异步处理特性,如果希望使用该特性,则必须按照如下的方式启用: 对于使用传统的部署描述文件 (web.xml) 配置 Servlet 和过滤器的情况,Servlet 3.0 为 <servlet> 和 <

关于servlet3.0中的异步servlet

刚看了一下维基百科上的介绍,servlet3.0是2009年随着JavaEE6.0发布的: 到现在已经有六七年的时间了,在我第一次接触java的时候(2011年),servlet3.0就已经出现很久了,但是到现在,里边的一些东西还是没有能够好好地了解一下 最近在研究java的长连接,在了解jetty中的continuations机制的时候也重新了解了一下servlet3.0中的异步servlet机制,通过看几个博客,加上自己的一些测试,算是搞明白了一些,在这里记录一下: 在服务器的并发请求数量比

Servlet初始化与异步支持

Shared libraries(共享库) / runtimes pluggability(运行时插件能力) 1.Servlet容器启动会扫描,当前应用里面每一个jar包的 ServletContainerInitializer的实现2.提供ServletContainerInitializer的实现类: 必须绑定在,META-INF/services/javax.servlet.ServletContainerInitializer文件的内容就是ServletContainerInitiali

java编程(2)——servlet和Ajax异步请求的接口编程(有调用数据库的数据)

第一步: 1.为项目配置 Tomcat 为 server: 2.导入 mysql的jar包 到项目目录中: 第二步:编码 1.数据库连接类ConnectMysql.java代码: 1 package com.testing.mysql; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 6 public class ConnectMysql { 7 //设置连接的成员变量 8 public Connection

深入Jetty源码之Servlet框架及实现(Servlet、Filter、Registration)

概述 Servlet是Server Applet的缩写,即在服务器端运行的小程序,而Servlet框架则是对HTTP服务器(Servlet Container)和用户小程序中间层的标准化和抽象.这一层抽象隔离了HTTP服务器的实现细节,而Servlet规范定义了各个类的行为,从而保证了这些"服务器端运行的小程序"对服务器实现的无关性(即提升了其可移植性).在Servlet规范有以下几个核心类(接口):ServletContext:定义了一些可以和Servlet Container交互的

servlet3.0 新特性——异步处理

Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下: 首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理: 接着,调用业务接口的某些方法,以完成业务处理: 最后,根据处理的结果提交响应,Servlet 线程结束. 其中第二步的业务处理通常是最耗时的,这主要体现在数据库操作,以及其它的跨网络调用等,在此过程中,Servlet 线程一直处于阻塞状态,直到业务方法执行完毕.在处理业务的过程中,Servlet 资源一直被占用而得不到释放,对于并发较大