簡體   English   中英

使用 ThreadLocal 將 state 保存在 spring bean 中

[英]Saving state in spring bean using ThreadLocal

我有兩個使用命令模式進行通信的服務(A 和 B)

命令:

public interface Command {
    void execute() throws CommandExecutionException;
    void rollback() throws CommandExecutionException;
    String commandName();
}

我的用例是

  1. 使用命令與服務 A 通信
  2. 如果與服務 A 通信的任何命令發生錯誤,則回滾之前為 A 執行的所有命令
  3. 使用命令與服務 B 通信
  4. 如果在與服務 B 通信的任何命令中發生錯誤,則回滾之前為 B 執行的所有命令,以及為 A 執行的所有命令(因為如果我們到達這一點,所有之前都已執行)

現在我這樣做:

@Service
public class A{
    // some autowired dependencies
    private final ThreadLocal<List<Command>> executedCommandsByThread = new ThreadLocal<>();

       @Override
    public void executeServiceA() throws ServiceAException{
        // create commands
        Command command1 = new CommandA1(...);
        Command command2 = new CommandA2(...);
        Command command3 = new CommandA3(...);

        List<Command> commandList = new LinkedList<>();
        // add them to linked list
        commandList.add(command1 );
        commandList.add(command2 );
        commandList.add(command3 );

        RollBackCommandManager commandManager = new RollBackCommandManager(commandList);

        try {
            //execute
            commandManager.execute();
            executedCommandsByThread.set(commandList);
        } catch (CommandManagerException e) {
            LOGGER.info("Service A failed");
            throw new ServiceAException("Service A failed", e);
        }
    }

    public void rollback() {
        List<Command> commandList = executedCommandsByThread.get();
        CommandManager commandManager = new RollBackCommandManager(commandList);
        try {
            commandManager.rollback();
            executedCommandsByThread.remove();
        } catch (CommandManagerException e) {
            throw new ServiceAException("Service A rollback failed", e);
        }
    }
}

和乙

@Service
public class B{
    // some autowired dependencies
    private final ThreadLocal<List<Command>> executedCommandsByThread = new ThreadLocal<>();

       @Override
    public void executeServiceB() throws ServiceBException{
        // create commands
        Command command1 = new CommandB1(...);
        Command command2 = new CommandB2(...);
        Command command3 = new CommandB3(...);

        List<Command> commandList = new LinkedList<>();
        // add them to linked list
        commandList.add(command1 );
        commandList.add(command2 );
        commandList.add(command3 );

        RollBackCommandManager commandManager = new RollBackCommandManager(commandList);

        try {
            //execute
            commandManager.execute();
            executedCommandsByThread.set(commandList);
        } catch (CommandManagerException e) {
            LOGGER.info("Service B");
            throw new ServiceBException("Service B failed", e);
        }
    }

    public void rollback() {
        List<Command> commandList = executedCommandsByThread.get();
        CommandManager commandManager = new RollBackCommandManager(commandList);
        try {
            commandManager.rollback();
            executedCommandsByThread.remove();
        } catch (CommandManagerException e) {
            throw new ServiceBException("Service B rollback failed", e);
        }
    }
}

CommandManager處理命令的執行,如果發生錯誤,它會回滾所有以前執行的命令。

我正在使用 Facade 處理這些服務:

@Service
@AllArgsConstructor
class Facade {
  private final ServiceA serviceA;
  private final ServiceB serviceB;

  public void communicate(){
     try{
        serviceA.executeServiceA();
        serviceB.executeServiceB();
     }catch( ServiceAException e ) {
        throw new FacadeException();
     }catch( ServiceBException e ){
       // error occured while executing commands for B. Command manager in ServiceB handles rollback of commands for B, but we need to rollback commands for A too;
       serviceA.rollback();
     }
  }
}

如評論中所述。 ServiceB發生異常時, ServiceB的命令由ServiceB中的CommandManager回滾。 但我也需要回滾在ServiceA中執行的命令——因此我需要記住執行了哪些命令。 我為此使用Threadlocal ,但ServiceA是 Spring bean - 我不確定這種“在 spring bean 中保存 state 的方式是” 此代碼是后端的一部分,將使用 Rest-API 與其他服務/前端通信 - 應用程序服務器將為每個請求調用這個單獨的線程,因此ThreadLocal解決方案理論上應該是安全的。 但是,沒有更好的方法嗎?

感謝幫助

我會警告不要將 ThreadLocal 用於此類事情。 您創建了對某些操作在同一線程中執行的事實的依賴。 這個假設現在可能成立,但是如果另一個毫無戒心的開發人員改變了實現,以某種方式使其成為多線程呢? 在這種情況下,這可能不太可能,但總的來說,我會考慮以這種方式使用 ThreadLocal 是一種不好的做法。

我看到了一些替代方案。 如果 A 和 B 總是在 HTTP 請求的上下文中使用,您可以考慮使用請求范圍的 bean。 在這種情況下,會為每個 API 請求創建一個新的 bean 實例。 這是一篇關於 bean 作用域的博客: https://www.baeldung.com/spring-bean-scopes

第二種選擇是讓服務 A 返回已執行命令的列表,並讓服務 a 的 rollback() 方法接受要回滾的命令列表。

您的代碼可能是一個簡化,但如果我正確閱讀它,您有一個 class RollbackCommandManager 可以執行或回滾來自 A 或 B 的命令。因此,真正使 A 和 B 唯一的唯一因素是它們定義了不同的命令. 我傾向於做這樣的事情,它的額外優勢是可以輕松添加更多服務,如 A 和 B。

public abstract class CommandServiceBase{



    public void execute(List<Command> executedCommandsContext) {
        List<Command> commands = getCommands();

        RollBackCommandManager commandManager = new RollBackCommandManager(commandList);

        try {
            //execute
            commandManager.execute();
            executedCommandsContext.addAll(commandList);
        } catch (CommandManagerException e) {
            RollBackCommandManager commandManager = new RollBackCommandManager(executedCommandsContext);
            commandManager.rollback()
            throw new CommandServiceException("Service has failed", e);
        }
    }

    private abstract void getCommands();
}

然后讓 A 和 B 擴展 CommandServiceBase,並實現 getCommands();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM