【问题标题】:Spring Java config, @Autowire vs. Constructor Injection, @Transactional and CGLIBSpring Java 配置、@Autowire 与构造函数注入、@Transactional 和 CGLIB
【发布时间】:2014-10-30 05:21:09
【问题描述】:

我们一直在使用 @Autowired 加上基于 Java 的 Spring 配置并取得了一些成功,但现在,我们正在失去控制。每个人都开始到处添加自动装配的依赖项,造成循环和奇怪的错误。

所以我们正在考虑使用构造函数注入和 Spring 配置的自动装配。

旧:

class Bean {
   @Autowired Foo foo;
}

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

新:

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

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

这很好用(它驱使人们拆分 bean,而不是使用 10 多个构造函数参数创建怪物)。

但是当Bean 有一个用@Transactional 注释的方法时它会失败,因为CGLIB 然后尝试创建一个代理失败,因为它找不到无参数的构造函数。

解决办法是什么?

【问题讨论】:

  • 我只会重构 @Transactional 类以提供具有业务逻辑的接口 - 并注入这些业务接口,而不需要使用 cglib。
  • 使用与类相同的方法创建接口,例如void saveSomething(Something a),并将该接口注入客户端 bean,而不是注入类实例。原始类(注册为 Spring bean)将实现该接口,但 客户端 将通过接口引用它。附言将@Transactional 留在方法的实现上,不要将其移至接口。
  • 当使用接口时,它会导致使用 JDK 动态代理,它可以由任意 bean 创建。您可能还想尝试升级 spring 版本(到 4.0 或 4.1),因为我记得该区域的一些修复应该允许 cglib 在这些情况下工作。但是我仍然更喜欢界面,但这只是我。还有一个关于构造函数注入的注释,它也可以与@Autowired一起使用,只需用@Autowired而不是字段来注释你的构造函数。同样对于 cglib 来说,拥有一个受保护的构造函数就足够了,它只需要它来进行子类化。
  • 这两种说法都来自Java语言的限制。 CGLIB 使用继承来生成代理类,但是在创建子类实例时不能绕过调用“super()”构造方法。一些语言缓解了这个问题,例如在 Google Dart 中,类会自动定义接口。另一个问题是注解不是inherited from interfaces,所以接口上的@Transactional 并不总是有效。所以这些问题只是 Java/JVM 的陷阱。
  • 基本上,是的。但如果“任何东西”实际上是一个 java 接口,那么 Spring 框架根本不会使用 cglib,而是使用标准的JDK API。还要记住,cglib 代理类继承了所有字段,但它们从不使用,因为代理方法调用会将工作委托给代理后面的原始 bean(并且您必须注意所有公共方法都不是最终的),所以java 接口的方法更简洁一些。

标签: spring autowired transactional proxy-classes cglib


【解决方案1】:

你有几个可能的解决方案

  1. 在您的类中引入接口
  2. 将 Spring 版本至少升级到 4.0
  3. 添加protected无参数构造函数

介绍接口

在为您的类引入接口时,您可以放弃使用 CgLib。然后 Spring 将能够使用围绕接口工作的 JDK 动态代理。它围绕已经存在的 bean 实例创建一个代理,并且该代理实现了它所包装的类的所有接口。这样,你的类是否有无参数构造函数都没有关系。

升级到 Spring 4

在 Spring 4.0 中添加了支持以允许代理缺少无参数构造函数的类(请参阅 SPR-10594)。要启用此升级您的 Spring 版本并将Objenesis 添加到您的类路径,Spring 4 带有自己重新打包的 cglib 版本,因此不再需要它。

需要注意的一点是,如果在构造函数中进行空检查或初始化逻辑,则应该有一个没有逻辑的构造函数,在 cglib 创建实例的情况下它可能会失败。我怀疑它将 null 传递给所有构造函数参数(或原语的一些默认值)。

添加protected无参数构造函数

Cglib 需要能够创建一个用于包装实际类的实例。在你的类中有一个 protected 构造函数就足够了,这样 cglib 就可以调用它了。

【讨论】:

  • 最后一点:所以如果代理中的字段全部为空,CGLIB 不会失败,因为代理将始终委托给真正的 bean?
  • 是的,这就是代理的全部意义所在。它拦截方法调用,执行拦截器,最后调用实际 bean 上的实际方法。如果您的构造函数中有逻辑(空检查、初始化逻辑等),它可能会失败。
  • 我认为 objenesis 允许绕过构造函数代码,但我不确定 Spring 是否使用这种行为。 objenesis.org/tutorial.html
  • 如果对象在类路径上,Spring 会使用它。阅读答案中链接的问题。
猜你喜欢
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 2018-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多