企业应用程序中有一些常见的场景,通常是同步和阻塞的,例如数据库或远程服务器调用。在这些情况下,对这些方法的调用需要一些时间才能返回,通常是因为客户端线程被阻塞等待响应。让我们考虑一个简单的例子——一个 Java EE servlet,它使用服务查询数据库,并打印记录列表,在我们的例子中是作者列表(是的,我知道,我有点以自我为中心):
@WebServlet(urlPatterns = "/authors")
public class AuthorServlet extends HttpServlet {
...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final List<Author> authors = authorService.getAuthors();
final ServletOutputStream out = resp.getOutputStream();
for (Author author : authors) {
out.println(user.toString());
}
}
}
您必须等待在 authorService.getAuthors() 中实现的数据库查询调用完成以获取响应。因此,在处理请求之前,客户端会一直处于阻塞状态。
让我们尝试使 servlet 异步:
@WebServlet(urlPatterns = "/authors", asyncSuppported = true)
public class AuthorServlet extends HttpServlet {
...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
final AsyncContext asyncCtx = req.startAsync();
asyncCtx.start(() -> {
final List<Author> authors = authorService.getAuthors();
printAuthors(authors, asyncCtx.getResponse().getOutputStream());
asyncCtx.complete();
});
}
private void printAuthors (List<Author> authors, ServletOutputStream out) {
for (Author author : authors) {
out.println(author.toString());
}
}
}
通过一个简单的更新,我们使我们的 servlet 异步,让我们的客户端在等待接收对所发出请求的响应时执行其他操作。
好吧,您可以争辩说现在没有人编写 servlet。但是,你确定吗?
在 MSA 中,标准通信基于 RESTful Web 服务,在 Java EE 世界中,由 JAX-RS 实现——这些规范建立在 servlet 规范之上。
然后,我们可以以 RESTful 方式重新访问我们的简单 servlet:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAuthors() {
final List<Author> authors = authorService.getAuthors();
return Response.ok(authors).build();
}
}
现在,我们可以让它异步:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}
注释、@Asynchronous 和 上下文依赖注入 (CDI) 方法参数 @Suspended AsyncResponse response 用于使其异步。快速获胜!
但是请记住,永远不要永远等待,永远不要将您的应用程序与其他服务的问题结合起来。因此,如果出现不可恢复的问题,请始终设置超时以中止调用。
这是我们简单示例的最终版本:
@Path("/authors")
public class AuthorResource {
...
@GET
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void getAuthors(@Suspended AsyncResponse response) {
response.setTimeout(2, TimeUnit.SECONDS);
response.setTimeoutHandler(resp ->
resp.resume(Response.status(Status.REQUEST_TIMEOUT).build()));
final List<Author> authors = authorService.getAuthors();
response.resume(Response.ok(authors).build());
}
}
如果您的应用程序包含重负载和软负载查询的混合,这种方法非常有用。这样,您可以获得更高的吞吐量并有机会微调您的应用服务器。
我们已经讨论了如何异步公开我们的 API。但是你 应该怎么做才能在业务逻辑层得到同样的结果呢?
我们可以使用 EJB 来实现它。有些人认为 EJB 是魔鬼:我不同意。
这种负面印象是由于与 2.1 版本相关的复杂且性能不佳的实现而产生的。新的从3.0版本到3.2版本,简化了使用和配置,并以大量的新特性丰富了平台。
我认为使用 EJB 远程公开您的服务是不赞成的。您永远不应该通过 EJB 远程通信,因为它是一种强耦合且性能较差的方式。但是,EJB 本地 可以在 Java EE 之外使用container 因为它们只是 POJO,简化了一些核心主题,例如线程安全的处理、并发性、事务生命周期等。我认为 Jakarta EE 中可以在下一个特性中实现的改进将是从规范中删除远程 EJB。
现在,让我们回到我们的业务层实现。
这是经典的同步实现:
@Stateless
public class AuthorService {
public Author createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return author;
}
}
让我们让它异步:
@Stateless
public class AuthorService {
@Asynchronous
public Future<Author> createAuthor(final String name, final String surname) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
return new AsyncResult<>(author);
}
}
这很简单——告诉容器使调用异步,使用 @Asynchronous 注释,并将返回类型从经典 POJO 更改为 Future
,实现通过 AsyncResult。
之后,我们可以实现我们的 RESTful 类来使用新特性:
@Path("/authors")
public class AuthorResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response createAuthor(@FormParam("name") final String name, @FormParam("surname")
final String surname) {
final Future<Author> authorFuture = authorService.createAuthor(name, surname);
try {
final Author author = authorFuture.get(2, TimeUnit.SECONDS);
return Response.ok(author).build();
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return Response.serverError().build();
}
}
}
但是,我们可以使用 Java SE 8 中引入的 CompletableFuture 类以更好的方式做到这一点:
@Stateless
public class AuthorService {
@Asynchronous
public void createAuthor(final String name, final String surname, final
CompletableFuture<Author> promise) {
final Author author = new Author(UUID.randomUUID(), name, surname);
em.persist(author);
promise.complete(author);
}
}
我们没有返回 Future,而是创建了我们的方法 void 并告诉 Java EE 容器注入一个负责通知结束的 CompletableFuture的操作。
现在,我们可以重新审视 RESTful 实现:
@Path("/authors")
public class AuthorResource {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
@Asynchronous
public void createAuthor(@FormParam("name") final String name, @FormParam("surname") final
String surname, @Suspended AsyncResponse response) {
CompletableFuture<Author> promise = new CompletableFuture<>();
authorService.createAuthor(name, surname, promise);
promise.thenApply(response::resume);
}
}
我更喜欢这种方法而不是使用 Future。 Future 是目前在 Java EE 中实现异步的唯一解决方案,但与 CompletableFuture 相比,它有一些局限性;例如,它无法构建和使用新的 lambdas 功能。