Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-5955

Usage of custom scopes breaks functioning of circular references

    Details

    • Type: Improvement
    • Status: Resolved
    • Priority: Major
    • Resolution: Won't Fix
    • Affects Version/s: 3.0 M3
    • Fix Version/s: None
    • Component/s: Core
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      The following construct brings up a BeanCurrentlyInCreationException.

       
      <bean name="A" class="foo.bar.X" scope="session">
        <property name="refToB" ref="B"/>
      </bean>
       
      <bean name="B" class="foo.bar.Y" scope="session">
        <property name="refToA" ref="a"/>
      </bean>

      As the example shows clearly, there are no problematic references via constructor-arg involved and the same construct but with scope="singleton" does actually work. I see no reason why circular references should not be possible for beans that have a custom scope. Therefor this seems clearly a bug.

        Activity

        Hide
        juergen.hoeller Juergen Hoeller added a comment -

        I'm afraid I have to disagree: By design, Spring supports circular references for singleton beans only. That's simply a known limitation.

        We deliberately chose to not support circular references for scoped beans when we introduced them back in Spring 2.0. Personally, if I got to choose again, I would stick with this decision and not even support circular references for singletons beans either. Circular references introduce more problems that they solve, even for singletons. Some party always gets a not-quite-fully initialized reference there. Anyway, let's leave the well-known circular reference discussion aside and focus on potential solutions...

        The underlying reason for the limitation with scoped beans is the Scope SPI. The get operation accepts an ObjectFactory and provides atomicity guarantees, i.e. it never returns a half-initialized object. The scope backend may even build those objects on remote servers or the like, returning a serialized form of it, or it may return a proxy to a cluster-wide shared object. (Custom scopes are used for those kinds of things as well, e.g. with Terracotta as a backend.) In such a scoping scenario, circular dependencies are a no-go to begin with.

        So what can we do about it? We could define a new optional operation for Scope implementations, exposing a reference while the object is still being initialized. That's not quite trivial in the details for several reasons (such as doing it on demand only, as well as dealing with clustering side effects of premature HttpSession.setAttribute calls) but doable in principle, at least for request and session scope. We're going to revisit this for Spring 3.1, next to conversation management which happens to be a related topic anyway.

        Juergen

        Show
        juergen.hoeller Juergen Hoeller added a comment - I'm afraid I have to disagree: By design, Spring supports circular references for singleton beans only . That's simply a known limitation. We deliberately chose to not support circular references for scoped beans when we introduced them back in Spring 2.0. Personally, if I got to choose again, I would stick with this decision and not even support circular references for singletons beans either. Circular references introduce more problems that they solve, even for singletons. Some party always gets a not-quite-fully initialized reference there. Anyway, let's leave the well-known circular reference discussion aside and focus on potential solutions... The underlying reason for the limitation with scoped beans is the Scope SPI. The get operation accepts an ObjectFactory and provides atomicity guarantees, i.e. it never returns a half-initialized object. The scope backend may even build those objects on remote servers or the like, returning a serialized form of it, or it may return a proxy to a cluster-wide shared object. (Custom scopes are used for those kinds of things as well, e.g. with Terracotta as a backend.) In such a scoping scenario, circular dependencies are a no-go to begin with. So what can we do about it? We could define a new optional operation for Scope implementations, exposing a reference while the object is still being initialized. That's not quite trivial in the details for several reasons (such as doing it on demand only, as well as dealing with clustering side effects of premature HttpSession.setAttribute calls) but doable in principle, at least for request and session scope. We're going to revisit this for Spring 3.1, next to conversation management which happens to be a related topic anyway. Juergen
        Hide
        iterator Dirk Scheffler added a comment -

        Thank you for your fast reply and the respect you pay to the issue.

        Actually my sample was kept simple to show the effect. In my project I use an own custom scope but the the effect is the same. So I would really appreciate to have a way to support circular references and also would do extra effort in my custom scope implementation for it. I think there are scopes where it does not make sense to support circular references and there are scopes where it isn't even possible - so it depends on the implementation. This would be great for me.

        Show
        iterator Dirk Scheffler added a comment - Thank you for your fast reply and the respect you pay to the issue. Actually my sample was kept simple to show the effect. In my project I use an own custom scope but the the effect is the same. So I would really appreciate to have a way to support circular references and also would do extra effort in my custom scope implementation for it. I think there are scopes where it does not make sense to support circular references and there are scopes where it isn't even possible - so it depends on the implementation. This would be great for me.
        Hide
        iterator Dirk Scheffler added a comment -

        Dear Juergen,

        I like to ask again if this could be done for spring 3.1.

        It would be very nice if it would be possible to support circular references for all custom scope. It is no problem iI this would mean some extra effort for the custom scope implementor. I have own custom scopes (beside session, request) that I want to work with circular references.

        Probably we can also think together about what can be done to deal with the problem of the imcompleteness of some circular referenced bean. Are you interested in such a communication? But this would be just for the future. For now we should accept this problem, because in GUI environments the circular references come in by adding event handlers that will on react after the system has been built up completely.

        What do you think?

        Show
        iterator Dirk Scheffler added a comment - Dear Juergen, I like to ask again if this could be done for spring 3.1. It would be very nice if it would be possible to support circular references for all custom scope. It is no problem iI this would mean some extra effort for the custom scope implementor. I have own custom scopes (beside session, request) that I want to work with circular references. Probably we can also think together about what can be done to deal with the problem of the imcompleteness of some circular referenced bean. Are you interested in such a communication? But this would be just for the future. For now we should accept this problem, because in GUI environments the circular references come in by adding event handlers that will on react after the system has been built up completely. What do you think?
        Hide
        iterator Dirk Scheffler added a comment -

        A preliminary solution for the problem

        I was able to develop a preliminary solution that works for me and like to share it with the others that think circular references are a natural phenomenon and cannot be left out only because it brings technical problems. This solutions enables circular references for beans of any scope type. Normally circular references are only supported for beans of the normal singleton scope. Please be aware that you will not get fully initialized beans if you use circular references.

        Of course my solution could have problems and I would like to know if you find anything.

        The solution consists of two parts:

        1. An InstantiationAwareBeanPostProcessor implementation (named EarlyReferenceFixBeanPostProcessor) which must be registered for an ApplicationContext in your xml.
        2. An subclassed BeanFactory implementation (I show it here for GenericApplicationContext)

        EarlyReferenceFixBeanPostProcessor Implementation

        public class EarlyReferenceFixBeanPostProcessor 
            extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
         
          private BeanFactory beanFactory;
          
          private static Map<BeanFactory, Map<String, Object>> earlyReferences = new HashMap<BeanFactory, Map<String,Object>>();
         
          @Override
          public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
          }
          
          public static Map<String, Object> getEarlyReferences(BeanFactory beanFactory) {
            Map<String, Object> refs = earlyReferences.get(beanFactory);
            if (refs == null) {
              refs = new HashMap<String, Object>();
              earlyReferences.put(beanFactory, refs);
            }
            return refs;
          }
          
          public static Object getEarlyReference(BeanFactory beanFactory, String beanName) {
            return getEarlyReferences(beanFactory).get(beanName);
          }
          
          protected Map<String, Object> getEarlyReferences() {
            return getEarlyReferences(beanFactory);
          }
          
          @Override
          public boolean postProcessAfterInstantiation(Object bean, String beanName)
              throws BeansException {
            getEarlyReferences().put(beanName, bean);
            return super.postProcessAfterInstantiation(bean, beanName);
          }
          
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName)
              throws BeansException {
            getEarlyReferences().remove(beanName);
            return super.postProcessBeforeInitialization(bean, beanName);
          }
        }

        Registration of the EarlyReferenceFixBeanPostProcessor

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          ">
          
          <bean class="org.springframework.beans.factory.config.EarlyReferenceFixBeanPostProcessor"/>
          
        </beans>

        BeanFactory subclassing when using GenericApplicationContext

        public static void main(String[] args) {
          GenericApplicationContext ctx = new GenericApplicationContext(new DefaultListableBeanFactory(){
            @Override
            protected Object doGetBean(String name, Class requiredType,
                Object[] args, boolean typeCheckOnly)
                throws BeansException {
              String beanName = transformedBeanName(name);
              
              Object bean = EarlyReferenceFixBeanPostProcessor.getEarlyReference(this, beanName);
              if (bean != null)
                return bean;
              else
                return super.doGetBean(name, requiredType, args, typeCheckOnly);
            }
          });
        }

        Show
        iterator Dirk Scheffler added a comment - A preliminary solution for the problem I was able to develop a preliminary solution that works for me and like to share it with the others that think circular references are a natural phenomenon and cannot be left out only because it brings technical problems. This solutions enables circular references for beans of any scope type. Normally circular references are only supported for beans of the normal singleton scope. Please be aware that you will not get fully initialized beans if you use circular references. Of course my solution could have problems and I would like to know if you find anything. The solution consists of two parts: An InstantiationAwareBeanPostProcessor implementation (named EarlyReferenceFixBeanPostProcessor) which must be registered for an ApplicationContext in your xml. An subclassed BeanFactory implementation (I show it here for GenericApplicationContext) EarlyReferenceFixBeanPostProcessor Implementation public class EarlyReferenceFixBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {   private BeanFactory beanFactory; private static Map<BeanFactory, Map<String, Object>> earlyReferences = new HashMap<BeanFactory, Map<String,Object>>();   @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public static Map<String, Object> getEarlyReferences(BeanFactory beanFactory) { Map<String, Object> refs = earlyReferences.get(beanFactory); if (refs == null) { refs = new HashMap<String, Object>(); earlyReferences.put(beanFactory, refs); } return refs; } public static Object getEarlyReference(BeanFactory beanFactory, String beanName) { return getEarlyReferences(beanFactory).get(beanName); } protected Map<String, Object> getEarlyReferences() { return getEarlyReferences(beanFactory); } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { getEarlyReferences().put(beanName, bean); return super.postProcessAfterInstantiation(bean, beanName); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { getEarlyReferences().remove(beanName); return super.postProcessBeforeInitialization(bean, beanName); } } Registration of the EarlyReferenceFixBeanPostProcessor <? xml version = "1.0" encoding = "UTF-8" ?> < beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "> < bean class = "org.springframework.beans.factory.config.EarlyReferenceFixBeanPostProcessor" /> </ beans > BeanFactory subclassing when using GenericApplicationContext public static void main(String[] args) { GenericApplicationContext ctx = new GenericApplicationContext(new DefaultListableBeanFactory(){ @Override protected Object doGetBean(String name, Class requiredType, Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object bean = EarlyReferenceFixBeanPostProcessor.getEarlyReference(this, beanName); if (bean != null) return bean; else return super.doGetBean(name, requiredType, args, typeCheckOnly); } }); }
        Hide
        juergen.hoeller Juergen Hoeller added a comment -

        Closing groups of outdated issues. Please reopen if still relevant.

        Show
        juergen.hoeller Juergen Hoeller added a comment - Closing groups of outdated issues. Please reopen if still relevant.

          People

          • Assignee:
            juergen.hoeller Juergen Hoeller
            Reporter:
            iterator Dirk Scheffler
            Last updater:
            Juergen Hoeller
          • Votes:
            1 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              2 years, 17 weeks, 6 days ago