Java应用开发:企业级开发
上QQ阅读APP看书,第一时间看更新

1.4 IoC

IoC(Inversion of Control)是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。

IoC称为控制反转,简单来说就是将对象的创建及对象生命周期管理的过程交由Spring框架来处理,从此在开发过程中不再需要关注这些细节,而是在需要时由Spring框架提供处理,这样的机制称为控制反转。

1.4.1 IoC入门案例

视频

IoC入门案例

下面通过一个IoC入门案例介绍如何使用IoC。使用IoC时是通过Spring容器获取对象并使用的,不是使用new创建对象的。下面详细介绍IoC入门案例的实现过程。

(1)在MyEclipse中右击项目面板,在弹出的快捷菜单中选择New→Java Project命令,在打开的Create Java Project对话框中输入项目名称为Spring-day01-demo02,单击Finish按钮,如图1-21所示。

(2)使用Spring之前需要导包,可以将Spring-day01-first项目中的包复制过来。选择Spring-day01-first项目中的libs文件并右击,在弹出的快捷菜单中选择Copy命令复制文件,然后选择Spring-day01-demo02项目名称并右击,在弹出的快捷菜单中选择Paste命令粘贴文件,如图1-22所示。在打开的对话框中单击OK按钮即可完成导入包。

图1-21 创建项目

图1-22 复制文件进行导入包

(3)展开复制的libs文件,全选包并右击,在弹出的快捷菜单中选择Build Path→Add to Build Path命令,即可添加复制的包,如图1-23所示。

图1-23 添加包

(4)然后使用xml文件进行配置,因为在Spring-day01-first项目中已经设置spring_beans的模板,现在只需创建xml文件,然后使用现有模板即可。右击src文件,在弹出的快捷菜单中选择New→File命令,打开New File对话框,在File name文本框中输入applicationContext.xml,单击Finish按钮,如图1-24所示。

(5)在创建的applicationContext.xml文件中输入spring,然后按【Alt+/】组合键,在打开的列表中选择spring_beans,再按【Enter】键即可直接使用spring_beans模板进行约束,如图1-25所示。

图1-24 创建xml文件

图1-25 使用spring_beans模板

(6)生成约束后还要生成对应的对象,因此需要创建类。首先在src文件夹中创建com.daojie.domain包,再创建Person类。在Person类中提供eat和sleep两个成员方法,相关代码如下:

(7)在applicationContext.xml文件中配置Person类相应的信息,相关代码如下:

前5行代码是使用spring_beans模板约束的代码,也就是步骤(5)操作完后自动生成的代码。第7行代码将Person类交由Spring进行处理,其中id等号右侧双引号中内容为将要获取对象对应的id。id一般为类名,全部小写。

(8)创建com.daojie.test包,并在该包中创建TestDemo类,然后在TestDemo类中创建test01方法。因为创建的方法是测试用的,所以在上方输入@Test,添加完后编译不通过。按【Ctrl+1】组合键,在打开的列表中选择第一项,即可编译通过,如图1-26所示。

图1-26 创建测试方法

(9)在TestDemo类中输入test01的测试代码,相关代码如下:

第10行代码初始化Spring容器,因为要使用Spring容器中的内容必须要初始化容器;第12行代码使用getBean根据bean的id获取Spring容器中的对象,然后再导入包并强制转换。

(10)测试代码输入完成后,右击test01,在弹出的快捷菜单中选择Run As→1 JUnit Test命令,在打开的对话框中单击OK按钮即可执行test01方法,并输出结果,如图1-27所示。

同时在JUnit中显示测试运行是正常的,如图1-28所示。如果测试失败,会显示红色数据条并显示Errors的数量。

图1-27 测试结果

图1-28 测试运行结果

1.4.2 IoC的实现原理

视频

IoC实现原理和注意事项

在初始化一个Spring容器时,Spring框架会去解析指定的xml文件,当解析到其中的<bean>标签时,会根据该标签中Class属性指定的类的全路径名,通过反射创建该类的对象,并将该对象存入内置的Map中管理。其中键就是该标签的id值,值就是该对象。

当通过getBean方法从容器中获取对象时,其实就是根据传入的条件在内置的Map中寻找是否有匹配的键值对,如果有则将该键值对中保存的对象返回,如果没有匹配到则抛出异常。

下面通过loC入门案例介绍IoC的实现原理。首先在TestDemo类代码中初始化Spring容器,其中包含xml文件,那么就会加载该文件;然后根据<bean>标签的Class字符串创建对象,并且根据id将创建的对象放在Map中管理。

此步骤可以通过以下伪代码进一步描述,相关伪代码如下:

