spring框架-2
容器中的bean
从本质上来看,Spring容器就是超级大工厂,Spring容器中的 Bean就是该工厂的产品。Spring容器能产生哪些产品,则完全取决于开发者在配置文件中的配置。
对于开发者而言,使用Spring框架主要是做两件事:1、开发Bean。2、配置Bean。对于Spring来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成“依赖注入”——这就是loC的本质。这就要求开发者在使用Spring框架时,眼中看到的是“XML配置”,心中想的是“Java代码”。
Bean的定义和别名
default-lazy-init:指定beans元素下配置的所有Bean默认的延迟初始化行为。
default-merge:指定beans元素下配置的所有Bean默认的融入行为。
default-autowire:指定 beans元素下配置的所有Bean默认的自动装配行为。
default-autowire-candidates:指定 beans元素下配置的所有 Bean是否作为自动装配的候选Bean。
default-init-method:指定该beans元素下配置的所有Bean默认的初始化方法。
default-destory-method:指定beans元素下配置的所有Bean默认的回收方法。
上述beans中的属性,可以在每个bean子元素中指定,将属性名default去掉即可。区别是,beans中属性的定义对所有的bean起作用,为bean元素指定这些属性,只对特定Bean起作用;如果两者所指定的属性不一致,bean下指定的属性会覆盖beans下指定的属性。bean元素是 beans元素的子元素,beans 元素可以包含多个bean子元素,每个bean子元素定义一个Bean,每个Bean 对应 Spring 容器里的一个Java 实例。
定义Bean时,通常需要指定两个属性:id,确定该Bean的唯一标识,class,指定该Bean的具体实现类。如果需要Bean指定别名,有两种方式。
定义bean元素时,通过name属性指定别名:如果需要为Bean实例指定多个别名,则可以在name属性中使用逗号、冒号或者空格来分隔多个别名,后面通过任一别名即可访问该Bean实例。
通过alias元素为已有的Bean指定别名。
在一些极端的情况下,程序无法在定义Bean时就指定所有的别名,而是需要在其他地方为一个已经存在的Bean指定别名,则可使用alias元素完成,该元素可指定如下两个属性。
(1) name:该属性指定一个Bean实例的标识名,表明将为该Bean实例指定别名。
(2) alias:指定别名。示例: <bean id="person"class="" name="zhangsan"/> <alias name="person" alias="jack" />
属性配置
<property name="nam"value="HH"></property>
ref 配置
<property name="userDao"ref="userDaoOO"></property>
自动装配
Spring能自动装配Bean与Bean之间的依赖关系,即无须使用ref显式指定依赖Bean,而是由Spring容器检查XML配置文件内容,根据某种规则,为调用者Bean注入被依赖的Bean。
Spring的自动装配可以通过beans元素的default-autowire属性指定,该属性对配置文件所有的Bean起作用;也可以通过对bean元素的autowire属性指定,该属性只对该Bean起作用。在同一个Spring容器中完全可以让某些Bean使用自动装配,面另一些Bean不使用自动装配。
自动装配可以减少配置文件的工作量,但降低了依赖关系的透明性和清晰性。
autowire、default-autowire属性可以接受如下值。
- no:不使用自动装配。Bean依赖必须通过ref元素定义。这是默认配置,在比较大的部署环境中不鼓励改变这个配置,显示配置合作者能够得到更清晰的依赖关系。
- byName:根据 setter方法名进行自动装配。Spring 容器查找容器中的全部 Bean,找出其i
d与 setter方法名去掉set前缀,并小写首字母后同名的Bean来完成注入。如果没有找到匹配的Bean实例,则Spring不会进行任何注入。 - byType:根据setter方法的形参类型来自动装配。Spring容器查找容器中全部的Bean,如果正好有一个Bean类型与setter方法的形参类型匹配,就自动注入这个Bean;如果找到多个这样的Be an,就抛出一个异常,如果没有找到这样的Bean,则什么都不操作,setter方法也不会被调用。
- constructor:与byType类似,区别是用于自动匹配构造器的参数。如果容器不能恰好找到一个与构造器参数类型匹配的Bean,则会抛出异常。
- autodetect:Spring容易根据Bean内部结构、自行决定使用constructor或byType策略。如果找到一个默认的构造函数,那么就会应用byType策略。
<bean id="b" class="com.origin.d701.B">
<property name="BName" value="wangwu"></property>
</bean>
<bean id="a" class="com.origin.d701.A" autowire="byName">
<property name="AName" value="zhangsan"></property>
</bean>
@Autowired 和 @Resource 的区别?
- @Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找
- @Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找
嵌套bean
如果某个Bean类A依赖Bean类B,而B不想被Spring直接访问,则可以使用嵌套Bean。不能直接访问的含义是:Spring不能在容器中通过id去获取这个Bean的实例。嵌套Bean就是使用bean元素,配置成property 元素或constructor-args的子元素,那么该bean元素配置的Bean仅仅作为setter注入、构造注入的参数。由于容器不能获取嵌套Bean,因此不需要指定id属性。
<bean id="a" class="com.origin.d701.A" autowire="byName">
<property name="AName" value="zhangsan"></property>
<property name="b">
<bean class="com.origin.d701.B">
<property name="BName" value="wangwu"></property>
</bean>
</property>
</bean>
注入集合
如果需要调用形参类型为集合的setter方法,或调用形参类型为集合的构造器,则可以使用集合元素list、set、map 和 props 分别来设置类型为 List、Set、Map 和 Properties的集合参数值。
<bean id="a" class="com.origin.d701.A" autowire="byName">
<property name="AName" value="zhangsan"></property>
<property name="b">
<bean class="com.origin.d701.B">
<property name="BName" value="wangwu"></property>
</bean>
</property>
<property name="bookList">
<list>
<value>操作系统</value>
<value>数据结构</value>
</list>
</property>
<property name="stuMap">
<map key-type="java.lang.String">
<entry key="c01">
<bean class="java.lang.Integer">
<constructor-arg type="java.lang.String" value="1"/>
</bean>
</entry>
</map>
</property>
<property name="config">
<props>
<prop key="address">山东</prop>
<prop key="height">180</prop>
</props>
</property>
<property name="otherSet">
<set value-type="java.lang.Object">
<value>你要干啥</value>
<bean class="com.origin.d630.User">
<property name="name" value="zhangsi"></property>
<property name="age" value="18"></property>
</bean>
</set>
</property>
<property name="pets">
<array value-type="java.lang.String">
<value>狗</value>
<value>猫</value>
</array>
</property>
</bean>
组合属性
public class A {
private String aName;
private B b;
}
<bean id="a" class="com.origin.d701.A" autowire="byName">
<property name="AName" value="zhangsan"></property>
<property name="b.BName" value="wangwu"></property>
</bean>
当获取a类时,因为b没有实例化,会出现空指针错误。
private B b = new B();
创建bean的方式
在大多数情况下,Spring容器直接通过new关键字调用构造器来创建Bean实例,而class属性指定了Bean实例的实现类。因此,bean元素必须指定Bean实例的class属性,但这并不是实例化Bean的唯一方法。Spring支持使用如下方式来创建Bean。
- 调用构造器创建Bean。
- 调用静态工厂方法创建Bean。
- 调用实例工厂访法创建Bean。
创建Bean静态工厂创建Bean
factory-method="getPet"
public class PetFactory {
public static Pet getPet(String type){
System.out.println(type+"-------");
if ("dogggg".equals(type)){
return new Dog();
} else {
return new Cat();
}
}
}
创建Bean实例工厂方法创建Bean
实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需要工厂类即可,而调用实例工厂方法则需要工厂实例。所以配置实例工厂方法与配置静态工厂方法基本相似,只有一点区别:配置静态工厂方法使用class指定静态工厂类,而配置实例工厂方法则使用factory
-bean指定工厂实例。
采用实例工厂方法创建Bean的bean元素时需要指定如下两个属性:
- factory-bean:该属性的值为工厂Bean的 id。
- factory-method:该属性指定实例工厂的工厂方法
<bean id="petFactory" class="com.origin.d701.PetFactory"></bean>
<bean id="dog" factory-bean="petFactory" factory-method="getPet">
<constructor-arg value="dogggg"></constructor-arg>
<property name="name" value="黑狗"></property>
</bean>
public class PetFactory {
public Pet getPet(String type){
System.out.println(type+"-------");
if ("dogggg".equals(type)){
return new Dog();
} else {
return new Cat();
}
}
}
抽象 bean
在实际开发中,有可能会出现这样的情况:随着项目越来越大Spring配置文件出现了多个bean,配置具有大致相同的配置信息,只有少量信息不同,这将导致配置文件出现很多重复的内容。如果保留这些配置,则可能导致的问题是:
- 配置文件臃肿。
- 后期难以修改、维护。
为了解决上面问题,可以考虑把多个bean配置中相同的信息提取出来,集中成配置模板——这个配置模板并不是真正的Bean。因此Spring不应该创建该配置模板,于是需要为该bean配置增加abstract属性值为true表示这是个抽象Bean。
抽象Bean不能被实例化,Spring容器不会创建抽象Bean实例。抽象Bean的价值在于被继承,抽象Bean通常作为父Bean被继承。抽象Bean只是配置信息的模板,指定abstract为true即可阻止Spring实例化该Bean.因此抽象Bean可以不指定 class 属性。
将大部分相同信息配置成抽象Bean之后,将实际的Bean实例配置成该抽象Bean的子Bean即可。子Bean定义可以从父Bean继承实现类、构造参数、属性值能配置信息,除此之外,子Bean配置可以增加新的配置信息,并可以指定新的配置信息覆盖父Bean的定义。
通过为一个bean元素指定parent属性即可指定该Bean是一个子Bean,parent 属性指定该Bean所继承的父 Bean 的 id。子 Bean 无法从父 Bean 继承如下属性:depends-on、autowire、singleton、scope、lazy-init,这些属性只能从子Bean定义中获取,或采用默认值。
<bean id="bTemplete" abstract="true">
<property name="bname" value="haha"></property>
</bean>
<bean id="b1" class="com.yuand.bean.B"parent="bTemplete"></bean>
<bean id="b2"class="com.yuand.bean.B" parent="bTemplete"></bean>
在配置文件中b1和b2都指定了parent=”bTemplete”,表明这两个Bean都可以从父Bean那里继承得到配置信息。虽然这两个Bean没有直接指定proerty子元素,但他们会从bTemplete模板那里继承得到两个property子元素。
与java中的抽象类没有任何关系
Spring中的Bean继承与Java中的继承截然不同,前者是实例与实例之间参数的延续,后者则是一般到特殊的细化。前者是对象与对象之间的关系,后者则是类与类之间的关系。Spring中Bean的继承和Java中Bean的继承
有如下区别:
Spring中的子Bean和父Bean可以是不同类型,但Java中的继承则可保证子类是一种特殊的父类。
Spring中Bean的继承是实例之间的关系,因此主要表现为参数值的延续;而Java中的继承是类之间的关系,主要表现为方法、属性的延续。
Spring中的子Bean不可作为父Bean使用,不具备多态性;Java中的子类实例完全可以当成父类实例使用。
工厂 bean
此处的工厂Bean,与前面介绍的实例工厂Bean,或者静态工厂Bean有所区别:前面那些Bean是标准的工厂模式,Spring只是负责调用工厂方法来创建Bean的实例;此处的工厂Bean是Spring的一种特殊Bean,这种工厂 Bean 必须实现FactoryBean接口。
FactoryBean接口是工厂Bean的标准接口,把实现FactoryBean接口的工厂Bean部署在容器中后,如果程序通过getBean方法来获取它时,容器返回的不是FactoryBean实现类的实例,而是返回FactoryBean的产品。(即通过工厂所创建的对象被返回)
FactoryBean接口提供如下三个方法:
T getObject():负责返回该工厂 Bean 生成的Java 实例。
Class<?>getObjectType():返回该工厂Bean生成的Java实例的实现类。
boolean isSingleton():表示该工厂Bean生成的Java实例是否是单例模式。
配置FactoryBean与配置普通Bean的定义没有区别.但当程序向Spring容器请求获取该Bean时,容器返回该FactoryBean的产品,而不是返回该FactoryBean本身。所以,实现FactoryBean接口的最大作用在于:Spring容器返回的是该Bean实例的getObject()方法的返回值。而getObject()方法由开发者负责实现,所以返回什么类型就由开发者自己决定。
@Data
public class CreateObjFactoryBean implements FactoryBean<Object> {
private String className;
@Override
public Object getObject() throws Exception {
Class clazz = Class.forName(className);
return clazz.newInstance();
}
@Override
public Class<?> getObjectType() {
return Object.class;
}
}
<bean id="b" class="com.origin.d701.CreateObjFactoryBean">
<property name="className" value="com.origin.d701.B">
</property>
</bean>
从配置文件来看,部署工厂Bean与部署普通Bean其实没有任何区别、同样只需要为该Bean配置id、class属性即可。但Spring对FactoryBean接口的实现类的处理有所不同。Spring容器会自动检测容器中所有的Bean,如果发现某个 Bean实现了FactoryBean接口,Spring容器就会在实例化该 Bean、根据 property执行setter方法之后,额外要调用该 Bean的getObject方法,并将返回值作为容器中的Bean。
强制初始化bean
在大多数情况下,Bean之间的依赖非常直接,Spring容器返回Bean实例之前,先要完成Bean依赖关系的注入。假如 Bean A 依赖于Bean B,程序请求Bean A时,Spring容器会自动先自动初始化 Bean B,再将Bean B注入BeanAy最后将具备完整依赖的BeanA返回给程序。
在极端的情况下,Bean之间的依赖关系不够直接。比如某个类的初始化块中使用其他Bean,Spring总是先初始化主调Bean,当执行初始化块时,被依赖的Bean可能还没有实例化,此时将引发异常。为了显示指定被依赖Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。配置文件如下:
<bean id="two" class="com.yuand.bean.Two" depends-on="one">
<property name="twoName"value="twotwo"></property>
</bean>
<bean id="one" class="com.yuand.bean.One">
<property name="oneName"value="oneone"></property>
</bean>
默认情况下,加载顺序是,从上往下加载。就是先加载Two,再加载One。但是Two加上depends-on=”one”之后,就代表,如果想要加载Two,得先加载依赖的One。所以,就变成了先One后Two了
Bean的生命周期
Spring可以管理singleton作用域的Bean的生命周期,Spring可以精确地知道该Bean何时被创建、何时被初始化完成、容器何时准备销毁该Bean实例。
对于prototype作用域的Bean,Spring容器仅仅负责创建,当容器创建Bean实例之后,Bean实例完全交给客户端代码管理,容器不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring都会产生一个实例交给客户端程序,就不再过问了。
对于singleton作用域的Bean,每次客户端代码请求时都返回同一个共享实例,客户端代码不能控制Bean的销毁,Spring容器负责跟踪Bean实例的产生、销毁。Spring容器可以在创建Bean之后,进行某些通用资源申请;还可以在销毁Bean实例之前,先回收某些资源,比如数据库连接。
对于singleton作用域的Bean,Spring容器知道Bean何时实例化结束、何时销毁,Spring可以管理实例化结束之后和销毁之前的行为,即Bean的存活期间的行为。
依赖关系注入之后的行为
Spring提供两种方式在Bean全部属性设置成功后执行特定情况。
- 使用init-method 属性。
- 实现InitializingBean接口。
第一种方式,使用init-method属性指定某个方法应在Bean全部依赖关系设置结束后自动执行。使用这种方式不需要将代码与Spring的接口耦合在一起,代码污染小。
第二种方式,让 Bean类实现InitializingBean接口,该接口提供一个方法afterPropertysSet(),Spring容器会在为该Bean注入依赖关系之后,调用该Bean所实现的afterPropertysSet方法。如果某个Bean实现了InitializingBean接口,当Bean的所有依赖关系被设置完成后,Spring容器会自动调用该Bean实例的afterPorpertiesSet方法。其执行结果与采用init-method属性指定生命周期方法几乎一样,但实现InitializingBean接口污染了代码,是侵入式设计,因此不推荐采用。
Bean销毁之前的行为
与定制初始化行为相似,Spring也提供两种方式定制Bean实例销毁之前的特定行为,这两种方式如下:
- 使用destroy-method属性。
- 实现DisposableBean接口。
让 Bean类实现DisposableBean接口,需实现接口中声明的 destroy方法,该方法在 Bean被销毁前调用,第二种方式同样为侵入式设计,不推荐采用。销毁之前的行为与依赖关系注入之后的行为原理一样,不再进行详细叙述。
<bean id="b" class="com.origin.d701.B" init-method="f1" destroy-method="f2">
<property name="BName" value="zhangs"></property>
</bean>