在你学习Spring之前,你肯定听说过“控制反转”、“依赖注入”、“上下文”等名词,伴随着这些名词的,是一些冗长晦涩的解释,这些解释并没有什么显著的错误,但是因为太过抽象导致初学者们无法直观地去理解它们。
废话不多说,我们通过举例或者写代码来一步一步理解它们。
在一个系统里,可能有多个业务部分,比如用户服务,订单服务,数据服务等等,在代码里,这些服务也许分布在多个类中,可能叫做UserService或者OrderService等等。为了简便,我们假设系统里有A、B、C、D四个类,他们有如下的依赖关系:
简单来说,D是B的成员,A和B是C的成员。以前Spring这种东西没有出现的时候,你可能手动地去创建ABCD对象,再设置他们的依赖关系,如下图:
上面的代码看起来很简单,也不复杂。问题是当你的系统规模扩大了以后,有上百个对象需要初始化以及设置依赖的时候,复杂度就直线上升了。
为了降低复杂度,减少他们之间的耦合,这个时候我们就需要用上Spring容器了。程序里的对象都可以扔到容器里,对象只需要告诉容器它需要哪些依赖就行,容器自动初始化好并给它。比如上面ABCD那个例子,C告诉容器它需要A和B,B告诉容器它需要D,这样ABCD四个对象都可以扔到容器里了,当你想用C的时候,就从容器里面拿出来,这时候C的成员就已经包含了A和B了(我们目前只讨论单例的情况,就是一个类只有一个对象)。
容器通过“配置”来了解对象之间的依赖关系。
在Spring框架里,ApplicationContext就代表了容器(又叫应用程序上下文),容器里的对象,又叫Bean。配置有两种方式,一种是xml,一种是Java配置(或者说代码配置)。早些年的时候,Java Web(SSH)开发为人诟病的,就是臃肿的xml配置,虽然目前Spring仍然支持xml配置和混合配置,不过Spring Boot已经 建议 使用Java配置了。我们来通过一个例子来看看如何使用容器和配置:
图中显示了项目的源码结构、程序的入口、配置以及依赖。
上图写出了ABCD的源码。
配置文件是Config.java,它有一个@Configuration注解,这表明Config类是配置类。它还有另外一个注解@ComponentScan,这个注解表示容器应该去扫描程序的代码,看看那些组件应当被初始化为Bean。哪些类会被初始化为Bean呢?我们看看ABCD的源码,发现他们各自都有注解@Component,这个注解就表示了它所在的类是组件,需要初始化Bean。C和B的成员变量都用@Autowired注解来修饰,这样容器就知道他们都依赖哪些Bean了,ABCD的对象都生成好之后,容器就把D对象赋值给B的成员,把A和B的对象赋值给C的成员。
如果你运行程序,会发现输出的结果是true,也就是C里的A成员不为空。这样你是不是就理解了Spring容器的作用了呢?你的程序不必再在ABCD的外部去设置他们的依赖了,反而只需要在ABCD的类的内部指明依赖就行,这个从外而内的过程就是控制反转(IoC,Inversion of Control),控制权交给了Spring容器,Spring容器也叫IoC容器。你还可能听过依赖注入(DI,Dependency Injection),如果你看网上文章没明白,权当他们是一样的好了。
对于开源库,他们的类肯定没有@Component注解,这样肯定不能通过扫描来加入容器。那就需要写点代码了。Config类中有一个objectMapper方法,返回了一个ObjectMapper对象(ObjectMapper是Jackson的关键类),objectMapper方法有个@Bean注解,表示返回的对象会加入到容器中。ObjectMapper对象可以在objectMapper方法里,进行一些修改呀,配置呀等等。
这个时候ObjectMapper对象还没人用,如果ABCD任何一个类想用,只需要在类中加一个成员 @Autowired ObjectMapper objectmapper;
,Spring容器会自动把ObjectMapper对象赋值到这个成员上。非常方便,尤其是在大项目里,类非常多,一行代码就把你需要的对象拿过来用,简直爽的不行。在实践中,依赖的类常常是个接口,使用者只调用接口就行,至于容器给它哪种实现并不重要,这个逻辑在“配置”里就可以控制。这样,程序的耦合度进一步减小了。
在我们之前 运行第一个Spring Boot应用 的时候,我们曾经添加了一个控制器,他有个@Controller注解,这个注解相当于一个特殊的@Component,只不过@Controller表示当前的组件是用来处理网络请求的。类似的还有,@Service用来表示业务相关的组件,@Repository用来表示数据获取相关的组件。他们都是通过自动扫描放到Spring容器中的。
为了让大家理解Spring容器,本文忽略了很多技术上的细节,比如Bean的命名,比如说@ComponentScan的扫描路径和注解参数,比如声明依赖除了在成员上加@Autowired以外还能通过构造方法或者setter等等。我相信你懂了Spring容器的概念之后,这些都不是什么问题。