一、依赖注入详细配置
1、通过P名称空间为Bean注入值
1、简化setter方式注入的配置,可以不使用
<property />
子标签来为属性设置值,直接在<bean />
标签中设置各依赖项的值。2、注入值类型说明:
对于String、基本类型及其包装类使用
p:属性名="值"
。对于对象类型使用
p:属性名-ref="值"
,表示引用一个Bean。3、实现步骤:
xml中导入p名称空间:
xmlns:p="http://www.springframework.org/schema/p"
写带前缀属性:
p:属性="值"
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<!--Teacher类配置-->
<bean id="teacher1" class="com.itan.beans.Teacher" p:name="张三" p:age="30"/>
</beans>
@Test
public void test01() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Teacher bean = context.getBean("teacher1", Teacher.class);
System.out.println(bean);
}
/**
* 运行结果:
* setter属性注入age
* setter属性注入name
* Teacher{姓名='张三', 年龄=25}
*/
2、null值注入
1、Spring将默认值为空的属性的值视为空字符串。如果将value设置为null,Spring会识别成“null”字符串,如果要真正的设置为null,就应该使用
<null />
标签,才表示真正的注入null值。
<bean id="teacher2" class="com.itan.beans.Teacher">
<!--空的value属性被当做空字符串-->
<property name="name" value="" />
<property name="age" value="18" />
<!--只有null标签才表示真正的注入null-->
<property name="sex">
<null />
</property>
</bean>
@Test
public void test01() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Teacher bean = context.getBean("teacher2", Teacher.class);
System.out.println(bean.getName() == null);
System.out.println(bean.getAge());
System.out.println(bean.getSex() == null);
System.out.println(bean);
}
/**
* 运行结果:
* false
* 18
* true
* Teacher{姓名='', 年龄=18', 性别=}
*/
3、ref引用
1、在
<constructor-arg />、<property />、<entry />
标签中有一个ref属性,用于将Bean的指定的属性的值设置为容器管理的另一个Bean的引用,也就是引用类型属性设置依赖的方式。2、在为引用类型属性设置依赖之前,必须保证被引用的Bean已经被初始化,ref属性的值要与被引用的Bean的id或name属性中的一个值相等。
3、使用ref为引用类型设置依赖,获取的到的Bean与直接通过IoC容器中获取到的Bean,是同一个Bean。
public class Subject {
private String name;
private Teacher teacher;
public Subject(String name, Teacher teacher) {
this.name = name;
this.teacher = teacher;
System.out.println("构造器依赖注入");
}
public Teacher getTeacher() {
return teacher;
}
@Override
public String toString() {
return "Subject{" +
"科目='" + name + '\'' +
", 老师='" + teacher + '\'' +
'}';
}
}
<!--Teacher类配置-->
<bean id="teacher3" class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--Subject类配置-->
<bean id="subject" class="com.itan.beans.Subject">
<constructor-arg value="Java" />
<!--使用ref属性引用teacher-->
<constructor-arg ref="teacher3" />
<!--或者使用ref标签引用teacher-->
<!--<constructor-arg>-->
<!-- <ref bean="teacher3"></ref>-->
<!--</constructor-arg>-->
</bean>
@Test
public void test01() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Subject subject = context.getBean("subject", Subject.class);
System.out.println(subject);
// 通过subject获取teacher
Teacher teacher1 = subject.getTeacher();
// 通过ioc容器直接获取teacher
Teacher teacher2 = context.getBean("teacher3", Teacher.class);
// 比较两个是否相等
System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}
}
/**
* 运行结果:
* Subject{科目='Java', 老师='Teacher{姓名='张三', 性别='男', 年龄=32}'}
* teacher1与teacher2是否相等:true
*/
4、内部Bean
1、在
<constructor-arg />、<property />、<entry />
以及一些集合标签中可以使用<bean />
子标签,表示一个内部Bean,如果要为一个引用类型的属性设置依赖,在不使用ref属性或者ref标签的情况下,可以定义自己的内部的Bean。2、与外部Bean的区别是:
内部Bean不需要定义id或name属性,因为这个对象就相当外部Bean自己的对象,就算设置了id或name,容器也不会使用这些值作为Bean的名字,而且也不能通过IoC获取Bean,相当于内部Bean是一个匿名的
。
容器在创建时也会忽略内部Bean的scope作用域属性
。
<!--Subject类配置-->
<bean id="subject1" class="com.itan.beans.Subject">
<constructor-arg value="Java" />
<constructor-arg>
<!--注意:内部bean设置id或name属性无效,也无法从IoC中获取到-->
<bean class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
</constructor-arg>
</bean>
@Test
public void test02() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Subject subject = context.getBean("subject1", Subject.class);
System.out.println(subject);
}
/**
* 运行结果:
* Subject{科目='Java', 老师='Teacher{姓名='张三', 性别='男', 年龄=32}'}
*/
5、集合属性的注入
1、Spring支持集合注入方式,提供了一系列的标签
<list>、<set>、<map>、<props>、<array>
分别为Java中的List、Set、Map、Properties、Array类型的集合注入依赖,由于集合的元素既可以是基本类型也可以是对象甚至集合,因此集合注入非常灵活。另外集合注入支持泛型转换,注入的时候会自动将value的字符串值转换为对应泛型类型。
@Data
public class CollectionBean {
private List<Object> list;
private Set<Object> set;
private Map<Object, Object> map;
private Properties properties;
private Object[] array;
}
List注入示例:
<!--CollectionBean类配置-->
<bean id="collectionBean" class="com.itan.beans.CollectionBean">
<property name="list">
<!--list标签表示list集合,用于定义多个元素-->
<list>
<!--value标签用于定义字面量的值作为集合元素-->
<value>111</value>
<!--也可以使用Bean标签来定义Bean对象作为集合的元素-->
<bean class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--也可以使用ref引用一个外部Bean作为集合的元素-->
<ref bean="teacher3" />
</list>
</property>
</bean>
@Test
public void test03() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
CollectionBean collectionBean = context.getBean("collectionBean", CollectionBean.class);
List<Object> list = collectionBean.getList();
System.out.println(list);
}
/**
* 运行结果:
* [111, Teacher{姓名='张三', 性别='男', 年龄=32}, Teacher{姓名='张三', 性别='男', 年龄=32}]
*/
Set注入示例:
<!--CollectionBean类配置-->
<bean id="collectionBean1" class="com.itan.beans.CollectionBean">
<property name="set">
<!--set标签表示set集合,用于定义多个元素-->
<set>
<!--value标签用于定义字面量的值作为集合元素-->
<value>111</value>
<!--也可以使用Bean标签来定义Bean对象作为集合的元素-->
<bean class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--也可以使用ref引用一个外部Bean作为集合的元素-->
<ref bean="teacher3" />
</set>
</property>
</bean>
@Test
public void test04() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
CollectionBean collectionBean = context.getBean("collectionBean1", CollectionBean.class);
Set<Object> set = collectionBean.getSet();
System.out.println(set);
}
/**
* 运行结果:
* [111, Teacher{姓名='张三', 性别='男', 年龄=32}, Teacher{姓名='张三', 性别='男', 年龄=32}]
*/
Map注入示例:
<!--CollectionBean类配置-->
<bean id="collectionBean2" class="com.itan.beans.CollectionBean">
<property name="map">
<!--map标签表示set集合,用于定义多个元素-->
<map>
<!--map标签中首先需要定义entry标签,表示一个键值对-->
<entry key="测试" value="哈哈哈哈" />
<!--key和value都可以引用外部bean-->
<entry>
<!--key可以使用内部bean,或者集合等等-->
<key>
<bean class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
</key>
<!--也可以引用外部Bean-->
<ref bean="subject" />
</entry>
<!--注意value标签只能是注入字面量值,如果想要对象类型的value,那么直接使用Bean标签就行了-->
<entry key="Oracle">
<!--<value></value>-->
<bean class="com.itan.beans.Teacher">
<property name="name" value="李四" />
<property name="age" value="35" />
<property name="sex" value="男" />
</bean>
</entry>
</map>
</property>
</bean>
@Test
public void test05() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
CollectionBean collectionBean = context.getBean("collectionBean2", CollectionBean.class);
Map<Object, Object> map = collectionBean.getMap();
System.out.println(map);
}
/**
* 运行结果:
* {测试=哈哈哈哈, Teacher{姓名='张三', 性别='男', 年龄=32}=Subject{科目='Java', 老师='Teacher{姓名='张三', 性别='男', 年龄=32}'}, Oracle=Teacher{姓名='李四', 性别='男', 年龄=35}}
*/
Properties注入示例:
<!--CollectionBean类配置-->
<bean id="collectionBean3" class="com.itan.beans.CollectionBean">
<property name="properties">
<!--props标签表示properties集合,prop表示一个键值对-->
<props>
<!--表示一个String类型键值对-->
<prop key="测试">哈哈哈</prop>
<prop key="123">456</prop>
</props>
<!--实际上也可以放map,但是要求String类型的key和value-->
<!--<map>-->
<!-- <entry key="测试" value="哈哈哈"/>-->
<!--</map>-->
</property>
</bean>
@Test
public void test06() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
CollectionBean collectionBean = context.getBean("collectionBean3", CollectionBean.class);
Properties properties = collectionBean.getProperties();
System.out.println(properties);
}
/**
* 运行结果:
* {123=456, 测试=哈哈哈}
*/
Array注入示例:
<!--CollectionBean类配置-->
<bean id="collectionBean4" class="com.itan.beans.CollectionBean">
<property name="array">
<!--array标签表示array数组,用于定义多个元素-->
<array>
<!--value标签用于定义字面量的值作为数组元素-->
<value>111</value>
<!--也可以使用Bean标签来定义Bean对象作为数组的元素-->
<bean class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--也可以使用ref引用一个外部Bean作为集合的元素-->
<ref bean="teacher3" />
</array>
</property>
</bean>
@Test
public void test07() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
CollectionBean collectionBean = context.getBean("collectionBean4", CollectionBean.class);
Object[] array = collectionBean.getArray();
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
/**
* 运行结果:
* 111
* Teacher{姓名='张三', 性别='男', 年龄=32}
* Teacher{姓名='张三', 性别='男', 年龄=32}
*/
6、value字面量
1、对于基本类型、String、包装类类型的属性,可以直接使用value属性的字符串值来注入值。
2、在最后注入的时候Spring会自动将这些值从String 转换为属性或参数的实际类型。
3、也可以使用
<value />
标签来设置值4、上面一些示例中使用到value标签,请参考。
7、级联属性赋值
1、级联属性可以修改属性的属性,
但是原来的Bean的属性值可能会被修改
。
<!--Teacher类配置-->
<bean id="teacher3" class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--Subject类配置-->
<bean id="subject2" class="com.itan.beans.Subject">
<constructor-arg value="Java" />
<!--使用ref属性引用teacher-->
<constructor-arg ref="teacher3" />
<!--为级联属性赋值,修改属性的属性-->
<property name="teacher.name" value="李四" />
</bean>
@Test
public void test08() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Teacher teacher = context.getBean("teacher3", Teacher.class);
// 获取bean对象
Subject subject = context.getBean("subject2", Subject.class);
System.out.println(teacher);
System.out.println(subject);
}
/**
* 运行结果:可以看到在修改级联属性后,被引用的原Bean中的属性值被改变
* Teacher{姓名='李四', 性别='男', 年龄=32}
* Subject{科目='Java', 老师='Teacher{姓名='李四', 性别='男', 年龄=32}'}
*/
8、parent继承(复用属性值)
1、在
<bean />、<ref />
标签中有一个parent
属性,它用于指定目标Bean将使用父Bean的属性和配置,用于相同属性以及值的复用,如果当前Bean中有与父Bean相同的属性,且当前Bean中为某一个相同的属性重新设置了值,那么当前Bean的其他属性值将复用父Bean的,重新设置值的属性将用自己的值。2、parent属性的值与目标父Bean的id属性或name属性中的一个值相同。
<!--Teacher类配置-->
<bean id="teacher4" class="com.itan.beans.Teacher">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--Teacher类配置,使用parent指定继承自teacher4这个bean-->
<bean id="teacher5" class="com.itan.beans.Teacher" parent="teacher4">
<!--为name设置自己的值-->
<property name="name" value="李四" />
</bean>
@Test
public void test09() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Teacher teacher = context.getBean("teacher5", Teacher.class);
System.out.println(teacher);
}
/**
* 运行结果:
* Teacher{姓名='李四', 性别='男', 年龄=32}
*/
9、abstract抽象(只允许被继承)
1、在
<bean />
标签中有一个abstract属性,当值为true时,表示当前Bean是一个抽象的,不能获取它的实例,只能被别人用来继承,类似于一个属性和值的模版;如果尝试获取则会抛出如下错误:Error creating bean with name 'XXXXXXX': Bean definition is abstract
<!--Teacher类配置-->
<bean id="teacher4" class="com.itan.beans.Teacher" abstract="true">
<property name="name" value="张三" />
<property name="age" value="32" />
<property name="sex" value="男" />
</bean>
<!--Teacher类配置-->
<bean id="teacher5" class="com.itan.beans.Teacher" parent="teacher4">
<property name="name" value="李四" />
</bean>
@Test
public void test09() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取bean对象
Teacher teacher5 = context.getBean("teacher5", Teacher.class);
System.out.println(teacher5);
Teacher teacher4 = context.getBean("teacher4", Teacher.class);
System.out.println(teacher4);
}
/**
* 运行结果:
* Teacher{姓名='李四', 性别='男', 年龄=32}
* org.springframework.beans.factory.BeanIsAbstractException: Error creating bean with name 'teacher4': Bean definition is abstract
*/
10、depends-on强制顺序
1、如果某个Bean是另一个Bean的依赖项,则通常意味着这个Bean被设置为另一个Bean的属性。但是,有时Bean之间的依赖关系不太直接,他们之间没有直接的引用关系。
2、
<bean />
标签中的depends-on属性可以强制在初始化使用此元素的Bean之前初始化一个或多个指定的Bean,depends-on的值是先初始化的Bean的id或者name,需要指定多个时,可以使用“,”、“;”、“ ”符号分隔,Spring会按照depend-on中定义的顺序来初始化Bean。
<bean id="people" class="com.itan.beans.People" depends-on="person,teacher6"/>
<bean id="person" class="com.itan.beans.Person"/>
<bean id="teacher6" class="com.itan.beans.Teacher"/>
@Test
public void test10() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
}
/**
* 运行结果:
* Person初始化
* Teacher初始化
* People初始化
*/
11、scope作用域
1、
<bean />标签中的scope属性用于定义Bean的作用域
,Spring Framework支持六个作用域,其中后面四个作用域仅在使用web感知的ApplicationContext时可用。还可以创建自定义范围。2、Spring 5.x支持的作用域属性如下:
singleton
:单例的(默认值),Bean在IoC容器中仅有一个对象实例;在容器启动完成之前就已经创建好对象,只要容器在,对象一直活着,并且不会再创建;任何获取Bean的操作都是同一个对象。
prototype
:原型的(多实例),一个Bean可以有多个实例,容器启动时不会初始化设置为prototype的Bean;在每次获取的时候都会创建一个新的Bean实例。
request
:将单个Bean定义限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的Bean实例,该实例是在单个Bean定义的后面创建的。
session
:将单个Bean定义限定为HTTP的生命周期Session
。
application
:将单个Bean定义限定为ServletContext
。
websocket
:将单个Bean定义限定为WebSocket
<!--单实例Bean-->
<bean id="person" class="com.itan.beans.Person" scope="singleton" />
<!--多实例Bean-->
<bean id="teacher" class="com.itan.beans.Teacher" scope="prototype"/>
@Test
public void test01() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
}
/**
* 运行结果:在容器初始化时候,并没有创建Teacher实例
* Person初始化
*/
@Test
public void test02() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Person person1 = context.getBean("person", Person.class);
Person person2 = context.getBean("person", Person.class);
System.out.println("person1与person2是否相等:" + (person1 == person2));
System.out.println("================================");
Teacher teacher1 = context.getBean("teacher", Teacher.class);
Teacher teacher2 = context.getBean("teacher", Teacher.class);
System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}
/**
* 运行结果:单例的Person只会初始化一次,并且每次获取实例都是相同的,多例的Teacher在每次获取时,
* 才会初始化,并且每个实例不相同。
* Person初始化
* person1与person2是否相等:true
* ================================
* Teacher初始化
* Teacher初始化
* teacher1与teacher2是否相等:false
*/
12、静态工厂方式创建Bean
1、本质上是工厂模式的应用,对象由我们创建,但是由Spring调用一次静态的创建方法,将创建的对象存入容器。
静态工厂类本身并不会实例化
。2、使用静态工厂方法实例化Bean时,
<bean />标签中的class属性不再是要获取的Bean的全路径类名,而是静态工厂的全路径类名,并使用factory-method的属性指定获取bean对象的工厂方法的名称(注意该方法必须是静态方法)
。
/**
* @Date: 2022/11/13
* 静态工厂
*/
public class StaticFactory {
/**
* 静态方法,调用方式:StaticFactory.getPerson()
* @return
*/
public static Person getPerson() {
System.out.println("静态工厂方法创建对象开始");
Person person = new Person("张三", 20);
person.setSex("男");
person.setEmail("111111@qq.com");
return person;
}
}
<!--静态工厂(不需要实例化工厂本身)-->
<bean id="person1" class="com.itan.factory.StaticFactory" factory-method="getPerson" />
@Test
public void test03() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Person person1 = context.getBean("person1", Person.class);
System.out.println(person1);
}
/**
* 运行结果:
* 静态工厂方法创建对象开始
* Person{姓名='张三', 性别='男', 年龄=20', 邮箱=111111@qq.com}
*/
13、实例工厂方式创建Bean
1、
实例工厂本身需要实例化工厂类的,然后调用工厂实例中的非静态方法创建Bean
。2、使用实例工厂方法实例化Bean时,
<bean />标签中的class属性置空,使用factory-bean的属性指定实例工厂的名字,使用factory-method属性指定非静态工厂方法的名称
。3、
factory bean虽然代表一个工厂,其实例仍然交给Spring管理。
/**
* @Date: 2022/11/13
* 实例工厂
*/
public class InstanceFactory {
/**
* 实例方法,调用方式:new InstanceFactory().getPerson()
* @return
*/
public Person getPerson() {
System.out.println("实例工厂方法创建对象开始");
Person person = new Person("李四", 20);
person.setSex("女");
person.setEmail("2222222@qq.com");
return person;
}
}
<!--实例工厂(需要实例化工厂本身)-->
<bean id="instanceFactory" class="com.itan.factory.InstanceFactory" />
<!--factory-bean:指定当前bean使用那个工厂创建-->
<bean id="person2" factory-bean="instanceFactory" factory-method="getPerson" />
@Test
public void test04() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Person person1 = context.getBean("person2", Person.class);
System.out.println(person1);
}
/**
* 运行结果:
* 实例工厂方法创建对象开始
* Person{姓名='李四', 性别='女', 年龄=20', 邮箱=2222222@qq.com}
*/
14、FactoryBean创建Bean
1、
FactoryBean
是一个工厂类接口,可以通过实现该接口定制实例化Bean
的逻辑。2、提供了三种方法:
T getObject()
:返回此工厂创建的对象的实例。该实例可能会被共享,具体取决于该工厂是返回单例还是原型。
Class<?> getObjectType()
:返回getObject()
方法返回的对象类型,如果不知道类型,则返回null
。
default boolean isSingleton()
:获取这个工厂管理的对象是否为单例的,true
表示单例(默认值),false
表示非单例。3、注意:
isSingleton
方法返回false并不一定表示返回的对象是单实例的。扩展SmartFactoryBean
接口的实现可以通过其SmartFactoryBean.isPrototype()
方法显式指示独立实例。
如果isSingleton()实现返回false,则简单地假定未实现此扩展接口的普通FactoryBean实现始终返回独立实例
。
默认实现返回true,因为FactoryBean通常管理一个单例实例
。
IoC容器在启动的时候不会创建实现FactoryBean接口类getObject方法返回的实例,只有从IoC容器中获取的时候才会创建对应的实例
。
/**
* @Date: 2022/11/15
* 自定义FactoryBean,实现FactoryBean接口,Spring会自动调用工厂方法创建实例
*/
public class CustomFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
System.out.println("自定义FactoryBean工厂");
Person person = new Person("王武", 40);
person.setSex("男");
person.setEmail("333333@qq.com");
return person;
}
@Override
public Class<?> getObjectType() {
return Person.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="customFactoryBean" class="com.itan.factory.CustomFactoryBean" />
@Test
public void test05() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 在获取的时候才会创建customFactoryBean类中返回的对象
Object customFactoryBean = context.getBean("customFactoryBean");
System.out.println(customFactoryBean.toString());
}
/**
* 运行结果:
* 自定义FactoryBean工厂
* Person{姓名='王武', 性别='男', 年龄=40', 邮箱=333333@qq.com}
*/
二、生命周期回调
1、概述
1、Spring在容器初始化Bean之后(完成依赖注入后)和销毁前都提供了回调的方法,称之为生命周期的回调方法,Spring提供了三种方式来完成生命周期的回调。
2、回调接口方式
1、初始化回调:实现
InitializingBean
接口中的afterPropertiesSet
方法。在容器为Bean设置了所有必要的属性后,该接口让Bean执行初始化工作。2、销毁回调:实现
DisposableBean
接口中的destroy
方法。在包含目标Bean的容器被销毁时进行回调。3、注意:
官方不建议使用
InitializingBean、DisposableBean
接口来完成生命周期的回调,原因是它们会将代码耦合到Spring中,建议使用下面的两种方式来完成生命周期的回调。
对于单实例Bean来说生命周期顺序:容器启动(构造器)->初始化方法->容器销毁(销毁方法)
。
对于多实例Bean来说生命周期顺序:在获取Bean的前提下(多实例Bean在获取的时候才进行初始化),顺序如下:构造器->初始化方法->容器销毁不会调用Bean的销毁方法
。
/**
* @Date: 2022/11/15
* 生命周期回调
*/
public class Lifecycle implements InitializingBean, DisposableBean {
private String name;
private String sex;
public Lifecycle() {
System.out.println("Lifecycle构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Lifecycle设置name属性值");
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
System.out.println("Lifecycle设置sex属性值");
this.sex = sex;
}
@Override
public String toString() {
return "Lifecycle{" +
"姓名='" + name + '\'' +
", 性别='" + sex +
'}';
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Lifecycle执行初始化回调方法");
}
@Override
public void destroy() throws Exception {
System.out.println("Lifecycle执行销毁回调方法");
}
}
<bean id="lifecycle" class="com.itan.beans.Lifecycle">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
</bean>
@Test
public void test06() {
// 通过配置文件创建容器对象
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Lifecycle lifecycle = context.getBean("lifecycle", Lifecycle.class);
System.out.println(lifecycle);
// 销毁容器
context.close();
}
/**
* 运行结果:
* Lifecycle构造方法
* Lifecycle设置name属性值
* Lifecycle设置sex属性值
* Lifecycle执行初始化回调方法
* Lifecycle{姓名='张三', 性别='男}
* Lifecycle执行销毁回调方法
*/
通过运行结果可以看出:
Spring容器启动,初始化Bean时优先执行构造方法,并且在为属性注入值之后,才会执行初始化回调方法,容器销毁时,执行销毁回调方法
。
3、自定义方法方式
1、
<bean />
标签中的init-method、destroy-method
两个属性用来指定Bean初始化回调和销毁回调方法的。
/**
* @Date: 2022/11/15
* 生命周期回调
*/
public class Lifecycle1 {
private String name;
private String sex;
public Lifecycle1() {
System.out.println("Lifecycle1构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("Lifecycle1设置name属性值");
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
System.out.println("Lifecycle1设置sex属性值");
this.sex = sex;
}
@Override
public String toString() {
return "Lifecycle1{" +
"姓名='" + name + '\'' +
", 性别='" + sex +
'}';
}
public void init() throws Exception {
System.out.println("Lifecycle1执行初始化回调方法");
}
public void destroy() throws Exception {
System.out.println("Lifecycle1执行销毁回调方法");
}
}
<bean id="lifecycle" class="com.itan.beans.Lifecycle1" init-method="init" destroy-method="destroy">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
</bean>
@Test
public void test06() {
// 通过配置文件创建容器对象
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Lifecycle1 lifecycle = context.getBean("lifecycle", Lifecycle1.class);
System.out.println(lifecycle);
// 销毁容器
context.close();
}
/**
* 运行结果:
* Lifecycle1构造方法
* Lifecycle1设置name属性值
* Lifecycle1设置sex属性值
* Lifecycle1执行初始化回调方法
* Lifecycle1{姓名='张三', 性别='男}
* Lifecycle1执行销毁回调方法
*/
4、注解方式
1、Spring2.5中引入了注释的支持,可以使用
@PostConstruct和@PreDestroy注解
实现初始化回调和销毁回调。
@PostConstruct
:在Bean创建完成并且属性注入值完成,执行初始化方法。
@PreDestroy
:在容器销毁Bean之前,执行销毁方法。
/**
* @Date: 2022/11/15
* 生命周期回调
*/
@Component
public class Lifecycle2 {
private String name;
private String sex;
public Lifecycle2() {
System.out.println("Lifecycle2构造方法");
}
@Override
public String toString() {
return "Lifecycle2{" +
"姓名='" + name + '\'' +
", 性别='" + sex +
'}';
}
@PostConstruct
public void init() throws Exception {
this.name = "张三";
this.sex = "男";
System.out.println("Lifecycle2执行初始化回调方法");
}
@PreDestroy
public void destroy() throws Exception {
System.out.println("Lifecycle2执行销毁回调方法");
}
}
@Test
public void test06() {
// 通过注解创建容器对象,配置扫描的类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.itan.beans");
Lifecycle2 lifecycle = context.getBean("lifecycle2", Lifecycle2.class);
System.out.println(lifecycle);
// 销毁容器
context.close();
}
/**
* 运行结果:
* Lifecycle2构造方法
* Lifecycle2执行初始化回调方法
* Lifecycle2{姓名='张三', 性别='男}
* Lifecycle2执行销毁回调方法
*/
5、生命周期回调方法总结
1、可以将上面3种方式结合使用,如果多种方式一起使用,每种方式都配置了一个不同的方法名,那么他们的执行顺序将会如下面的顺序所示。但是如果他们都配置了同一个方法名,那么该方法只会执行一次。
2、同一个Bean配置多个生命周期机制,初始化方法不同,调用顺序如下:
@PostConstruct
注解修饰的方法。
InitializingBean
接口里面的afterPropertiesSet()
方法。基于XML的自定义的初始化方法。
3、Destroy方法的调用顺序相同:
@PreDestroy
注解修饰的方法。
DisposableBean
接口里面的destroy()
方法。基于XML的自定义的销毁方法。
6、BeanPostProcessor容器扩展点
1、
BeanPostProcessor
接口也叫后置处理器,其作用是在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加自定义逻辑
。2、方法说明:
postProcessBeforeInitialization
:在任何Bean初始化回调(如InitializingBean的afterPropertiesSet或自定义初始化方法)之前,将此BeanPostProcessor应用于给定的新Bean实例。
postProcessAfterInitialization
:在任何Bean初始化回调(如InitializingBean 的afterPropertiesSet或自定义初始化方法)之后,将此BeanPostProcessor应用于给定的新Bean实例。3、注意:
/**
* @Date: 2022/11/17
* 自定义BeanPostProcessor
*/
public class CustomBeanPostProcessor implements BeanPostProcessor {
/**
* bean在调用初始化方法之前调用
* @param bean bean实例
* @param beanName bean的名称
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":将要调用初始化方法了");
return bean;
}
/**
* bean在调用初始化方法之后调用
* @param bean bean实例
* @param beanName bean的名称
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":初始化方法执行完了");
return bean;
}
}
<bean id="lifecycle" class="com.itan.beans.Lifecycle1" init-method="init" destroy-method="destroy">
<property name="name" value="张三"/>
<property name="sex" value="男"/>
</bean>
<bean id="customBeanPostProcessor" class="com.itan.beans.CustomBeanPostProcessor" />
@Test
public void test07() {
// 通过配置文件创建容器对象
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 销毁容器
context.close();
}
/**
* 运行结果:
* Lifecycle1构造方法
* Lifecycle1设置name属性值
* Lifecycle1设置sex属性值
* lifecycle:将要调用初始化方法了
* Lifecycle1执行初始化回调方法
* lifecycle:初始化方法执行完了
* Lifecycle1执行销毁回调方法
*/
三、基于XML的自动装配
1、概述
1、Spring支持某些情况下对于依赖自动注入的情况,通过检查ApplicationContext的内容,可以让Spring自动解析、配置Bean的依赖,不需要手写配置文件。
2、自动装配的优点:
可以减少手动指定属性或通过构造参数为Bean注入所需要的值。
自动装配可以自动更新对象的所依赖的属性的值。(比如,某个类新添加了依赖项,则无需修改配置文件即可自动装配该依赖)。
3、
注意
:仅支持自定义类型的Bean进行自动装配,不支持基本数据类型的自动装配
2、自动装配的几种模式
1、使用基于XML的配置元数据时,
可以通过设置< bean />标签的autowire属性来设置自动注入模式
,共有四种:
no
:默认值,不启用自动装配,通过手工设置ref属性来进行装配Bean。虽然配置麻烦,但是依赖关系明确。
byName
:通过属性名称自动装配,Spring寻找一个与需要自动装配的属性同名的Bean,并为其注入。例如,如果一个Bean定义被设置为按名称自动装配,并且它包含一个master
属性(即它有一个setMaster
方法),那么IoC容器(通过ioc.getbean("xxx")方式
)在配置文件中查找id或name
的值为master
的Bean,并使用setter
方法为其注入。注意这里的自动setter相比前面的手动setter注入有限制,即setXXX方法的XXX一定要对应一个容器中实例的name才行,否则不会注入。
byType
:通过属性类型查找Bean依赖的对象并为其注入。例如,如果一个Bean定义被设置为按类型自动装配,并且它包含一个master属性(即它有一个setMaster方法),属性master的类型为com.Master,那么IoC容器(通过ioc.getbean(xxx.class)方式
)在配置文件中查找class为com.Master的Bean,然后通过setter方法为其注入。如果容器中有多个相同类型的Bean,那么就会抛出expected single matching bean but found x: xxx
异常。
constructor
:同byType装配方式一样,也是通过类型查找依赖对象,与byType的区别在于它不是使用setter方法注入,而是使用构造器注入。如果容器中有多个相同的类型的Bean,也会抛出expected single matching bean but found x: xxx
异常。
3、通过名称装配
public class Room {
private int length;
private int width;
// 依赖其他的属性
private Bed bed;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
public void setBed(Bed bed) {
this.bed = bed;
}
@Override
public String toString() {
return "Room{" +
"长='" + length + '\'' +
", 宽='" + width + '\'' +
", 床=" + bed +
'}';
}
}
public class Bed {
private String brand;
private float price;
public void setBrand(String brand) {
this.brand = brand;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "Bed{" +
"品牌='" + brand + '\'' +
", 价格='" + price +
'}';
}
}
<bean id="bed" class="com.itan.beans.Bed">
<property name="brand" value="全友床垫" />
<property name="price" value="1399" />
</bean>
<!--通过autowire属性自动注入依赖的bean-->
<bean id="room" class="com.itan.beans.Room" autowire="byName">
<property name="length" value="8" />
<property name="width" value="5" />
</bean>
@Test
public void test08() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Room room = context.getBean("room", Room.class);
System.out.println(room);
}
/**
* 运行结果:
* Room{长='8', 宽='5', 床=Bed{品牌='全友床垫', 价格='1399.0}}
*/
注意:如果存在多个相同类型的Bean,并且自动装配类型设置为byType,那么会抛出如下错误:
4、通过构造器装配
1、构造器自动装配流程:
先按照有参构造器参数的类型进行装配,有就装配,没有就直接设置为NULL
。
如果按照类型找到了多个,就按照有参构造器的参数名作为id或name继续匹配,有就装配,没有就直接设置为NULL
。2、相比较byName与byType方式的自动装配来说,构造器方式装配不会报错。
public class Room {
private int length;
private int width;
// 依赖其他的属性
private Bed bed;
// 空参构造
public Room() {}
// 构造器
public Room(Bed bed) {
System.out.println("构造注入bed");
this.bed = bed;
}
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
public void setBed(Bed bed) {
this.bed = bed;
}
@Override
public String toString() {
return "Room{" +
"长='" + length + '\'' +
", 宽='" + width + '\'' +
", 床=" + bed +
'}';
}
}
<bean id="bed" class="com.itan.beans.Bed">
<property name="brand" value="全友床垫" />
<property name="price" value="1399" />
</bean>
<bean id="bed1" class="com.itan.beans.Bed">
<property name="brand" value="联乐床垫" />
<property name="price" value="999" />
</bean>
<!--通过autowire属性自动注入依赖的bean-->
<bean id="room" class="com.itan.beans.Room" autowire="constructor">
<property name="length" value="8" />
<property name="width" value="5" />
</bean>
@Test
public void test08() {
// 通过配置文件创建容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Room room = context.getBean("room", Room.class);
System.out.println(room);
}
/**
* 运行结果:
* 构造注入bed
* Room{长='8', 宽='5', 床=Bed{品牌='全友床垫', 价格='1399.0}}
*/