【问题标题】:Limitations on where or when Beans are accessible可访问 Bean 的位置或时间的限制
【发布时间】: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 方法,而不是在该方法中创建一个新实例。

标签: java spring javabeans


【解决方案1】:
@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);
    }
}

Spring 只会将引用注入到它管理的类实例(又名 bean)中。当您在方法中创建 bean 并将它们直接注入其他 bean 时,这些新创建的 bean 是 Spring Managed bean,因此不符合 Spring 的任何自动布线或后处理条件。

代替

ResourceBasedPreInvocationAdvice expressionAdvice = new ResourceBasedPreInvocationAdvice();

您应该将该代码移动到 @Bean 方法,以便它成为 Spring 托管 bean 并注入依赖项。

@Bean
public ResourceBasedPreInvocationAdvice expressionAdvice() {
    return new ResourceBasedPreInvocationAdvice();
}

并且只是引用这个方法而不是创建一个新实例。

ResourceBasedPreInvocationAdvice expressionAdvice = expressionAdvice(); 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多