【发布时间】:2016-07-15 12:52:26
【问题描述】:
我面临无法访问我创建的 Bean 的情况,这让我想知道它们在哪里以及何时出现!?
我制作了两个 Bean,一个作用域为 singleton,另一个作用域为 request。我已经通过在RestController 类中自动装配它们来确保它们得到正确实现。毫无疑问,它们是人口稠密的。
现在我编写了一个扩展PreInvocationAuthorizationAdvice 的授权检查器类。作为一个授权类,我需要访问当前用户的信息。所以我将当前用户的 Bean 自动连接到这个类,这是 request 作用域 Bean。我还需要一个定制的 ACL 引擎,它以singleton 的方式自动装配。但是当我达到需要使用这两个属性的时候,它们都是null!
那么,我可以在何时何地访问 Bean 有什么限制?
顺便说一句,我的 @Configuration 类也由 @ComponentScan({"my.base.package"}) 注释,这是我指定类的父包,包括 @Autowired 属性。
[更新]
我想我找到了问题所在,但我正在努力寻找解决方案。
具有@Autowired 属性的类被实例化为 Bean 本身。我认为这个迟到的 Bean 在它所依赖的其他 Bean 之前被实例化,因此它们还不可用。无论如何我可以指定被实例化的 Bean 的顺序吗?
[附注]
任何将此问题标记为“离题,因为:这个问题似乎与编程无关”的人太有趣了:)
[更新]
仅当@Autowired 属性为空时的示例。
这些是我的配置类:
@Configuration
@PropertySource("/config.properties")
@ComponentScan({"my.package"})
public class AppConfig implements ApplicationContextAware
{
private ApplicationContext appContext;
@Autowired
private Environment env;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
this.appContext = applicationContext;
}
@Bean
public RedissonClient getRedisson()
{
//Code ommited: returning a redisson connection.
}
}
@Configuration
@ComponentScan({"my.pacakge"})
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration
{
@Bean
public AclEngine getAclEngine()
{
return new AclEngine();
}
@Autowired
private RedissonClient redisson;
@Bean
@Scope(value = "request")
public User getCurrentUser()
{
//Code ommited: retrieving the user from Redisson and returning it.
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception
{
auth.authenticationProvider(authenticator());
}
@Bean
public AuthenticationProvider authenticator()
{
return new AclAuthenticationProvider();
}
@Bean
HttpSessionSecurityContextRepository getHttpSessionSecurityContextRepository()
{
HttpSessionSecurityContextRepository x = new HttpSessionSecurityContextRepository();
x.setAllowSessionCreation(false);
return x;
}
@Bean
SecurityContextPersistenceFilter getSecurityContextPersistenceFilter()
{
return new SecurityContextPersistenceFilter(getHttpSessionSecurityContextRepository());
}
@Override
protected AccessDecisionManager accessDecisionManager()
{
try {
AffirmativeBased ab = (AffirmativeBased) super.accessDecisionManager();
List<AccessDecisionVoter<? extends Object>> advs = ab.getDecisionVoters();
ResourceBasedPreInvocationAdvice expressionAdvice = new ResourceBasedPreInvocationAdvice();
List<AccessDecisionVoter<? extends Object>> toBeRemoved = new ArrayList<>();
for (AccessDecisionVoter<? extends Object> adv : advs) {
if (adv instanceof PreInvocationAuthorizationAdviceVoter) {
toBeRemoved.add(adv);
}
}
for (AccessDecisionVoter<? extends Object> adv : toBeRemoved) {
advs.remove(adv);
}
advs.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
return ab;
}
catch (ClassCastException ex) {
ArrayList decisionVoters = new ArrayList();
ResourceBasedPreInvocationAdvice expressionAdvice = new ResourceBasedPreInvocationAdvice();
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
return new AffirmativeBased(decisionVoters);
}
}
public class AclAuthenticationProvider implements AuthenticationProvider
{
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
return null;
}
@Override
public boolean supports(Class<?> authentication)
{
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer
{
public SessionInitializer()
{
super(SecurityConfig.class);
}
}
}
最后我面临的问题是:
public class ResourceBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice
{
@Autowired
private User currentUser;
@Autowired
private AclEngine aclEngine;
@Override
public boolean before(Authentication authentication, MethodInvocation methodInvocation, PreInvocationAttribute preInvocationAttribute)
{
//Where I want to access currentUser and aclEngine but they are null.
//I can trace the code to this point without any Exception thrown!
}
}
【问题讨论】:
-
能否分享一些重现该问题的代码示例?
-
如果你使用过
@Autowired,它们不能为空,如果它们为空,你会得到一个异常。显示您正在使用的配置。此外,在使用 Spring Boot 时,您只需执行SecurityContextHolder.getContext().getAuthentication()即可获取当前用户。 -
不,它们不能为空...自动连接支持会检查这一点,如果它不能满足依赖关系,它将在启动时抛出异常,从而停止应用程序的引导。因此,请求的是您的实际配置,而不是您期望工作的配置描述。
-
相信我,我知道 Spring 在写书和作为定期撰稿人时的工作原理。但是,如果您想质疑我对 Spring 的了解,请成为我的客人。您似乎不想改进您的问题并添加所需的信息,所以我支持这个问题。如果您的 bean 是按照您所说的那样构建的,那么依赖项不能为空,因为这违反了 Spring 中自动连接的工作方式,如果无法满足依赖项,则规则很简单,您的应用程序将会崩溃。
-
我要最后一次告诉你,
ResourceBasedPreInvocationAdvice的实例不是 Spring 托管的 bean。您正在该方法中创建一个实例(正如我之前提到的)。该实例对 spring 是不可见的,永远不会被处理,也永远不会自动连接。正如我之前解释的那样,spring 只会将 bean 注入到 spring 托管实例中。简而言之,为ResourceBasedPreInvocationAdvice创建一个@Bean方法,而不是在该方法中创建一个新实例。