5

I have a Wicket panel in which I want to inject bean using @SpringBean

public class SomePanel extends Panel {

  @SpringBean
  private BlogSummaryMailGenerator blogSummaryMailGenerator;

}

But this BlogSummaryMailGenerator has injection via constructor defined like this:

@Component
public class BlogSummaryMailGenerator {

  private BlogRepository blogRepository;
  private BlogPostRepository blogPostRepository;

  @Autowired
  public BlogSummaryMailGenerator(BlogRepository blogRepository,
                                BlogPostRepository blogPostRepository) {
    this.blogRepository = blogRepository;
    this.blogPostRepository = blogPostRepository;
  }
}

And when SomePanel is instantiated I am getting an exception

Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721) ~[cglib-3.1.jar:na]
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499) ~[cglib-3.1.jar:na]
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[cglib-3.1.jar:na]
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) ~[cglib-3.1.jar:na]
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) ~[cglib-3.1.jar:na]
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285) ~[cglib-3.1.jar:na]
at org.apache.wicket.proxy.LazyInitProxyFactory.createProxy(LazyInitProxyFactory.java:191) ~[wicket-ioc-7.2.0.jar:7.2.0]

Adding empty no-args constructor to the BlogSummaryMailGenerator solves this issue but adding such code only to make injection work is wrong and I would like to avoid it.

Any suggestions how to make @SpringBean work with beans using injection via constructor?

Tomasz Dziurko
  • 1,668
  • 2
  • 17
  • 21

4 Answers4

8

The real problem is in CGLIB. It requires a default constructor to be able to create the proxy instance. The real Spring bean is created separately by Spring and has no such restrictions. The default constructor needed by CGLIB could be even private as far as I remember.

Update: Since Wicket 9.5.0 Wicket could also use ByteBuddy instead of CGLib.

Another solution is to use an interface for this bean. Then Wicket will use JDK Proxy instead of CGLIB and in this case there is no need of default constructor in the implementation.

martin-g
  • 17,243
  • 2
  • 23
  • 35
6

Solution

To be able to take advantage of constructor injection for @SpringBean in Wicket components you just have to add Objenesis to your compile time dependencies.

Explanation

Objenesis is a little and less known byte code manipulation library which (in opposite to CGLIB provided together with Wicket) is able to create a proxy object for a class which has no default (no args) constructor. If you add it as a compile dependency to your project then Wicket will switch it's internal lazily initializable proxy creation logic to take advantage of Objenesis (instead CGLIB which requires no args constructor) while instantiating a proxy. Unfortunately this feature is not documented but I can confirm it works in my case.

Marcin Kłopotek
  • 5,271
  • 4
  • 31
  • 51
  • You can find why adding _Objenesis_ to the project classpath solves the problem by inspecting [`LazyInitProxyFactory`](https://github.com/apache/wicket/blob/038ea517c5f1ab4428072ce3a5eab5bc1c173e6f/wicket-ioc/src/main/java/org/apache/wicket/proxy/LazyInitProxyFactory.java#L171) source of the [Apache Wicket](https://github.com/apache/wicket) project on GitHub. – Marcin Kłopotek Apr 05 '16 at 10:06
  • Hello @Marcin, I need your help too in fixing the `java.lang.NoClassDefFoundError: Could not initialize class net.sf.cglib.proxy.Enhancer`. I added Objenesis and the variable is returning `true` as well, however `&& !hasNoArgConstructor(type)` is returning `false` because of which `cglib` is used again for `type` `SavedRequestAwareAuthenticationSuccessHandler`. Question link - https://stackoverflow.com/q/67113694/819866 chat link - https://chat.stackoverflow.com/transcript/message/52009909#52009909 – Sunil Kumar Apr 17 '21 at 16:58
0

The error message is pretty clear. Wicked trying to create instance of proxy class for BlogSummaryMailGenerator with CGLIB library. Since you didn't (or you can't) provide arguments to constructor, it looking for contstructor with no arguments. But it can't, and you get error.

Just replace constructor injection with property injection, and create no argument constructor:

@Component
public class BlogSummaryMailGenerator {

  @Autowired
  private BlogRepository blogRepository;

  @Autowired
  private BlogPostRepository blogPostRepository;

  public BlogSummaryMailGenerator() {}

}

Actually, you do not need to declare an empty constructor. I did it just for clarity. Note, that BlogRepository and BlogPostRepository should be declared as beans (marked with @Component annotation, or created as @Bean in Spring configuration).

UPDATE:

When you add SpringComponentInjector in your WebApplication.init(), you can specify false for third paramter, which means 'wrapInProxies'. Wicket will never wrap Spring beans in porxy, and you can use @Autowired for constructors.

@Override
public void init()
{

    super.init();

    AnnotationConfigApplicationContext springContext = 
         new AnnotationConfigApplicationContext();
    springContext.register(SpringConfig.class);
    springContext.refresh();
    getComponentInstantiationListeners().add(new SpringComponentInjector(this, 
         springContext, false));
}
Ken Bekov
  • 13,696
  • 3
  • 36
  • 44
  • But property injection is bad for testability and I want to avoid using such hacks just to use more sophisticated beans with Wicket. I hope that maybe there's a solution to use contructor based injection somehow. – Tomasz Dziurko Feb 08 '16 at 07:20
  • You have to understand, that this is not hack at all. In Spring world this way is one of three usual ways to inject data to bean. – Ken Bekov Feb 08 '16 at 07:47
  • Yes, but changing my approach to DI to worse one only to use SpringBean is not a good solution in my opinion. That is why I am looking for something better. But as @martin-g points, it can be hard to overcome. – Tomasz Dziurko Feb 08 '16 at 08:57
  • Turning of `wrapInProxies` will have severe consequences especially in clustered environments. It will force _Wicket_ components to be serialized along with its dependency graph which in best case scenario results in excessive memory usage and in worse case will broke the app due to serialization exceptions at runtime. – Marcin Kłopotek Mar 31 '16 at 05:30
  • 1
    @KenBekov I can agree that field injection is one of three ways to inject data to bean but it's error prone and not recommended appraoch even by Spring developers, see ["Why field injection is evil"](http://olivergierke.de/2013/11/why-field-injection-is-evil/) by Oliver Gierke, one of the Spring developers. – Marcin Kłopotek Mar 31 '16 at 05:42
  • @MarcinKłopotek thank you for link. It was useful to know this point of view. – Ken Bekov Mar 31 '16 at 06:16
0

The correct way to solve this is not to add Objenesis to your project, but to inject interfaces instead of concrete implementations, as @martin-g already explained (of course, we do not always have the privilege to be able to do the right thing, but when we do, we should do it).

I have a project that started to give the exact same error and stack after a library update I still don't exactly understand (complete Maven dependency hell, I inherited it, go easy on me). The reason was that I was creating a Spring request-scoped bean from a concrete subclass of ListModel<MyClass> and Wicket was hell bent on wrapping that class into a lazy loaded proxy, which it couldn't do because there was no zero-args-constructor.

I fixed it by changing the configuration class to create a named instance of IModel<List<MyClass>> and by defining the injected dependency using the name.

In the configuration class:

@Bean(name = "namedModel")
@RequestScope
public IModel<List<MyClass>> myObjectList() {
    return new MyClass(parameters);
}

In the component:

@Inject
@Named("namedModel")
private IModel<List<MyClass>> myModel;
Torben
  • 3,805
  • 26
  • 31