前言
使用Spring框架最核心的两个功能就是IOC和AOP。IOC也就是控制反转,我们将类的实例化、依赖关系等都交由Spring来处理,以达到解耦合、利用复用、利于测试、设计出更优良程序的目的。而对用户来说,操作最对的便是注解。在Spring中提供了三类注解方式,下面我们就逐一分析。最后,你会发现,你最常用、看起来最方便的形式确实最不推荐的一种形式。
1.Field注入方式
在属性上添加注解:
@Autowired private BookMapper bookMapper;
此种注解方式,应用最广泛:
- 注入简单,只需在字段上添加@Autowired或@Resource;
- 减少大量冗余代码,美观;
- 新增Field时不需要过多代码修改。
但是此种方式spring官方并不推荐,在IDEA中你可能会收到以下警告:
这个警告意思就是不推荐变量注入方式,如果使用万能键可能会有IDEA的推荐方式:使用构造器的方式强制注入。
2.set方法注入方式
private BookMapper bookMapper; @Autowired public void setBookMapper(BookMapper bookMapper) { this.bookMapper = bookMapper; }
这种方式IDEA不会产生警告,Spring3.x推荐的注入方式,但并没有被广泛应用,当初推荐的理由:
- 解决了构造器注入的笨重;
- 可以让类在之后重新配置或者重新注入。
3.构造函数+final注入方式
private final BookMapper bookMapper; //@Autowired 在只有一个构造函数情况下可以不写注解 public BookServiceImpl(BookMapper bookMapper) { this.bookMapper = bookMapper; }
Spring4.x推荐的注入方式。对比Field注入:
- 代码臃肿
- 新增Field修改麻烦
- 当Field多余5个时不符合构造方法的基本规范,显得笨重、臃肿;
为什么spring4.x之后推荐构造函数注入?
从代码上来看,构造函数注入好像并没有显现出来它的优势,但问什么Spring4.x会推翻之前推荐的setter注入,采用构造函数注入呢?官方的理由汇总如下:
- 依赖不可变:加入了final来约束修饰Field,这条是很显然的;
- 依赖不可为空:在实例化的时候会检查构造函数参数是否为空,如果为空(未找到改类型的实例对象)则会抛出异常。
- 单一职责:当使用构造函数注入时,如果参数过多,你会发现当前类的职责过大,需要进行拆分。而使用Field注入时,你并不会意识到此问题。
- 更利于单元测试:按照其他两种方式注入,当单元测试时需要初始化整个spring的环境,而采用构造方法注入时,只需要初始化需要的类即可,即可以直接实例化需要的类。
- 避免IOC容器以外环境调用时潜在的NPE(空指针异常)。
- 避免循环依赖。
- 保证返回客户端(调用)的代码的时候是完全初始化的状态。
虽然Field注入方式代码是如此的简洁美观,但它存在一些问题:
- 不能有效的指明依赖。相信很多人都遇见过一个bug,依赖注入的对象为null,在启动依赖容器时遇到这个问题都是配置的依赖注入少了一个注解什么的。这种方式就过于依赖注入容器了,当没有启动整个依赖容器时,这个类就不能运转,在反射时无法提供这个类需要的依赖。
- 依赖注入的核心思想之一就是被容器管理的类不应该依赖被容器管理的依赖,换成白话来说就是如果这个类使用了依赖注入的类,那么这个类摆脱了这几个依赖必须也能正常运行。然而使用变量注入的方式是不能保证这点的。
而如果是采用构造器注入或者set注入,就可以避免上诉问题。
- 使用set方式时,这是一种选择注入,可有可无,即使没有注入这个依赖,那么也不会影响整个类的运行。
- 使用构造器方式时已经显式注明必须强制注入。通过强制指明依赖注入来保证这个类的运行。
总结
变量注入的方式应该尽量避免,但是存在即合理,根据具体情况可以采用最适合的方式。
如果你在项目中使用了lombok,那么构造函数方式代码可以更加简洁,并且运行良好: