3

We've been using @Autowired plus Java-based Spring configuration with some success but now, we're losing control. Everyone is starting to add autowired dependencies everywhere, creating cycles and strange bugs.

So we are considering using constructor injection and autowiring of Spring configurations.

Old:

class Bean {
   @Autowired Foo foo;
}

@Configuration
@Import( FooCfg.class )
class BeanCfg {
   @Bean public Bean bean() { return new Bean(); }
}

New:

class Bean {
   public Bean(Foo foo) {...}
}

@Configuration
class BeanCfg {
   @Autowired FooCfg fooCfg;
   @Bean public Bean bean() { return new Bean(fooCfg.foo()); }
}

This works pretty well (and it drives people to split beans instead of creating monsters with 10+ constructor arguments).

But it fails when Bean has a method annotated with @Transactional since CGLIB then tries to create a proxy which fails since it can't find a no-argument constructor.

What's the solution for this?

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • I would just refactor `@Transactional` classes to provide interfaces with business logic - and inject those business-interfaces instead, without resorting to cglib at all. – Boris Treukhov Sep 05 '14 at 09:49
  • @BorisTreukhov: That comment doesn't make sense to me. How can an interface provide business logic? And how would not resorting to CGLIB make methods work which need transactions? – Aaron Digulla Sep 05 '14 at 09:51
  • 1
    Create interface with the same methods that the class has, say `void saveSomething(Something a)` and inject that interface to the client bean instead of injecting the class instance. The original class(which is registered as Spring bean) will implement that interface, but *the client* will refer to it via interface. P.S. Leave `@Transactional` on the implementation of the method, *do not* move it to interface. – Boris Treukhov Sep 05 '14 at 10:00
  • 1
    When using an interface it leads to using JDK Dynamic proxies which can be created of any arbitrary bean. You also might want to try upgrading the spring version (to 4.0 or 4.1) as I recall some fixes in this area which should allow cglib to work in these situation. However I still prefer interface but that is just me. Also a note on constructor injection that can also work with `@Autowired` just annotate your constructor with `@Autowired` instead of the fields. Also for cglib it is enough to have a protected constructor it just needs it for subclassing. – M. Deinum Sep 05 '14 at 10:05
  • 1
    These both statements come from limitations of Java language. CGLIB uses inheritance to generate the proxy classes, however you can't bypass calling 'super()` constructor methods when creating a subclass instance. Some languages alleviate the probrem, in Google Dart for example classes automatically define interfaces. Another problem is that annotations are not [inherited from interfaces](http://stackoverflow.com/a/5551597/241986), so `@Transactional` on interfaces does not always work. So these problems are just Java/JVM gotchas. – Boris Treukhov Sep 05 '14 at 10:11
  • P.S. Spring 4 cglib somehow manages to bypass the requirement, but I believe that it uses some kind of hack. – Boris Treukhov Sep 05 '14 at 10:14
  • @BorisTreukhov: I think I see where this is going. CGLIB just needs an interface, not an actual implementation. So I can give it basically anything as long as this "anything" can eventually be cast to the real bean. If that "anything" is a mere interface, that means it can't have any dependencies over which CGLIB could stumble while creating the proxy. Is that about right? – Aaron Digulla Sep 05 '14 at 11:53
  • 1
    Basically, yes. But if that "anything" is literally a *java interface* then Spring framework won't use cglib at all, and will use the standard [JDK API](http://stackoverflow.com/questions/10664182/what-is-the-difference-between-jdk-dynamic-proxy-and-cglib). Also keep in mind that cglib proxy class inherits all the fields but they are never used as the proxy method calls will delegate the work to the original bean behind the proxy(and you have to watch that all public methods are not final), so the java interface approach is somewhat more neat. – Boris Treukhov Sep 05 '14 at 12:51

1 Answers1

5

You have a couple of possible of solutions

  1. Introduce interfaces your classes
  2. Upgrade the Spring Version to at least 4.0
  3. Add protected no-arg constructor

Introduce Interfaces

When introducing interfaces for your classes you can drop the usage of CgLib. Spring will then be able to use JDK Dynamic Proxies which work around interfaces. It creates a proxy around an already existing bean instance and that proxy implements all the interfaces of the class it is wrapping. That way it doesn't matter if your class has a no-arg constructor or not.

Upgrade to Spring 4

In Spring 4.0 support was added to allow proxying of classes with a missing no-arg constructor (See SPR-10594). To enable this upgrade your Spring version and add Objenesis to your classpath, Spring 4 comes with its own repacked cglib version so that shouldn't be needed anymore.

One thing to note is that you should have a constructor with no logic if you do null checks or init logic in the constructor it might fail in the case where cglib creates an instance. I would suspect that it pass null to all the constructor arguments (or some default for primitives).

Added protected no-arg constructor

Cglib needs to be able to create an instance which is used to wrap the actual class. It should be enough to have a protected constructor in your classes so that cglib can call it.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • re the last point: So CGLIB doesn't fail if the fields in the proxy are all null since the proxy will always delegate to the real bean? – Aaron Digulla Sep 05 '14 at 11:51
  • 1
    Yes that is the whole point of a proxy. It intercepts the method calls, executes the interceptors and finally calls the actual method on the actual bean. It might fail if you have logic in your constructor (null checks, init logic etc.). – M. Deinum Sep 05 '14 at 11:56
  • I think that objenesis allows to bypass constructor code, but I'm not sure Spring uses this behavior. http://objenesis.org/tutorial.html – Boris Treukhov Sep 05 '14 at 13:06
  • 2
    Spring uses it IF objenesis is on the classpath. Read the issue that is linked in the answer. – M. Deinum Sep 05 '14 at 13:08