最后从容器中获取对象,也就是从Map中获取person,如果获取不到对象,则会抛出异常。

通过伪代码进行描述,相关代码如下:

1.4.3 IoC注意事项

下面对IoC的注意事项进行介绍,具体如下:

•默认情况下,多次获取同一个id的bean,得到的将是同一个对象。

•即使是同一个类,如果配置过多个<bean>标签,具有不同的id,每个id都会在内置Map中有一个键值对,其中的值是这个类创建的不同的对象。

•同一个<beans>标签下不允许配置多个相同id的<bean>标签,如果配置,则启动抛出异常。

下面举例说明。在loC入门案例的TestDemo类的代码中再创建p1对象,然后输出p和p1的地址。相关代码如下:

执行test01方法,输出结果如图1-29所示。

图1-29 输出结果

输出p和p1的地址是完全一样的,说明多次获取同一个id的bean,得到的将是同一个对象。

接着再举例说明第二个注意事项,首先在applicationContext.xml文件中配置两个<bean>标签,其中类是相同的,id是不同的,分别为person和person1,相关代码如下:

然后在TestDemo类的代码中再创建p1对象,并输出p和p1的地址。该部分代码与验证第一个注意事项的代码一样。执行test01方法,输出结果如图1-30所示。

图1-30 输出结果

可见输出p和p1的地址是不一样的,说明p和p1是不同的对象。因为在初始化Spring容器时,加载xml文件查找到第一个<bean>标签时,就会创建对象并且以person作为id放在Map中,同样的道理,为第二个<bean>标签创建对象时会以person1作为id放在Map中。创建了两次对象,所以其地址是不一样的。

最后举例说明第三个注意事项,在xml中创建两个相同id的<bean>标签,执行test01方法,输出结果显示异常,如图1-31所示。说明同一个<beans>标签下不允许配置多个相同id的<bean>标签。

图1-31 输出结果异常

1.4.4 IoC获取对象的方式

通过context.getBeans()方法获取bean时,可以通过以下两种方式获取:

•传入id值。

•传入class类型。

下面举例介绍两种获取方法,在TestDemo类中输入两种获取对象的代码,相关代码如下:

视频

获取对象方式和别名标签

执行test01方法,输出结果正常。第12行代码是通过传入id值获取bean;第14行代码是通过传入class类型获取bean。

通过class类型获取bean时,如果同一个类配置过多个bean,则在获取时因为无法确定到底要获取哪个bean而抛出异常。

下面举例说明,在applicationContext.xml文件中配置两个<bean>标签,其中class相同,id不同,分别为person和personx,相关代码如下:

执行test01方法,输出结果如图1-32所示。

从输出结果可见,通过传入id获取bean是成功的,而通过class类型获取bean时出现异常。因为id通常是唯一的,不存在这样的问题,所以建议大家尽量使用id获取bean。

图1-32 输出结果

1.4.5 别名标签

在Spring中提供的别名标签<alias>可以为配置的<bean>起一个别名,要注意的是,这仅仅是对指定的<bean>起的一个额外名字,并不会额外创建对象存入Map。

别名标签格式:

下面举例说明别名标签的使用方法,在IoC入门案例中,可以将person起名为personx,相关代码如下:

为配置的<bean>起别名后,通过传入id获取Spring容器中的对象时,可以使用原名也可以使用别名。

1.4.6 Spring创建对象的方式

视频

类的无参构造方法创建对象

Spring可以通过以下4种方式创建对象:

•通过类的无参构造方法创建对象。

•通过静态工厂创建对象。

•通过实例工厂创建对象。

•通过Spring工厂创建对象。

1.通过类的无参构造方法创建对象

在IoC入门案例中,使用的就是通过类的无参构造方法创建对象。当用最普通方式配置一个<bean>时,默认就是采用类的无参构造方法创建对象。在Spring容器初始化时,通过<bean>上配置的class属性反射得到字节码对象,通过newInstance()创建对象。

这种方式下Spring创建对象时,要求类必须有无参的构造方法,否则无法通过反射创建对象,会抛出异常。

在Java中创建类时,默认提供无参构造方法,所以在IoC入门案例中创建Person类时也包含无参构造方法。下面在Person类中提供无参构造方法进行验证,相关代码如下:

执行test01方法,输出结果如图1-33所示。

从输出结果可见调用了无参构造方法。而如果在类中创建了有参构造方法,则默认不会提供无参构造方法。如果在上面的Person类的无参构造方法后面再创建有参构造方法,执行test01方法,则输出结果异常。

图1-33 输出结果

2.通过静态工厂创建对象

视频

静态工厂创建对象

