Wicket @SpringBean 和 Spring @Autowired 通过构造函数注入

Wicket @SpringBean and Spring @Autowired with injection via constructor

我有一个 Wicket 面板,我想在其中使用 @SpringBean 注入 bean

public class SomePanel extends Panel {

  @SpringBean
  private BlogSummaryMailGenerator blogSummaryMailGenerator;

}

但是这个 BlogSummaryMailGenerator 通过如下定义的构造函数注入:

@Component
public class BlogSummaryMailGenerator {

  private BlogRepository blogRepository;
  private BlogPostRepository blogPostRepository;

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

并且在实例化 SomePanel 时出现异常

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]

向 BlogSummaryMailGenerator 添加空的无参数构造函数解决了这个问题,但是添加这样的代码只是为了使注入工作是错误的,我想避免它。

关于如何通过构造函数使用注入使 @SpringBean 与 bean 一起工作有什么建议吗?

错误信息很清楚。试图用 CGLIB 库为 BlogSummaryMailGenerator 创建代理 class 的实例真是太糟糕了。由于您没有(或不能)向构造函数提供参数,因此它会寻找不带参数的构造函数。但它不能,你会得到错误。

只需将构造函数注入替换为 属性 注入,并创建无参数构造函数:

@Component
public class BlogSummaryMailGenerator {

  @Autowired
  private BlogRepository blogRepository;

  @Autowired
  private BlogPostRepository blogPostRepository;

  public BlogSummaryMailGenerator() {}

}

实际上,您不需要声明一个空的构造函数。我这样做只是为了清楚起见。请注意,BlogRepositoryBlogPostRepository 应声明为 beans(用 @Component 注释标记,或在 Spring 配置中创建为 @Bean)。

更新:

当您在WebApplication.init()中添加SpringComponentInjector时,您可以将第三个参数指定为false,即'wrapInProxies'。 Wicket 永远不会将 Spring beans 包装在 porxy 中,您可以使用 @Autowired 作为构造函数。

@Override
public void init()
{

    super.init();

    AnnotationConfigApplicationContext springContext = 
         new AnnotationConfigApplicationContext();
    springContext.register(SpringConfig.class);
    springContext.refresh();
    getComponentInstantiationListeners().add(new SpringComponentInjector(this, 
         springContext, false));
}

真正的问题出在 CGLIB 中。它需要一个默认构造函数才能创建代理实例。真正的Spring bean是由Spring单独创建的,没有这样的限制。据我所知,CGLIB 所需的默认构造函数甚至可以是 private

更新:自 Wicket 9.5.0 以来,Wicket 也可以使用 ByteBuddy 而不是 CGLib。

另一种解决方案是为此 bean 使用接口。然后 Wicket 将使用 JDK Proxy 而不是 CGLIB,在这种情况下,实现中不需要默认构造函数。

解决方案

为了能够在 Wicket 组件中利用 @SpringBean 的构造函数注入,您只需将 Objenesis 添加到编译时依赖项中。

说明

Objenesis 是一个鲜为人知的字节码操作库,它(与 CGLIB 一起提供相反Wicket) 能够为没有默认(无参数)构造函数的 class 创建代理对象。如果您将它作为编译依赖项添加到您的项目中,那么 Wicket 将切换它的内部延迟初始化代理创建逻辑以利用 Objenesis(而不是 CGLIB 在实例化代理时不需要 args 构造函数)。不幸的是,此功能未记录在案,但我可以确认它适用于我的情况。

解决这个问题的正确方法不是将 Objenesis 添加到您的项目中,而是注入接口而不是具体实现,正如@martin-g 已经解释的那样(当然,我们并不总是有特权做正确的事,但当我们做正确的事时,我们应该这样做)。

我有一个项目在库更新后开始给出完全相同的错误和堆栈我仍然不完全理解(完整的 Maven 依赖地狱,我继承了它,对我放轻松)。原因是我正在从 ListModel<MyClass> 的具体子 class 创建一个 Spring 请求范围的 bean,而 Wicket 一心想将 class 包装到一个延迟加载的代理中,它不能这样做,因为没有零参数构造函数。

我通过更改配置 class 来创建 IModel<List<MyClass>> 的命名实例并使用该名称定义注入的依赖项来修复它。

配置中class:

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

在组件中:

@Inject
@Named("namedModel")
private IModel<List<MyClass>> myModel;