从IoC容器中获取Bean
说明:
(1)在前三篇博客中,介绍三种创建对象的方式;本篇博客就介绍如何从IoC容器中提取对象;
(2)然后,本篇博客也附带介绍了<bean标签的id属性和name属性;
从IoC容器中获取Bean
简介
说明:
(1) 很容易理解,直接根据对象的标识,从IoC容器中去获取对象就可以了;
示例
<bean>中【id属性】和【name属性】
【id属性】和【name属性】相同点和区别简介
(1) 本质上来说,【id属性】和【name属性】是一样的;
(2) 无论如何,在一个配置文件中是不允许重复
(3) 如果当前工程有多个配置文件,在不同的配置文件中允许重复;而且,后创建的对象会覆盖IoC容器中先创建的那个对象;
(1) 见【2.【id属性】和【name属性】演示】中的【演示2】 ;
【id属性】和【name属性】演示
演示1: 如果当前工程有多个配置文件,在不同的配置文件中允许重复;而且,后创建的对象会覆盖IoC容器中先创建的那个对象;
比如这儿有多个配置文件:
…………………………
说明:
(1) (PS:意外收获:【ClassPathXmlApplicationContext()】方法的参数是可变参数;)
演示2:name属性,可以定义多个对象标识
id属性,建议只写一个对象标识
说明:在绝大部分情况下,一个对象有一个标识就够了;所以,一般我们优先选用【id属性】;
Spring的<bean允许,既没有【id属性】也没有【name属性】的; (很少遇到,作一般性了解)
这种情况,在日常开发中很少遇到,仅作一般性了解;
路径表达式用法
说明:
(1) 本篇博客的内容是:路径表达式的内容;包括【加载单个配置文件】、【加载多个配置文件】、【当前类路径是什么意思】、【路径表达式的几种写法】
(2) 本篇文章还有一个问题尚未解决:路径表达式中,如何以相对路径的方式,访问在包中的配置文件。也就是,IDEA如何设置访问相对路径下的文件,这个问题暂时搁置
路径表达式:加载单个配置文件
【当前类路径】是程序编译后的路径
路径表达式:使用数组、加载多个配置文件
类路径表达式的几种写法
(0) 上面的config.xml就是指代applicationContext.xml;
(1)class:config.xml
(2)classpath:com/imooc/config
……………………………………………………
这儿还有疑问? 遇到未解决的问题??????????? 这个问题暂时搁置吧~~
这是因为:
idea默认不会把.java下的xml配置文件转移到生成的target中。也就是说上述文件读取需要使用如下绝对路径:注意:前面需要加上file:
(3)classpath*:com/imooc/config.xml
这个的意思是:去扫描【所有jar包】和【自己工程中,com.imooc包】中的名字是conifig.xml的文件
(4)classpath:config-*.xml
意思是:扫描classes根路径下,名字为config开头的xml文件;
由此衍生的一个问题:前面在介绍<bean的id和name属性时,介绍过遇到多个配置文件时,后加载创建的对象会覆盖掉之前创建的对象;那么按照这种通配符的写法,如果遇到多个config开头的xml文件时,先加载哪个嘞?:其是按照文件名ASCII码的顺序加载,比如有configa.xml和configb.xml;那么会先加载configa.xml;
从这个通配符的写法、可以加载多个配置文件的例子可知:我们配置文件的命名最好有一定规则;自然,在一般情况下,也要尽量保持所有配置文件中的<bean的id或name都是唯一的;因为,只要<bean的id或name都是唯一的,无论配置文件加载顺序如何,都不会出现对象覆盖的问题了;
(5)classpath:com//config.xml
意思是:扫描com包下,任何子包中的名为config.xml的文件;
很少使用这种路径表达式,因为扫描范围太大了;在通常情况,会使用一个单独的目录来专门保存所有的配置文件;
(6)file:c:/config.xml
意思是:扫描本地磁盘C盘下,名字为config.xml的文件;
这种路径表达式很少使用;
利用setter实现对象依赖注入
说明:
(1) Spring IoC容器则通过配置的方式,完成了【对象实例化】和【对象与对象之间的依赖关系】;从而,可以解决传统编码方式的不足;
(2) 在前面,介绍的是【对象的实例化】;即,在IoC容器中,实例化单个Bean;然后,在前面几篇中,介绍【对象实例化】的时候,这些类的属性都是基本数值类型,所以,比如前面在实例化如Apple对象的时候,也不把这些基本数值类型的属性看成是对象,即也不认为在实例化Apple对象的时候蕴含了【对象与对象之间的依赖关系】;
(3) 然后,接下来就介绍,如何在Spring IoC容器中设置【对象与对象之间的依赖关系】;这个过程称之为依赖注入;在这儿,比如,我们把类中的基本数值类型也看成是对象了;
(4) 【对象依赖注入】有两种方式:基于setter方法注入对象,和基于构造方法注入对象;本篇博客介绍基于setter方法注入对象;
(5)【基于setter方法注入对象】其过程就是,先通过无参构造获取对象,然后调用对应的setter方法给对象的属性赋值,完成对象的初始化;
一:对象依赖注入:引入
可以看到,要实现“小孩吃苹果”这个操作,Child类对象就要依赖于Apple类对象;但是基于IoC容器的理念,不是在Child对象中new一个Apple对象,而是由IoC容器,将Child对象和Apple对象创建好了之后,再动态的进行依赖注入;依赖注入,说白了就是将两个对象关联起来;
二:对象依赖注入:这儿介绍对象依赖注入的第一种方式【基于setter方法注入对象】
(1)【对象依赖注入】有两种方式:基于setter方法注入对象,和,基于构造方法注入对象;
(2)基于setter方法注入对象:这是日常开发中常用的方式;
(3)基于setter方法注入对象:其有两种使用场景;
1.基于setter方法注入对象:第一种使用场景:静态数值注入:直接给Apple对象的如String、float等属性赋值;
这种场景最重要的是要理解:一些基本数据类型的属性(比如,String,float,int),给这些属性赋值的时候,也算是对象依赖注入,因为这些基本数据类型都有对应的包装类;
(1)一个标准的范例
创建一个maven项目:s03:
创建两个演示用的类:Apple类和Child类:
Apple类:
说明:
(1) Apple类没什么好说的,只是一个简单的javaBean;
(2) 强调一下,Apple类的属性都是private的,然后可以通过setter方法来给这些属性赋值;
Child类:
说明:
(1) Child类没什么好说的,只是一个简单的javaBean;
(2) 强调一下,Child类的属性都是private的,然后可以通过setter方法来给这些属性赋值;
然后,在pom文件中,引入Spring Framework的依赖;
然后,在resources目录下创建Spring配置文件:applicationContext.xml:
applicationContext.xml:
说明:
(1) 使用诸如【<property name=“title”
value=“红富士”/】的方式时,是通过调用对应的setter方法来完成对象的实例化的;
创建程序入口类,去IoC容器中获取对象,测试:
SpringApplication类:
说明:
(1) 为了验证,其确实是通过调用setter方法来给属性赋值的,在Apple类的setter方法中,输出点东西:
(2) 在Apple类的无参构造中,输出一点内容:用以说明,【利用setter实现对象依赖注入】来创建对象的时候,其是【先使用无参构造创建对象,然后再使用setter方法给对象属性赋值】的;
(3) 运行结果:说明IoC容器在实例化Apple对象的时候,发现诸如【<property name=“title”
value=“红富士”/】后,是通过对应的setter方法,来给当前对象的属性赋值的;
(2)一种错误的情况:比如,此时在Apple类中增加一个price属性,但是不添加该属性的setter方法:
如果,此时在applicationContext.xml中,也通过【<property name=“price”
value=“20.7”/】去给Apple对象的price属性赋值的时候:
但是,不管这个报错,如果强制执行嘞?
报错信息部分摘录:
解决办法,就是在Apple类中加上price属性的setter方法;
(3)然后,说明一点:IoC容器也会自动把【可以强转为数字的字符串】转为【对应的数字类型】
但是,在日常开发中,一个类中,并不是所有的属性都是静态数值。(自然,不能否认,在这儿静态数值有对应的包装类,其也可以看成是对象啦)
( 接上 )
2.基于setter方法注入对象:第二种使用场景:一般意义上的对象的注入:给Child对象的Apple属性赋值;
(1)一个标准范例
在applicationContext.xml中配置一个Child对象:
applicationContext.xml:
说明:
(1) 啰嗦一下,两个对应的说明
在程序入口类SpringApplication类中,去IoC容器中获取对象,测试:
说明:
(1) 为了更好的看到对象的创建过程,这儿在Child的无参构造中打印一点东西;在Child的setName()方法和setApple()方法中打印一点东西;
(2) 运行结果
3.Summary
IoC在项目中的重要用途
说明:
(1) 一个案例:说明如何使用IoC容器,来使软件过程中的对象解耦,进而让软件团队成员协作之间也出现解耦;
(2) 通过这个案例,能够体验,通过使用spring框架,能够实现【对象之间的解耦】,从而可以实现【人与人之间的解耦】,最终目的是【大幅度提升项目的开发效率】;
(3) 这篇博客中的内容比较简单,就是【在开发一个大型软件时,开发不同模块的人可以维护不同的applicationContext.xml,实现对象之间的解耦,从而实现人与人之间的解耦】,但这种思想十分重要!
(4)这篇博客长期意义不是太大,仅仅看下就行,因为后面会在实际项目中更加规范、细致、完整地演示spring的应用。
一:案例演示
1.准备一个演示用项目:s04
为了演示,创建一个新的工程s04:
然后,引入spring Framework的依赖:
然后,创建spring配置文件:
说明:
(1) 为什么要创建两个配置文件?
这是出于项目管理的目的;在一个团队中,往往有技术好的人,有技术差点的人;Dao,实现数据库的增删改查,这是比较简单的,这部分工作可以交给初级程序员去开发;Service,是系统中最核心的实现代码,这部分需要交给中高级程序员去开发;开发Dao的初级程序员和开发Service的中高程序员,各维护一个配置文件好点,这样以后,每个人只负责自己的模块就行,可以减少工作上的交叉;
2.创建对应的类
dao包中的BookDao接口:
dao包中的BookDaoImpl(Impl表示,这是BookDao接口的实现类):
说明:
(1) 这个类只是演示用的,只打印了一句话;
service包中的BookService类:
说明:
(1) service中需要调用dao中的方法;所以,在service类中需要有dao的对象,自然后面,我们使用的spring
IoC的方式去注入【BookDao类型的对象的,这个属性】;
3.在application配置文件中去设置IoC容器中的对象
在application中去配置对象:
applicationContext-dao.xml:
说明:
(1) 啰嗦一点
applicationContext-service.xml:
说明:
(1) 因为在service对象中,需要用到BookDao类型的对象,自然去设置BookService类中的bookDao属性就可以啦;
(2) 在applicationContext-service.xml中直接去ref引用applicationContext-dao.xml中的bean其会报错(可能是IDEA版本比较老的问题);设置一下就OK了;( _ _
PS:这个问题,比较好的、一劳永逸式的设置方式尚不清楚???__ )
4.创建程序入口类,测试
创建程序入口类BookShopApplication类,去测试:
说明:
(1) 运行结果
二:Summary
1.一个案例
需求: 比如,这个系统要更换数据库,把MySQL数据库换成Oracle数据库;
策略:
(1) 编写Oracle的Dao
由于Mysql和Oracle在底层语法上存在不同,所以为此,Dao部分需要重新编写,由于前面编写了BookDao接口,为了应对Oracle的需求,只需要写一个实现了BookDao接口的面向Oracle的实现类就好了;
(2) 在applicationContext-dao.xml中将bookDao那个<bean的class更换为新编写的Oracle的dao类
这样就OK了。
(3) 再次运行的结果:
(4)
之所以dao部分修改了,service部分不会报错的原因是:我们已经定义了BookDao接口,无论是MySQL数据库的实现Dao类,还是Oracle数据库的实现Dao都实现了相同的接口,所以,在IoC容器注入的时候,并不会因为实现类发生了变化,而产生无法注入的情况;
2.Conclusion
可以看到,这儿很明确的实现了对象与对象之间的解耦,进而可以实现人和人的工作实现了解耦;即,正是由于对象之间解耦了,所以工作的方式也产生了变化,由【原先的多人协商才能决定一个事情】变成了【两个人之间约定一个<bean就可以了】;这种变化,对于大型软件更加重要,可以节约大量的时间等成本。也可以看到,spring框架之所以在实际中会被大规模商用的原因;也能了解到,为什么spring框架比其他框架都显得更为重要的原因;
利用构造方法实现对象依赖注入
说明:
(1) 上面介绍了使用setter方法注入对象;本篇博客就介绍使用构造方法注入对象;
(2) 基于【带参构造方法】实例化对象;]中的使用构造方法来实例化对象】非常相似,只是传入的信息由静态的数值变成了动态引入的对象;
一:利用构造方法实现对象依赖注入
继续基于【[利用setter实现对象依赖注入]】中的s03工程:
基于【构造方法实现对象依赖注入】的方式,在applicationContext.xml中增加对应的 <bean>:
为了方便观察效果,在Child类的有参构造中输出一点内容:
运行结果:
可以看到,此时是通过带参构造的方式去实现对象依赖注入的:
分析:传值过程
二:Phased summary
(1) 这么多创建对象的方式,还好,很简单;
(2) 要想实现某种功能,按照Spring的SOP去做就行;
(3) 自然,底层都是Spring框架在支撑;为了能够更好的理解其背后的原理,就能感到适当、适时阅读Spring源码的必要性;
注入集合对象
本篇博客内容: 如果类的一个属性是集合时,如何注入这个集合对象?本篇博客就是介绍这些内容;
说明:
(1) 注入集合对象还是比较常用的;
(2) 在可以使用【value-ref】或者【ref】的地方,都可以支持内置<bean>;
为了方便后面演示,创建一个基于maven的项目s05;
readme.md:项目说明文档
pom.xml:
说明:
(1) 没什么好说的,就是设置国内maven仓库;引入spring框架的依赖;
applicationContext.xml:
说明:
(1) 就是xml的文档说明;spring配置文件的schema约束;
Computer类:一个简单的bean
说明:
(1) 一个简单点bean没什么好说的;注意使用IDEA工具自动生成getter与setter方法,还有重写toString方法便于演示
Company类:一个简单的bean
说明:
(1) Company类的属性是集合类型,以便后面演示如何注入集合类型对象;
SpringApplication类:程序入口类
一:List
案例:
在applicationContext.xml中定义 <bean>:
说明:
(1) 这儿是通过<property来设置的属性,即也就是使用setter方法实现的依赖注入;
运行结果:
List在传递过程中,类型的变化:
List的特点是允许出现重复的:
二:Set
案例:
在applicationContext.xml中定义 <bean>:
运行结果:
Set在传递过程中,类型的变化:
这是因为:
三:Map:
案例:
在applicationContext.xml中定义 <bean>:
运行结果:
问题与不足:
解决办法:在可以使用【value-ref】或者【ref】的地方,都可以支持内置<bean,如下:
Map在传递过程中,类型的变化:
四:Properties
与Map类似,Properties也是键值对,只是Properties的键值均要求是String;所以,在日常开发中,常用Properties保存一些静态的文本数据,如登陆时密码用户名这些;
关于Properties的详细内容,去参考jdk文档中有关properties的介绍就可以啦。
案例:
在applicationContext.xml中定义 <bean>:
运行结果:
Properties在传递过程中,类型的变化:
查看容器内对象
说明:
(1) 如何查看容器内有多少个对象?
前面介绍了如何在容器内创建对象和设置对象之间的关系;但是,所有这些信息,都是我们自动脑补去想象的;如果一个项目足够大,对象很多,单靠去想就很吃力,那么我们如何知道当前容器中到底有哪些对象,这些对象又是什么类型的呐?
(2)主要内容包括:
【context.getBeanDefinitionNames()】:获取容器中所有id的数组;【context.getBean(beanName).getClass().getName()】获取bean的类型;【context.getBean(beanName).toString()】:获取bean的具体内容;
1.为了演示:在applicationContext.xml中编写如下内容:
说明:
(0) 在applicationContext.xml中写了很多种<bean,其目的就是尽可能的为【后面的演示】提供不同情况的<bean;
(1) 在【【id属性】和【name属性】的区别;】就已经介绍过,一个<bean>可以没有id或name;所以,这儿也在applicationContext.xml中添加了没有id或name的匿名<bean>;
(2) 因为【在可以使用【value-ref】或者【ref】的地方,都可以支持内置<bean】,所以这儿也添加了一个内置的<bean;
2.编写入口类:查看容器中的对象
SpringApplication类:
说明:
(1) 【context.getBeanDefinitionNames()】:获取容器中所有<bean id>的数组;
可以发现,那些没有写id或name的匿名<bean>也是有的,而且都保留了,(经过实测,即使是这两个对象<bean>完全一样)不存在覆盖的情况;
然后,可以发现,在IoC容器中,那个内置<bean>并没有对应的beanId;
(2) 对于那些没有写id或name的匿名<bean,如何获取?
在【<bean>标签【id属性】和【name属性】的区别】已知,要获取一个匿名<bean>,需要使用类的全路径,那么对于一个类有多个匿名<bean>的情况,又如何获取我们想要的<bean>嘞?
要想获取指定的bean就需要带上后面的【#+数字了】
;
其实,由于这种具体的对象和其在applicationContext.xml中书写<bean的顺序有关,而这是非常容易搞错的,比较难维护,所以在实际中匿名<bean使用的并不多;
(3)【context.getBean(beanName).getClass().getName()】获取bean的类型
(4)【context.getBean(beanName).toString()】:获取bean的具体内容