很多时候,面对的类是无法通过无参构造方法创建的,例如该类没有无参构造或是抽象类等情况时,无法要求Spring通过无参构造方法创建对象,此时可以使用静态工厂的方式创建。

下面举例说明通过静态工厂创建对象的方法。先创建com.daojie.factory包,并在包里创建CalendarStaticFactory类获取日历对象。在该类中创建方法可以获取日历对象,相关代码如下:

第5行代码用于通过创建静态工厂获取日历对象,其中Calendar为抽象类;第6行代码获取日历对象的实例,其中有一个静态方法。

接着在applicationContext.xml文件中进行配置,相关代码如下:

在代码中除了创建id和class外,还调用了类中的静态方法getCalendar,完成配置。

最后在TestDemo测试类中创建test02方法,相关代码如下:

第4行代码初始化Spring容器;第6行代码传入id以获取bean;第7行代码输出了日历对象。执行test02方法,即可输出日历对象,如图1-34所示。

图1-34 输出结果

3.通过实例工厂创建对象

视频

实例工厂创建对象

实例工厂也可以解决类无法通过无参构造创建的问题,解决的思路和静态工厂类似,只不过实例工厂提供的方法不是静态的。Spring需要先创建出实例工厂的对象,再调用实例工厂对象上指定的普通方法创建对象,所以实例工厂也需要配置到Spring中管理。

下面接着IoC的入门案例说明通过实例工厂创建对象的方法,首先创建CalendarFactory类,并且创建普通方法获取日历对象,相关代码如下:

接着需要在applicationContext.xml文件中配置<bean>标签,相关代码如下:

第一个<bean>标签配置实例工厂的内容,加载Spring时会创建相应的对象;第二个<bean>标签调用CalendarFactory类中的方法。

最后在TestDemo测试类中创建test03方法,相关代码如下:

第4行代码初始化Spring容器;第6行代码传入id获取bean;第7行代码输出日历对象。执行test03方法,即可输出日历对象,如图1-35所示。

图1-35 输出结果

4.通过Spring工厂创建对象

Spring内置了工厂接口,也可以通过实现这个接口开发Spring工厂,然后通过这个工厂创建对象。

下面同样接着IoC入门案例说明通过Spring工厂创建对象的方法,首先创建CalendarSpring-Factory类并实现接口,相关代码如下:

视频

Spring工厂创建对象

第4行代码实现接口并通过泛型限制生成对象的类;第7行代码使用getObject()方法获取Spring工厂生产的对象;第13行代码使用getObjectType()方法获取当前工厂生产的对象的类型;第21行代码使用isSingleton()方法决定Spring工厂生产对象时是否采用单例模式。

注意:

使用isSingleton()方法时,如果返回true,那么Spring容器中该对象只创建一次,后续重复使用,被称为单例模式;如果返回false,那么Spring容器中每次获取该对象时,都会创建新的对象,被称为多例模式。

接着在applicationContext.xml文件中配置<bean>标签,配置方法和通过无参构造方法创建对象一样,相关代码如下:

最后在TestDemo测试类中创建test04方法,test04的代码和test03的代码相似,只是通过bean的id获取对象代码中的id不同,应为calendarspring。执行test04方法,即可正确输出日历对象。

1.4.7 Spring单例模式

视频

Spring单例模式

Spring容器管理的bean在默认情况下是单例模式的,即一个bean只会创建一个对象,存在内置Map中,之后无论获取多少次该bean,都返回同一个对象。Spring默认采用单例方式,减少了对象的创建,从而减少了内存的消耗。

但在实际开发中,是存在多例需求的,Spring也提供了选项可以将bean设置为多例模式。

下面接着IoC入门案例说明Spring单例模式的使用,首先在com.daojie.domain包中创建Cart类,该类中无任何内容,相关代码如下:

接着在application Context.xml文件中配置<bean>标签,代码如下:

最后在TestDemo测试类中创建test05方法,相关代码如下:

第6、8行代码通过相同的id获取对象。第7、9行代码分别输出c和c1的地址,如果两个地址一样,说明Spring默认采用单例模式,两个地址不同,则说明Spring默认采用多例模式。

执行test05方法,输出结果如图1-36所示。说明Spring默认采用单例模式。

图1-36 输出结果

如果要设置Spring为多例模式,可在applicationContext.xml文件中将配置的<bean>代码进行修改,修改后的代码如下:

在代码中,scope="prototype"表示设置为多例模式,如果scope="singleton",则表示设置为单例模式。

设置为多例模式后,执行test05方法,输出结果如图1-37所示。

图1-37 输出结果

从输出结果可见c和c1的地址不同,说明Spring为多例模式。

