4.1 Spring配置概述
4.1.1 Spring容器高层视图
Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
Bean配置信息是Bean的元数据信息,由4个方面组成:
* Bean的实现类;
* Bean的属性信息,如数据源的连接数、用户名、密码;
* Bean的依赖关系,Spring根据依赖关系配置完成Bean之间的装配;
* Bean的行为配置,如生命周期范围及生命周期各过程的回调函数等;
Bean元数据信息在Spring容器中的内部对应是由一个个BeanDefinnition形成的Bean注册表。
4.1.2 基于XML的配置
Spring1.0的配置文件采用DTD格式,Spring2.0以后使用Schema的格式。
4.2 Bean基本配置
4.2.1 装配一个Bean
<bean id="car" class="com.baobaotao.simple.Car" />
<bean id="boss" class="com.baobaotao.simple.Boss" />
这段配置信息提供了实例化Bean的名称,通过容器的getBean(“car”)即可获得,class对应实现类。Spring IOC容器据此创建这两个Bean的实例。
4.2.2 Bean的命名
建议使用id命名,由于name属性名无规范没有限制。如没有设置名称,则默认为class为名称。
4.3 依赖注入
4.3.1 属性注入
1.要求Bena提供一个默认的构造函数。
2.为需要注入的属性提供对应的Setter方法(可以没有这个属性和Getter方法)。
<bean id="car" class="com.baobaotao.ditype.Car">
<property name="maxSpeed">
<value>
498
</value>
</property>
<property name="brand">
<value>
GT-R
</value>
</property>
<property name="price">
<value>
2000000.00
</value>
</property>
</bean>
JavaBean关于属性命名的特殊规范(实测没有出现错误)
实际测试没有出现错误。代码贴上
启动Spring容器:
package com.baobaotao.attr;
public static void main(String[] args) {
// TODO Auto-generated method stub
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource rs = resolver.getResource("classpath:com/baobaotao/attr/beans.xml");
BeanFactory bf = new XmlBeanFactory(rs);
System.out.println("init BeanFactory.");
Foo foo = bf.getBean("foo", Foo.class);
System.out.println("car bean is resdy for use");
}
对应的Bean类
package com.baobaotao.attr;
public class Foo {
private String iDCode;
public void setIDCode(String iDCode) {
this.iDCode = iDCode;
}
}
对应的Bean配置文件
<bean
id="foo"
class="com.baobaotao.attr.Foo">
<property name="iDCode" value="070101"/>
</bean>
4.3.2 构造函数注入
按类型匹配入参
通过构造函数注入保证在语法级提供必须提供某些属性值。
<bean
id="car1"
class="com.baobaotao.ditype.Car">
<constructor-arg type="java.lang.String">
<value>
GT-R
</value>
</constructor-arg>
<constructor-arg type="double">
<value>
2000.00
</value>
</constructor-arg>
</bean>
package com.baobaotao.ditype;
public class Car {
private String brand;
private double price;
public Car(String brand, double price) {
this.brand = brand;
this.price = price;
}
}
在的声明顺序并不是确定构造函数入参的顺序,Spring的配置文件采用和元素标签顺序无关的策略。与web.xml配置文件顺序相关的策略是不同的(Log4J的配置和Spring的配置)。
按索引匹配入参
public Car2(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
<bean
id="car2"
class="com.baobaotao.ditype.Car2">
<constructor-arg index="0" value="GT-R"/>
<constructor-arg index="1" value="USA"/>
<constructor-arg index="2" value="2000.00"/>
</bean>
联合使用类型和索引匹配入参
<bean
id="car3"
class="com.baobaotao.ditype.Car3">
<constructor-arg index="0" type="java.lang.String">
<value>
GT-R
</value>
</constructor-arg>
<constructor-arg index="1" type="java.lang.String">
<value>
USA
</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>
2000
</value>
</constructor-arg>
</bean>
public Car3(String brand, String corp, double price) {
this.brand = brand;
this.corp = corp;
this.price = price;
}
public Car3(String brand, String corp, int maxSpeed) {
this.brand = brand;
this.corp = corp;
this.maxSpeed = maxSpeed;
}
如若有type属性,则需要使用方式,而不能直接
通过自身类型反射匹配入参
public Car4(String brand, Car car, Car2 car2) {
this.brand = brand;
this.car = car;
this.car2 = car2;
}
<bean
id="car4"
class="com.baobaotao.ditype.Car4">
<constructor-arg>
<value>
GT-R
</value>
</constructor-arg>
<constructor-arg>
<ref bean="car"/>
</constructor-arg>
<constructor-arg>
<ref bean="car2"/>
</constructor-arg>
</bean>
<bean id="car" class="com.baobaotao.ditype.Car" />
<bean id="car2" class="com.baobaotao.ditype.Car2" />
但是需要Car,Car2默认的构造方法。否则报错:No default constructor found。
循环依赖问题
Spring容器能顺利实例化以构造函数注入方式配置的Bean有一个前提:Bean构造函数入参引用的对象必须已经准备就绪。由于这个机制的限制,如果两个Bean都采用构造函数注入而且都通过构造函数入参引用对方,就会发生类似于线程死锁的循环依赖问题。例如:
public CarFromCar2(String brand, Car2 car2){
}
public Car2FromCar(String brand, Car car){
}
<bean
id="carfromcar2"
class="com.baobaotao.ditype.CarFromCar2">
<constructor-arg index="0" value="GT-R" />
<constructor-arg index="1" ref="car2fromcar" />
</bean>
<bean
id="car2fromcar"
class="com.baobaotao.ditype.Car2FromCar">
<constructor-arg index="0" value="GT-R" />
<constructor-arg index="1" ref="carfromcar2" />
</bean>
4.3.3 工厂方法注入
非静态工厂方法
<!-- 工厂类Bean -->
<bean id="carFactory" class="com.baobaotao.ditypefactory.CarFactory" />
<!-- 根据配置返回工厂类指定的Bean -->
<bean id="car5"
factory-bean="carFactory"
factory-method="createGT_RCar" />
public class CarFactory {
public Car createGT_RCar(){
Car car = new Car();
return car;
}
}
静态工厂方法
<!-- 根据配置返回工厂类指定的Bean -->
<bean id="car6"
class="com.baobaotao.ditypefactory.CarFactoryStatic"
factory-method="createGT_RCar" />
createGT_RCar方法加上静态修饰即可,然后在配置文件修改,无须实例工厂Bean,直接调用。
4.3.4 选择注入方式的考量
实际为准,不推荐用工厂方法,因为需要额外的类和代码。而且与业务无关。
4.4 注入参数详解
在Spring配置文件中,不但可以将String、int注入到Bean中,还可以将集合、Map等注入,还可以注入已经定义的Bean。
4.4.1 字面值
<bean id="car" class="com.baobaotao.attr.Car">
<property name="brand">
<value>
<![CDATA[红旗&CA72]]>
</value>
</property>
</bean>
4.4.2 引入其他Bean
Spring IOC容器中定义的Bean可以相互引用,IOC容器则充当红娘的角色。
public class Boss{
public void setCar(Car car){
}
}
<bean id="car" class="com.baobaotao.attr.Car" />
<bean id="boss" class="com.baobaotao.attr.Boss">
<property name="car">
<ref bean="car"></ref>
</property>
</bean>
元素可以通过以下三个属性引用容器中其他Bean。
bean:该属性引用同一容器或父容器的Bean;
local:该属性只能引用同一配置文件中定义的Bean;
parent:引用父容器的Bean;
启动Spring容器
ClassPathXmlApplicationContext pFactory = new ClassPathXmlApplicationContext(
new String[]{"com/baobaotao/beanparent/beans1.xml"});
ClassPathXmlApplicationContext factoryApplicationContext = new ClassPathXmlApplicationContext(
new String[]{"com/baobaotao/beanparent/beans2.xml"}, pFactory);
System.out.println("init BeanFactory.");
Boss boss = factoryApplicationContext.getBean("boss", Boss.class);
System.out.println("car bean is resdy for use");
boss.getCar();
对应的beans1.xml和beans2.xml文件
<bean
id="car"
class="com.baobaotao.beanparent.Car">
<property name="brand" value="GT-R" />
<property name="maxSpeed" value="498" />
<property name="price" value="200000" />
</bean>
<bean
id="car"
class="com.baobaotao.beanparent.Car">
<property name="brand" value="吉利" />
<property name="maxSpeed" value="200" />
<property name="price" value="2000" />
</bean>
<bean
id="boss"
class="com.baobaotao.beanparent.Boss">
<constructor-arg>
<ref parent="car"/>
</constructor-arg>
</bean>
4.4.3 内部Bean
类似匿名内部类
<bean id="boss" class="com.baobaotao.attr.Boss">
<property name="car">
<bean class="com.baobaotao.attr.Car">
<constructor-arg index="0" type="java.lang.String"/>
</bean>
</property>
</bean>
4.4.4 null值
什么都不输入为空字符串
<bean id="car" class="com.baobaotao.attr.Car">
<property name="brand"><value><null/></value></property>
</bean>
4.4.5 级联属性
<bean id="boss" class="com.baobaotao.attr.Boss">
<!-- 以圆点的方式定义级别属性,Boss.getCar().setBrand("GT-R") -->
<property name="car.brand" value="GT-R"/>
</bean>
4.4.6 集合类型属性
List
public void setF(List f){}
<bean id="boss1" class="com.baobaotao.attr.Boss">
<property name="f">
<list>
<value>望</value>
<value>闻</value>
<value>问</value>
<value>切</value>
</list>
</property>
</bean>
Set
和List一样的配置,只需要改成set
Map
<bean>
<property>
<map>
<entry>
<key>
<value>
AM
</value>
</key>
<value>
见面
</value>
</entry>
<entry>
<key>
<ref bean="key" />
</key>
<ref bean="value" />
</entry>
</map>
</property>
</bean>
Properties
Properties类型其实可以看成是Map的特例,键值对只能是字符串。
public void setMails(Properties m){}
<bean>
<property>
<props>
<prop key="jobMail">1@163.com></prop>
<prop key="lifeMail">1@qq.com></prop>
</props>
</property>
</bean>
强类型集合
public void setJobTime(Map<String, Integer> jobTime){}
和普通配置一样,只不过配置文件中会被自动转换。
集合合并
允许子集成父的同名属性集。这样子Bean拥有父的基础属性。
<bean id="parentBoss" abstract="true" class="com.baobaotao.attr.Boss">
<property name=favorites>
<set>
<value>看报</value>
<value>写字</value>
<value>高尔夫</value>
</set>
</property>
</bean>
<bean id="childBoss" parent="parentBoss">
<property name="favorites">
<set merge="true">
<value>爬山</value>
<value>游泳</value>
</set>
</property>
</bean>
通过util命名空间配置集合类型的Bean
...
//可以不用指定class
<util:list id="a" list-class="java.util.LinkedList">
<value>看报</value>
</util:list>
4.4.7 简化配置方式
使用p命名空间
<bean id="car" class="com.baobaotao.ditype.Car">
<property name="brand" value="GTR"/>
<property name="maxSpeed" value="489"/>
<property name="price" value="20000"/>
</bean>
<bean id="boss" class="com.baobaotao.ditype.Boss">
<property name="car" ref="car"/>
</bean>
///////////////////使用P命名空间后///////////////////
<bean id="car" class="com.baobaotao.ditype.Car"
p:brand="GTR"
p:maxSpeed="200"
p:price="200000" />
<bean id="boss" class="com.baobaotao.ditype.Boss"
p:car-ref="car" />
4.4.8 自动装配
Spring IOC容器通过反射机制获得Bean实现类的结构信息。掌握容器中所有Bean的这些信息后,Spring IOC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须我们通过显式的方式进行配置。Spring可以按照某些规则进行Bean的自动装配。
元素提供了一个指定自动装配类型的属性:autowire=”<自动装配类型>”。四种自动装配类型。
byName:根据名称自动匹配。假设Boss有一个名为car的属性,容器中刚好有一个名为car的Bean,Spring就会自动将其装配给Boss的car属性。
byType:根据类型自动匹配。假设Boss有一个Car类型的属性,容器中刚好有一个Car类型的Bean,Spring就会自动将其装配给Boss的这个属性。
constructor:与ByType类似,只不过它是针对构造函数注入而言,如果Boss有一个构造函数,构造函数包含一个Car类型的入参,如果容器中有一个Car类型的Bean,则Spring将自动把这个Bean作为Boss构造函数的入参。否则抛出异常
autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配:如果Bean提供了默认的构造函数,则采用byType;否则采用constructor。
实际很少用,因为没有配置文档清晰明显。
4.5 方法注入
如果希望往singleton的Boss中注入prototype的Car,并希望每次调用Boss Bean的getCar()时都能够返回一个新的Car Bean。
4.5.1 lookup方法注入
Spring IOC容器拥有复写Bean方法的能力。CGLib类包可以在运行期动态操作Class字节码,为Bean动态创建子类或实现类。
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 引用Spring的多个Schema空间的格式定义文件 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean
id="car"
class="com.baobaotao.injectfun.Car"
p:brand="GTR"
p:price="2000"
scope="prototype">
</bean>
<!-- 让getCar()每次返回新的Car Bean -->
<bean
id="magicBoss"
class="com.baobaotao.injectfun.MagicBoss">
<lookup-method name="getCar" bean="car"/>
</bean>
</beans>
public interface MagicBoss {
Car getCar();
}
4.5.2 方法替换
<bean id="boss1" class="com.baobaotao.injectfun.Boss1">
<replaced-method name="getCar" replacer="boss2" />
</bean>
<bean id="boss2" class="com.baobaotao.injectfun.Boss1"/>
4.6 之间的关系
4.6.1 继承
<!-- 定义为抽象been -->
<bean id="abstractCar" class="com.baobaotao.tagdepend.Car"
p:brand="GTR" p:price="200000.00" p:color="黑色" abstract="true" />
<bean id="car3" p:color="红色" parent="abstractCar" />
<bean id="car4" p:color="白色" parent="abstractCar" />
父bean主要功能主要为了简化子bean的配置,所以一般声明为abstract=”true”,表明不实例化。
4.6.2 依赖
有时候依赖关系不明显,但是如果一个Bean的参数需要在另一个Bean执行之后再通过一系列的逻辑确定,则需要指定前置依赖的Bean。如果需要依赖多个Bean,可以通过逗号,空格或分号的方式。
<bean id="manager" class="com.baobaotao.tagdepend.CacheManager"
depends-on="sysInit" />
<bean id="sysInit" class="com.baobaotao.tagdepend.SysInit" />
4.6.3 引用
假设一个要引用另一个的id属性值
<bean id="car" class="com.baobaotao.Car"/>
<bean id="boss" class="com.baobaotao.Boss"
p:carId="car" scope="prototype" />
/////以上配置只能在运行期发现问题,下面在容器启动时就检查正确性////
<bean id="boss" class="com.baobaotao.Boss">
<property name="carId">
<idref bean="car" />
</property>
</bean>
4.7 整合多个配置文件
<import resource="classpath:com/baobaotao/impt/beans1.xml" />
4.8 Bean作用域
singleton
在Spring IOC容器中仅存一个Bean实例,Bean以单实例的方式存在
prototype
每次从容器调用Bean时,都返回一个新的实例。调用getBean(),相当于执行new XxxBean()
request
每次HTTP请求都会创建一个新的Bean。仅适用于WebApplicationContext环境
session
同一个HTTP Session共享一个Bean,不同HTTP Session使用不同的Bean。仅适用于WebApplicationContext环境
globalSession
同一个全局Seesion共享一个Bean,一般用于Portlet应用环境。仅适用于WebApplicationContext环境
4.8.1 singleton作用域
<bean id="car" class="com.baobaotao.Car" scope="singleton" />
<bean id="boss1" class="com.baobaotao.Boss" p:car-ref="car" />
<bean id="boss2" class="com.baobaotao.Boss" p:car-ref="car" />
指向的同一个Bean,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中。lazy-init=”true”
4.8.2 prototype作用域
启动时不实例化,而是每次调用getBean(“car”)就实例化。
4.8.3 Web应用环境相关的Bean作用域
利用HTTP请求监听器进行配置。在ContextLoaderListener将Web容器与Spring容器整合,Spring容器启动和关闭操作由Web容器的启动和关闭事件触发。而这里需要进行HTTP的事件的监听。
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
request作用域
<bean name="car" class="com.baobaotao.Car" scope="request" />
这样每次HTTP请求调用到car Bean时,Spring容器创建一个新的Bean,请求处理完毕后,销毁这个Bean。
session作用域
<bean name="car" class="com.baobaotao.Car" scope="session" />
作用域横跨真个HTTP Session,Session中所有的HTTP请求都共享同一个car Bean,当HTTP Session结束后,实例才被销毁。
globalSession作用域
<bean name="car" class="com.baobaotao.Car" scope="globalSession" />
类似session作用域,不过仅在Portle的Web应用中使用。
4.8.4 作用域依赖问题
<bean name="car" class="com.baobaotao.Car" scope="request">
<aop:scoped-proxy />
</bean>
<bean id="boss" class="com.baobaotao.Boss">
<property name="car" ref="car" />
</bean>
Spring通过动态代理技术,能够让单例boss Bean引用到对应HTTP请求的car Bean。
4.9 FactoryBean
用户可以通过实现该工厂接口定制实例化Bean的逻辑。
4.10 基于注解的配置
4.10.1 使用注解定义Bean
Spring容器成功启动的三大要件分别是:Bean定义信息、Bean实现类以及Spring本身。Bean定义信息是可以通过XML文件、注解以及类实现。下面是使用注解定义的一个Bean:
@Component("userDao")
public class UserDao{
}
等价于
<bean id="userDao" class="com.baobaotao.UserDao" />
@Repository:用于对DAO实现类进行标注
@Service:用于对Service实现类进行标注
@Controller:用于对Controller实现类进行标注
@Component:用于对实现类进行标注
4.10.2 使用注解配置信息启动Spring容器
Spring提供context命名空间(xmlns:context)通过扫描类包以应用注解定义Bean的方式。
<context:component-scan base-package="com.baobaotao.anno" />
如果仅希望扫描特定的包而非基包下的所有类,如下:
<context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class" />
4.10.3 自动装配Bean
使用@Autowired进行自动注入
使用@Autowired的required属性
@Autowired(required=false)即使Spring找不到匹配的Bean也不报错,默认是true,即会报错。
使用@Qualifier指定注入Bean的名称
&emsp;@Autowired注入的是类,而@Qualifier(“beanname”)注入的是某个name
public class LogonService{
@Autowired
private LogDao logDao;
//注入的Bean名为userDao,类型为UserDao
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
对类方法进行标注
public class LogonService{
//自动将LogDao传给方法入参
@Autowired
public void setLogDao(LogDao logDao){
}
@Autowired
@Qualifier("userDao")
public void setUserDao(UserDao userDao){
}
@Autowired
public void init(@Qualifier("userDao")UserDao userDao, LogDao logDao){
}
}
对集合类进行标注
4.10.4 Bean作用范围及生命过程方法
@Scope(“”)指定作用范围,singleton或者prototype。
@PostConstruct和@PreDestroy,容器初始化和销毁时执行的方法,可以有多个。
4.11 基于Java类的配置
4.11.1 使用Java类提供Bean定义信息
下面两个配置是等价的:
@Configuration
public class AppConf{
@Bean
public UserDao userDao(){
return new UserDao();
}
@Bean
public LogDao logDao(){
return new LogDao();
}
@Bean
public LogonService logonService(){
LogonService logonService = new LoonService();
logonService.setLogDao(logDao());
logonService.setUserDao(userDao());
return logonService;
}
}
<bean id="userDao" class="com.baobaotao.UserDao" />
<bean id="logDao" class="com.baobaotao.LogDao" />
<bean id="logonService" class="com.baobaotao.LogonService"
p:logDao-ref="userDao" p:userDao-ref="logDao" />
如果注入的Bean不在一个包下:
@Configuration
public class ServiceConfig{
@Autowired
private DaoConfig daoConfig;
@Bean
public LogonService logonService(){
LogonService logonService = new LogonService();
logonService.setLogDao(daoConfig.logDao());
logonService.setUserDao(daoConfig.userDao());
return logonService;
}
}
调用daoConfig的logDao()和userDao()方法,就相当于将DaoConfig配置类中定义的Bean注入进来。Spring会对配置类所有标注@Bean的方法进行”改造”(AOP增强),将对Bean生命周期管理的逻辑植入进来。所以我们调用daoConfig.logDao()方法不是简单的执行方法逻辑,而是从Spring容器中返回相应Bean的单例。
4.11.2 使用基于Java类的配置信息启动Spring容器
直接通过@Configuration类启动Spring容器
可以查看使用ClassPathXmlApplicationContext调用xml文件启动Spring容器的方法。
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
ApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(DaoConfig.class);
ctx.register(ServiceConfig.class);
ctx.refresh();
通过XML配置文件引用@Configuration的配置
<context:component-scan base-package="com.baobaotao.conf" resource-pattern="AppConf.class" />
通过Configuration配置类引用XML配置信息,即类与XML混用
<bean id="userDao" class="com.baobaotao.UserDao" />
@Configuration
@ImportResource("classpath:com/baobaotao/conf/beans3.xml")
public class LogonAppConfig{
@Bean
@Autowired
public LogonService logonService(UserDao userDao){
}
}
4.12 不同Bean配置方式比较
4.13 小结
用户不但可以通过属性注入的方式建立Bean和Bean的依赖,也可以通过构造函数的方式完成。
用户可以设置Bean的属性,也可以通过ref引用本地容器,父容器,本文件的Bean。
标签可以建立继承、依赖、引用的关系。
Spring提供了5个Bean作用范围。还可以用代码定义新的作用域。singleton、prototype、request、session、globalSession。
@Component以及@Repository、@Service、@Controller配合@Autowired可以很好地基于注解的配置进行Bean的定义和注入。基于注解。
@Configuration注解后就可以为Spring容器提供Bean的定义信息,在类方法中标注Bean即相当于定义了一个Bean。基于Java类。