Reputation: 933
There are at least 2 approaches to put Spring beans into a context configutation:
@Bean
inside the configuration class.@ComponentScan
on the configuration class.I was expecting that there are no difference between the 2 approaches in terms of the resulting Spring beans.
However, I found an example to demonstrate the difference:
// 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");
}
The above code is to test the security annotation in the service UserInfoService
. However, it will fail on testGetUserInfoWithoutUser()
. The reason is that the bean userInfoService
didn't get proxied by Spring Security. Therefore, the call userInfoService.getUserInfo()
didn't get blocked by the annotation @PreAuthorize("isAuthenticated()")
.
However, if I replace the @Bean
annotation with @ComponentScan
, everything will start to work. That is, the bean userInfoService
will get proxied and the call userInfoService.getUserInfo()
will be blocked by the @PreAuthorize
annotation.
Why are the approaches between the @Bean
and @ComponentScan
different? Did I miss something?
P.S. The full example is here and the above mentioned fix is here.
Upvotes: 3
Views: 898
Reputation: 90527
It is because @ContextHierarchy
will create multiple spring contexts with parent children hierarchy. In your case, TestSecurityContext
defines the bean configuration for the parent context while UserInfoServiceTest.Config
defines for the children context.
If there is no @ComponentScan
on the UserInfoServiceTest.Config
, the security related beans are defined in the parent context which are invisible to the UserInfoService
bean in the children context , and hence it does not get proxied by Spring Security.
On the other hand , if you define @ComponentScan
on the UserInfoServiceTest.Config
, it will also scan all the @Configuration
beans from the package that containing UserInfoService
(and all of its sub package) . Because TestSecurityContext
is also inside this package and hence it get scanned and the security related beans are also configured for the children context. UserInfoService
in children context will then get proxied by Spring Security.(Note : In this case ,both the parent and children context has their own set of security related beans)
By the way , if you just need a single context , you can simply use @ContextConfiguration
:
@ContextConfiguration(classes= {TestSecurityContext.class,UserInfoServiceTest.Config.class})
public class UserInfoServiceTest {
public static class Config {
@Bean
public UserInfoService userInfoService() {
return new UserInfoServiceImpl();
}
}
}
Upvotes: 3