IoC 操作 Bean 管理 - 依赖注入(DI)
文章目录
Bean 管理指的是两个操作:
- Spring 创建对象
- Spring 注入属性(依赖注入,DI)
- Bean 管理操作有 2 种实现方式:基于 xml 配置文件方式实现 和 基于注解方式实现。
- 这里介绍基于 xml 方式实现的依赖注入。
关于 Spring 的学习 Demo,GitHub地址:https://github.com/Jacks5320/Spring-Study
1 创建对象
使用的标签:<bean></bean>
- 作用:使用默认的构造函数创建对象。
- 属性:
id
属性:对象唯一标识,类似 Map 的 key-value 结构,key 是 id,value 是 class属性。class
属性:类的全限定类名,用于告诉 Spring 创建的是哪个对象。name
属性:作用与 id 属性一样用于定位类,但是 id 属性只能定义一个,而 name 属性中可以定义多个,多个命名之前用逗号隔开。factory-bean
指的是工厂类的id
。factory-method
指的是创建对应对象的工厂方法名。scope
指定作用范围,取值:singleton,单例方式。prototype,多例方式。init-method
指定对象初始化时执行的方法,值为方法名。destroy-method
指定对象消亡时执行的方法,值为方法名。- …
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<!-- 用 id 命名 -->
<bean id="stu" class="com.jk.demo1.Student" />
<!-- 用 name 指定命名 -->
<bean name="stu,student" class="com.jk.demo1.Student">
<!-- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入 Spring) -->
<bean id="userFactory" class="com.jk.factory.UserFactory">
<bean id="user" factory-bean="userFactory" factory-method="getUser">
<!-- 使用工厂中的静态方法创建对象 -->
<bean id="user" class="com.jk.factory.UserFactory" factory-method="getUser">
</beans>
- 如上所示,创建了
com.jk.demo1.Student
对象,默认采用的是无参构造函数,如果只创建了有参构造,没有无参构造,程序会报错。
获取对象并使用:
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
// 通过反射获取创建的对象
Student stu = context.getBean("stu", Student.class);
// 使用对象
System.out.println(stu);
- 关于
ClassPathXmlApplicationContext
前篇博客已经解释过了,是用于加载 xml 文件的。 getBean()
方法用于获取经过 xml 解析、反射和工程模式后创建对象。- 第一个参数为要获取的
Bean
对象的id
。 - 第二个参数为要实例化对象的字节码文件,如果不加,则需要强制转换返回值类型。
- 第一个参数为要获取的
2 基本注入
使用到的标签:
<bean>
标签内部的<constructor-arg>
标签,这是基于有参构造注入的方式,所以要求要有有参构造,对有参构造提供的参数进行注入。<bean>
标签内部的<property>
标签,这是基于 setter 注入的方式,所以要求有 set 方法的属性才能使用此标签注入。
2.1 基于构造函数注入
标签及属性:<constructor-arg>
- 作用:对有参构造提供的参数进行依赖注入。
- 属性:
type
:用于指定注入参数的类型,该类型是构造函数参数列表中的某个或某些个参数类型。index
:用于指定构造函数中索引位置的参数赋值,参数索引的位置从 0 开始。name
:用于指定给构造函数中指定名称的参数赋值。(常用)- 以上三个属性用于指定给构造函数中的哪个参数赋值,以下两个属性用于指定注入的值
value
:用于指定注入的值,只能是基本类型和 String 类型ref
:用于指定其他的 bean 类型属性。指的是在Spring的IoC核心容器中出现过的bean对象(Spring 中配置创建的对象)。
- 特点:
- 在获取 bean 对象时,注入数据是必须的操作,否则对象无法创建成功。
Java 类:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
xml 配置
<bean id="user" class="com.jc.spring5.User">
<constructor-arg name="name" value="小明"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
</bean>
2.2 基于 setter 方法注入
标签:<property>
- 作用:对有 set 方法的属性进行注入。
- 属性:
name
属性 : 用于指定调用的 setter 方法的名称(这里的名称是指setXXX中的XXX,且首字母小写)。value
属性 : 用于指定注入的值,只能是基本类型和 String 类型。ref
:用于指定其他的 bean 类型属性。指的是在 Spring 的 IoC 核心容器中出现过的 bean 对象(Spring 中配置创建的对象),值为注入 Bean 的 id 值。
- 特点:
- 创建对象时,没有明确的限制,可以直接使用默认无参构造函数。如果某个成员必须有值,在获取对象时,如果没有配置 setter 方法会导致属性为空。
Java 类
public class Person {
private String name;
private String gender;
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
xml 配置文件
<bean id="user" class="com.jc.spring5.User">
<property name="name" value="小明"></property>
<property name="age" value="12"></property>
</bean>
2.3 p 名称空间注入(了解)
标签:<bean>
标签的 p:property(value)
属性,property
是拥有 set
方法的属性名,value
是注入值。
作用:也是基于 setter
注入的方式进行依赖注入。
注意:需要引入 p 名称空间 xmlns:p="http://www.springframework.org/schema/p"
<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">
<!--p 名称空间注入-->
<bean id="stu" class="com.jk.demo1.Person" p:name="小明" p:gender="男"/>
</beans>
- 当需要注入属性很多时,p 名称空间注入的可读性不如
<property>
标签,所以作为了解即可。 - 大多数情况下还是使用的
setter
注入。
3 null 值和 xml 中的特殊值
1 注入 null 值
<!--注入 null 值-->
<!--基于构造函数-->
<bean id="student" class="com.jk.demo1.Student">
<constructor-arg name="name">
<null></null>
</constructor-arg>
<constructor-arg name="age" value="12"/>
</bean>
<!--基于 setter 方法-->
<bean id="person" class="com.jk.demo1.Person">
<property name="gender" value="女"/>
</bean>
- 对于构造注入来说,构造函数的每个参数都必须注入值,如果不想注入值,可以使用
null
标签来替代注入值。 - 注意:注入
null
的前提是对应的属性允许存在null
值,如果不允许null
值,则注入会报错,如 int 这些基本类型,如果需要 null 值,则可以定义成它们的包装类型,如Integer
或赋值成 0。 - 对于
setter
注入来说,不调用某个属性的 set 方法,就会对应注入null
值,或者注入默认值。
2 注入 xml 中的特殊符号
<!--注入特殊值-->
<!--特殊值注入:实体方式-->
<bean id="student2" class="com.jk.demo1.Student">
<constructor-arg name="name" value="<刘备>"/>
<constructor-arg name="age" value="12"/>
</bean>
<!--特殊值注入:<![CDATA[含特殊符号的字符串]]>-->
<bean id="person2" class="com.jk.demo1.Person">
<property name="name">
<value><![CDATA[<刘备>]]></value>
</property>
</bean>
- 在
xml
中,<
、>
、&
等字符是不能直接存入的,否则xml
语法检查时会报错。 - 可以使用
![CDATA[ ]]
这种格式注入含特殊符号的值。 <![CDATA[ ]]>
是xml
语法。在CDATA
内部的所有内容会被标记为纯文本。- 如果不想用
<![CDATA[ ]>
,必须将特殊符号转义为实体,如<
对应的实体<
、>
对应的实体>
、;
对应的实体&
。
4 Bean 类型注入
标签及属性:<property>
标签中的 ref
属性。
作用:注入 Bean
类型的属性。
取值:要注入 Bean
的 id
。
Java 类:
public class Department {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Department{" +
"name='" + name + '\'' +
'}';
}
}
public class Employee {
private String name;
//员工属于某一个部门,使用对象形式表示
private Department dept;
//set
public void setName(String name) {this.name = name;}
public void setDept(Department dept) {this.dept = dept;}
// get
public Department getDept() {return dept;}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", dept=" + dept +
'}';
}
}
4.1 外部注入
<bean id="d1" class="com.jk.demo2.Department">
<property name="name" value="管理部"/>
</bean>
<bean id="e1" class="com.jk.demo2.Employee">
<property name="name" value="刘备"/>
<property name="dept" ref="d1"/>
</bean>
- 外部注入就是将要注入的
Bean(Department类)
在配置文件中进行注入,然后通过ref
属性注入到指定的Bean(Employee类)
中。
4.2 级联注入
<bean id="d2" class="com.jk.demo2.Department"/>
<bean id="e2" class="com.jk.demo2.Employee">
<property name="name" value="关羽"/>
<!--级联注入-->
<property name="dept" ref="d2"/>
<property name="dept.name" value="财务部"/>
</bean>
- 级联注入就是将要
Department
对象在配置文件中创建出来,然后通过ref
属性注入Employee
对象中,最后使用.
表达式给Department
对象注入属性。 - 需要注意的是,
.
表达式需要借助于getter
方法。
4.3 内部注入
<bean id="e3" class="com.jk.demo2.Employee">
<property name="name" value="张飞"/>
<!--内部注入-->
<property name="dept">
<bean class="com.jk.demo2.Department">
<property name="name" value="安保部"/>
</bean>
</property>
</bean>
- 内部注入就是,直接将
Department
对象在Employee
对象的<Property>
标签中进行创建并注入相应的属性。
5 集合类型注入
5.1 基本类型的集合注入
使用的标签及属性:<property>
中的各集合标签。
- 用于给 List结构集合注入的标签:list、array、set
- 用于给 Map 结构集合注入的标签:map、props
结构相同,标签可以互换。
Java 类:
public class Collection {
private String[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void setArray(String[] array) {
this.array = array;
}
public void setList(List<String> list) {
this.list = list;
}
public void setSet(Set<String> set) {
this.set = set;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "Collection{" +
"array=" + Arrays.toString(array) +
", list=" + list +
", set=" + set +
", map=" + map +
", properties=" + properties +
'}';
}
xml 配置文件:
<bean id="c" class="com.jk.demo3.Collection">
<!--数组-->
<property name="array">
<array>
<value>刘备</value>
<value>关羽</value>
<value>张飞</value>
</array>
</property>
<!--list-->
<property name="list">
<list>
<value>吕布</value>
<value>貂蝉</value>
</list>
</property>
<!--set-->
<property name="set">
<set>
<value>周瑜</value>
<value>小乔</value>
</set>
</property>
<!--map-->
<property name="map">
<map>
<entry key="刘备" value="玄德"/>
<entry key="关羽" value="云长"/>
<entry key="张飞" value="益德"/>
</map>
</property>
<!--property-->
<property name="properties">
<props>
<prop key="初级">小学</prop>
<prop key="中级">中学</prop>
<prop key="高级">大学</prop>
</props>
</property>
</bean>
5.2 对象类型的集合注入
使用的标签:
- Map 结构:
<property>
标签内部的<map>
标签内部的<entry>
标签中的value-ref
属性,值为 xml 中创建对象的id
- List 结构:
<property>
标签内部的<list>
标签内部的<ref>
标签,值为 xml 中创建对象的id
Java类:
public class Course {
private String name;
public void setName(String name) {
this.name = name;
}
}
public class Student2 {
private Map<String, Course> map;
private List<Course> list;
//getter setter toString 这里节约篇幅不写,实际需要加上。
}
xml 配置文件:
<!-- 创建多个多选 -->
<bean id="c1" class="com.jk.demo4.Course">
<property name="name" value="Java程序开发"/>
</bean>
<bean id="c2" class="com.jk.demo4.Course">
<property name="name" value="Java程序开发"/>
</bean>
<!-- 注入属性 -->
<bean id="student" class="com.jk.demo4.Student2">
<property name="map">
<map>
<entry key="第一门课" value-ref="c1"/>
<entry key="第二门课" value-ref="c2"/>
</map>
</property>
<property name="list">
<list>
<ref bean="c1"/>
<ref bean="c2"/>
</list>
</property>
</bean>
List
等集合可以使用<ref>
标签中的bean
属性注入对象类型的属性。Map
集合可以使用value-ref
注入对象类型的属性
5.3 提取公共集合
标签及属性:util:
, 需要引入 util
名称空间
作用:提供公共集合
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--提取公共集合-->
<util:list id="courseList">
<bean class="com.jk.demo4.Course">
<property name="name" value="Java程序设计"/>
</bean>
<bean class="com.jk.demo4.Course">
<property name="name" value="数据库设计"/>
</bean>
</util:list>
<!--注入公共集合-->
<bean id="s1" class="com.jk.demo4.Student2">
<property name="list" ref="courseList"/>
</bean>
<!-- 注入公共集合 -->
<bean id="s2" class="com.jk.demo4.Student2">
<property name="list" ref="courseList"/>
</bean>
</beans>
6 引入外部配置文件
用到的标签及属性:
<context:property-placeholder>
标签的location
属性,用于指定外部属性配置文件的位置。<property>
标签,借助 el 表达式提取出外部属性文件的值。
Java 类
public class Course {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Course{" +
"name='" + name + '\'' +
'}';
}
}
外部属性配置文件 course.properties
course.name="Java程序设计"
配置注入:需要引入 context 名称空间。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:com/jk/b_DI_xml/course.properties"/>
<!--将外部配置文件的内容注入到对象中-->
<bean id="course" class="com.jk.b_DI_xml.demo4.Course">
<property name="name" value="${course.name}"/>
</bean>
</beans>
7 Bean 作用范围
标签及属性:bean
标签中的 scope
属性。
作用:用于设置 Bean 对象的作用范围。
可选值:
singleton
:默认值,表示单实例对象。特点是:加载 Spring 配置文件的时候,就会创建一个单实例对象。prototype
:表示多实例对象。特点是:在调用 getBean() 方法时创建多实例对象。request
:作用于 web 应用的请求范围session
:作用于 web 应用的会话范围global-session
:作用域集群环境的会话范围,或者是全局会话范围,当不是集群环境时,就是 session- 在 Spring 5 版本中,后面的可选值逐渐被淘汰,只剩下
singleton
和prototype
两个可选值了。
作用范围验证
<!--默认情况下,单例对象:scope="singleton"-->
<bean id="s1" class="com.jk.demo5.ScopeDemo"/>
<!--使用多例对象:scope="prototype"-->
<bean id="s2" class="com.jk.demo5.ScopeDemo" scope="prototype"/>
public class TestDemo2 {
ApplicationContext context;
@Before
public void init() {
context = new ClassPathXmlApplicationContext("bean.xml");
}
@Test
public void testSingleton() {
ScopeDemo s1 = context.getBean("s1", ScopeDemo.class);
ScopeDemo s2 = context.getBean("s1", ScopeDemo.class);
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2); // true
}
@Test
public void testPrototype() {
ScopeDemo s1 = context.getBean("s2", ScopeDemo.class);
ScopeDemo s2 = context.getBean("s2", ScopeDemo.class);
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
8 Bean 的生命周期
- 单例 Bean:bean的生命与容器保持一致,只要容器存在,就一直活着,关闭容器需要手动调用
ClassPathXmlApplicationContext
实现类中的的close
方法。 - 多例 Bean:当我们使用时 Spring 框架才创建,对象只要还在用,就活着,当对象长时间不用且没有别的对象引用时,由 Java 垃圾回收机制回收。
8.1 单例 Bean 生命周期
- 1 通过构造函数创建
Bean
实例(无参构造),生命开始。 - 2 为
Bean
注入属性(设置属性值或对其他Bean
的引用,调用setter
方法) - 3 调用
Bean
的初始化方法(需要自行配置) - 4
Bean
可以使用了(获取到对象)。 - 5 当容器关闭时,调用
Bean
的销毁方法(多例对象不会消亡)
Java类
public class BeanLife {
private String name;
public BeanLife() {
System.out.println("无参构造执行了。。。");
}
public void init(){
System.out.println("初始化方法执行了。。。");
}
public void destroy(){
System.out.println("对象消亡了。。。");
}
}
xml 配置
<bean id="l1" class="com.jk.demo6.BeanLife" init-method="init" destroy-method="destroy" />
init-method
用于初始化之前执行的方法destroy-method
用于 Bean 消亡时执行的方法
测试方法
public class TestDemo2 {
//测试生命周期
@Test
public void testLife(){
ClassPathXmlApplicationContext context2 = new ClassPathXmlApplicationContext("bean8.xml");
BeanLife l1 = context2.getBean("l1", BeanLife.class);
System.out.println(l1);
//手动关闭容器
context2.close();
}
}
8.2 配置后置处理器后 Bean 的生命周期
- 1 通过构造函数创建 Bean 实例(无参构造),生命开始。
- 2 为 Bean 注入属性(设置属性值或对其他 Bean 的引用,调用 setter 方法)
- 3 把 Bean 实例传递给 Bean 后置处理器的方法
postProcessBeforeInitialization
。 - 4 调用 Bean 的初始化方法(需要进行配置)
- 5 把 Bean 实例传递给 Bean 后置处理器的方法
postProcessAfterInitialization
。 - 6 Bean 可以使用(对象获取到了)
- 7 当容器关闭时,调用 Bean 的销毁方法(需要进行配置销毁的方法),生命结束。(多例对象不会消亡)
后置处理器:
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器在初始化之前执行了。。。");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器在初始化之后执行了。。。");
return bean;
}
}
- 后置处理器需要实现
BeanPostProcessor
接口。 postProcessBeforeInitialization
方法会在初始化方法执行之前执行。postProcessAfterInitialization
方法会在初始化方法执行之后执行。
xml 配置:
<!--Bean 生命周期验证-->
<bean id="l1" class="com.jk.demo6.BeanLife" init-method="init" destroy-method="destroy"/>
<!--后置处理器-->
<bean class="com.jk.demo6.MyBeanPost"/>
- 需要注意的是:配置后置处理器过后,会为当前容器中的所有 Bean 实例添加后置处理器。
以上就是关于基于 xml 方式的依赖注入,多结合实例方便理解~~