介绍
在运行静态代码分析工具或从IDE检查/分析代码时,您可能会遇到有关@Autowired
字段的以下警告:
不建议现场注入
这篇文章展示了Spring中可用的不同类型的注入,以及推荐使用每种注入的模式。
注射类型
尽管当前有关Spring Framework (5.0.3)的文档仅定义了两种主要的注入类型,但实际上有三种:
- 基于构造函数的依赖注入
- 基于Setter的依赖注入
- 基于字段的依赖注入
后者是静态代码分析工具所抱怨的一种,但是它被定期且广泛地使用。
您甚至可以在一些Spring指南中看到这种注入方法,尽管在文档中不建议这样做:
基于构造函数的依赖注入
在基于构造函数的依赖关系注入中,类构造函数使用注释,@Autowired
并随要注入的对象包含可变数量的参数。
1
2
3
4
5
6
7
8
9
10
11
|
@Component
public class ConstructorBasedInjection {
private final InjectedBean injectedBean;
@Autowired
public ConstructorBasedInjection(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
|
基于构造函数的注入的主要优点是,您可以将注入的字段声明为final,因为它们将在类实例化期间启动。这对于所需的依赖关系很方便。
基于Setter的依赖注入
在基于setter的依赖项注入中,setter方法用注释@Autowired
。一旦使用无参数构造函数或无参数静态工厂方法实例化Bean,Spring容器就会调用这些setter方法,以注入Bean的依赖关系。
1
2
3
4
5
6
7
8
9
10
11
|
@Component
public class ConstructorBasedInjection {
private InjectedBean injectedBean;
@Autowired
public void setInjectedBean(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
|
基于字段的依赖注入
在基于字段的依赖项注入中,字段/属性用注释@Autowired
。类实例化后,Spring容器将设置这些字段。
1
2
3
4
5
6
7
|
@Component
public class ConstructorBasedInjection {
@Autowired
private InjectedBean injectedBean;
}
|
如您所见,这是注入依赖项的最干净的方法,因为它避免了添加样板代码,并且无需为该类声明构造函数。代码看起来不错,简洁,简洁,但是正如代码检查员已经向我们暗示的那样,这种方法存在一些缺点。
基于字段的依赖注入缺点
禁止使用不可变的字段声明
基于字段的依赖项注入不适用于声明为final / immutable的字段,因为必须在类实例化时实例化此字段。声明不可变依赖性的唯一方法是使用基于构造函数的依赖性注入。
减轻违反单一责任原则的情况
如您所知,在面向对象的计算机编程中,SOLID的首字母缩写定义了五个设计原则,这些原则将使您的代码易于理解,灵活和可维护。
SOLID中的S代表单一责任原则,这意味着类仅应负责软件应用程序功能的单个部分,并且其所有服务应与该责任狭义地保持一致。
使用基于字段的依赖项注入,在类中拥有很多依赖项确实很容易,而且一切看起来都很好。如果改用基于构造函数的依赖注入,则随着更多的依赖关系添加到您的类中,构造函数变得越来越大,并且代码开始散发出臭味,发出明确的信号表明有问题。
拥有超过十个参数的构造函数是一个明显的信号,表明该类有太多的协作者,这也许是个很好的时机,可以开始将类拆分为更小和更可维护的部分。
因此,尽管现场注入并不能直接导致违反单一责任原则,但可以肯定的是,它可以通过隐藏信号来提供足够的帮助,否则这些信号将是很清楚的。
与依赖注入容器紧密耦合
使用基于字段的注入的主要原因是避免获取器和设置器的样板代码或为类创建构造函数。最后,这意味着可以设置这些字段的唯一方法是通过Spring容器实例化该类并使用反射将其注入,否则这些字段将保持为空,并且您的类将被破坏/无用。
依赖项注入设计模式将类依赖关系的创建与类本身分开,从而将此责任传递给类注入器,从而使程序设计可以松散耦合,并遵循单责任和依赖关系反转原则(再次为SOLID)。因此最后,通过再次与类注入器(在本例中为Spring)耦合,使通过自动装配其字段的类实现的解耦丢失,从而使该类在Spring容器之外无效。
这意味着,如果您想在应用程序容器之外使用类,例如用于单元测试,则由于没有其他可能的方法(除了反射)来设置自动装配字段,您不得不使用Spring容器来实例化您的类。
隐藏的依赖
在使用依赖项注入模式时,受影响的类应通过使用构造函数公开必需的依赖项或使用方法(设置程序)公开可选的依赖项,以使用公共接口明确公开这些依赖项。使用基于字段的依赖项注入时,该类将这些依赖项固有地隐藏在外部世界中。
结论
我们已经看到,由于其许多缺点,无论看起来多么优雅,都应尽可能避免进行基于字段的注入。然后推荐的方法是使用基于构造函数和基于setter的依赖项注入。建议将基于构造函数的注入用于所需的依赖项,以使它们成为不可变的并防止它们为null。对于可选的依赖项,建议使用基于Setter的注入。
https://blog.marcnuri.com/field-injection-is-not-recommended/
@Autowired注解注入实现原理
1.解析@Autowired的类AutowiredAnnotationBeanPostProcessor
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware
spring中通过AutowiredAnnotationBeanPostProcessor来解析注入注解为目标注入值。该class实现了InstantiationAwareBeanPostProcessorAdapter,MergedBeanDefinitionPostProcessor接口,重写的方法将在IOC创建bean的时候被调用。
2. 调用AutowiredAnnotationBeanPostProcessor的方法流程
刚才已经提到了是spring创建bean的时候调用,即
当使用DefaultListableBeanFactory来获取想要的bean的时候。如下:
调用的是AbstractBeanFactory中的getBean方法,继续追踪源码最后在AbstractAutowireCapableBeanFactory类中的createBean方法中找到了调用解析@autowired的方法:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
...
...
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); // 调用postProcessMergedBeanDefinition方法
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
... ...
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper); // 调用postProcessAfterInstantiation方法postProcessPropertyValues方法
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
...
...
然后来看AutowiredAnnotationBeanPostProcessor的两个类做了些什么
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if (beanType != null) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); // 从缓存中获取InjectionMetadata,未取到则构造注入的metadata
metadata.checkConfigMembers(beanDefinition);
}
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();
Class<?> targetClass = clazz;
do {
final LinkedList<InjectionMetadata.InjectedElement> currElements =
new LinkedList<InjectionMetadata.InjectedElement>();
// 成员变量
ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
AnnotationAttributes ann = findAutowiredAnnotation(field); // 获取成员变量中带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann); // required属性是false or true
currElements.add(new AutowiredFieldElement(field, required)); // 添加到注入列表
}
}
});
// 方法
ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // 有桥接方法的返回桥接方法的最原始方法,否则返回自身
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod); // 获取方法上带有注入注解(@Autowired,@value,@inject)并加入AnnotationAttributes列表
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) { // 检测合法性
if (logger.isWarnEnabled()) {
logger.warn