简体   繁体   中英

Wicket @SpringBean and Spring @Autowired with injection via constructor

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?

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.

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.

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.

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));
}

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM