当前位置:

首页 > 编程开发 > spring boot项目使用@Async注解的坑怎么解决

spring boot项目使用@Async注解的坑怎么解决

背景前段时间,一个同事小姐姐跟我说她的项目起不来了,让我帮忙看一下,本着助人为乐的精神,这个忙肯定要去帮。于是,我在她的控制台发现了如下的异常信息:Exceptioninthread"main"org.springframework.beans.factory.BeanCurrentlyInCreationException:Errorcreatingbeanwithname'AService':Beanwithname'AService'hasbeeninjectedin

本文目录

      背景

      前段时间,一个同事小姐姐跟我说她的项目起不来了,让我帮忙看一下,本着助人为乐的精神,这个忙肯定要去帮。

      于是,我在她的控制台发现了如下的异常信息:

      Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
       at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
       at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)

      看到BeanCurrentlyInCreationException这个异常,我的第一反应是出现了循环依赖的问题。但是仔细一想,Spring不是已经解决了循环依赖的问题么,怎么还报这个错。于是,我就询问小姐姐改了什么东西,她说在方法上加了@Async注解。

      这里我模拟一下当时的代码,AService 和 BService 相互引用,AService的 save() 方法加了 @Async 注解。

      @Component
      public class AService {
          @Resource
          private BService bService;
          @Async
          public void save() {
          }
      }
      @Component
      public class BService {
          @Resource
          private AService aService;
      }

      也就是这段代码会报BeanCurrentlyInCreationException异常,难道是@Async注解遇上循环依赖的时候,Spring无法解决?为了验证这个猜想,我将@Async注解去掉之后,再次启动项目,项目成功起来了。于是基本可以得出结论,那就是@Async注解遇上循环依赖的时候,Spring的确无法解决。

      虽然问题的原因已经找到了,但是又引出以下几个问题:

      • @Async注解是如何起作用的?

      • 为什么@Async注解遇上循环依赖,Spring无法解决?

      • 出现循环依赖异常之后如何解决?

      @Async注解是如何起作用的?

      @Async注解起作用是靠AsyncAnnotationBeanPostProcessor这个类实现的,这个类会处理@Async注解。AsyncAnnotationBeanPostProcessor这个类的对象是由@EnableAsync注解放入到Spring容器的,这也是为什么需要使用@EnableAsync注解来激活让@Async注解起作用的根本原因。

      AsyncAnnotationBeanPostProcessor

      spring boot项目使用@Async注解的坑怎么解决

      类体系

      这个类实现了 BeanPostProcessor 接口,实现了 postProcessAfterInitialization 方法,是在其父类AbstractAdvisingBeanPostProcessor 中实现的,也就是说当Bean的初始化阶段完成之后会回调 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法。之所以会回调,是因为在Bean的生命周期中,当Bean初始化完成之后,会回调所有的 BeanPostProcessor 的 postProcessAfterInitialization 方法,代码如下:

      @Override
      public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {
          Object result = existingBean;
          for (BeanPostProcessor processor : getBeanPostProcessors()) {
               Object current = processor.postProcessAfterInitialization(result, beanName);
               if (current == null) {
                  return result;
               }
               result = current;
           }
          return result;
      }

      AsyncAnnotationBeanPostProcessor 对于 postProcessAfterInitialization 方法实现:

      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName) {
          if (this.advisor == null || bean instanceof AopInfrastructureBean) {
         // Ignore AOP infrastructure such as scoped proxies.
              return bean;
          }
          if (bean instanceof Advised) {
             Advised advised = (Advised) bean;
             if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                 // Add our local Advisor to the existing proxy's Advisor chain...
                 if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                 }
                 else {
                    advised.addAdvisor(this.advisor);
                 }
                 return bean;
              }
           }
           if (isEligible(bean, beanName)) {
              ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
              if (!proxyFactory.isProxyTargetClass()) {
                 evaluateProxyInterfaces(bean.getClass(), proxyFactory);
              }
              proxyFactory.addAdvisor(this.advisor);
              customizeProxyFactory(proxyFactory);
              return proxyFactory.getProxy(getProxyClassLoader());
           }
           // No proxy needed.
           return bean;
      }

      该方法的主要作用是用来对方法入参的对象进行动态代理的,当入参的对象的类加了@Async注解,那么这个方法就会对这个对象进行动态代理,最后会返回入参对象的代理对象出去。至于如何判断方法有没有加@Async注解,是靠 isEligible(bean, beanName) 来判断的。由于这段代码牵扯到动态代理底层的知识,这里就不详细展开了。

      spring boot项目使用@Async注解的坑怎么解决

      AsyncAnnotationBeanPostProcessor作用

      综上所述,可以得出一个结论,那就是当Bean创建过程中初始化阶段完成之后,会调用 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 的方法,对加了@Async注解的类的对象进行动态代理,然后返回一个代理对象回去。

      虽然这里我们得出@Async注解的作用是依靠动态代理实现的,但是这里其实又引发了另一个问题,那就是事务注解@Transactional又或者是自定义的AOP切面,他们也都是通过动态代理实现的,为什么使用这些的时候,没见抛出循环依赖的异常?难道他们的实现跟@Async注解的实现不一样?不错,还真的不太一样,请继续往下看。

      AOP是如何实现的?

      我们都知道AOP是依靠动态代理实现的,而且是在Bean的生命周期中起作用,具体是靠 AnnotationAwareAspectJAutoProxyCreator 这个类实现的,这个类会在Bean的生命周期中去处理切面,事务注解,然后生成动态代理。这个类的对象在容器启动的时候,就会被自动注入到Spring容器中。

      AnnotationAwareAspectJAutoProxyCreator 也实现了BeanPostProcessor,也实现了 postProcessAfterInitialization 方法。

      @Override
      public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
          if (bean != null) {
             Object cacheKey = getCacheKey(bean.getClass(), beanName);
             if (!this.earlyProxyReferences.contains(cacheKey)) {
                 //生成动态代理,如果需要被代理的话
                 return wrapIfNecessary(bean, beanName, cacheKey);
             }
           }
          return bean;
      }

      通过 wrapIfNecessary 方法就会对Bean进行动态代理,如果你的Bean需要被动态代理的话。

      spring boot项目使用@Async注解的坑怎么解决

      AnnotationAwareAspectJAutoProxyCreator作用

      也就说,AOP和@Async注解虽然底层都是动态代理,但是具体实现的类是不一样的。一般的AOP或者事务的动态代理是依靠 AnnotationAwareAspectJAutoProxyCreator 实现的,而@Async是依靠 AsyncAnnotationBeanPostProcessor 实现的,并且都是在初始化完成之后起作用,这也就是@Async注解和AOP之间的主要区别,也就是处理的类不一样。

      Spring是如何解决循环依赖的

      Spring在解决循环依赖的时候,是依靠三级缓存来实现的。我曾经写过一篇关于三级缓存的文章,如果有不清楚的小伙伴可以 关注微信公众号 三友的java日记,回复 循环依赖 即可获取原文链接,本文也算是这篇三级缓存文章的续作。

      简单来说,通过缓存正在创建的对象对应的ObjectFactory对象,可以获取到正在创建的对象的早期引用的对象,当出现循环依赖的时候,由于对象没创建完,就可以通过获取早期引用的对象注入就行了。

      而缓存ObjectFactory代码如下:

      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
          Assert.notNull(singletonFactory, "Singleton factory must not be null");
          synchronized (this.singletonObjects) {
             if (!this.singletonObjects.containsKey(beanName)) {
                 this.singletonFactories.put(beanName, singletonFactory);
                 this.earlySingletonObjects.remove(beanName);
                 this.registeredSingletons.add(beanName);
             }
          }
      }

      所以缓存的ObjectFactory对象其实是一个lamda表达式,真正获取早期暴露的引用对象其实就是通过 getEarlyBeanReference 方法来实现的。

      getEarlyBeanReference 方法:

      protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
          Object exposedObject = bean;
          if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
              for (BeanPostProcessor bp : getBeanPostProcessors()) {
                  if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                     SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                     exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                  }
             }
          }
          return exposedObject;
      }

      getEarlyBeanReference 实现是调用所有的 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法。

      而前面提到的 AnnotationAwareAspectJAutoProxyCreator 这个类就实现了 SmartInstantiationAwareBeanPostProcessor 接口,是在父类中实现的:

      @Override
      public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.contains(cacheKey)) {
              this.earlyProxyReferences.add(cacheKey);
          }
          return wrapIfNecessary(bean, beanName, cacheKey);
      }

      这个方法最后会调用 wrapIfNecessary 方法,前面也说过,这个方法是获取动态代理的方法,如果需要的话就会代理,比如事务注解又或者是自定义的AOP切面,在早期暴露的时候,就会完成动态代理。

      这下终于弄清楚了,早期暴露出去的原来可能是个代理对象,而且最终是通过AnnotationAwareAspectJAutoProxyCreator这个类的getEarlyBeanReference方法获取的。

      但是AsyncAnnotationBeanPostProcessor并没有实现SmartInstantiationAwareBeanPostProcessor,也就是在获取早期对象这一阶段,并不会调AsyncAnnotationBeanPostProcessor处理@Async注解。

      为什么@Async注解遇上循环依赖,Spring无法解决?

      这里我们就拿前面的例子来说,AService加了@Async注解,AService先创建,发现引用了BService,那么BService就会去创建,当Service创建的过程中发现引用了AService,那么就会通过AnnotationAwareAspectJAutoProxyCreator 这个类实现的 getEarlyBeanReference 方法获取AService的早期引用对象,此时这个早期引用对象可能会被代理,取决于AService是否需要被代理,但是一定不是处理@Async注解的代理,原因前面也说过。

      于是BService创建好之后,注入给了AService,那么AService会继续往下处理,前面说过,当初始化阶段完成之后,会调用所有的BeanPostProcessor的实现的 postProcessAfterInitialization 方法。于是就会回调依次回调 AnnotationAwareAspectJAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法实现。

      这段回调有两个细节:

      • AnnotationAwareAspectJAutoProxyCreator 先执行,AsyncAnnotationBeanPostProcessor 后执行,因为 AnnotationAwareAspectJAutoProxyCreator 在前面。

      spring boot项目使用@Async注解的坑怎么解决

      • AnnotationAwareAspectJAutoProxyCreator处理的结果会当入参传递给 AsyncAnnotationBeanPostProcessor,applyBeanPostProcessorsAfterInitialization方法就是这么实现的

      AnnotationAwareAspectJAutoProxyCreator回调:会发现AService对象已经被早期引用了,什么都不处理,直接把对象AService给返回

      AsyncAnnotationBeanPostProcessor回调:发现AService类中加了@Async注解,那么就会对AnnotationAwareAspectJAutoProxyCreator返回的对象进行动态代理,然后返回了动态代理对象。

      这段回调完,是不是已经发现了问题。早期暴露出去的对象,可能是AService本身或者是AService的代理对象,而且是通过AnnotationAwareAspectJAutoProxyCreator对象实现的,但是通过AsyncAnnotationBeanPostProcessor的回调,会对AService对象进行动态代理,这就导致AService早期暴露出去的对象跟最后完全创造出来的对象不是同一个,那么肯定就不对了。

      同一个Bean在一个Spring中怎么能存在两个不同的对象呢,于是就会抛出BeanCurrentlyInCreationException异常,这段判断逻辑的代码如下:

      if (earlySingletonExposure) {
        // 获取到早期暴露出去的对象
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            // 早期暴露的对象不为null,说明出现了循环依赖
            if (exposedObject == bean) {
                // 这个判断的意思就是指 postProcessAfterInitialization 回调没有进行动态代理,如果没有那么就将早期暴露出去的对象赋值给最终暴露(生成)出去的对象,
                // 这样就实现了早期暴露出去的对象和最终生成的对象是同一个了
                // 但是一旦 postProcessAfterInitialization 回调生成了动态代理 ,那么就不会走这,也就是加了@Aysnc注解,是不会走这的
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                     // allowRawInjectionDespiteWrapping 默认是false
                     String[] dependentBeans = getDependentBeans(beanName);
                     Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                     for (String dependentBean : dependentBeans) {
                         if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                             actualDependentBeans.add(dependentBean);
                        }
                     }
                     if (!actualDependentBeans.isEmpty()) {
                         //抛出异常
                         throw new BeanCurrentlyInCreationException(beanName,
                                 "Bean with name '" + beanName + "' has been injected into other beans [" +
                                 StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                 "] in its raw version as part of a circular reference, but has eventually been " +
                                 "wrapped. This means that said other beans do not use the final version of the " +
                                 "bean. This is often the result of over-eager type matching - consider using " +
                                 "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                     }
            }
         }
      }

      所以,之所以@Async注解遇上循环依赖,Spring无法解决,是因为@Aysnc注解会使得最终创建出来的Bean,跟早期暴露出去的Bean不是同一个对象,所以就会报错。

      出现循环依赖异常之后如何解决?

      解决这个问题的方法很多

      1、调整对象间的依赖关系,从根本上杜绝循环依赖,没有循环依赖,就没有早期暴露这么一说,那么就不会出现问题

      2、不使用@Async注解,可以自己通过线程池实现异步,这样没有@Async注解,就不会在最后生成代理对象,导致早期暴露的出去的对象不一样

      3、可以在循环依赖注入的字段上加@Lazy注解

      @Component
      public class AService {
          @Resource
          @Lazy
          private BService bService;
          @Async
          public void save() {
          }
      }

      4、从上面的那段判断抛异常的源码注释可以看出,当allowRawInjectionDespiteWrapping为true的时候,就不会走那个else if,也就不会抛出异常,所以可以通过将allowRawInjectionDespiteWrapping设置成true来解决报错的问题,代码如下:

      @Component
      public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
          @Override
          public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
              ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
          }
      }

      虽然这样设置能解决报错的问题,但是并不推荐,因为这样设置就允许早期注入的对象和最终创建出来的对象是不一样,并且可能会导致最终生成的对象没有被动态代理。

      本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
      作者最新文章
      编程开发
      相关文章 更多
      springBoot项目常用目录有哪些
      springBoot项目常用目录有哪些

      springBoot项目常用目录springBoot项目的目录结构及名规范介绍基于SpringBoot开发时的目录结构及命名规范,通过介绍能够为你解决,在实际项目中如何规划目录结构?如何命名目录更规范?各个目录分别代表什么含义?等三个问题。目录说明servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1|-servicex-common//模块2|-servicex-gateway//模块3|-serv

      解析SpringBoot和SpringMVC之间的异同
      解析SpringBoot和SpringMVC之间的异同

      解析SpringBoot和SpringMVC之间的异同SpringBoot和SpringMVC是Java领域中非常重要的开发框架。虽然它们都属于Spring框架的一部分,但是在使用和功能上有一些明显的区别。本文将对SpringBoot和SpringMVC进行比较,解析它们之间的异同。首先,让我们来了解一下SpringBoot。SpringBo

      探索SpringCloud和SpringBoot在微服务领域的不同应用方式
      探索SpringCloud和SpringBoot在微服务领域的不同应用方式

      近年来,随着云计算和分布式架构的兴起,微服务架构的应用越来越广泛。而SpringCloud和SpringBoot作为Java开发中的两个重要框架,对于微服务的实现起到了重要的作用。然而,很多人对于它们在微服务领域的不同应用方式还存在一定的疑惑。本文将从不同的角度探索SpringCloud和SpringBoot在微服务中的应用方式。首先,我们来了解一下Spri

      SpringBoot和SpringMVC有何区别?
      SpringBoot和SpringMVC有何区别?

      SpringBoot与SpringMVC的不同之处在哪里?SpringBoot和SpringMVC是两个非常流行的Java开发框架,用于构建Web应用程序。尽管它们经常分别被使用,但它们之间的不同之处也是很明显的。首先,SpringBoot可以被看作是一个Spring框架的扩展或者增强版。它旨在简化Spring应用程序的初始化和配置过程,以帮助开发人

      比较SpringBoot和SpringMVC的异同
      比较SpringBoot和SpringMVC的异同

      SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

      SpringBoot与SpringMVC的不同点是什么?
      SpringBoot与SpringMVC的不同点是什么?

      SpringBoot和SpringMVC是Java开发中常用的两个框架,它们都是由Spring框架所提供的,但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。一、SpringBoot的特点:简化配置:SpringBoot通过约定优于配置的原则,大大简化了项目的配置过程。它可以自动配置项目所需要的参数,开发人

      比较和选择指南:SpringCloud与SpringBoot的功能比较
      比较和选择指南:SpringCloud与SpringBoot的功能比较

      SpringCloud和SpringBoot是目前Java领域中最热门的开源框架,它们分别提供了一套完善的微服务架构和快速构建应用程序的解决方案。本文将对它们的功能进行比较,并给出选择指南,以帮助读者了解它们的优势和适用场景。SpringBoot是一个用于开发Java应用程序的框架,它提供了一个简化的开发流程,集成了大量常用的功能和组件,减少了开发者的工作量

      SpringCloud与SpringBoot:分析它们之间的相似与不同
      SpringCloud与SpringBoot:分析它们之间的相似与不同

      深入剖析SpringCloud和SpringBoot的异同引言:在构建企业级应用程序时,Spring框架已经成为开发者们的首选。它提供了一种简单而强大的方式来构建独立于平台的应用程序。Spring框架由两个核心模块组成:SpringBoot和SpringCloud。虽然它们都可以帮助开发者快速搭建应用程序,但它们的目标和用途存在一些差异。本文将深入剖析Spr

      从架构角度看SpringCloud与SpringBoot的差异
      从架构角度看SpringCloud与SpringBoot的差异

      从架构角度看SpringCloud与SpringBoot的差异引言:在当今的互联网时代,构建分布式系统已经成为了一种必要的需求。而SpringBoot和SpringCloud正是为了满足这个需求而诞生的。尽管它们都是由Spring框架所提供的解决方案,但从架构角度来看,它们存在着一些重要的差异。本文将从架构的角度出发,对SpringBoot和SpringCl

      Java API 开发中使用 SpringBoot 的利弊分析
      Java API 开发中使用 SpringBoot 的利弊分析

      JavaAPI开发中使用SpringBoot的利弊分析随着互联网的快速发展和技术的不断更新迭代,Java技术在开发中的广泛应用引起了人们的重视。而SpringBoot作为一种微服务架构的解决方案,已经逐渐成为Java开发中的热门选择。那么,在JavaAPI开发中使用SpringBoot,到底有哪些利弊呢?下面来分析一下。利:简化开发

      查看更多
      精品专题 更多
      装机必备
      装机必备

      正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

      Windows
      Windows

      正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

      PDF教程
      PDF教程

      正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

      Mac软件 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      Aperty macOS版
      Aperty macOS版
      macOS

      Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

      WINDOWS 更多
      讯飞智作 网页版
      讯飞智作 网页版
      Windows/ macOS/ Linux

      讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

      火山引擎 网页版
      火山引擎 网页版
      Windows/ macOS/ Linux

      火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

      PixTV 网页版
      PixTV 网页版
      Windows

      PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。