Spring Boot应用循环依赖问题求解:Telegram Bot场景
这个循环依赖问题很典型——你的AssistantBot(带@Component)和UpdateReceiver(带@Service)通过构造器互相依赖,Spring容器在初始化时没法同时完成两者的实例化,自然就抛出循环依赖的异常了。下面给你几个可行的解决办法,从快速修复到架构优化的都有:
方案1:用Setter注入替代其中一个构造器依赖
Spring对Setter注入的循环依赖支持更好,你可以把其中一个类的构造器依赖改成Setter注入。比如修改UpdateReceiver,只在构造器注入TextMessageHandler和CallbackQueryHandler,用Setter注入AssistantBot:
@Service public class UpdateReceiver { private TextMessageHandler textMessageHandler; private CallbackQueryHandler callbackQueryHandler; private AssistantBot bot; // 构造器只注入非循环的依赖 public UpdateReceiver(TextMessageHandler textMessageHandler, CallbackQueryHandler callbackQueryHandler) { this.textMessageHandler = textMessageHandler; this.callbackQueryHandler = callbackQueryHandler; } // Setter注入Bot,Spring 4.3+可以不用加@Autowired public void setBot(AssistantBot bot) { this.bot = bot; } public void handleUpdate(Update update) { // 原有逻辑不变 if (update.hasMessage() && update.getMessage().hasText()){ textMessageHandler.handleTextMessage(update); } else if (update.hasCallbackQuery()) { callbackQueryHandler.handleCallbackQuery(update.getCallbackQuery()); } else { bot.sendTextMessage(update.getMessage().getChatId(), "I can't handle this!"); } } }
这样Spring可以先初始化AssistantBot(它只需要UpdateReceiver的实例,而UpdateReceiver现在可以先通过构造器创建出来),之后再把AssistantBot注入到UpdateReceiver的setBot方法里,完美解决循环问题。
方案2:抽离消息发送服务(推荐,解耦架构)
如果想从根源上解决耦合问题,建议把发送消息的逻辑抽成独立的MessageSender服务,这样UpdateReceiver不再直接依赖AssistantBot,而是依赖专门的发送服务,彻底消除循环依赖:
首先创建MessageSender类:
@Component @Log4j2 public class MessageSender { private final AssistantBot bot; public MessageSender(AssistantBot bot) { this.bot = bot; } public synchronized void sendTextMessage(long chatId, String message) { SendMessage textMessage = new SendMessage() .enableMarkdown(false) .setChatId(chatId) .setText(message); try { bot.execute(textMessage); } catch (TelegramApiException e) { e.printStackTrace(); } } // 其他发送消息的方法也移到这里 }
然后修改AssistantBot,移除发送消息的方法,只保留Update转发逻辑:
@Component @Log4j2 public class AssistantBot extends TelegramLongPollingBot { private final UpdateReceiver updateReceiver; public AssistantBot(UpdateReceiver updateReceiver) { this.updateReceiver = updateReceiver; } @Override public void onUpdateReceived(Update update) { updateReceiver.handleUpdate(update); } // 原来的sendTextMessage等方法已经移到MessageSender,这里删掉 }
最后修改UpdateReceiver,注入MessageSender而非AssistantBot:
@Service public class UpdateReceiver { private TextMessageHandler textMessageHandler; private CallbackQueryHandler callbackQueryHandler; private MessageSender messageSender; public UpdateReceiver(TextMessageHandler textMessageHandler, CallbackQueryHandler callbackQueryHandler, MessageSender messageSender) { this.textMessageHandler = textMessageHandler; this.callbackQueryHandler = callbackQueryHandler; this.messageSender = messageSender; } public void handleUpdate(Update update) { if (update.hasMessage() && update.getMessage().hasText()){ textMessageHandler.handleTextMessage(update); } else if (update.hasCallbackQuery()) { callbackQueryHandler.handleCallbackQuery(update.getCallbackQuery()); } else { // 用MessageSender发送消息 messageSender.sendTextMessage(update.getMessage().getChatId(), "I can't handle this!"); } } }
这个方案不仅解决了循环依赖,还让代码结构更清晰,符合单一职责原则——AssistantBot专注于接收Update,MessageSender专注于发送消息,UpdateReceiver专注于路由Update,后续维护起来也更方便。
方案3:用@Lazy延迟初始化依赖
如果不想修改太多代码,可以用@Lazy注解让Spring创建一个代理对象,延迟初始化其中一个依赖,从而绕过循环:
比如在AssistantBot的构造器里给UpdateReceiver加@Lazy:
@Component @Log4j2 public class AssistantBot extends TelegramLongPollingBot { private final UpdateReceiver updateReceiver; public AssistantBot(@Lazy UpdateReceiver updateReceiver) { this.updateReceiver = updateReceiver; } @Override public void onUpdateReceived(Update update) { updateReceiver.handleUpdate(update); } // 发送方法不变 }
或者在UpdateReceiver的构造器里给AssistantBot加@Lazy:
@Service public class UpdateReceiver { private final TextMessageHandler textMessageHandler; private final CallbackQueryHandler callbackQueryHandler; private final AssistantBot bot; public UpdateReceiver(TextMessageHandler textMessageHandler, CallbackQueryHandler callbackQueryHandler, @Lazy AssistantBot bot) { this.textMessageHandler = textMessageHandler; this.callbackQueryHandler = callbackQueryHandler; this.bot = bot; } // handleUpdate方法不变 }
这个方法最快捷,但只是绕过了循环依赖,并没有解决代码耦合的问题,适合临时修复或者小型项目使用。
内容的提问来源于stack exchange,提问作者takotsubo

