使用Spring进行自我注入

75

我尝试了以下使用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在其中一个答案的评论中提到了它:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.spring.service.Service] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}


5
额外问题:自我注射的目的是什么? - Orkun
3
简单的用例:您希望在同一类中调用另一个方法时,@Transactional注释能够正常工作。调用this.myMethod()将忽略事务,但是self.myMethod()应该创建事务。请参见第5.1节 潜在陷阱-事务和代理 - Snackoverflow
@Snackoverflow,另一个奖励问题:this.myMethod() 如何忽略事务? - GB11
3
据我所知,当方法被注释为@Transactional并且类被标记为@Autowired作为依赖项时,Spring实际上会注入一个代理实例来包装您的类实例,并且在代理实现中,该方法也被一个带有事务逻辑的代理方法包装。如果您直接使用this.myMethod(),则是从您的类实例代码内部进行引用您的类实例方法目录,并且没有调用具有事务逻辑的注入代理。https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring - Snackoverflow
1
@GB11 这里还有另一个来源:Spring文档:8.6代理机制(它是一篇简短的阅读材料)。 - Snackoverflow
8个回答

67

更新:2016年2月

自动装配将在Spring Framework 4.3中得到官方支持。该实现可以在此GitHub提交记录中查看。


您无法进行自我自动装配的根本原因是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之前不能)。

至于从语义上讲这是否是预期行为,那是另外一个问题。;)

我会问问Juergen他对此有何看法。

敬礼,

Sam(核心Spring Committer)

p.s.我已经打开了一个Spring JIRA问题来考虑使用@Autowired支持按类型进行自动装配。请随意观看或投票支持此问题:https://jira.springsource.org/browse/SPR-8450


2
@Amit - 这解释了一切!他们只是排除了自动装配的候选项,而没有检查其他的,比如@Resource等。 - Premraj
4
我认为在某些场合下,类似这样的东西非常有用。例如,在需要将方法包装在事务代理中(尤其是使用 requires_new 时),可能会自我调用的情况下。需要“分离”这种功能通常会导致反模式和不良设计。 - nvrs
7
同意nvrs的观点,同类@Transactional代理是导致我出现问题的原因。当你拥有成熟的代码库时,“使用AspectJ而不是CGLib”并不是一个有用的答案。使用@Resource可能会解决我的问题,但是我们的标准是使用@Autowired,所以最好使用它。 - Noah Yetter
2
@Xiangyu,Juergen Hoeller在三天前刚刚记录了这个:https://github.com/spring-projects/spring-framework/commit/c6752e6023c4948c0f2c007c166ab8ecf677d7b6 - Sam Brannen
7
记得添加 @Lazy - OrangeDog
显示剩余4条评论

44

这段代码也可以正常工作:

@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,如果该bean已经创建,但尚未初始化@Autowired在初始化之前就可以工作,但它找不到相同的bean。因此,@Resource可能会在@Autowired之后和@PostConstruct之前起作用。

但我不确定,只是猜测而已。无论如何,这是个好问题。


9
酷啊,将上下文注入到你的应用程序bean中。这是最佳实践! - Andrii Andriichuk
6
如果一个类需要意识到其代理本身,那么它已经违反了依赖注入的抽象原则,因此我认为访问容器也不会更糟糕。 - sinuhepop
1
为什么会破坏依赖注入?我认为在注入自身方面并没有任何问题,例如在使用事务方法时。 - Andrii Andriichuk
不确定是讽刺还是真心话,@AndriiAndriichuk。像我这样偶然看到这个问题的用户通常对最佳实践并不太熟悉。 - T Tse
2
@TTse 将应用程序上下文注入到服务bean中(与Spring定制无关)是错误的方法。 - Andrii Andriichuk
显示剩余2条评论

3

1
顺便提一下,解决自我调用问题的更优雅的解决方案是使用AspectJ Load-Time Weaving来处理事务代理(或任何AOP引入的代理)。
例如,在注释驱动的事务管理中,您可以使用“aspectj”模式,如下所示:
<tx:annotation-driven mode="aspectj" />

请注意,默认模式为“代理”(即JDK动态代理)。
敬礼,
Sam

AspectJ能够与JDK代理一起工作吗?我猜它需要CGLib,对吗? - Premraj
AspectJ 处理自我调用问题,但会产生其他问题。而且据我上次查看 SpringSource 文档,它并不被推荐使用。 - nvrs

1
根据上述代码,我没有看到循环依赖。您将某个Service实例注入到UserService中。被注入的Service的实现不一定需要是另一个UserService,因此不存在循环依赖。
我不明白为什么您要将UserService注入到UserService中,但我希望这只是理论尝试或类似的东西。

我只有一个服务实现,那就是UserService :).. 相同的代码在使用Spring @Autowired时失败了,但在使用@Resource时可以工作。 - Premraj
5
如果服务周围使用了Spring缓存代理,并且您希望内部调用受益于此缓存,将一个bean连接到它自己可能会很有用。 - Shamu

1

另一种方法:

@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生成。 - ennth
不知道你正在构建什么,很难猜测你那边发生了什么。此外,三年前的这段代码是我今天会避免的东西。如果你似乎需要自我注入,可能你有一个架构问题。你可以将每个概念属于其自己的类别分开,那么你就不必再进行自我注入了。 - Gilson

0

看起来Spring会创建和配置一个对象,然后将其放置在bean查找上下文中。但是,在Java的情况下,我认为它会创建对象并将其绑定到名称上,在配置期间,当按名称查找对象时,它会在上下文中被找到。


0

这是我为小到中型项目提供的解决方案。没有 AspectJ 或应用程序上下文魔法,它与单例和构造函数注入一起工作,并且非常易于测试。

@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {

    private final PersonDao _personDao;

    @Autowired
    public PersonDao(PersonDao personDao) {
        _personDao = personDao;
    }
}

由于某些原因,这对我来说不起作用。即使设置了特殊标志也不起作用。然而有效的方法是使用字段注入而不是构造函数注入,并使用@Autowired@Lazy注释标记自注入的bean。 - chill appreciator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接