今天开始系统学习Spring框架,使用的书籍是Spring in action第三版中文版。之前已有一定的Spring基础,有些部分可能不会记录的非常详细。
依赖注入将对象的使用和对象的管理(创建、赋值等)进行了解耦。
Spring通过应用上下文(Application Context)装载Bean的定义并把它们组装起来,Spring应用上下文全权负责对象的创建和组装,Spring自带了几种应用上下文的实现,它们之间的主要区别仅仅是如何加载它们的配置(xml配置、注解配置等)
//加载myxml.xml配置文件
ApplicationContext context = new
ClassPathXmlApplicationContext("myxml.xml");
//获得Bean
User user = (User)context.geatBean("User");
*
容器是Spring框架的核心,Spring容器使用依赖注入管理构成应用的组件,它会创建相互协作的组件之间的关系。
Spring自带了两种不同的容器:
1、BeanFactory,由org.Springframework.beans.factory.BeanFactory接口定义,他是最简单的容器,提供基本的DI支持
2、应用上下文,由org.springframework.context.ApplicationContext接口定义,基于BeanFactory之上构建,并提供面向应用的服务,例如从属性文件解析文本信息的能力,以及发布应用事件给感兴趣的事件监听者的能力。
*:大部分情况下我们使用第二种容器。
Spring自带了几种类型的应用上下文。下面3钟是比较常用的:
1、ClassPathXmlApplicationContext:加载位于应用系统classpath下的一个或多个XML文件。
2、FileSystemXmlApplicationContext:读取文件系统下的XML配置文件并加载上下文定义。
3、XmlWebApplicationContext:读取Wweb应用下的XML配置文件并装载上下文。
1、Spring对Bean进行实例化。
2、Spring将值和Bean的引用注入Bean对应的属性中。
3、如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()接口方法
4、如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()接口方法,将BeanFactory容器实例传入。
5、如果Bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext接口方法,将应用上下文的引用传入。
6、如果Bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()接口方法。
7、如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()接口方法。类似地,如果Bean使用init-method声明了初始化方法,该方法也会被调用。
8、如果Bean实现了BeanPostProcessor接口,Spring将调用它们的post-PoressAfterInitialization()方法。
9、此时此刻,Bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到应用上下文被销毁。
10、如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法。同样,如果Bean使用destory-method声明了销毁方法,该方法也会被调用。
第一步:
声明Bean,即创建对应的类
package xie;
public class Juggler implements Performer {
private int beanBags = 3;
public Juggler() {
}
public Juggler(int beanBags) {
this.beanBags = beanBags;
}
public void perform() throws PerformanceException {
Sysotem.out.println("JUGGLING" + beanBags + "BEANBAGS");
}
}
第二步:
创建Spring配置,有两种方式
xml方式:
在XML文件中声明Bean时,所有的Bean配置在Beans根元素下。
一个典型的SpringXML配置文件:
< beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd>
< !-- Bean declarations go here -->
< /beans>
在< beans>元素内,可以配置所有的Spring配置信息,包括< bean>声明。beans命名空间不是唯一的Spring命名空间,Spring的核心框架自带了10个命名空间的配置:
在Spring中配置声明的类:
< bean id=”duke” class=”xie.Juggler” />
这样就创建了一个由Spring容器管理的Bean,id属性定义了Bean的名字,也作为该Bean在Spring容器中的引用。
当Spring容器加载该Bean时,Spring将调用默认的构造器来实例化duke Bean,实际上,duke会使用如下代码来创建:
new xie.Juggler();
可以使用如下代码加载Spring上下文
ApplicationContext ctx = new ClassPathXmlApplication("***.xml");
Performer performer = (Performer) ctx.getBean("duke");
performer.perform();
如果不想使用默认的构造方法,则可以这样配置:
< bean id="duke" class="xie.Juggler">
< constructor-arg value="15" />
< /bean>
通过使用< constructor-arg>标签,可以告诉Spring额外的构造信息,此时对象的创建将调用带一个参数的那个构造方法。
如果在构造时想传入一个对象作为参数:
< bean id="obj1" class="...">
< bean id="duke" class="xie.Juggler">
< constructor-arg ref="obj1" />
< /bean>
这样,就可以吧名字为obj1的Bean作为duke构造器的参数了。(此处忽略了实现的可行性,只关注配置方法)
*注:如果属性是简单类型,则可以使用value。如果是对象类型,则使用ref
使用factory-method装配Bean
可以使用factory-method属性指定创建对象的方法
< bean id="theSingle"
class="..."
factory-method="getInstance" />
所有Spring Bean默认都是单例,要改变Bean的作用范围,只需要配置Bean的scope属性,scope属性有以下取值
*注:Spring中有关单例的概念限于Spring上下文的范围内,不像真正的单例,在每个类加载器中保证只有一个实例。Spring的单例Bean只能保证在每个应用上下文中只有一个Bean实例。没人可以阻止你使用传统方式实例化用一个Bean,或者你甚至可以定义几个< bean>声明来实例化一个bean。
为Bean定义初始化和销毁操作,需要使用init-method和destory-method参数来配置< bean>元素。init-method属性指定在初始化Bean时要调用的方法,destory-method属性指定了Bean从容器中移除之前要调用的方法。
< bean id="auditorium"
class="..."
init-method="turnOnLights"
destory-method="turnOffLights" />
auditorium Bean实例化后会立即调用turnOnLights()方法,在该Bean从容器中移除和销毁前,会调用turnOffLights()方法。
在< beans>标签中可以使用default-init-method和default-destory-method方法为所有的bean设置默认的初始化和销毁方法。
在Spring中可以使用< property>元素配置Bean的属性。< property>和< constructor-arg>在很多方面都类似,只不过一个是通过构造器参数注入值,一个是通过setter方法注入值。
< bean id="kenny" class="...">
< property name="song" value="my love"/>
< /bean>
一旦Kenny被实例化,Spring就会调用< property>元素所指定属性的setter方法为该属性注入值。通过上述配置,Kenny实例的song属性将被设置为“my love”。
*注:value属性可以指定String/int/float/double/boolean/long/char等简单类型的值。为它设置String类型和int等类型方式是一样的:使用“值”。指定引用对象使用ref属性
注入内部Bean
内部Bean:定义在其他Bean内部的Bean
< bean id="kenny" class="...">
< property name="instrument" >
< bean class="..." />
< /property>
< /bean>
内部Bean通过直接声明一个< bean>元素作为< property>元素的子节点而定义的。内部Bean不仅限于setter注入,我们还可以把内部Bean装配到构造方法的参数中。
*注:内部Bean没有id属性,因为我们永远不会通过名字来引用内部Bean,这也突出了使用内部Bean的最大缺点:它不能被复用。内部Bean仅适用于一次注入,而且不能被其他Bean所引用。
Spring的命名空间p提供了另一种Bean属性的装配方式。
通过加入一段声明可以使用命名空间p
< beans xmlns="http://www.springframework.org/schema/beans"
< !--加入的声明-->
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd>
< !-- Bean declarations go here -->
< /beans>
现在,可以使用P:作为< bean>元素所有属性的前缀来装配Bean的属性。
< bean id="kenny" class="..."
< !--使用my love装配song属性(通过setter方法) -->
p:song = "my love"
< !--使用id为saxophone的Bean装配instrument属性 -->
p:instrument-ref = "saxophone" />
Spring提供了4中类型的集合配置元素:
使用< list>
< bean id="hank" class="...">
< property name="instruments">
< list>
< ref bean="guiter" />
< ref bean="cymbal" />
< ref bean="harmonica" />
< /list>
< /property>
< /bean>
无论是< list>还是< set>都可以用来装配类型为java.util.collection的任意实现或者数组的属性。也可以使用< set>装配List集合,虽然这样有点怪,但确实似乎可以的,但是必须确保list集合中的每个元素都是唯一的(< set>重复的属性不会注入)。
装配map
< bean id="hank" class="...">
< property name="instruments">
< map>
< entry key="GUITER" value-ref=“guiter”>
...
< /map>
< /property>
< /bean>
上述中map key为String类型,value为对象类型
装配Properties
当要注入的类型为Properties时
< bean id="hank" class="...">
< property name="instruments">
< !--定义一个Properties集合-->
< props>
< !--定义成员-->
< prop key="GUITAR">STRUM < /prop>
< prop key="CYMBAL">CRASH < /prop>
< prop key="HARMONICA">HUM < /prop>
< /props>
< /property>
< /bean>
< property>的name属性声明了字段的名称,Properties的每个成员都有< prop>元素定义,其中“GUITAR”为Properties成员的键,它的值为“STRUM”
装配空值
使用< null/>元素即可。
< property name="xxx">
< null/>
< /property>
使用#{ }界定符来书写Spring表达式
简单的SpEL(Spring表达式)
< property name="count" value="#{5}"/>
这个表达式将count字段的值设为5,SpEL表达式还可以和非SpEL表达式混用:
< property name="count" value="the value is #{5}"/>
String类型的字面值可以使用单引号或者双引号作为字符串的界定符。
< property name="userName" value="#{"小明"}"/>
布尔类型
< property name="enabled" value="#{false}"/>
复杂的SqEL
引用Bean:可以通过Bean的ID直接引用Bean
< property name="instrument" value="#{saxophone}"/>
引用Bean的属性
< !--使用Bean ID为kenny的song属性值作为值-->
< property name="song" value="#{kenny.song}"/>
引用Bean的方法
假设有一个songSelector Bean,该Bean有一个selectSong()方法
< property name="song" value="#{songSelector.selectSong()}"/>
转换大小写
< !--将返回的字母转换为大写-->
< property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>
上述代码中如果selectSong()返回的是null,则会抛出一个NULLPointerException异常。使用null-safe存取器可以避免异常
< !--使用?判断-->
< property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>
Spring提供了4中自动装配策略。
1、byName:把Bean的属性具有相同名字(或ID)的其他Bean自动装配到Bean的对应属性中。
通过设置Bean的id与需要被自动装配的Bean对应的类的字段名称相同完成自动装配
< !--需要设置autowire属性-->
< bean id="kenny" class="MyClass" autowire="byName"/>
< !--如果Spring中还有一个Bean的id为name,并且MyClass类中有name属性,那么Bean kenny会自动为其属性name注入Bean name-->
2、byType:把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。
byType使用与byName类似,只不过此处检测的是类型是否相同。如果匹配到多个Bean类型相同,Spring会抛出异常。
经测试,byType会匹配子类型,同样如果匹配到多个子类型也会抛出异常。
3、constructor:把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。
< bean id="duke" class="..." autowire="constructor" />
这种方式和byType有一样的局限性,如果匹配到多个Bean类型或多个构造器,Spring都会抛出异常。
4、autodetect:首先尝试使用constructor进行自动装配,如果失败,再尝试byType进行自动装配。
< bean id="duke" class="..." autowire="autodetect" />
设置首选Bean
设置Bean的primary属性为true可以将Bean设置为首选Bean,但是Spring默认所有Bean的primary属性为true
< !--这个Bean是首选Bean-->
< bean id="saxophone" class="..." primary="false" />
排除某个Bean
设置Bean的autowire-candidate属性为false可以排除这个Bean(自动装配时将不会考虑使用这个Bean)
< !--这个Bean将不会被自动装配检测-->
< bean id="saxophone" class="..." autowire-candidate="false" />
默认自动装配
如果需要为Spring应用上下文的所有Bean均配置相同的autowire属性,只需要在根元素< beans>上添加default-autowire属性
< !--设置beans下所有Bean默认使用byType装配-->
< beans xmlns="..."
......
default-autowire="byType">
......
< /beans>
默认情况下default-autowire属性的值是none即不使用自动装配。
*注:使用bean标签中的autowire属性可以覆盖默认设置。
混合使用自动装配和显式装配
使用自动装配的同时也可以自己显式的装配字段。被显示装配的字段将不会被自动装配
< bean id="kenny" class="..." autowire="byType">
< property name="song" value="Jingle Bells">
< /bean>
Bean kenny的song字段通过setName方法设置成”Jingle Bells”,而这个类的其他字段将被自动装配(byType)
注解详细介绍
要使用注解装配,首先要在Spring中启用它,在< beans>标签下添加如下代码
< context:annotation-config />
使用注解可以实现更小颗粒度的自动装配(可以只装配某一个字段)
Spring3支持3种不同的自动装配注解
- Spring自带的@Autowired注解
- JSR-330的@Inject注解
- JSR-250 的@Resource注解
@Autowired
@Autowired可以标注任何方法,字段和构造器。
@Autowired //即使user是私有字段,也不需要setter方法即可注入,因为Spring直接通过反射注入属性值。
private User user
---------
@Autowired //自动注入user
public void show(User user);
-----------
@Autowired //构造器也一样可以注入
public Car(User user);
当使用@Autowired注解时,Spring会搜索对应的Bean并将Bean注入。但是,如果没有Bean被匹配或多个Bean被匹配,使用这个注解就会可能产生异常。
可选的自动装配
通过设置@Autowired的required属性为false,当没有找到合适的Bean时,就不会产生异常,而是会赋值为NULL。
@Autowired(required=false) //如果没有找到合适的Bean,instrument的值设为NULL
private Instrument instrument
*注:required属性可以用于@Autowired注解使用的任何地方,但是当使用在构造器时,只有一个构造器可以将@Autowired的required属性设置为true。其他的构造器必须设置为false。此外,当使用@autowired标注多个构造器时,Spring会从所有满足装配条件的构造器中选择满足条件最多的那个构造器。
@Value注解
@Value可以注入简单类型的值:String、int、boolean等
@Value("xiaoming") /为name属性注入值xiaoming
private String name;
在@Value注解中也可以使用SpEL表达式
@Value(*#{systenProperties.myFavoriteSong}*)
private String song;
使用< context:component-scan>可以指定对应包自动检测Bean,如果要扫描多个包,使用逗号分隔开(使用这个标签指定的范围,除了完成了< context:annotation-config>的工作,还能自动检测Bean和定义Bean)
< beans .....>
< context:component-scan base-package="com.springinaction.springidol">
< /context:component-scan>
< /beans>
base-package属性标识了< context:component-scan>标签所扫描的包。下面介绍这个标签检测Bean的方法:
使用注解标识Bean
这个标签指定包下的类可以使用集中注解表明这个类为Spring Bean
- @Component 是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何层次。
- @Service 通常作用在业务层,但是目前该功能与 @Component 相同。
- @Constroller 通常作用在控制层,但是目前该功能与 @Component 相同。
- @Repository 注解便属于最先引入的一批,它用于将数据访问层 (DAO 层 ) 的类标识为 Spring Bean。
当使用@Component注解一个类时(在扫描范围内),这个类会被标识为一个Bean,Bean的ID默认为类的无限定名,例如Guiter类标识后Bean的id默认为guiter,
这个注解也可以使用一个参数设置Bean的id,例如@Component(“myGuiter”)设置Bean的Id为myGuiter
过滤扫描组件、
在< context:component-scan>标签下可以使用< context:include-filter>或者< context:exclude-filter>。通过这两个标签的type和expression属性可以定义过滤策略。
type属性指定过滤器类型:
< context:component-scan
base-package="com.springinaction.springidol">
< !--将指定包下所有派生于Instrument类的类自动标识为Bean-->
< context:include-filter type="assignable"
expression="com.springinaction.springidol.Instrument" />
< !--除了被@SkipIt注解标识的类-->
< context:exclude-filter type="annotation"
expression="com.springinaction.springidol.SkipIt">
< /context:component-scan>