Spring Framework
  1. Spring Framework
  2. SPR-3150

Create CGLIB proxy instances honoring <constructor-arg> tags

    Details

    • Type: Improvement Improvement
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.0.3
    • Fix Version/s: 4.0 RC1
    • Component/s: Core:AOP
    • Labels:
      None
    • Last commented by a User:
      false

      Description

      This problem was encountered with Snapshot build 98 of version 2.0.3.

      The following bean definition of a custom-scoped bean (the class is a backport of JDK 1.5's java.util.concurrent.ArrayBlockingQueue to work on JDK 1.4):

      <bean class="edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue" scope="report">
          <constructor-arg value="3" />
          <aop:scoped-proxy />
      </bean>
      

      Failed with the error below. The class in question has no empty constructor, it needs an integer value, which is specified by <constructor-arg>.

      Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue#55a338': Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Couldn't generate CGLIB subclass of class [class edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
      Caused by: org.springframework.aop.framework.AopConfigException: Couldn't generate CGLIB subclass of class [class edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
      Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
      	at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)
      	at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
      	at net.sf.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
      	at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
      	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
      	at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
      	at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
      	at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:193)
      	at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:107)
      	at org.springframework.aop.scope.ScopedProxyFactoryBean.setBeanFactory(ScopedProxyFactoryBean.java:109)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1074)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:430)
      	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBeanDefinition(BeanDefinitionValueResolver.java:197)
      	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:107)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1017)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:810)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:426)
      	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:252)
      	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:144)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:249)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:163)
      	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:358)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:869)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:782)
      	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:426)
      	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:252)
      	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:144)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:249)
      	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:163)
      	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:280)
      	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:357)
      

      I am using CGLib 2.1.3 (the one that ships with Hibernate 3.2.1)

      This problem does not occur in version 2.0.0

        Issue Links

          Activity

          Hide
          Juergen Hoeller added a comment -

          We do generally not support CGLIB proxies for classes that do not have default constructors, neither for standard AOP proxies nor for scoped proxies. (Well, to be exact, we do internally, but we're not exposing this functionality to applications.) Creating proxy instances with constructor arguments will usually lead to the proxy class internally initializing state, which is very undesirable. In general, only use CGLIB proxy classes that allow for 'stateless' construction.

          In your case, I would strongly recommend to use

          <aop:scoped-proxy proxy-target-class="false"/>

          for such a bean, in particular since ArrayBlockingQueue implements a BlockingQueue interface anyway!

          Juergen

          Show
          Juergen Hoeller added a comment - We do generally not support CGLIB proxies for classes that do not have default constructors, neither for standard AOP proxies nor for scoped proxies. (Well, to be exact, we do internally, but we're not exposing this functionality to applications.) Creating proxy instances with constructor arguments will usually lead to the proxy class internally initializing state, which is very undesirable. In general, only use CGLIB proxy classes that allow for 'stateless' construction. In your case, I would strongly recommend to use <aop:scoped-proxy proxy-target-class="false"/> for such a bean, in particular since ArrayBlockingQueue implements a BlockingQueue interface anyway! Juergen
          Hide
          Bruno Navert added a comment -

          Point taken. I was forced to use CGLIB proxies due to another bug in 2.0.0, but that bug has since been fixed in 2.0.3 so it's not an issue anymore.
          Thanks for the info.

          Show
          Bruno Navert added a comment - Point taken. I was forced to use CGLIB proxies due to another bug in 2.0.0, but that bug has since been fixed in 2.0.3 so it's not an issue anymore. Thanks for the info.
          Hide
          Kent Tong added a comment -

          Having a constructor that takes arguments does NOT imply that the object is stateful. For example, one singleton bean could refer to another singleton bean using constructor injection.

          On the other hand, even if a default constructor is used, it is possible to be stateful (eg, establishing a DB connection in that constructor).

          With the current behavior, it is impossible to create a CGLIB proxy for it if constructor injection is used. Therefore, I'd suggest change this behavior and give a warning in the documentation that the constructor will be executed twice.

          Show
          Kent Tong added a comment - Having a constructor that takes arguments does NOT imply that the object is stateful. For example, one singleton bean could refer to another singleton bean using constructor injection. On the other hand, even if a default constructor is used, it is possible to be stateful (eg, establishing a DB connection in that constructor). With the current behavior, it is impossible to create a CGLIB proxy for it if constructor injection is used. Therefore, I'd suggest change this behavior and give a warning in the documentation that the constructor will be executed twice.
          Hide
          Tomasz Nurkiewicz added a comment -

          This limitation is even more annoying when working with Scala:

          @Service
          class Foo @Autowired() (bar: Bar) {
          
              def buzz() = bar.buzz()
          
          }

          Here I can inject dependencies via constructor and not only Scala compiler will automatically create private fields for each injected dependency, but also these fields will be final. However when CGLIB proxy is used I am forced to add dummy c-tor and keep it in sync with the primary one:

          def this() {this(null)}
          Show
          Tomasz Nurkiewicz added a comment - This limitation is even more annoying when working with Scala: @Service class Foo @Autowired() (bar: Bar) { def buzz() = bar.buzz() } Here I can inject dependencies via constructor and not only Scala compiler will automatically create private fields for each injected dependency, but also these fields will be final. However when CGLIB proxy is used I am forced to add dummy c-tor and keep it in sync with the primary one: def this () { this ( null )}
          Hide
          Oleg Alexeyev added a comment -

          It basically means that constructor injection doesn't work if AOP is enabled and an object doesn't implement interfaces. Or do I miss something? This is now exactly the problem I faced with. And now have to get back to setter injection or create an interface, which don't really need.

          Show
          Oleg Alexeyev added a comment - It basically means that constructor injection doesn't work if AOP is enabled and an object doesn't implement interfaces. Or do I miss something? This is now exactly the problem I faced with. And now have to get back to setter injection or create an interface, which don't really need.
          Hide
          Ilya Kazakevich added a comment - - edited

          Regular project has a lot of services. Most of them have final fields with pointers to other services. All of them are created using [constructor-arg] in xml.

          Now I need to wrap them to implement logging and I do not want to use AspectJ weaving.
          Why can't you support constructor parameters injection in Spring-AOP?

          That works perfectly in CGLIB, so nothing prevents you from doing it. You only need to pass arguments to CGLIB.

          BTW, you do have "Cglib2AopProxy#setConstructorArguments", why not to use it?

          Show
          Ilya Kazakevich added a comment - - edited Regular project has a lot of services. Most of them have final fields with pointers to other services. All of them are created using [constructor-arg] in xml. Now I need to wrap them to implement logging and I do not want to use AspectJ weaving. Why can't you support constructor parameters injection in Spring-AOP? That works perfectly in CGLIB, so nothing prevents you from doing it. You only need to pass arguments to CGLIB. BTW, you do have "Cglib2AopProxy#setConstructorArguments", why not to use it?
          Hide
          Przemysław Pokrywka added a comment -

          Kent, Tomasz, Oleg and Ilya had a valid point. Even parameterless constructors can have side-effects, so clear warning in the docs would be useful. And with the warning in place there's no longer need to annoy users of constructor-based injection.

          Spring was meant to be agnostic about setter/constructor injection, wasn't it?

          Show
          Przemysław Pokrywka added a comment - Kent, Tomasz, Oleg and Ilya had a valid point. Even parameterless constructors can have side-effects, so clear warning in the docs would be useful. And with the warning in place there's no longer need to annoy users of constructor-based injection. Spring was meant to be agnostic about setter/constructor injection, wasn't it?
          Hide
          Andrzej Winnicki added a comment -

          Przemysław is right.

          Constructor injection is becoming more and more popular. We need either clear warning in the docs: you can't use constructor-injection with CGLIB and AOP, or have this issue fixed.

          Show
          Andrzej Winnicki added a comment - Przemysław is right. Constructor injection is becoming more and more popular. We need either clear warning in the docs: you can't use constructor-injection with CGLIB and AOP, or have this issue fixed.
          Hide
          Juergen Hoeller added a comment -

          Let's consider this fixed as of 4.0 RC1, since SPR-10594 introduced an ObjenesisCglibAopProxy that bypasses constructor invocation for a CGLIB proxy instance altogether, avoiding any kind of side effect from constructor invocation (even in case of a default constructor). This is used by default with an embedded version of Objenesis in Spring Framework 4.0 now.

          Juergen

          Show
          Juergen Hoeller added a comment - Let's consider this fixed as of 4.0 RC1, since SPR-10594 introduced an ObjenesisCglibAopProxy that bypasses constructor invocation for a CGLIB proxy instance altogether, avoiding any kind of side effect from constructor invocation (even in case of a default constructor). This is used by default with an embedded version of Objenesis in Spring Framework 4.0 now. Juergen

            People

            • Assignee:
              Juergen Hoeller
              Reporter:
              Bruno Navert
              Last updater:
              Juergen Hoeller
            • Votes:
              15 Vote for this issue
              Watchers:
              19 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                17 weeks, 4 days ago