【问题标题】:Self injection with Spring使用 Spring 进行自我注入
【发布时间】:2011-07-06 09:05:06
【问题描述】:

我在 Spring 3.x 中尝试了以下代码,但以 BeanNotFoundException 失败,它应该根据我之前提出的问题的答案 - Can I inject same class using Spring?

@Service
public class UserService implements Service{
    @Autowired
    private Service self;
}

由于我在 Java 6 上尝试过这个,我发现以下代码可以正常工作:

@Service(value = "someService")
public class UserService implements Service{
    @Resource(name = "someService")
    private Service self;
}

但我不明白它是如何解决循环依赖的。

编辑:
这是错误消息。 OP 在对其中一个答案的评论中提到了它:

原因:org.springframework.beans.factory.NoSuchBeanDefinitionException:没有为依赖项找到类型为 [com.spring.service.Service] 的匹配 bean:预计至少有 1 个 bean 有资格作为此依赖项的自动装配候选者。依赖注解:{@org.springframework.beans.factory.annotation.Autowired(required=true)}

【问题讨论】:

  • 额外问题:这里自我注入的目的是什么?
  • @OrkunOzen 简单用例:您希望 @Transactional 注释在从内部调用同一类的另一个方法时正常工作。调用this.myMethod() 将忽略事务,但self.myMethod() 应该创建事务。见Section 5.1. Potential Pitfalls - Transactions and Proxies
  • @Snackoverflow,另一个额外的问题:this.myMethod() 如何忽略事务?
  • @GB11 AFAIK 当方法用@Transactional 注释并且类是@Autowired 作为依赖项时,Spring 实际上会注入一个包装您的类实例的代理实例,并且在代理实现中方法也由具有事务逻辑的代理方法包装。如果你直接使用this.myMethod(),你是在你的类实例代码中做的,引用你的类实例方法目录,而不是用事务逻辑调用注入的代理. spring.io/blog/2012/05/23/…
  • @GB11 这是另一个来源; Spring documentation: 8.6 Proxying mechanisms(短读)

标签: java spring dependency-injection ioc-container


【解决方案1】:

更新:2016 年 2 月

自动装配将在 Spring Framework 4.3 中得到正式支持。实现可以在这个GitHub commit中看到。


您不能自己自动装配的最终原因是 Spring 的 DefaultListableBeanFactory.findAutowireCandidates(String, Class, DependencyDescriptor) 方法的实现明确排除了这种可能性。这在此方法的以下代码摘录中可见:

for (String candidateName : candidateNames) {
    if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, descriptor)) {
        result.put(candidateName, getBean(candidateName));
    }
}

仅供参考:bean 的名称(即尝试自动装配自身的 bean)是 beanName。该 bean 实际上是一个自动装配候选者,但上面的 if 条件返回 false(因为 candidateName 实际上等于 beanName)。因此,您根本无法将 bean 与自身自动装配(至少从 Spring 3.1 M1 开始)。

现在至于这是否是语义上的预期行为,这是另一个问题。 ;)

我会问于尔根,看看他要说什么。

问候,

Sam(核心 Spring 提交者)

附言我已经打开了一个 Spring JIRA 问题,以考虑使用 @Autowired 支持按类型自动装配。欢迎在此处观看或投票支持此问题:https://jira.springsource.org/browse/SPR-8450

【讨论】:

  • @Amit - 这确实解释了!他们只排除自动装配的候选人,而不检查@Resource等其他候选人。
  • 我认为这样的东西在某些场合可能非常有用,例如有时您需要由事务代理(尤其是 requires_new)包装一个方法,该代理可能是自调用的。不得不“分离”这些功能通常会导致反模式和一般设计不佳
  • 同意 nvrs,同级 @Transactional 代理是让我来到这里的原因。当您拥有成熟的代码库时,“使用 AspectJ 而不是 CGLib”不是一个有用的答案。使用@Resource 可能会解决我的问题,但@Autowired 会更好,因为这是我们的标准。
  • @Xiangyu,Juergen Hoeller 三天前刚刚记录了这一点:github.com/spring-projects/spring-framework/commit/…
  • 记得加@Lazy
【解决方案2】:

此代码也有效:

@Service
public class UserService implements Service {

    @Autowired
    private ApplicationContext applicationContext;

    private Service self;

    @PostConstruct
    private void init() {
        self = applicationContext.getBean(UserService.class);
    }
}

我不知道为什么,但似乎 Spring 可以从 ApplicationContext 获取 bean,如果是 created,但不是 initialized@Autowired 在初始化之前工作,它找不到相同的 bean。所以,@Resource 可能在@Autowired 之后和@PostConstruct 之前工作。

但我不知道,只是猜测。无论如何,好问题。

【讨论】:

  • 酷,将上下文注入您的应用程序 bean。这是有史以来的最佳实践!
  • @avrilfanomar:好吧,如果一个类需要意识到自己有一个代理,那么无论如何你都在破坏 DI 抽象,所以我认为访问你的容器并不会更糟。
  • 为什么它会破坏 DI?我认为注入 self 没有坏处,例如使用应有的事务方法。
  • 我在上面尝试了同样的事情,在 UserService 类中有更多字段。但是当我做self.morefield.xxx时。它抛出 NPE
  • @TTse 将应用程序上下文注入服务 bean(与 Spring 定制无关)是错误的方法。
【解决方案3】:

顺便说一句,自调用问题的更优雅的解决方案是使用 AspectJ Load-Time Weaving 作为您的事务代理(或您正在使用的任何 AOP 引入的代理)。

例如,使用注解驱动的事务管理,可以使用“aspectj”模式如下:

<tx:annotation-driven mode="aspectj" />

请注意,默认模式是“代理”(即 JDK 动态代理)。

问候,

山姆

【讨论】:

  • aspectj 可以使用 JDK 代理吗?我猜它需要 CGLib 对吧?
  • 好吧 aspectj 处理自调用但会产生其他问题,上次我检查它不推荐 SpringSource 文档。
【解决方案4】:

鉴于上面的代码,我没有看到循环依赖。 您将一些服务实例注入到 UserService 中。 注入的Service的实现不一定需要是另一个UserService所以没有循环依赖。

我不明白为什么要将 UserService 注入到 UserService 中,但我希望这是一个理论上的尝试。

【讨论】:

  • 我只有一个 Service 的实现,那就是 UserService :).. 相同的代码在 Spring @Autowired 上失败,但在 @Resource 上工作
  • 如果服务使用 spring 缓存代理,并且您希望内部调用从该缓存中受益,则将 bean 连接到自身可能很有用。
【解决方案5】:

Get AOP proxy from the object itself 问题建议使用AopContext.currentProxy() 的替代hacky 方法,可能适用于特殊情况。

【讨论】:

    【解决方案6】:

    看起来 spring 创建并配置了一个对象,然后将它放在 bean 查找上下文中。但是,在 Java 的情况下,我认为它会创建对象并将其与名称联系起来,并在通过在上下文中找到的名称查找对象时将其绑定到配置期间。

    【讨论】:

      【解决方案7】:

      只是另一种方法:

      @EnableAsync
      @SpringBootApplication
      public class Application {
      
          @Autowired
          private AccountStatusService accountStatusService;
      
          @PostConstruct
          private void init() {
              accountStatusService.setSelf(accountStatusService);
          }
      }
      
      @Service
      public class AccountStatusService {
          private AccountStatusService self;
      
          public void setSelf(AccountStatusService self) {
              this.self = self;
          }
      }
      

      这样,您的服务将处于代理状态。我这样做是为了在其内部使用异步方法。

      我已经尝试过@sinuhepop 解决方案:

      @PostConstruct
      private void init() {
          self = applicationContext.getBean(UserService.class);
      }
      

      它进行了注入,但服务不在代理内部,并且我的方法没有在新线程上运行。使用这种方法,它可以按我的意愿工作。

      【讨论】:

      • 尝试了一切,但无法正常工作。不确定是不是因为我有一个在另一个模块(多模块 maven 项目)中实现接口的具体类,并且我也有 SwaggerCodeGenPlugin 生成的类中某些方法的数据类型
      • 在不知道您构建的内容的情况下,很难猜测您身边发生了什么。另外,上面这段代码,从 3 年前开始,是我今天要避免的。如果您似乎需要自我注入,则可能您遇到了架构师问题。您可以将每个概念属于其自己的类的东西分开,然后您将不再需要自我注入。
      【解决方案8】:

      这是我针对中小型项目的解决方案。没有 AspectJ 或应用程序上下文魔法,它适用于单例和构造函数注入,并且非常容易测试。

      @Service
      @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
      class PersonDao {
      
          private final PersonDao _personDao;
      
          @Autowired
          public PersonDao(PersonDao personDao) {
              _personDao = personDao;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2012-01-24
        • 2012-02-11
        • 2013-12-08
        • 2011-02-06
        • 2015-03-01
        • 1970-01-01
        • 2010-11-29
        • 1970-01-01
        相关资源
        最近更新 更多