【问题标题】:How to write a Java interface whose implementation would potentially be asynchronous? [closed]如何编写一个实现可能是异步的 Java 接口? [关闭]
【发布时间】:2021-05-26 13:39:08
【问题描述】:

我需要什么

我有一个描述UserRepository 的合同(界面)。我希望这个存储库有一个方法允许从他/她的电子邮件中获取一个User,如果没有找到给定电子邮件的User,则抛出一个已检查异常.界面将如下所示:

interface UserRepository {

    User get(String email) throws UserNotFoundException;

}

可能的实现

现在让我们考虑这个合约的 2 个实现。一个将使用 内存中 存储 (1),另一个将使用 远程 存储 (2)。

  1. 关于 in-memory 存储,实现的操作将是阻塞的,但速度足够快而不必担心性能。

  2. 但是,如果我在 remote 存储(例如远程数据库)的何处实施此操作,则必须考虑性能/速度,因为我不会得到响应立即地。避免在等待响应到达时阻塞当前线程的解决方案是使用一些异步处理。

问题

显然,我当前的合同并非旨在以异步方式处理此操作。

现在,我是否必须更改我的合同(接口),因为我知道某些实现可能需要某种异步处理?换句话说,即使某些实现可能甚至不需要在另一个线程中异步执行代码,我是否也可以通过合同使此操作异步?

选项

这是我考虑过的两个选项:

interface UserRepository {

    /**
     * 1st option.
     * Maybe specify in the javadoc that some implementations might block the current thread.
     */
    User get(String email) throws UserNotFoundException;

    /**
     * 2nd option.
     * Method "async" by contract and lost of checked exceptions.
     */
    CompletableFuture<User> get(String email);

}

我真的不知道这里最好的选择是什么。我可能会在此处使用一些观察者/侦听器模式,但我认为对于这么小的操作来说它是矫枉过正的。

你怎么看?是否有解决此类问题的传统和概念方法?谢谢。

【问题讨论】:

  • 我认为问题分解不正确。如果get() 的调用者不会完成并且不会阻塞,他们需要注意操作可能不会完成。所以你不能在那里返回User。查看在该操作完成时提供异步回调。
  • 您认为访问远程数据库需要多长时间?
  • @markspace 即使在超时或请求失败的情况下抛出异常,我难道不能仍然返回User吗?
  • @Kayaman 这有什么关系?我的意思是,如果请求失败或超时怎么办?在这种情况下,阻塞操作将花费太多时间。
  • 如果您不打算阻塞,您将如何等待抛出异常?如果你不阻止,就没有办法做到这一点。如果您发现超时,那么您在等待超时时已被阻止。我没有看到任何其他方式。

标签: java asynchronous methods repository remote-access


【解决方案1】:

也许我误解了您的问题,但我怀疑需要让您的存储库接口了解异步实现是不必要的(并且可能是一个糟糕的设计选择)。

我建议使用Optional 作为接口方法的返回值,而不是返回User 并在未找到时抛出特殊异常。 Optional 类旨在包装可能合法为空的对象引用,而不是表示错误的空。在Generics syntax of Java 中,我们说Optional &lt; User &gt;,这意味着Optional 包装器要么专门携带User 对象,要么根本不携带任何东西。

所以让我们把界面改成这样。

package work.basil.demo.wait;

import java.util.Optional;

public interface UserRepository
{
    Optional < User > fetchByEmail ( String email );
}

这是该接口的一个实现,它返回一个假对象。

我们使用到达的任何电子邮件作为参数创建一个User 对象。对于该电子邮件地址,我们附加了一个用户名值Fake。这些值放在我们的User 对象中。

我们在 fetchByEmail 方法中休眠片刻,以模拟需要一段时间的数据库获取。

在底部,我们有两个结局。一行返回一个假用户。另一个返回一个空的Optional 来模拟找不到匹配项。您可以注释/注释任一行以切换行为。

package work.basil.demo.wait;

import java.time.Duration;
import java.util.Objects;
import java.util.Optional;

public class FakeUserRepository implements UserRepository
{
    @Override
    public Optional < User > fetchByEmail ( String email )
    {
        // Validate inputs.
        Objects.requireNonNull( email , "Email argument must be provided, cannot be null. Message # a6d23f55-8ac9-490b-9969-b444d3db0737." );
        if ( email.isBlank() ) { throw new IllegalArgumentException( "Email argument cannot be blank. Message # 793f58f5-c7be-4ebb-9351-0319809a6971." ); }

        // Sleep a moment to pretend that this method takes a while to hit the database before returning.
        try { Thread.sleep( Duration.ofSeconds( 2 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }

        // Return whatever email is sent, inventing fake `User` object.
        return Optional.of( new User( email , "Fake" ) );

        // Return empty optional, to simulate the case where the desired email address is not found to belong to any existing users.
//        return Optional.empty() ;
    }
}

这是我们简单的User 类。为简洁起见,我们使用 Java 16 的新 records 功能。您也可以定义一个常规类。

public record User(String email , String name) {}

最后,我们的应用程序类使用main 方法来演示此操作。

首先我们建立我们的存储库对象。这可以在您的真实应用生命周期的早期完成。

我们建立一个执行器服务来处理我们的后台线程。我们向该服务提交我们的任务实例,调用存储库的fetchByEmail 方法。这里我们使用 lambda 语法,但您也可以使用常规语法。请注意,在submit 之后,我们在我们的服务上调用shutdown。这将停止任何更多的提交,并在已经提交的任务完成后要求服务关闭。此调用不会 阻塞。

与此同时,我们继续做其他工作,其他工作不需要等待User 对象。在这段代码中,我们通过休眠当前线程来模拟其他工作。

最终,我们完成了没有User 对象的所有工作。所以我们在Future 对象上调用get此调用阻塞。当前线程等待后台线程完成并返回我们需要的User 对象。如果后台线程已经完成,这个get方法会立即返回。

解开Optional 后,我们手头有一个User 对象。或者我们发现 Optional 为空,在这种情况下,我们知道没有找到与该电子邮件地址输入值匹配的内容(或者在查询过程中出现问题)。

package work.basil.demo.wait;

import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;

public class App
{
    public static void main ( String[] args )
    {
        UserRepository userRepository = new FakeUserRepository();
        // …
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable < Optional < User > > task = ( ) -> userRepository.fetchByEmail( "fake@example.com" );
        Future < Optional < User > > future = executorService.submit( task );
        executorService.shutdown();  // Shuts down the executor service after previously submitted tasks are done/cancelled/failed.

        // Do some  stuff.
        // We sleep this thread to pretend that some other work is executing.
        try { Thread.sleep( Duration.ofSeconds( 3 ).toMillis() ); } catch ( InterruptedException e ) { e.printStackTrace(); }

        // Other work done. So now we need our `User` object from repository before we can proceed.
        if ( future.isCancelled() )
        {
            // Deal with task being cancelled.
        } else
        {
            Optional < User > optionalUser = null; // Blocks, waiting until done to return result. Or, if already done, immediately returns result.
            try { optionalUser = future.get(); } catch ( InterruptedException e ) { e.printStackTrace(); }catch ( ExecutionException e ) { e.printStackTrace(); }
            if ( optionalUser.isPresent() )
            {
                System.out.println( "user = " + optionalUser.get() + ". Message # 2bff0033-3000-4b77-b8e0-654cf51db88b." );
            } else // Else no user found, the Optional is empty.
            {
                System.out.println( "No user found. The Optional is empty. Message # cd733ffe-0a38-4813-9c80-98f5d9c35755." );
            }
        }

        if ( Objects.nonNull( executorService ) )
        {
            try { executorService.awaitTermination( 5 , TimeUnit.SECONDS ); } catch ( InterruptedException e ) { e.printStackTrace(); }
        }
        System.out.println( "Demo done. Message # e64cc194-2087-4aae-a142-3a27a4950483." );
    }
}

运行时,我们得到这样的输出。

用户 = 用户 [email=fake@example.com, name=Fake]。消息 # 2bff0033-3000-4b77-b8e0-654cf51db88b。

演示完成。消息 #e64cc194-2087-4aae-a142-3a27a4950483。

【讨论】:

    猜你喜欢
    • 2023-04-02
    • 2019-12-07
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 2012-10-02
    • 1970-01-01
    • 2016-07-02
    • 1970-01-01
    相关资源
    最近更新 更多