Abstract类中使用@Autowire
背景
项目使用的就是SpringBoot默认的结构,我看了下,依赖注入使用了最不推荐的字段注入。
字段注入
为了保持项目风格统一,省的有些理论派挑刺,还是延续字段注入的操作。
某个业务场景下,有一个抽象的父类和多个具体的子类,子类中需要用到父类注入的对象。
当即有人就说,这么写:
public abstract class AbstractClass{
@Autowired
protected InjectedBean injectedBean;
}
it works!
但是我们看到为了只让子类使用该对象,我们使用了protected
访问修饰符,但这意味着,子类也可以set该对象一个新的值。基础知识。
哦,改进一下呗
public abstract class AbstractClass{
@Autowired
protected final InjectedBean injectedBean;
}
final加持,完美的解决了问题。很可惜,不可以。
为什么呢?
Having @Autowired and final on a field are contradictory.
The latter says: this variable has one and only one value, and it's initialized at construction time.
The former says: Spring will construct the object, leaving this field as null (its default value). Then Spring will use reflection to initialize this field with a bean of type WorkspaceRepository.
If you want final fields autowired, use constructor injection
简单来说,就是二者赋值的时机不统一造成了互斥。
构造函数注入
那我们使用上面提到的,通constructor injection
注入试试
public abstract class AbstractClass{
private InjectedBean injectedBean;
@Autowired
public void AbstractClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
Spring 不会在抽象类的构造函数上解析 @Autowired 注解。可以通过子类的构造函数注入实现。
public abstract class AbstractClass{
private InjectedBean injectedBean;
public void AbstractClass(InjectedBean injectedBean) {
this.injectedBean = injectedBean;
}
}
public class ChildClass{
private InjectedBean injectedBean;
@Autowired
public void ChildClass(InjectedBean injectedBean) {
super(injectedBean);
}
}
这么写真的有些繁琐了...而且,就我的项目而言,我的初衷是给子类使用,这么岂不是多此一举。
好的,也就引出了Setter注入。
Setter注入
public abstract class AbstractClass{
private LogRepository logRepository;
@Autowired
public final void setLogRepository(LogRepository logRepository) {
this.logRepository = logRepository;
}
}
Setter的时候,标记为public final
。
Getter的时候,标记为protected
。
可以说是最佳实践了。
ref: https://segmentfault.com/a/1190000039053805
为什么不推荐使用字段注入?
事实上,当你使用IDE开发的时候,你使用了Spring的字段注入,你会得到一个提示:
Field injection is not recommended
当然是因为这种方式有好多缺点:
-
不允许不可变字段的声明,像我们刚才说的
@Autowired protected final
-
代码坏味道的潜在根源
因为字段注入是如此的方便,你可以“只要需要”就注入一个你想操作的对象,结果不知不觉中注入了十几个甚至几十个(见贤思齐,我所在的项目中确实已经有这样的问题了)。
反之,如果我们通过构造函数注入,随着注入对象的增多,构造函数的参数不断变多,你明显的就能闻到“坏味道”了,这时候你得开始想,是时候把当前类重新划分了,怎么能承担如此多的职责呢?职责单一忘了吗?SOLID天天盯着你们哪! -
和Spring的容器紧耦合
如果这点你不觉得有什么,那你是不是忘记写单元测试了?
单元测试的时候你也引入了Spring框架?就是为了使用“注入”这个功能?
如果我们使用构造函数注入,或者Setter注入,我们在脱离Spring框架之后(比如单元测试)的时候,我们可以通过其他方式传入目标对象。反之,我们只能强依赖Spring框架了。 -
隐藏了依赖
当使用依赖项注入模式时,受影响的类应该通过公开构造函数中所需的依赖项或使用方法(setter)的可选依赖项,使用公共接口清楚地公开这些依赖项。当使用基于字段的依赖项注入时,类本质上是向外部世界隐藏这些依赖项。
ref: https://blog.marcnuri.com/field-injection-is-not-recommended