第2章 装配Bean
这一章开始算正式讲Spring的操作步骤,原理,如何配置,为什么。
2.1 声明Bean
此书都是环绕着一个案例,一个JavaBean的选秀大赛,Spring是编程者,于是该书定义了第一个Spring接口.
代码我不是很想写,我还是以自己的方式来讲解代码吧
首先定义了一个Perfomer接口,里面就一个perform()方法。
2.1.1 创建Spring配置
按前面说的很清楚,Spring就像是一个容器,是一个基于容器的框架,如果没有配置Spring,那他将是一个空容器(我喜欢叫他空杯子),对我们也毫无用处,所以我们应该告诉Spring他需要加载哪些bean和如何装配这些bean,简单的来说就是应该往杯子里加入什么料。
自从Spring3.0开始,Spring就有了两种配置Bean的方式,一种是用xml配置,可以一个或者多个,还有一个就是用java注解的方式(我喜欢用这个)。
首先介绍用xml配置的,刚开始要在beans 元素下配置一些命名空间,具体含义如下
不做过多阐述,了解下就行。
2.1.2 声明一个简单的Bean
还记得刚刚定义的那个接口吗,现在要引入第一个人,这个人是一个杂技师,他声明了一个int类型的字段beanBages = 3 然后一堆构造方法
接着在xml上进行声明,然后加载Spring配置文件,过程略。
2.1.3通过构造器注入
还是刚刚那个杂技师,这时候采用构造器注入参数的办法,只需要在spring配置文件中对应的bean下添加<constructor-arg value="你想要的参数">即可,简单
通过构造器注入对象的引用
这下这个杂技师进化了,他不仅是一个杂技师,他还是一个会朗诵的杂技师,于是在原来的pojo中添加一个字段poem,当然这个字段所属于的java类型也是Poem,这个poem是通过构造器注入的,所以在构造器也写上相应的参数poem,当然,还要写一个Poem的interface,再由Snoonet29继承即可。
这时要配置xml了,首先配置我们刚刚写完的Snnonet29的bean 他的id =Snnonet29 ,接着在杂技师的bean中继续添加<constructor-arg ref="Snnonet29"> 注意!这里是ref不是value,ref是关联bean的,里面的值为bean的id,value 是关联具体的值,写1就是1,写2就是2!这里特别注意。
这里说一个细节:PoeticJuggler(会朗诵的杂技师)类中有两个构造方法,一个是两个参数的,一个是一个参数的,具体选择哪个方法是根据在配置中声明的注入类型,如果注入类型错误则报错。
通过工厂方法创建bean
有的时候静态工厂方法是实例化对象的唯一方法,所以Spring也支持通过<bean>元素的factory - method属性来装配工厂创建的bean
这里书上的例子为 : 首先创建了一个stage类,里面有一个静态的方法getInstance其中return的是Stage类型的(也就是工厂模式),该类为单例。
这里提醒下,stage没有公开的构造方法,取而代之的是一个静态的方法放回相同的实例(这就是单例),这下问题来了,Spring是怎么把一个没有公开的构造方法配置为一个Bean呢?
解决方案很简单,在bean中配置factory-method = “getInstance”。
2.1.4Bean的作用域
声明作用域的方法就是在bean中配置scope=“作用域” 用注解就是@scope(“作用域”),默认的是singleton (单例)
2.1.5 初始化和销毁Bean
当我们实例化一个Bean的时候,我们要执行一些初始化操作使其处于可用的状态,相反,如果不用了,就要将其从容器中移除
给Bean定义和销毁操作,只需要使用init-mehod 和destroy-mothod参数来配置bean
例子给了个很简单的开关灯,方法名分别为turnonlight turonofflight 然后再对于配置相应方法名就行,记住就可以了。
默认的 init-mehod 和destroy-mothod
这个就要在beans中配置default-init(destory)-method了 ,这是为应用上下文中所有的bean设置共同的初始化和销毁方法,意思就是,每一个bean前面都要启动该方法,每一个bean最后也要执行销毁方法,就是全局的。
2.2注入Bean属性
在我们声明字段的时候,同时也拥有一组存取的方法,也就是getter和setter,Spring可以借助set方法来配置属性的值,得以实现setter方式的注入。
这里书上的例子就不列举了,很简单
2.2.1注入简单的值
简单值无非就是简单java类型,什么int String float这些
注入方法相当简单,就是在bean下配置一个<property name=“字段名字” value="字段内容">一旦声明的类被实例化,Spring就会调用<property>元素所指的属性注入值,如果注入的属性为int ,value= “123” , 他会自动把“123” 转换为int类型的123 当然直接设置value=123应该也是可以的。
2.2.2引用其他的bean
不是所有引入的bean都是一个简单类型,书中介绍了一个演奏家要演奏一个saxphone 实现了Instrument接口,并且要通过setter注入
这时候在xml配置中首先声明一个saxphone的bean,然后property name=“字段名” ref = “saxphone” 注意后面是ref了, 关联一个bean用ref。然后就ok了
这些特性不是Spring的特性,而是面向接口的优势所在。
当然,如果这个演奏家不想演奏saxphone了,想演奏piano了,只需要定义一个piano类实现Instrument接口,然后在xml中配置该bean,把ref换成piano即可,是不是很方便。
注入内部的Bean
上面的saxphone和piano属于公共的bean,随便谁都能够引用,而且用的还是同一个saxphone,有的时候人就是那么自私,东西不想给别人分享,这样Spring也有办法,那就是定义一个内部的bean,
首先呢我们把刚刚的bean删了(其实不删也可以),把那些bean转移到演奏家的内部<bean>中。一样还是写<property>不过在其内部声明一个bean,指向一个class即可,这样只有在演奏家的内部才能使用该bean,别的bean用不到。
2.2.3使用Spring的命名空间P装配属性
这个就是为了少些尖括号,以另一种形式配置bean,略过。
2.2.4装配集合
讲了那么多都是装配简单属性(用value)和引用其他属性(用ref)。当Bean的属性值为复数(集合)的时候,应该如何配置呢?
Spring提供了四种类型的集合配置元素,具体如下:
装配List,Set 和 Array
装配List的步骤也简单,就是在bean的property里添加list元素,然后在list元素里面添加自己想要的成员<value><ref><bean><null> 都行,这个看需求。
这里注意的一点是,数组也可以用list元素来装配,同样的也可以用set来装配
再次说明,无论<list>还是<set>都可以用来装配类型为java.util.Collection的任意实现或者数组的属性,不能因为属性为Set就必须用set来装配,List也一样,不做过多阐述、
装配Map集合
map的装配方式和上面List的装配方式很相似,只需要把list元素改为map元素即可,但是在map元素里面的是一个<entry>里面的属性有key和value,分别对应map中的键值(这还用说?)
下面书上列举了一个表说明了,键和值可以是简单类型(key value)也可以是其他bean的应用(key-ref value-ref)
当键和值都不是String的时候可以用map元素,反之,则可以使用properties元素
装配Properties集合
其实装配Properties方式也是大同小异,就把上面的map换成props,里面的元素换为prop ,属性为key,key中直接输入String类型的值即可
props适用于String-to-String的情况
2.2.5装配空值
没什么说的,就是属性装配一个null值,使用<null/>元素
2.3 使用表达式装配
这里,在Spring3 中引入了一个Spring专用的表达式叫SpEL(全称请百度)。他是一种强大、简介。。。咳咳,反正后面就在说这个表达式多么好用多么牛逼,该书惯例。
这里提下SpEL的一些特性
- 使用Bean的ID来引用Bean
- 调用方法和访问对象的属性
- 对值进行算术,关系和逻辑运算
- 正则表达式的匹配
- 集合操作
2.3.1 SpEL的基本原理
SpEL表达式的目的就是为了通过计算获得某个值,也就是可以在配置文件中进行逻辑处理,最简单的SpEL求值就是对字面值、Bean属性或者某个常量进行求值。
字面值
顾名思义,字面值就是字面上的值,是什么就是什么,不用绕弯子,例如在property元素,我想给value一个5的值,value部分可以这样写:value=“#{5}”,我就纳闷了,这和value=5不是一样的吗?何必要脱下裤子来放屁,同理,boolean类型,String类型等,也可以通过这种方式来写,甚至可以混搭,比如value=“The value is #{5}”。说实话,挺无聊的,这样写完全没有必要,你以为这有什么优点,其实一点优点也没,书上作者也说了,“我承认在SpEL表达式中仅仅包含字面值没太大用处” ,后面还补了一句我感觉挺有道理的,“但是请记住复杂的SpEL表达式通常是由简单的表达式构成的”,
学习过程都是循序渐进的,不都是从简单的开始学起吗?
引用Bean、Properties和方法
SpEL表达式能做的另一个事情就是可以通过ID对Bean进行引用,来个例子,比如我要把一个saxphone的Bean装配到instrument的Bean中,可以这样写
<property name="instrument" value=#{saxphone}/>
这样看上去好像又觉得有点多余,我何必要用SpEL来写的,我直接用ref引用不是一样吗?还少打了字,没错!在这里的确又是多余的,没有看出任何的优越性。那么,SpEL的优越性到底在哪呢,好,翻一页继续看。
这里有一个需求,有一个模仿者叫carl,他没有自己的风格,或者说他的风格就是模仿别人唱的歌,现在我要把歌唱家kenny的歌装配到carl上让他去模仿,要怎么装配这个类呢?
<property name="song" value="#{kenny.song}"/>
这里可以看出,我们无法直接获得song这个属性,而是通过SpEL表达式来获得,通过kenny这个bean来取song这个字段,这样可能不好理解,其实换成java代码的话是这样
carl.setSong(kenny.getSong())
这样应该可以理解了吧
我们终于看到了一点SpEL的优越性,可以让他做出点有用的事情来了,但这仅仅是个开始。
我们不仅可以引用Bean的属性,我们还可以调用其方法,现在有一个需求:主角还是那个模仿者carl,这下他模仿的东西不能自己挑了,而是通过一个歌曲选择器(songSelect)的一个select()方法给模仿者随机挑选一个歌曲,可以这样写:
<property name="song" value="#{songSelect.selectSong()}"/>
不仅如此,我们还要把歌词全部装换为大写
<property name="song" value="#{songSelect.selectSong().toUpperCase()}"/>
这样需求就完成了,如果该方法返回一个null,则会抛出一个空指针异常,但是我们不想抛出异常,因为异常是一件很麻烦的事情,SpEL给我们提供了一个叫做null-safe存取器
<property name="song" value="#{songSelect.selectSong()?.toUpperCase()}"/>
对比上面的,也就多了“?”,还以为有多高大上,这样可以避免空指针异常,在执行方法前,会确保左边的方法不为null,当如果为null的时候,将不进行右边方法的操作,这样可以完全避免异常了。
操作类
在SpEL中可以用T()运算符来调用类的作用域和常量,例如使用java的Math类可以这样写 T(java.lang.Math)既然得到了Math类,那么Math类的一些常量和方法也很方便的装配到bean中,比如我们想取PI的值
<property name="pi" value="#{T(java.lang.Math).PI}"/>
其实这一个例子就够了,书上还写了一大段话,看着头疼。
2.3.2在SpEL值上执行操作
这里提供了一张运算符的表
使用SpEL进行数值运算
简单的说就是可以在SpEL表达式中对数值,字符串什么的进行所有java的基础算术运算,并且还增加了一个“^”的运算,这个运算用来执行乘方的比如2^3=8 3^2=9 例子就不列了,简单
再次重申下,在java语言中,+运算符可以用于字符串的连接
比较值
其实也和java一样,什么== < >这些 对应的文本类型eq lt这些。。java怎么用SpEL就怎么用,但是SpEL可以提供>=(大于等于) <=(小于等于)操作,这个是极为方便的。
逻辑表达式
逻辑运算符无非就是and or not(!) 操作,具体操作方法不多说,也不难,这有一个特别强调的地方就是SpEL没有提供and和or的运算符,也就是不能使用&和||来取代and or ,但是not却可以用!来替代,不知道发明这个的是怎么想的。
条件表达式
主要是三元运算符?:
这个在以前写java语言的时候经常用到,SpEL用法和java的基本上一样,但是SpEL有一个简化的例子
直接贴代码<property name="song" value = #{kenny.song?:‘happy’}
解释:这里SpEL表达式里判断 kenny.song是否为空,如果是,则把happy赋给song,反正,就把kenny.song赋给song,这属于三元运算符的简化,其实也可以按原来逻辑写,就是麻烦了点
2.3.3在SpEL中筛选集合
SpEL最让用户惊奇的技巧就是操作集合,来让我们看看是否真的有说的那么惊奇
在SpEL操作集合和java操作类似,但是SpEL同样具有基于属性值来过滤集合成员的能力。SpEL还可以从集合的成员中提取某些属性放到一个新的集合中
这里列举了一个City类,类里面定义了一个List集合,集合里放了很多城市,城市里有个人口数量的常量字段。
访问集合成员
前面说了,SpEL操作集合和java操作集合类似,那么回顾下java是如何取City中第二个元素呢? City[2],所以SpEL也是这样取的只不过多了#{}
中括号([])运算符会始终通过索引访问集合中的成员
[]运算同样可以获取Map元素,例如,加入在City对象以城市名作为key,则取value可以用City["cityname"]来取
[]运算符的另一种用法就是从Properties中取值,这里有点不一样
这里一个例子:我们通过<util:properties>元素在Spring中加载一个properties的配置文件,具体如下
<util:properties id="settings" location = "classpath:settings.properties">
这段代码意思是是把classpath里的一个setting.properties文件声明给settings,Spring吧这个文件加载了出来,现在,我们要从settings中访问一个名为username的属性(该属性在setting.properties有定义),如下所示
<properties name=“username” value = #{settings['username']}
后面说了Spring为SpEL创造了两种特殊的选择属性访问方式:systemEnviroment 和systemProperties ,这个等用到了再去百度了解吧。
查询集合成员
有一个需求:我们想从上面的城市集合中查询人口多余100000的城市,在一般的方式是先把所有城市装配到Bean的属性中,然后在到bean中增加过滤不符合条件的逻辑。在SpEL中是需要运用查询运算符(.?[])就可以简化操作 value = “#{cities .?[population gt 100000]}”
除了.?[] 还有.^[] 和 .$[]功能分别是从集合中查找第一个匹配项和最后一个匹配项,这里查找的只是一个单数,注意用词。
投影集合
首先是概念,什么是投影集合?
集合投影是从集合的每一个成员中选择特定的属性放入一个新的集合中,SpEL的投影运算符(.![])完全可以做到这点
有一个需求,我们要把上面City的name单独取出来放入一个新的,类型为Sring的新集合中,我们可以这样做
<property name="cityName" value="#{cities.![name]}">
这样,首先我们创建了一个cityName的集合,然后集合的每一个属性都被赋予一个String类型的集合,中括号里面的name属性决定了,结果要包含什么样的成员。
不仅我们可以投影一个属性,我们还可以把cities里面的多个属性投影进去,操作步骤就是把另一个属性也放入中括号里,和前一个属性以逗号(,)隔开。
SpEL就介绍到这里了,我们可以看到他一些强大的地方,但是事务总不可能是完美的,因为SpEL最终还是一个字符串,不易于测试,也没用IDE来检测其语法,例如少了个括号,多了分号,是很难检测出来的,IDE只会告诉你你这行代码写错了,并没有告诉你哪里错了。所以书上给的建议是,在实在没办法用传统方式(或者用传统方式特别复杂)的情况下进行装配,可以考虑采用SpEL进行装配,但是也要小心,不要把过多的逻辑放入到SpEL中,否则自己会把自己绕晕。
2.4小结
划重点
1.Spring容器是Spring框架的核心
2.Spring自带了多种容器的实现,最简单的容器是BeanFactory,提供基础DI和Bean装配,当我们需要更高级的框架服务可以考虑使用ApplicationContext作为容器
3.装配Spring最常见的的方式是通过XML文件