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

3

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

  1. 在配置类中声明具有@Bean的方法。
  2. 在配置类上加上@ComponentScan

我原本期望这两种方法在生成的Spring bean方面没有任何区别。

然而,我找到了一个示例来证明它们之间的差异:

// 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()上失败了。原因是userInfoService这个bean没有被Spring Security代理。因此,调用userInfoService.getUserInfo()没有被@PreAuthorize("isAuthenticated()")注解所阻止。
但是,如果我使用@ComponentScan替换@Bean注解,一切都会开始工作。也就是说,userInfoService这个bean将被代理,调用userInfoService.getUserInfo()将被@PreAuthorize注解所阻止。
为什么@Bean@ComponentScan之间的方法不同?我漏掉了什么吗?
注:完整示例在这里,上述修复方法在这里

如果使用@Bean,难道不应该在工厂方法上也注释@PreAuthorize吗? - Marco Bolis
1个回答

3

@ContextHierarchy 会创建多个带有父子层级的Spring上下文。在本例中,TestSecurityContext 为父上下文定义了bean配置,而UserInfoServiceTest.Config 为子上下文定义了bean配置。

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

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

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

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

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

你的意思是子上下文中的UserInfoService bean对于父上下文中的安全相关bean是不可见的吗?你的第二段似乎描述了相反的情况? - johnlinp
是的,它们是相同的。关键在于安全相关的bean仅对同一上下文中的bean可见并适用。 - Ken Chan
我明白了。非常感谢您的回答。 - johnlinp

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