在Spring中,如果要代理的目标对象的类未实现任何接口,则将创建基于CGLIB的代理。 在Spring 4之前,基于CGLIB的代理类需要默认的构造函数。 这不是CGLIB库的限制,而是Spring本身。 幸运的是,从Spring 4开始,这不再是问题。 基于CGLIB的代理类不再需要默认的构造函数。 这如何影响您的代码? 让我们来看看。
依赖注入的惯用法之一是构造函数注入。 它通常在需要注入的依赖项时使用,并且在启动对象后不得更改。 在本文中,我不会讨论为什么和何时应该使用构造函数依赖项注入。 我假设您在代码中使用了这个习惯用法,或者您考虑使用它。 如果您有兴趣了解更多信息,请参见本文底部的资源部分。
不含豆的施工剂注射
具有以下协作者:
package pl.codeleak.services;import org.springframework.stereotype.Service;@Service
public class Collaborator {public String collaborate() {return "Collaborating";}
}
我们可以通过构造函数轻松注入它:
package pl.codeleak.services;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class SomeService {private final Collaborator collaborator;@Autowiredpublic SomeService(Collaborator collaborator) {this.collaborator = collaborator;}public String businessMethod() {return collaborator.collaborate();}}
您可能会注意到, Collaborator
和Service
都没有接口,但是它们都不是代理候选者。 因此,此代码将在Spring 3和Spring 4上正常运行:
package pl.codeleak.services;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import pl.codeleak.Configuration;import static org.assertj.core.api.Assertions.assertThat;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Configuration.class)
public class WiringTests {@Autowiredprivate SomeService someService;@Autowiredprivate Collaborator collaborator;@Testpublic void hasValidDependencies() {assertThat(someService).isNotNull().isExactlyInstanceOf(SomeService.class);assertThat(collaborator).isNotNull().isExactlyInstanceOf(Collaborator.class);assertThat(someService.businessMethod()).isEqualTo("Collaborating");}
}
代豆施工剂注射
在许多情况下,您的bean需要在运行时用AOP proxy
进行修饰,例如,当您想将声明性事务与@Transactional
注释一起使用时。 为了可视化,我创建了一个方面,将为SomeService
所有方法提供建议。 在定义了以下方面之后, SomeService
将成为代理的候选者:
package pl.codeleak.aspects;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class DummyAspect {@Before("within(pl.codeleak.services.SomeService)")public void before() {// do nothing}}
当我使用Spring 3.2.9重新运行测试时,出现以下异常:
Could not generate CGLIB subclass of class [class pl.codeleak.services.SomeService]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
可以通过为SomeService
提供默认的,无参数的构造函数来简单地解决此问题,但这不是我想要做的-因为我还需要使依赖关系成为非最终的。
另一个解决方案是为SomeService
提供接口。 但是,在很多情况下,您不需要创建接口。
更新到Spring 4可以立即解决该问题。 如文档所述:
基于CGLIB的代理类不再需要默认的构造函数。 通过objenesis库提供支持,该库以内联方式重新打包并作为Spring框架的一部分分发。 通过这种策略,不再为代理实例调用任何构造函数。
我创建的测试将失败,但可以看到为SomeService
创建了CGLIB代理:
java.lang.AssertionError:
Expecting:<pl.codeleak.services.SomeService@6a84a97d>
to be exactly an instance of:<pl.codeleak.services.SomeService>
but was an instance of:<pl.codeleak.services.SomeService$$EnhancerBySpringCGLIB$$55c3343b>
从测试中删除第一个断言后,它将运行得非常好。
资源资源
- 如果您需要了解有关构造函数依赖注入的更多信息,请查看Petri Kainulainen撰写的这篇出色文章: http ://www.petrikainulainen.net/software-development/design/why-i-changed-my-mind-about 场注入 。
- Spring4中的核心容器改进: http : //docs.spring.io/spring/docs/current/spring-framework-reference/html/new-in-4.0.html#_core_container_improvements
- 您可能也有兴趣阅读有关Spring的其他文章: Spring 4:带有Java 8 Date-Time API的@DateTimeFormat和
在Spring MVC应用程序中使用Bean Validation 1.1获得更好的错误消息
翻译自: https://www.javacodegeeks.com/2014/07/spring-4-cglib-based-proxy-classes-with-no-default-constructor.html