在Singleton bean中请求范围字段

时间:2013-07-03 18:12:28

标签: java spring annotations singleton

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

我有一个抽象类,在我的应用程序中可能会延长100次作为不同的单例spring bean。以此类定义为例:

/** 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 }
}

这是愚蠢的豆子定义:

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

困扰我的是,如果我有AbstractSingletonBean的100个扩展名,我需要100个SampleStateBean扩展名,只需要样板代码,以使其成为请求范围。有没有办法在setState()的扩展名中覆盖AbstractSingletonBean,并指出它应该动态创建一个新的请求范围的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创建一个新类?

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

它看起来并不是开箱即用的,所以我创建了一个注释,我调用@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。这是一个请求范围的属性,或多或少。