【.com原创文章】Java作为一种流行的开发语言,深受开发者的青睐。Java平台在提供丰富应用开发功能的同时,也暴露出其存在的问题。问题在于它缺乏将基本组件构建成完整系统的能力,因此开发者需要通过各种设计模式将开发好的组件组合起来构建最终的应用程序。为了解决这个问题,Spring架构引入了IoC组件,可以通过规范化的方式将不同的组件组合起来,使其成为一个完整的、可用的应用程序。从此,开发者无需手动设置对象的依赖关系,将这项工作交给Spring容器处理和管理,提升了开发体验。今天给大家讲解一下SpringIoC的实现原理,接下来我将学习以下内容:SpringIoC的由来和概念SpringIoC容器的优缺点IoC和DI的优缺点DI的自动加载由来和SpringIoC的概念介绍在SpringIoC之前,我们先来看看传统的对象(组件)依赖是如何做的,假设通过RESTFUL访问用户信息(User)。如图1所示,用户请求一个UserController获取User信息,UserController会调用UserService,UserService会处理User的业务逻辑。图1:依赖示例同时UserService会调用UserDao,UserDao负责调用数据库返回用户需要的信息。从这张图可以看出,UserController依赖于UserService,UserService依赖于UserDao。如图2所示,假设在UserController中需要使用UserService,则需要在其UserController的构造函数中实例化UserService。图2:传统依赖需要自己管理对象实例化,才能在save方法中使用UserService,调用其save方法。不同于传统的依赖方式,SpringIoC会通过一个XML文件来配置对象之间的关系。如图3所示,在beans标签中,定义了两个bean,分别是UserController和UserService。整个类(包括命名空间)都在Class属性中定义。图3:SpringIoC的DependencyXML配置需要注意的是,constructor-arg的ref在UserController的bean定义中指定为UserService。这里的意思是在UserController的构造函数中会引入UserService来说明两者之间的依赖关系,即UserController会依赖UserService。看完XML配置,回头看看代码的变化。如图4所示,在UserController构造函数的初始化参数中添加UserService作为依赖。图4:SpringIoC代码的变化,但是NewUserService的动作不再在UserController中完成,而是由Spring容器来完成。Spring容器完成UserService的初始化后,当UserController需要使用的时候,直接使用这个UserService实体就可以了。图5:SpringIoC的Spring容器下面我们来梳理一下SpringIoC做了什么,如图5所示:中间的Spring容器会读取XML配置文件中的信息,获取Bean之间的依赖关系。Spring容器通过反射机制创建对象实例。由于Spring容器管理着所有注册的bean,为后续建立它们之间的依赖关系奠定了基础。Spring容器通过bean之间的依赖关系创建实例,同时保证bean在使用依赖关系时直接去对应的实例,而不是自己创建实例。说白了,SpringIoC所做的就是管理和创建Bean实例,同时保证Bean之间的依赖关系。这里介绍一下SpringIoC,IoC(InversionofControl),也叫控制反转,就是对象定义它们的依赖关系的控制反转。原来这个过程是:谁用谁创造。比如上面的例子,UserController需要使用UserService,所以UserController创建了一个UserService的实例。引入IoC后,创建过程就相反了。这些UserController和UserService之间的依赖关系由XML文件定义,然后由Spring容器创建。这种从对象的用户到Spring容器的控制转移称为控制反转。即对象之间的依赖过程发生了变化,从原来的主动创建变成了现在的被动关联(因为有Spring容器的参与),这种控制翻转的现象称为控制反转。SpringIoC容器提到了IoC的由来和概念。其实就是一个用来管理对象初始化的容器。下面我们就为SpringIoC容器介绍一下它的主要功能。SpringIoC容器将创建对象,配置它们之间的依赖关系,并管理它们的生命周期(从创建到销毁)。SpringIoC容器管理的对象称为SpringBeans,也就是上面例子中提到的UserController和UserService。通过读取配置文件元数据提供的指令,容器知道要实例化、配置和组装哪些对象。这里的配置元数据就是上面例子中的XML,但是除了处理XML配置之外,还可以用Java注解或者Java代码来表示,可以理解为一种配置对象之间关系的方式。说了这么多SpringIoC容器的作用,那么在Spring中实现IoC容器的实际代表是谁呢?下面介绍两种代表SpringIoC容器的类型,即:①SpringBeanFactory容器是最简单的容器。org.springframework.beans.factory.BeanFactory接口来定义。BeanFactory或相关接口,如BeanFactoryAware、InitializingBean、DisposableBean,在Spring中仍然存在,目的是向后兼容大量与Spring集成的第三方框架。②SpringApplicationContext容器增加了更多企业特有的特性,比如从属性文件中解析文本信息的能力,以及向感兴趣的事件监听器发布应用事件的能力。容器由org.springframework.context.ApplicationContext接口定义。因为ApplicationContext容器包含了BeanFactory容器的所有功能,BeanFactory适用于轻量级的应用。这里我们重点关注ApplicationContext容器,看看它是如何实现SpringIoC容器的功能的。由于ApplicationContext是一个接口,因此有几种不同的实现。这些实现将针对不同的使用场景。下面列出了三种不同的实现:FileSystemXmlApplicationContext:实现从XML文件加载bean。初始化类时需要提供XML文件的完整路径。ClassPathXmlApplicationContext:它也实现了在XML文件中加载bean。与上述方法不同的是,不需要提供XML文件的完整路径,只需正确配置CLASSPATH环境变量,容器就会从CLASSPATH中搜索bean配置文件。WebXmlApplicationContext:实现在Web应用程序范围内加载XML文件中定义的bean。由于篇幅原因,这里先描述FileSystemXmlApplicationContext实现的SpringIoC容器。图6:FileSystemXmlApplicationContext实现SpringIoC容器如图6:在使用FileSystemXmlApplicationContext实现类之前,需要引入相关包。由于是接口ApplicationContext的实现类,所以需要引入ApplicationContext包和它自己的FileSystemXmlApplicationContext包。在实例化FileSystemXmlApplicationContext时,传入XML文件的地址,即上面配置bean对象的XML文件地址,这里是“C:/Test/src/Beans.xml”。最后通过FileSystemXmlApplicationContext的getBean方法,传入beanid获取bean对象的实例。这里传入“userController”,从而调用userController中的save方法完成业务。SpringIoC的优缺点介绍完SpringIoC的原理和容器的实现,相信大家对IoC有了一定的了解,但是任何技术和架构都有其优缺点。SpringIoC也不例外。在使用它之前,您需要了解它。有一个清醒的认识。第一部分优点:灵活性,由于可以灵活配置类之间的依赖关系,可以设置类对接口的依赖,对于实现对应的实现类,这种方式实现起来更方便依赖于接口的类,提倡面向接口编程,提高程序的可扩展性。可读性,每个bean之间的依赖关系清晰,而且由于SpringIoC容器管理bean实例,所以不需要创建一堆工厂类来生成不同的bean。可测试性,由于每个bean都通过IoC解耦,所以可以针对单个bean进行测试,bean之间的依赖关系也很明确。更换bean进行测试也很容易。哪里有优势,哪里就有劣势:复杂性。由于IoC容器的引入,对象生成步骤变得复杂。本来在哪儿使用,在哪儿生成对象,现在凭空多了一些XML配置依赖的关系,让系统调用变得不那么直观了。.因此,会增加团队的学习成本,团队需要提升这方面的技能。性能方面,IoC容器通过反射生成对象,在运行效率上有一定损失。它允许程序在运行时(而不是编译时)对成员进行操作。配置,IoC框架需要大量的配置工作,无形中会增加开发成本。IoC和DI前面提到了IoC和控制反转。一般来说,DI(DependencyInjection)或依赖注入是和IoC一起出现的。这两个概念有什么关系?2004年,MartinFowler正在探索IOC。在谈到控制倒置的问题时,他问道:“控制的哪些方面发生了倒置?”经过详细的分析和论证,他得出了答案:“获取依赖对象的过程被倒转了。”控制反转后,获取依赖对象的过程由IOC容器自行管理变为主动注入。因此,他给“控制反转”起了一个更合适的名字,叫做“依赖注入(DependencyInjection)”。他的回答其实给出了一个IOC的实现方式:注入。所谓依赖注入,就是IoC容器在运行时动态的向对象注入一定的依赖。就像上面提到的例子一样,将UserService注入UserController是由SpringIoC容器完成的。因此,DependencyInjection(DI)和InversionofControl(IOC)从不同的角度描述了同一件事,即通过引入IOC容器,使用依赖注入来实现对象之间的解耦。基于对IoC和DI这两个概念的理解,我们来看看DI的两种实现方式:constructor-basedDI和setter-basedDI。上面的例子中提到了基于构造函数的DI,以供回顾。如图7所示,在XML配置文件中定义了UserControllerbean,并将ref指定为UserService,即需要注入的bean。图7:ConstructorDI配置文件需要注意的是,这里通过设置contructor-arg来指定构造函数注入方式。如图8所示,类文件中UserController的构造函数传入的参数为UserService。SpringIoC容器完成依赖注入和对象初始化后,使用UserController中的对象实例进行后续的业务操作。图8:UserController使用依赖注入,初始化后的UserService对象如图9所示。配置bean节点稍做调整,将contructor-arg改为property,与setter方法的依赖关系UserService是通过property属性定义的。图9:UserController定义setter方法的依赖我们看一下类中的修改,如图10所示,与构造函数注入方法不同的是,在UserController中增加了一个setUserService方法来设置UserService的属性,以及传入的参数还是UserService。图10:类中setter方法的依赖注入,实现DI的自动加载。上面提到了bean对象是通过constructor和setter方法注入的,分别使用XML配置文件中的<:property>元素完成注入。为了减少XML的配置量,Spring容器可以不使用<:property>元素来配置bean之间的关系。这种注入方式称为自动装配。我们来看看几种自动组装的方法:①byType,该方法是通过属性数据类型自动组装的如果类中定义了与其他类的依赖,那么Spring容器会通过XML配置文件中的类型找到对应的依赖。关系bean然后与它相关联。此流程容器将尝试匹配和连接属性的类型。比如beanA定义了一个X类型的属性,Spring会在ApplicationContext中寻找一个X类型的bean,注入到beanA中。如果还是觉得抽象,我们来看下面的例子,如图11,当UserController设置UserService属性时,定义了与UserService的依赖关系。图11:定义UserController和UserService的依赖关系如图12所示,在XML配置文件中,UserController不需要使用property属性来定义与UserService的依赖关系,而是使用autowire="byType"的方法.图12:通过byType定义关系容器。类中通过setUserService传入的UserService类型会自动在配置文件中找到对应类型的UserService,从而完成UserController与UserService之间的依赖,即依赖注入。这种方法也是基于类型的自动加载。②constructor,适用于构造函数参数类型的自动加载。有了byType的基础,就很好理解了。例如,beanA的构造函数接受类型X的参数,容器会在XML中寻找X类型的bean,并在构造函数中将它们注入到beanA中。如图13所示,UserController在构造函数中定义了UserService作为初始化参数,它决定了UserController对UserService的依赖。图13:UserController在构造函数中将UserService定义为初始化参数如图14所示,UserController只需要在XML配置文件中设置autowire="constructor"即可。通过UserController类中的构造函数告诉容器将UserService注入到UserController中,完成UserController与UserService之间的依赖。这个方法也是基于构造函数的自动加载。图14:通过构造函数定义关系③byName。通过指定具体的bean名称,容器根据名称自动选择bean属性,完成依赖注入。例如:beanA定义了一个名为X的属性,容器会在XMLbean中寻找一个名为X的属性,将其注入到beanA中。如图15所示,在UserController中定义了一个名为myUserService的成员属性,其类型是用户服务。图15:在UserController中定义了一个名为myUserService的成员属性如图16所示,UserController的自动装配在XML配置中配置为“byName”。此时容器会根据类中定义的myUserService成员属性(变量)自动关联UserService,在UserController中设置setUserService时会自动加载UserService的实例。图16:XML文件中byName的定义总结本文从SpringIoC的由来出发,通过一个简单的对象依赖实例来说明SpringIoC解决的问题。它将对象的依赖从对象内部转移到IoC容器中,由容器负责对象的注册和依赖。说到SpringIoC容器,具体的工作是由BeanFactory和ApplicationContext接口完成的。对于常用的ApplicationContext接口的三个实现类,分别实现了根据XML加载实例、根据CLASSPATH加载实例和根据Web应用范围加载实例。在分析了IoC的优缺点之后,阐述了IoC与DI的关系。DI从另一个角度解释了IoC,即在IoC容器运行的过程中动态地向对象注入依赖。常见的依赖注入方式有:构造函数注入和setter方法注入。同时还介绍了DI的自动注入(加载),包括byType、constructor和byName三种类型。作者:崔浩简介:十六年开发架构经验。曾在惠普武汉交付中心担任技术专家、需求分析师、项目经理,后在一家初创公司担任技术/产品经理。善于学习,乐于分享。目前专注于技术架构和研发管理。编辑:陶佳龙征稿:如有意向投稿或寻求报道,请联系editor@51cto.com【原创稿件请注明原作者和出处为.com,合作网站转载】
