【问题标题】:Request scoped field in a Singleton bean单例 bean 中的请求范围字段
【发布时间】:2013-07-03 18:12:28
【问题描述】:

我知道可以在 Spring 中将请求范围的 bean 注入到单例 bean 中,所以我知道我正在尝试做的事情会起作用,我只是想知道是否有一种方法可以在没有这么多的情况下更简洁地表达它额外不必要的类定义。我是 Spring 注释的新手,所以也许有一个我不知道的注释。

我有一个抽象类,它将在我的应用程序中作为不同的单例 spring bean 扩展 100 次。以这个类定义为例:

/** The abstract class with a field that needs to be request-specific **/
public abstract class AbstractSingletonBean {

    private SampleState state;
    public SampleState getState() { return state; }
    public void setState(SampleState state) { this.state = state; }

    // Other fields that are just singleton here
}

还有一个 bean 定义可能是什么样子的示例:

@Component
public class SampleSingletonBean extends AbstractSingletonBean {

    @Resource(name="sampleState")
    public void setState(SampleState state) { super.setState(state); }
}

现在我们当然需要一个名为sampleState 的bean。所以我必须创建两个类:一个基类来定义SampleState 中的字段,然后是一个请求范围的bean 定义。这是因为AbstractSingletonBean 的每个扩展都需要它自己的请求范围的状态字段实例。

这可能是基类:

public class SampleState {
    private String fieldOne;
    public String getFieldOne() { return fieldOne }
    public void setFieldOne() { this.fieldOne = fieldOne }
}

这是这个愚蠢的 bean 定义:

@Component ("sampleState")
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SampleStateBean extends SampleState {}

困扰我的是,如果我有 100 个 AbstractSingletonBean 的扩展名,我将需要 100 个 SampleStateBean 的扩展名,只需要样板代码即可使其成为请求范围。有没有办法在AbstractSingletonBean 的扩展中覆盖setState() 并向spring 指示它应该动态创建一个新的请求范围bean 并将其注入这里?所以我的SampleSingletonBean 可能是这样的:

@Component
public class SampleSingletonBean extends AbstractSingletonBean {

    @Resource
    @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public void setState(SampleState state) { super.setState(state); }
}

当然这不起作用,因为@Resource 需要引用一个已经存在的bean。是否有另一个注释可以在不为每个 SampleState bean 创建新类的情况下完成此操作?

【问题讨论】:

    标签: java spring annotations singleton


    【解决方案1】:

    Spring 也可以注入抽象类。因此,如果每个 AbstractSingletonBean 后代只需要一个 SampleState(如您的示例中所示),您可以将 SampleState 的注入移动到抽象类。

    【讨论】:

    • 是的,但是每个 AbstractSingletonBean 实例都需要它自己的 SampleState 实例,而不是到处引用同一个 SampleState 请求范围 bean。
    • 对于请求范围的 bean,将为每个请求创建并注入一个不同的 SampleState bean 实例。
    • 对于每个请求和每个 AbstractSingletonBean 实例,我都需要一个不同的 SampleState 实例。因此,在一个请求中,如果我正在使用从 AbstractSingletonBean 扩展的 10 个 bean,则该请求将需要 10 个不同的 SampleState 实例。
    【解决方案2】:

    看起来这不是开箱即用的,所以我创建了一个名为 @AnonymousRequest 的注释,我将它放在我想要的字段上,并使用 BeanDefinitionRegistryPostProcessor 来完成创建 bean 的工作。基本上是这样的:

    for each bean in the BeanFactory
      if bean class has AnonymousRequest annotation
        create request scoped bean from field class
        create singleton bean to be request scoped bean wrapper
        set the annotated property value to the singleton wrapper
    

    这需要大量工作才能弄清楚 Spring 如何注册请求范围的 bean。您创建所需的 bean 定义作为请求范围的 bean。然后,您创建一个 RootBeanDefinition 类型的单例 bean,它充当请求范围 bean 的包装器,并在包装​​器上设置一个名为“targetBeanName”的属性,将其命名为您命名的请求范围 bean(“scopedTarget”+按照约定的单例 bean 名称) )。

    因此,真正了解这些东西的人可能会改进这一点,但这是我想出的:

      public void createRequestBeanFromSetterMethod(String containingBeanName, BeanDefinition containingBean, Method method, BeanDefinitionRegistry registry)
      {
        String fieldName = ReflectionUtil.getFieldNameFromSetter(method.getName());
        String singletonBeanName = containingBeanName+"_"+fieldName;
        String requestBeanName = "scopedTarget."+singletonBeanName;
    
        BeanDefinition requestBean = createAnonymousRequestBean(ReflectionUtil.getFieldTypeFromSetter(method), containingBean);
    
        RootBeanDefinition singletonBean = new RootBeanDefinition();
        singletonBean.setBeanClass(ScopedProxyFactoryBean.class);
        singletonBean.getPropertyValues().addPropertyValue("targetBeanName", requestBeanName);
    
        registry.registerBeanDefinition(singletonBeanName, singletonBean);
        registry.registerBeanDefinition(requestBeanName, requestBean);
    
        beanDefinition.getPropertyValues().addPropertyValue(fieldName, new RuntimeBeanReference(singletonBeanName));
    
      }
    
      private BeanDefinition createAnonymousRequestBean(Class<?> beanType, BeanDefinition parentBean)
      {
        BeanDefinition newBean = null;
        if (parentBean != null)
        {
          newBean = new GenericBeanDefinition(parentBean);
        }
        else
        {
          newBean = new GenericBeanDefinition();
        }
    
        if (beanType != null)
        {
          newBean.setBeanClassName(beanType.getName());
        }
    
        newBean.setScope("request");
        newBean.setAutowireCandidate(false);
    
        // This would have come from the Proxy annotation...could add support for different values
        String proxyValue = "org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass";
        BeanMetadataAttribute attr = new BeanMetadataAttribute(proxyValue, true);
        newBean.setAttribute(proxyValue, attr);
    
        return newBean;
      }
    

    它似乎工作!我现在有效地在上下文初始化之前创建了一个请求范围的 bean,该 bean 本地化到这个包含 bean。它或多或少是一个请求范围的属性。

    【讨论】:

      【解决方案3】:

      您可以尝试定义单个 SampleState 请求范围 bean,然后使用 spring 的查找方法将这个 bean 注入到任何您想要的位置。这对于原型范围 bean 来说效果很好。手指交叉它也适用于请求范围。

      AFAIK,目前还没有对查找方法的注释支持,所以要么使用它的 xml vis-a-vis 或者看看 javax.inject.Provider relevant question here

      【讨论】:

      • 我确信每次注入最终都会引用同一个 bean。原型 bean 就是这样工作的,因为根据定义,每次注入都是一个新的引用。
      猜你喜欢
      • 2017-01-22
      • 1970-01-01
      • 1970-01-01
      • 2013-01-21
      • 1970-01-01
      • 2011-11-11
      • 2012-05-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多