第02章 IOC容器和Bean的配置
IOC和DI
①IOC(
Inversion of Control):
反转控制。
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
②DI(
Dependency Injection):
依赖注入。
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
③IOC容器在Spring中的实现
[1]在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
[2]Spring提供了IOC容器的两种实现方式
(1)BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
(2)ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
④ApplicationContext的主要实现类
[1]ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
[2]FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
[3]在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
⑤ConfigurableApplicationContext
[1]是ApplicationContext的子接口,包含一些扩展方法
[2]refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
⑥WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
配置bean
配置形式:基于 XML 文件的方式, 和基于注解的方式
下面先介绍基于XML 文件的方式
从IOC容器获取Bean
<!-- class:bean的全类名,通过反射方式在IOC容器中创建bean,
要求bean必须有无参构造器
id:标识容器中的bean.id唯一 -->
<bean id="helloWorld" class="com.yshow.cn.bean.HelloWorld">
<property name="name" value="Spring"></property>
</bean>
通过id获取,此
id在 IOC 容器中必须是唯一的
HelloWorld bean = (HelloWorld) cxt.getBean("helloWorld");
通过类型获取bean
从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。
HelloWorld helloWorld = cxt.getBean(HelloWorld. class);
给bean的属性赋值
3.1 赋值的途经
①通过bean的setXxx()方法赋值
HelloWorld中使用的就是这种方式
②通过bean的构造器赋值
<bean id="car" class="com.yshow.cn.bean.Car">
<constructor-arg value="Audi"/>
<constructor-arg value="ShangHai"/>
<constructor-arg value="300000" />
</bean>
●通过索引值指定参数位置
<bean id="car" class="com.yshow.cn.bean.Car">
<constructor-arg value="Audi" index="0"/>
<constructor-arg value="ShangHai" index="1"/>
<constructor-arg value="300000" index="2"/>
</bean>
●通过类型不同区分重载的构造器
<!-- 使用构造器注入属性值可指定参数类型,以区分重载的构造器 -->
<bean id="car2" class="com.yshow.cn.bean.Car">
<constructor-arg value="BMW" type="java.lang.String"/>
<constructor-arg value="Bolin" type="java.lang.String"/>
<constructor-arg value="240" type="int"/>
</bean>
③给bean的级联属性赋值
<bean id="person" class="com.yshow.cn.bean.Person">
<property name="name" value="Tom"></property>
<property name="age" value="26"></property>
<property name="car" ref="car2"></property>
<property name="car.maxSpeed" value="360"></property>
</bean>
④p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean的属性。
使用p命名空间后,基于XML的配置方式将进一步简化。
<bean id="person4" class="com.yshow.cn.bean.Person" p:name="Andy" p:age="31" p:cars-ref="cars"></bean>
3.2 可以使用的值
①字面量
[1]可以使用字符串表示的值,可以通过value属性或value元素标签进行注入
<bean id="car2" class="com.yshow.cn.bean.Car">
<constructor-arg value="BMW" type="java.lang.String"/>
<constructor-arg type="java.lang.String">
<value>Bolin</value>
</constructor-arg>
<constructor-arg value="280" type="int">
</constructor-arg>
</bean>
[2]基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
[3]若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
<bean id="car2" class="com.yshow.cn.bean.Car">
<constructor-arg value="BMW" type="java.lang.String"/>
<!-- 若字面值中包含特殊字符, 则可以使用 CDATA 来进行赋值.-->
<constructor-arg type="java.lang.String">
<value><![CDATA[<Bolin^>]]></value>
</constructor-arg>
<constructor-arg value="280" type="int">
</constructor-arg>
</bean>
②null值
<bean id="person" class="com.yshow.cn.bean.Person">
<property name="name" value="Tom"></property>
<property name="age" value="26"></property>
<property name="car">
<null/>
</property>
</bean>
③外部已声明的bean
在 Bean 的配置文件中, 可以
通过 <ref> 元素或 ref 属性为 Bean 的属性或构造器参数指定对Bean 的引用.
<bean id="person" class="com.yshow.cn.bean.Person">
<property name="name" value="Tom"></property>
<property name="age" value="26"></property>
<!-- 可使用property的ref属性建立bean之间的引用关系 -->
<!-- <property name="car" ref="car2"></property> -->
<!-- 或者采用元素标签形式 -->
<property name="car">
<ref bean="car2" />
</property>
</bean>
④内部bean
当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。内部bean声明直接包含在<property>或<constructor-arg>元素里,不需要设置任何id或name属性
内部bean不能使用在任何其他地方
<bean id="person" class="com.yshow.cn.bean.Person">
<property name="name" value="Tom"></property>
<property name="age" value="26"></property>
<property name="car">
<bean class="com.yshow.cn.bean.Car">
<constructor-arg value="Ford"></constructor-arg>
<constructor-arg value="xian"></constructor-arg>
<constructor-arg value="200000" type="double"></constructor-arg>
</bean>
</property>
</bean>
3.3 集合属性
在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list>,<set>或<map>。
①数组和List
配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。
数组的定义和List一样,都使用<list>元素。
配置java.util.Set需要使用<set>标签,定义的方法与List一样。
<bean id="person1" class="com.yshow.cn.bean.Person">
<property name="name" value="Jerry"></property>
<property name="age" value="27"></property>
<property name="cars">
<list>
<ref bean="car"/>
<ref bean="car2"/>
</list>
</property>
</bean>
②Map
Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。
必须在<key>标签里定义键。
因为键和值的类型没有限制,所以可以自由地为它们指定<value>、<ref>、<bean>或<null/>元素。
可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。
<bean id="newPerson" class="com.yshow.cn.bean.NewPerson">
<property name="name" value="Ady"></property>
<property name="age" value="27"></property>
<property name="carMap">
<map>
<entry key="AA" value-ref="car"></entry>
<entry key="BB" value-ref="car2"></entry>
</map>
</property>
</bean>
③Properties
使用<props>定义java.util.Properties,该标签使用多个<prop>作为子标签。每个<prop>标签必须定义key属性
<bean class="com.yshow.spring.bean.DataSource" id="dataSource">
<property name="properties">
<props>
<prop key="userName">root</prop>
<prop key="password">root</prop>
<prop key="url">jdbc:mysql:///test</prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
④独立的集合bean
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间
<util:list id="cars">
<ref bean="car"/>
<ref bean="car2"/>
</util:list>
<bean id="person3" class="com.yshow.cn.bean.Person">
<property name="name" value="Jack"></property>
<property name="age" value="30"></property>
<property name="cars" ref="cars"></property>
</bean>
通过工厂创建bean
4.1 静态工厂
调用静态工厂方法创建bean是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不用关心创建对象
的细节。
声明通过静态方法创建的bean需要在bean的class属性里指定静态工厂类的全类名,同时在factory-method属性里指定工厂方法的名称。最后
使用<constrctor-arg>元素为该方法传递方法参数。
/**
* 静态工厂方法:直接调用某一个类的静态方法就可返回bean的实例
*/
public class StaticCarFactory {
private static Map<String, Car> cars = new HashMap<>();
static{
cars.put("Audi", new Car("Audi", 300000));
cars.put("Ford", new Car("Ford", 400000));
}
//静态工厂方法
public static Car getCar(String name){
return cars.get(name);
}
}
<!-- 通过静态工厂方法来配置bean
class:指向静态工厂方法全类名
factory-method:指向静态工厂方法的名字
constructor-arg:给静态工厂方法传入参数 -->
<bean id="car1"
class="com.yshow.cn.factory.StaticCarFactory"
factory-method="getCar">
<constructor-arg value="Audi"></constructor-arg>
</bean>
4.2 实例工厂
实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象
的创建细节。
实现方式
①配置工厂类实例的bean
②在factory-method属性里指定该工厂方法的名称
③使用 construtor-arg 元素为工厂方法传递方法参数
/**
* 实例工厂方法:先需要创建工厂本身,再调用工厂实例方法返回bean实例
*/
public class InstanceCarFactory {
private Map<String, Car> cars = null;
public InstanceCarFactory(){
cars = new HashMap<>();
cars.put("Audi", new Car("Audi", 300000));
cars.put("Ford", new Car("Ford", 400000));
}
public Car getCar(String brand){
return cars.get(brand);
}
}
<!-- 实验16:配置通过实例工厂方法创建的bean -->
<!-- 配置工厂的实例 -->
<!-- 由于实例工厂需要创建对象后才能调用方法,所以要先配置实例工厂的bean -->
<bean id="carFactory" class="com.yshow.cn.factory.InstanceCarFactory"></bean>
<!-- 通过实例工厂方法来配置bean
factory-bean:指定实例工厂的引用
factory-method:指向实例工厂方法的名字
constructor-arg:给实例工厂方法传入参数 -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="Ford"></constructor-arg>
</bean>
4.3 FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
//自定义的FactoryBean需要实现 FactoryBean接口
public class CarFactoryBean implements FactoryBean<Car>{
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
//返回bean的对象
@Override
public Car getObject() throws Exception {
return new Car(brand, 500000);
}
//返回bean的类型
@Override
public Class getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<!-- 实验17:配置FactoryBean★ -->
<!-- 通过FactoryBean来配置bean的实例 -->
<!-- FactoryBean经过IOC容器配置后获取到的是getObject()方法的返回值 -->
<bean id="car" class="com.yshow.cn.factorybean.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
bean的高级配置
5.1 配置信息的继承
① 背景
查看下面两个Address的配置,其中city属性是重复的。
<bean id="address1" class="com.yshow.cn.autowire.Address">
<!-- 重复的属性值 -->
<property name="city" value="BeiJing"></property>
<property name="street" value="dazhongsi"></property>
</bean>
<bean id="address2" class="com.yshow.cn.autowire.Address">
<!-- 重复的属性值 -->
<property name="city" value="BeiJing"></property>
<property name="street" value="wudaokou"></property>
</bean>
②配置信息的继承
<!-- 以address1作为父bean,继承后可以省略公共属性值的配置 -->
<bean id="address2" parent="address1">
<property name="street" value="xierqi"></property>
</bean>
Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean
子bean从父bean中继承配置,包括bean的属性配置
子bean也可以覆盖从父bean继承过来的配置
③补充说明
父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置<bean>的abstract 属性为true,这样Spring将不会实例化
这个bean
如果一个bean的class属性没有指定,则必须是抽象bean
<bean id="address" abstract="true">
<property name="city" value="BeiJing"></property>
<property name="street" value="dazhongsi"></property>
</bean>
并不是<bean>元素里的所有属性都会被继承。比如:autowire,abstract等。
也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。但此时abstract必须设为true。
<!-- 实验19:通过abstract属性创建一个模板bean -->
<!-- 设置了abstract="true"之后,这个bean就成了专门用于继承的模板配置,不能实例化 -->
<bean id="deptTemplate" class="com.atguigu.ioc.bean.Department" abstract="true">
<property name="deptName" value="teaching@"/>
<property name="decribe" value="goodDept@"/>
</bean>
<bean id="dept01" class="com.atguigu.ioc.bean.Department">
<property name="deptName" value="teaching~"/>
<property name="decribe" value="goodDept~"/>
</bean>
<bean id="dept02" class="com.atguigu.ioc.bean.Department" parent="dept01"/>
<bean id="dept03" class="com.atguigu.ioc.bean.Department" parent="deptTemplate">
<property name="decribe" value="VeryGoodDept@@@"/>
</bean>
5.2 bean之间的依赖
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建Person对象的时候必须创建Car。这里需要注意的是依赖关系不等于引用关系,Person即使依赖Car也可以不引用它。
<!-- 默认情况下按照bean配置的先后顺序创建对象,但被依赖的对象会先创建 -->
<bean id="car2" class="com.yshow.cn.autowire.Car">
<property name="brand" value="benchi"></property>
<property name="price" value="200000"></property>
</bean>
<bean id="person" class="com.yshow.cn.autowire.Person" p:name="Tom" p:address-ref="address2" depends-on="car2"/>
5.3 bean的作用域
在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。
<!-- IOC容器创建对象,默认情况下都是单实例的 -->
<!-- 可以通过scope属性设置为多实例的对象,设置为prototype后bean的实例会在获取bean的时候才创建 -->
<bean id="car2" class="com.yshow.cn.autowire.Car" scope="prototype">
<property name="brand" value="benchi"></property>
<property name="price" value="200000"></property>
</bean>
5.4 bean的生命周期
①Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
②Spring IOC容器对bean的生命周期进行管理的过程:
[1]通过构造器或工厂方法创建bean实例
[2]为bean的属性设置值和对其他bean的引用
[3]调用bean的初始化方法
[4]bean可以使用了
[5]当容器关闭时,调用bean的销毁方法
③在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
<!-- 实验22:创建带有生命周期方法的bean -->
<bean id="car2" class="com.yshow.cn.autowire.Car"
init-method="init2"
destroy-method="destroy"/>
Bean
初始化和销毁分别会调用 init2 和 destroy 方法
public class Bike {
private String brand;
public Bike() {
System.out.println("Bike's constructor...");
}
public void setBrand(String brand) {
System.out.println("setBrand...");
this.brand = brand;
}
public void init2(){
System.out.println("init...");
}
public void destroy(){
System.out.println("destroy...");
}
}
测试结果:
④bean的后置处理器
[1]bean后置处理器允许在调用
初始化方法前后对bean进行额外的处理
[2]bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
[3] bean后置处理器时需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
●postProcessBeforeInitialization(Object, String)
●postProcessAfterInitialization(Object, String)
⑤添加bean后置处理器后bean的生命周期
[1]通过构造器或工厂方法
创建bean实例
[2]为bean的
属性设置值和对其他bean的引用
[3]将bean实例传递给bean后置处理器的
postProcessBeforeInitialization()方法
[4]调用bean的
初始化方法
[5]将bean实例传递给bean后置处理器的
postProcessAfterInitialization()方法
[6]bean可以使用了
[7]当容器关闭时调用bean的
销毁方法
配置后置处理器:
<!-- 实验23:测试bean的后置处理器 -->
<bean class="com.yshow.cn.autowire.MyBeanPostProcess"></bean>
测试结果:
5.5 引用外部属性文件
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。
①直接配置
<!-- 直接配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl" value="jdbc:mysql:///test"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>
也可通过读取properties属性文件的方式配置
②创建properties属性文件
prop.userName=root
prop.password=root
prop.url=jdbc:mysql:///test
prop.driverClass=com.mysql.jdbc.Driver
③引入context名称空间
④指定properties属性文件的位置
<!-- 指定properties属性文件的位置 -->
<!-- classpath:xxx 表示属性文件位于类路径下 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
⑤从properties属性文件中引入属性值
<!-- 从properties属性文件中引入属性值 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${prop.userName}"/>
<property name="password" value="${p<!-- 从properties属性文件中引入属性值 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
<property name="jdbcUrl" value="${prop.url}"/>
<property name="driverClass" value="${prop.driverClass}"/>
</bean>
5.6 自动装配
①自动装配的概念
[1]手动装配:以value或ref的方式
明确指定属性值都是手动装配。
[2]自动装配:根据指定的装配规则,
不需要明确指定,Spring
自动将匹配的属性值
注入bean中。
②装配模式
[1]根据
类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配
<!-- 实验25:基于XML的自动装配 -->
<!-- 自动装配 -->
<!-- 使用autowire="byType"是根据类型进行自动装配,要求指定类型的bean是唯一的 -->
<bean id="address" class="com.yshow.cn.autowire.Address" p:city="BeiJing" p:street="changanjie"></bean>
<bean id="car" class="com.yshow.cn.autowire.Car" p:brand="Audi" p:price="200000"></bean>
<bean id="person" class="com.yshow.cn.autowire.Person" p:name="Tom" autowire="byType"></bean>
[2]根据
名称自动装配:必须将目标bean的名称和属性名设置的完全相同
<!-- 使用autowire="byName"是根据id值进行自动装配,将需要装配的属性的setXxx()方法所生成的属性名作为id值在IOC容器中查找 -->
<bean id="address" class="com.yshow.cn.autowire.Address" p:city="BeiJing" p:street="changanjie"></bean>
<bean id="car" class="com.yshow.cn.autowire.Car" p:brand="Audi" p:price="200000"></bean>
<bean id="person" class="com.yshow.cn.autowire.Person" p:name="Tom" autowire="byName"></bean>
[3]通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
XML 配置里的 Bean 自动装配的缺点:
1.在 Bean 配置文件里设置autowire属性进行自动装配将会装配 Bean 的所有属性.
然而, 若只希望装配个别属性时, autowire 属性就不够灵活了.
2.autowire 属性要么根据类型自动装配, 要么根据名称自动装配, 不能两者兼而有之.
③选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。
SpEL
6.1 简介
Spring Expression Language,Spring表达式语言,简称SpEL。支持运行时查询并可以操作对象图。和JSP页面上的
EL表达式、Struts2中用到的
OGNL表达式一样,SpEL根据JavaBean风格的
getXxx()、setXxx()方法定义的属性访问对象图,
完全符合我们熟悉的操作习惯。
6.2 基本语法
SpEL使用
#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL表达式。
6.3 使用字面量
●整数:<property name="count" value="
#{5}"/>
●小数:<property name="frequency" value="
#{89.7}"/>
●科学计数法:<property name="capacity" value="
#{1e4}"/>
●String类型的字面量可以使用单引号或者双引号作为字符串的定界符号
<property name=“name” value=
"#{'Chuck'}"/>
<property name='name' value=
'#{"Chuck"}'/>
<bean id="address01" class="com.yshow.cn.autowire.Address">
<!-- 使用spel为属性赋一个字面值 -->
<property name="city" value="#{'BeiJing'}"></property>
<property name="street" value="wudaokou"></property>
</bean>
●Boolean:<property name="enabled" value="
#{false}"/>
6.4 引用其他bean
<bean id="person" class="com.yshow.cn.autowire.Person">
<!-- 使用spel引用其它的bean -->
<property name="car" value="#{car}"></property>
<property name="name" value="Tom"></property>
</bean>
6.5 引用其他bean的属性值作为自己某个属性的值
<bean id="person" class="com.yshow.cn.autowire.Person">
<!-- 使用spel引用其它的bean的属性 -->
<property name="city" value="#{address.city}"></property>
<!-- 使用spel运算符 -->
<property name="info" value="#{car.price > 300000 ? '金领':'白领'}"></property>
<property name="name" value="Tom"></property>
</bean>
6.6 调用非静态方法
<!-- 创建一个对象,在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.atguigu.spel.bean.SalaryGenerator"/>
<bean id="employee" class="com.atguigu.spel.bean.Employee">
<!-- 通过对象方法的返回值为属性赋值 -->
<property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>
6.7 调用静态方法
<bean id="car" class="com.yshow.cn.autowire.Car">
<property name="brand" value="Audi"></property>
<property name="price" value="500000"></property>
<!-- 使用spel引用类的静态属性 -->
<property name="perimeter" value="#{T(java.lang.Math).PI * 80}"></property>
</bean>
6.8 运算符
①算术运算符:+、-、*、/、%、^
②字符串连接:+
③比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge
④逻辑运算符:and, or, not, |
⑤三目运算符:判断条件?判断结果为true时的取值:判断结果为false时的取值
⑥正则表达式:matches
通过注解配置bean
7.1 概述
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
7.2 使用注解标识组件
①普通组件:@Component
标识一个受Spring IOC容器管理的组件
②持久化层组件:@Repository
标识一个受Spring IOC容器管理的持久化层组件
③业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
④表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
⑤组件命名规则
[1]默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
[2]使用组件注解的value属性指定bean的id
注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Repository注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Repository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。
7.3 扫描组件
组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到。
1.指定被扫描的package
<context:component-scan base-package="com.yshow.cn.beans.annotation"/>
import org.springframework.stereotype.Controller;
@Controller
public class UserController{
@Repository("userRepository")
public class UserRepositoryImpl implements UserRepository
@Service
public class UserService
//获取bean
public void test01() {
UserService userService = (UserService) ctx.getBean("userService");
System.out.println(userService);
}
②详细说明
[1]
base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。
[2]当需要扫描多个包时可以使用逗号分隔。
[3]如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:
<!-- 使用resource-pattern属性可以指定在自动扫描的包内只扫描指定格式的类 -->
<context:component-scan
base-package="com.atguigu.component"
resource-pattern="autowire/*.class"/>
扫描User开头的类
<context:component-scan base-package="com.yshow.cn.beans.annotation" resource-pattern="User*.class"/>
[4]包含与排除
●<context:include-filter>子节点表示要包含的目标类
注意:通常需要与use-default-filters属性配合使用才能够达到“仅包含某些组件”这样的效果。即:通过将use-default-filters属性设置为false,禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了。
●<context:exclude-filter>子节点表示要排除在外的目标类
●component-scan下可以拥有若干个include-filter和exclude-filter子节点
●过滤表达式
类别 | 示例 | 说明 |
annotation | com.atguigu.XxxAnnotation | 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤。 |
assignable | com.atguigu.BaseXxx | 过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。 |
aspectj | com.atguigu.*Service+ | 所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。 |
regex | com\.atguigu\.anno\.* | 所有com.atguigu.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。 |
custom | com.atguigu.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口 |
<context:component-scan base-package="com.atguigu.ioc.component">
<!-- 指定在当前自动扫描的包下,要排除的类type属性:指定“用于排除的规则”的类型
取值annotation:表示根据某个组件注解进行排除,此时expression属性需指定注解的全类名 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:component-scan base-package="com.atguigu.ioc.component">
<!-- 指定在当前自动扫描的包下,要排除的类
type属性:指定“用于排除的规则”的类型
取值assignable:表示expression指定的类的全类名的类型的组件要排除 -->
<context:exclude-filter type="assignable" expression="com.atguigu.ioc.component.BookDao"/>
</context:component-scan>
<!-- use-default-filters="false"表示不使用默认的过滤规则,完全参照include-filter的配置决定是否加入IOC容器 -->
<context:component-scan base-package="com.atguigu.ioc.component" use-default-filters="false">
<!-- 指定在自动扫描的包下面要包含进来的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<context:component-scan base-package="com.yshow.cn.beans.annotation" use-default-filters="false">
<context:include-filter type="assignable" expression="com.yshow.cn.beans.annotation.UserController"/>
</context:component-scan>
③JAR包
必须在原有JAR包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar
7.4 组件装配
①需求
Controller组件中往往需要用到Service组件的实例,Service组件中往往需要用到Repository组件的实例。Spring可以通过注解的方式帮我们实现属性的装配。
②实现依据
在指定要扫描的包时,<context:component-scan> 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记了
@Autowired、@Resource或@Inject注解的属性。
③@Autowired注解
[1]根据类型实现自动装配。
[2]构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired注解
[3]默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的bean装配属性时,会抛出异常。
[4]若某一属性允许不被设置,可以设置@Autowired注解的required属性为 false
@Autowired(required=false)
private TestObject obj;
[5]默认情况下,当IOC容器里存在多个类型兼容的bean时,Spring会尝试匹配bean的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供bean的名称。Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。
两种方式解决
1.
@Repository("userRepository")
public class UserRepositoryImpl implements UserRepository {
2. public class UserService {
@Autowired
@Qualifier("userJdbcRepository")
private UserRepository userRepository;
或在set方法上
public class UserService {
private UserRepository userRepository;
@Autowired
@Qualifier("userJdbcRepository")
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
或放在方法的形参上也行
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(@Qualifier("userJdbcRepository")UserRepository userRepository) {
this.userRepository = userRepository;
}
[6]@Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。
[7]@Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。
[8]@Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
④@Resource
@Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称。
⑤@Inject
@Inject和@Autowired注解一样也是按类型注入匹配的bean,但没有required属性。
泛型依赖注入
8.1 简介
Spring 4.x中可以为子类注入子类对应的泛型类型的成员变量的引用。
8.2 实现
[1]组件基类
BaseRepository |
public class BaseRepository<T> { public void save() { System.out.println("Saved by BaseRepository"); } } |
BaseService |
public class BaseService<T> { @Autowired private BaseRepository<T> repository; public void add() { repository.save(); } } |
[2]组件实体类
UserRepository |
@Repository public class UserRepository extends BaseRepository<User>{ public void save() { System.out.println("Saved by UserRepository"); } } |
UserService |
@Service public class UserService extends BaseService<User>{ } |
[3]模型实体类
User |
public class User { } |
[4]测试
ApplicationContext ioc = new ClassPathXmlApplicationContext("di.xml"); UserService us = (UserService) ioc.getBean("userService"); us.add(); |
执行结果 |
Saved by UserRepository |
整合多个配置文件
9.1 Spring允许通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动Spring容器时,仅需要指定这个合并好的配置文件就可以。
9.2 import元素的resource属性支持Spring的标准的路径资源