2.1.1 字段注入
首先,我们来看字段注入。想要在一个类中通过字段的形式注入某个对象,就可以使用这个方式,示例代码如代码清单2-2所示。
代码清单2-2 字段注入示例代码
public class ClientService { @Autowired private HealthRecordService healthRecordService; public void recordUserHealthData() { healthRecordService.recordUserHealthData(); } }
可以看到,通过@Autowired注解,字段注入的实现方式非常简单而直接,代码的可读性也很高。事实上,字段注入是三种注入方式中最常用,也是最容易使用的一种,但它也是三种注入方式中最应该避免使用的。如果你使用过IDEA,可能会遇到“Field injection is not recommended”这个提示,告诉你不建议使用字段注入。针对这一点,你可能会觉得很诧异。我们来分析一下原因。
1)字段注入的最大问题是对象的外部可见性。正如在前面的ClientService类中,我们通过定义一个私有变量healthRecordService来注入该接口的实例。显然,这个实例只能在ClientService类中被访问,脱离了容器环境就无法进行访问,如代码清单2-3所示。
代码清单2-3 脱离容器使用ClientService示例代码
ClientService clientService = new ClientService(); clientService.recordUserHealthData();
执行这段代码的结果就是抛出一个NullPointerException空指针异常,原因是无法在ClientService的外部实例化HealthRecordService对象。采用字段注入,类与容器的耦合度过高,我们无法脱离容器来使用目标对象。如果我们编写测试用例来验证ClientService类的正确性,那么想要使用HealthRecordService对象,就只能通过反射的方式,这种做法实际上是不符合JavaBean开发规范的,而且可能导致一直无法发现空指针异常。
2)字段注入的第二个问题是可能导致潜在的循环依赖。所谓循环依赖,就是两个类之间互相进行注入,示例代码如代码清单2-4所示。
代码清单2-4 基于字段注入的循环依赖示例代码
public class ClassA { @Autowired private ClassB classB; } public class ClassB { @Autowired private ClassA classA; }
显然,这里的ClassA和ClassB发生了循环依赖。上述代码在Spring中是合法的,容器启动时并不会报任何错误,而只有在使用到具体某个ClassA或ClassB时才会报错。
3)字段注入的第三个问题是我们无法设置需要注入的对象为final,也无法注入那些不可变对象,这是因为字段必须在类实例化时进行实例化。
基于以上三点,IDEA以及Spring官方都不推荐开发人员使用字段注入这种方式。那么,我们推荐的注入方式是哪种呢?答案是构造器注入。