[英]Saving state in spring bean using ThreadLocal
我有兩個使用命令模式進行通信的服務(A 和 B)
命令:
public interface Command {
void execute() throws CommandExecutionException;
void rollback() throws CommandExecutionException;
String commandName();
}
我的用例是
現在我這樣做:
@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.