@ComponentScan 和 @Bean 在上下文配置中有什么区别?

What's the difference between @ComponentScan and @Bean in a context configuration?

至少有两种方法可以将 Spring bean 放入上下文配置中:

  1. 在配置中用 @Bean 声明一个方法 class。
  2. @ComponentScan放在配置class上。

我原以为这两种方法在生成的 Spring beans 方面没有区别。

不过,我找到了一个例子来说明区别:

// UserInfoService.java

public interface UserInfoService
{
    @PreAuthorize("isAuthenticated()")
    String getUserInfo ();
}
// UserInfoServiceTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextHierarchy({
        @ContextConfiguration(classes = TestSecurityContext.class),
        @ContextConfiguration(classes = UserInfoServiceTest.Config.class)
})
public class UserInfoServiceTest
{
    @Configuration
    public static class Config
    {
        @Bean
        public UserInfoService userInfoService ()
        {
            return new UserInfoServiceImpl();
        }
    }

    @Autowired
    private UserInfoService userInfoService;

    @Test
    public void testGetUserInfoWithoutUser ()
    {
        assertThatThrownBy(() -> userInfoService.getUserInfo())
                .isInstanceOf(AuthenticationCredentialsNotFoundException.class);
    }

    @Test
    @WithMockUser
    public void testGetUserInfoWithUser ()
    {
        String userInfo = userInfoService.getUserInfo();
        assertThat(userInfo).isEqualTo("info about user");
    }

以上代码是为了测试服务UserInfoService中的安全注解。但是,它将在 testGetUserInfoWithoutUser() 上失败。原因是 bean userInfoService 没有被 Spring 安全代理。因此,调用 userInfoService.getUserInfo() 没有被注解 @PreAuthorize("isAuthenticated()").

阻塞

但是,如果我用 @ComponentScan 替换 @Bean 注释,一切都会开始工作。也就是说,bean userInfoService 将被代理并且调用 userInfoService.getUserInfo() 将被 @PreAuthorize 注释阻止。

为什么 @Bean@ComponentScan 的方法不同?我错过了什么吗?

P.S。完整的例子是 here and the above mentioned fix is here.

这是因为 @ContextHierarchy 将创建多个具有父子层次结构的 spring 上下文。在您的例子中,TestSecurityContext 定义了父上下文的 bean 配置,而 UserInfoServiceTest.Config 定义了子上下文。

如果 UserInfoServiceTest.Config 上没有 @ComponentScan ,安全相关的 bean 是在父上下文中定义的,对于子上下文中的 UserInfoService bean 是不可见的,因此它不会被 Spring 安全代理。

另一方面,如果您在 UserInfoServiceTest.Config 上定义 @ComponentScan,它还会从包含 UserInfoService 的包中扫描所有 @Configuration bean(并且它的所有子包)。因为 TestSecurityContext 也在这个包中,因此它被扫描并且安全相关的 bean 也被配置为子上下文。 UserInfoService 在子上下文中将由 Spring 安全代理。(注意:在这种情况下,父上下文和子上下文都有自己的一组安全相关 bean)

顺便说一下,如果您只需要一个上下文,您可以简单地使用 @ContextConfiguration:

@ContextConfiguration(classes= {TestSecurityContext.class,UserInfoServiceTest.Config.class})
public class UserInfoServiceTest {

    public static class Config {
        @Bean
        public UserInfoService userInfoService() {
            return new UserInfoServiceImpl();
        }
    }
}