@ComponentScan 和 @Bean 在上下文配置中有什么区别?
What's the difference between @ComponentScan and @Bean in a context configuration?
至少有两种方法可以将 Spring bean 放入上下文配置中:
- 在配置中用
@Bean
声明一个方法 class。
- 将
@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
的方法不同?我错过了什么吗?
这是因为 @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();
}
}
}
至少有两种方法可以将 Spring bean 放入上下文配置中:
- 在配置中用
@Bean
声明一个方法 class。 - 将
@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
的方法不同?我错过了什么吗?
这是因为 @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();
}
}
}