【问题标题】:How to resolve circular dependency for Spring context?如何解决 Spring 上下文的循环依赖?
【发布时间】:2020-05-07 15:56:34
【问题描述】:

我有三个班级:

open class RedirectProcessor(
    private val adProcessor: AdProcessor
) {

    fun run(depth: Int): String = 
        if (depth < 3) adProcessor.run(depth + 1) else "redirect"
}
open class FallbackProcessor(
    private val adProcessor: AdProcessor
) {

    fun run(depth: Int): String = 
        if (depth < 3) adProcessor.run(depth + 1) else "fallback"
}
open class AdProcessor(
    private val redirectProcessor: RedirectProcessor,
    private val fallbackProcessor: FallbackProcessor
) {

    fun run(depth: Int): String = 
        depth.toString() + 
        redirectProcessor.run(depth) +
        fallbackProcessor.run(depth)
}

因此,它们相互依赖。我尝试如下配置spring上下文:

@Configuration
class Config {

    @Bean
    @Lazy
    fun redirectProcessor(): RedirectProcessor = RedirectProcessor(adProcessor())

    @Bean
    @Lazy
    fun fallbackProcessor(): FallbackProcessor = FallbackProcessor(adProcessor())

    @Bean
    fun adProcessor() = AdProcessor(
        redirectProcessor = redirectProcessor(),
        fallbackProcessor = fallbackProcessor()
    )
}

我知道我必须使用@Lazy 注释。如果我用 @Component 注释标记我的服务并在构造函数中使用 @Lazy 它工作正常。但是我需要使用 @Bean 注释来定义 bean,这会导致问题。有什么办法解决吗?

【问题讨论】:

    标签: java spring kotlin


    【解决方案1】:

    我不能说 Kotlin(目前我对 kotlin 的了解相当有限),但在 Java 中,最后一个可用的 spring 版本(5.2.6.RELEASE)

    我已经将它与您的示例的以下“kotlin to java”翻译一起使用:

    public class RedirectProcessor {
        private final AdProcessor adProcessor;
    
        public RedirectProcessor(AdProcessor adProcessor) {
            this.adProcessor = adProcessor;
        }
    
        public String run(int depth) {
            if(depth < 3) {
                return adProcessor.run(depth + 1);
            }
            else {
                return "redirect";
            }
        }
    }
    
    
    public class FallbackProcessor {
        private final AdProcessor adProcessor;
    
        public FallbackProcessor(AdProcessor adProcessor) {
            this.adProcessor = adProcessor;
        }
    
        public String run(int depth) {
            if(depth < 3) {
                return adProcessor.run(depth + 1);
            }
            else {
                return "fallback";
            }
        }
    }
    
    public class AdProcessor {
        private RedirectProcessor redirectProcessor;
        private FallbackProcessor fallbackProcessor;
    
        public AdProcessor(RedirectProcessor redirectProcessor, FallbackProcessor fallbackProcessor) {
            this.redirectProcessor = redirectProcessor;
            this.fallbackProcessor = fallbackProcessor;
        }
    
        public String run (int depth) {
            return depth + redirectProcessor.run(depth) + fallbackProcessor.run(depth);
        }
    }
    

    那么诀窍就是以不同的方式使用配置(但从 Java 配置规则的角度来看,这是完全“合法”的方式):

    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
    
        @Bean
        public RedirectProcessor redirectProcessor (@Lazy AdProcessor adProcessor) {
            return new RedirectProcessor(adProcessor);
        }
    
        @Bean
        public FallbackProcessor fallbackProcessor (@Lazy AdProcessor adProcessor) {
            return new FallbackProcessor(adProcessor);
        }
    
        @Bean
        public AdProcessor adProcessor (RedirectProcessor redirectProcessor, FallbackProcessor fallbackProcessor) {
            return new AdProcessor(redirectProcessor, fallbackProcessor);
        }
    
        @EventListener
        public void onApplicationStarted(ApplicationStartedEvent evt) {
            AdProcessor adProcessor = evt.getApplicationContext().getBean(AdProcessor.class);
            String result = adProcessor.run(2);
            System.out.println(result);
        }
    }
    

    注意@Lazy 注释在参数上的用法,而不是在 bean 本身上。

    监听器仅用于测试目的。运行应用程序打印23redirectfallback3redirectfallback

    现在为什么它会起作用?

    当 spring 看到这样的 @Lazy 注释参数时 - 它会从参数类创建一个运行时生成的代理(使用 CGLIB)。

    此代理以包装 bean 的方式运行,并且仅在第一次“需要”时才会完全创建此 bean(请阅读,在这种情况下我们将调用此 bean 的方法)。

    如果您使用@Component,则与以下声明相同:

    @Component
    public class FallbackProcessor {
        private final AdProcessor adProcessor;
    
        public FallbackProcessor(@Lazy AdProcessor adProcessor) {
            this.adProcessor = adProcessor;
        }
    
        public String run(int depth) {
           ...
        }
    }
    

    附带说明,我没有在最后一个示例中将@Autowired 放在FallbackProcessor 类的构造函数上,只是因为如果有一个构造函数,spring 会“识别”并使用它来注入所有依赖项。

    SO 的following tutorialthis somewhat old 线程也可能是相关的(值得一读)。

    【讨论】:

    • 我试过这种方式。我得到 NPE,因为 CGLIB 代理在调用期间没有被替换
    • 所以你的 spring 版本中是否有一个 bug 已在我的版本中修复 - 也许你可以像我一样尝试使用最后一个版本的 spring?或者它与kotlin有关? (我已经测试过这个解决方案,它在我的机器上运行良好)
    • 我尝试了不同的 spring 版本,结果是一样的。所以,我想这可能是由 Kotlin 引起的。
    • 所以,解决方法是将AdProcessor中的run方法标记为open
    【解决方案2】:

    我遇到了同样的问题,@Autowire 注释由于某种我不知道的原因不起作用。

    所以我使用了另一种解决方法:

    • 注入 ApplicationContext 而不是 bean 本身
    • ApplicationContext检索bean实例

    代码如下:

    class ServiceA(
        private val serviceB: ServiceB
    ) {
    ......
    }
    
    
    class ServiceB(
        private val applicationContext: ApplicationContext
    ) {
        private val serviceA: ServiceA by lazy {
            // we need this logic for only once
            // so "property delegated by lazy ..." is perfect for this purpose
            applicationContext.getBean(ServiceA::class.java)
        }
    ......
    }
    
    

    【讨论】:

      猜你喜欢
      • 2012-03-15
      • 2016-09-21
      • 2020-11-12
      • 2021-04-26
      相关资源
      最近更新 更多