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

ref: https://stackoverflow.com/questions/34580033/spring-io-autowired-the-blank-final-field-may-not-have-been-initialized

简单来说,就是二者赋值的时机不统一造成了互斥。

构造函数注入

那我们使用上面提到的,通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

当然是因为这种方式有好多缺点:

  1. 不允许不可变字段的声明,像我们刚才说的 @Autowired protected final

  2. 代码坏味道的潜在根源
    因为字段注入是如此的方便,你可以“只要需要”就注入一个你想操作的对象,结果不知不觉中注入了十几个甚至几十个(见贤思齐,我所在的项目中确实已经有这样的问题了)。
    反之,如果我们通过构造函数注入,随着注入对象的增多,构造函数的参数不断变多,你明显的就能闻到“坏味道”了,这时候你得开始想,是时候把当前类重新划分了,怎么能承担如此多的职责呢?职责单一忘了吗?SOLID天天盯着你们哪!

  3. 和Spring的容器紧耦合
    如果这点你不觉得有什么,那你是不是忘记写单元测试了?
    单元测试的时候你也引入了Spring框架?就是为了使用“注入”这个功能?
    如果我们使用构造函数注入,或者Setter注入,我们在脱离Spring框架之后(比如单元测试)的时候,我们可以通过其他方式传入目标对象。反之,我们只能强依赖Spring框架了。

  4. 隐藏了依赖
    当使用依赖项注入模式时,受影响的类应该通过公开构造函数中所需的依赖项或使用方法(setter)的可选依赖项,使用公共接口清楚地公开这些依赖项。当使用基于字段的依赖项注入时,类本质上是向外部世界隐藏这些依赖项。

ref: https://blog.marcnuri.com/field-injection-is-not-recommended

posted @ 2021-08-03 09:58  talentzemin  阅读(2698)  评论(0编辑  收藏  举报