Spring默认情况下是单例模式的,存在内置Map中,之后无论获取多少次该bean,都返回同一个对象,该对象是被Spring容器所持有的,那么bean的生命周期有多长呢?只有容器退出时,bean才会被销毁。

Spring在多例模式下,在初始化Spring容器时只是管理bean,并没有创建对象,在调用bean时Spring才会创建对象。每次创建的都是新对象,而且该对象并没有被Spring容器所持有,如果要销毁对象则取决于程序员什么时候写代码销毁。

下面接着入门案例说明单例模式和多例模式下是什么时候创建对象的,首先在Cart类中创建无参构造方法,创建对象时会调用无参构造方法。Spring为多例模式,执行test05方法,输出结果如图1-38所示。

如果Spring为单例模式,执行test05方法,输出结果如图1-39所示。

图1-38 多例模式输出结果

图1-39 单例模式输出结果

通过在test05方法的初始化代码中打断点并逐行运行代码可知,在Spring单例模式下,执行初始化Spring容器代码时调用无参构造方法。在Spring多例模式下,只有调用bean代码时才调用无参构造方法,而且是调用一次bean就调用一次无参构造方法。

注意:

在Java代码中添加断点可以查看程序的执行过程,在代码区域的某行代码左侧双击即可添加断点。然后右击test05方法,在弹出的快捷菜单中选择Debug As→JUnit Test命令,然后在弹出的对话框中单击Yes按钮。此时,选中打断点的代码,按【F6】功能键即可执行该行代码,同时显示输出结果。

1.4.8 Spring懒加载机制

视频

Spring懒加载机制

Spring懒加载机制可以规定指定的bean不在启动时立即创建,而是在后续第一次使用时才创建,从而减轻在启动过程中对时间和内存的消耗。

懒加载机制只对单例模式bean有作用,对于多例模式bean设置懒加载没有意义。

下面接着入门案例说明Spring懒加载机制的应用,首先在com.daojie.domain包中创建Dog类,并添加无参构造方法,相关代码如下:

接着在applicationContext.xml文件中配置<bean>标签,使用单例模式和懒加载机制,相关代码如下:

最后在测试代码中创建test06方法,相关代码如下:

执行test06方法,输出结果如图1-40所示。

图1-40 单例模式懒加载机制输出结果

从输出的结果无法确定无参构造方法是哪一行代码调用的,如果懒加载机制有效,将是第8行代码调用无参构造;如果懒加载机制不起作用,则是第6行代码调用的。通过打断点并逐行运行代码可知,执行第6行代码时并没有调用无参构造方法,执行第8行代码时才调用无参构造方法,说明懒加载机制有效。

如果需要将applicationContext.xml文件中所有bean都使用懒加载机制,只需要修改生成xml文件时的自动输入内容即可,修改后代码如下:

将光标定位在第5行代码最右侧的>括号前,按【Enter】键,然后输入default-lazy-init="true"代码即可。

注意:

如果同时设定全局懒加载机制和指定局部懒加载机制,并且配置不同时,那么局部配置覆盖全局配置。例如,局部懒加载机制为false,全局懒加载机制为true,运行时全局配置对于该局部配置无效。

1.4.9 配置初始化和销毁方法

视频

配置初始化和销毁方法

在Spring中,如果某个bean在初始化之后或销毁之前要做一些额外操作时,可以为该bean配置初始化和销毁的方法,在这些方法中完成功能。

方法执行顺序:在Spring创建bean对象时,先创建对象(通过无参构造或工厂),然后立即调用init方法执行初始化操作,最后此bean就可以调用其他普通方法,而在对象销毁之前Spring容器调用其destory方法执行销毁操作。

下面接着入门案例举例说明,首先创建com.daojie.dao包,在该包中创建ProdDao类,假设该类在DAO层,即数据访问层,希望在调用方法之前是和数据库连接的,而不使用对象时是断开数据库连接的。根据以上需求输入代码如下:

接着,在applicationContext.xml文件中配置<bean>标签,相关代码如下:

在代码中,init-method为初始化方法,可以保证在ProdDao类中调用方法之前调用;destroy-method为销毁之前调用的方法,可以保证在销毁之前调用。

最后在TestDemo测试类中创建test07方法,相关代码如下:

第7行代码调用ProdDao类中addProd()方法;第8行代码使用close()方法销毁容器。执行test07方法,输出结果如图1-41所示。

图1-41 配置初始化和销毁方法的输出结果

从输出结果可知,首先调用无参构造方法,然后在调用addProd()方法之前调用init()方法执行初始化操作,调用addProd()方法后销毁对象之前调用destory()方法执行销毁前断开数据库的操作。