文章目录
控制反转就是将对象的创建和对象之间调用的管理,交给Spring。我们在一个对象中需要另一个对象的资源无需去考虑如何去获得,Spring自动帮我们完成对象获取这个过程。
目的
降低耦合度
底层原理
xml解析、工厂模式、反射
关于工厂模式,有兴趣的可以看一下我的另一篇博客:传送门
Bean管理
概述
Spring的Bean管理包含两个操作:对象的创建和属性的注入。
想要实现可以使用配置文件的形式也可以使用注解。
基于xml
基于xml文件的形式实现Bean管理简单来说就是在配置文件中添加bean标签,然后在标签上面添加对应的属性就行。
只有一个对象,没有属性
<bean id="user" class="com.modevil.bean.User">
</bean>
在这种情况下,只用写一个bean标签,然后写上两个属性就行了。
- id:这个对象的唯一标识符,我们可以使用这个东西来获取到这个对象。
- class:全类名,spring依靠反射来创建对象,就是通过这个来获取到类的。
注入普通的属性
正常情况,我们创建一个对象肯定是有各种各样的属性,然后就可以使用下面的方法来注入对应的属性。
有以下两种注入方法:
- 使用set方法注入:这种情况就是先使用无参构造创建对象,然后再使用set方法,设置属性值。
- 使用有参构造注入:直接使用有参的构造方法注入属性值。
使用set方法注入
在这种情况下,无参构造方法和每个属性对应的set方法不能少,不然会报错:
No default constructor found; nested exception is java.lang.NoSuchMethodException: com.modevil.bean.User.<init>()
Invalid property 'address' of bean class [com.modevil.bean.User]: Bean property 'address' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
正常的就是如下的配置:
<bean id="user" class="com.modevil.bean.User">
<property name="id" value="1"/>
<property name="userName" value="张三"/>
<property name="email" value="qqq@qqq.com"/>
<property name="address" value="中国"/>
<property name="sex" value="男"/>
</bean>
- name:JavaBean文件的属性名
- value:要注入的值
使用构造方法注入
在这种情况需要创建一个参数为要注入的各个属性的构造函数,建议多创建一个无参构造。
<bean id="user2" class="com.modevil.bean.User">
<constructor-arg name="id" value="1"/>
<constructor-arg name="userName" value="张三"/>
<constructor-arg name="email" value="qqq@qqq.com"/>
<constructor-arg name="address" value="中国"/>
<constructor-arg name="sex" value="男"/>
</bean>
name和value的意义和上面相同,另外就是constructor-arg还有另外一个属性index,可以替代name的作用,这个东西是指向构造方法中的第几个参数的索引,从0开始。
注入对象属性
引用
很简单,property和constructor-arg有另外一个属性,ref这个就是注入对象所使用的标签,ref的值就是配置文件中其他对象的id。
<bean id="user" class="com.modevil.bean.User">
<property name="address" ref="address"/>
</bean>
<bean id="user2" class="com.modevil.bean.User">
<constructor-arg name="address" ref="address"/>
</bean>
<bean id="address" class="com.modevil.bean.Address">
</bean>
内部bean
也可以直接在标签内部再写一个bean标签,然后像其他bean一样写各种属性,但是这样的内部bean无法通过id被其他类引用。
<bean id="user1" class="com.modevil.bean.User">
<property name="address">
<bean class="com.modevil.bean.Address">
</bean>
</property>
</bean>
如果再在配置文件中配置一个新的address对象,获取到的和内部bean是同一个对象(因为spring默认是单例模式),即使注入的值不同(会重新进行赋值,但是对象相同),仍然是同一个对象。
级联赋值
如果我们需要单独对address的属性进行赋值,我们可以使用这种情况,使用方法就是对象名.属性名,但是需要注意的是仍然需要对address对象单独赋值,不然会报空指针异常。
<bean id="user3" class="com.modevil.bean.User">
<property name="address" ref="address"/>
<property name="address.zipCode" value="123"/>
<property name="address.nation" value="拉拉"/>
<property name="address.full" value="亚洲中国"/>
</bean>
注入集合属性
注入数组、list、set、map类型,使用下面的方法:
普通的注入
<bean id="collections0" class="com.modevil.bean.Collections">
<property name="integerArray">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
<property name="stringArray">
<array>
<value>第一个</value>
<value>第二个</value>
<value>第三个</value>
</array>
</property>
<property name="list">
<list>
<value>你睡了吗?</value>
</list>
</property>
<property name="map">
<map>
<entry key="哈哈" value="1"/>
</map>
</property>
<property name="set">
<set>
<value>0</value>
</set>
</property>
</bean>
如果是上面几种集合中的元素是对象,可以使用ref这个属性,直接指向引用的对象,也可以直接创建。
使用引用
也可以使用引用的方式进行属性注入,如果是这样的话需要添加一个依赖
<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">
然后下面是几种实现方法
<bean id="collections1" class="com.modevil.bean.Collections">
<property name="integerArray">
<array>
<value>1</value>
<value>2</value>
</array>
</property>
<property name="stringArray">
<array>
<value>第一个</value>
<value>第二个</value>
<value>第三个</value>
</array>
</property>
<property name="list" ref="list"/>
<property name="map" ref="map"/>
<property name="set" ref="set"/>
</bean>
<util:list id="list">
<value>这是我的list</value>
</util:list>
<util:set id="set">
<value>123</value>
</util:set>
<util:map id="map">
<entry key="天上地下" value="123"/>
</util:map>
自动装配
自动装配就是Spring根据属性类型,或者是属性名自动的进行属性的注入操作。
具体的只需要加上一个autowire属性就行了
<bean id="user" class="com.modevil.bean.User" autowire="byType">
常用的属性有以下两个:
- byType:依据属性类型进行注入
- byName:依据属性名进行注入
基于注解
有空再说
作用域和生命周期
作用域
默认Spring创建的对象都是单例,如果想要设置成多例或者是其他的情况,需要进行以下设置:
<bean id="user" class="com.modevil.bean.User" scope="prototype">
</bean>
关键点就是scope这个属性,常用的有以下两个值:
- prototype:多例模式,每次获取都会创建一个新的对象。
- singleton:对于一个类只会创建一个对象,不管配置了多少次;如果多次配置,会根据配置的属性再次进行注入。
生命周期
对Spring的bean对象有以下的生命周期:
- 通过无参构造创建对象
- 调用set方法对属性进行赋值
- 调用配置中的初始化方法
- bean正式“创建”成功,可以被其他对象拿到使用
- 当容器销毁的时候,调用销毁的方法
对于上面的过程,有以下的说明
- 如果是使用构造参数进行属性注入,1、2过程会换成调用对应的构造方法
- 只有scope设置为singleton的对象的销毁,spring才会调用相应的方方法;如果是prototype这样创建的方法,spring并不负责销毁时调用方法,所以不存在调用对应的方法。
以下是对这些配置的一个小案例
<bean id="people" class="com.modevil.bean.People" init-method="init" destroy-method="destroy">
<property name="name" value="王五"/>
</bean>
- init-method:指定初始化方法
- destroy-method:指定销毁的方法
package com.modevil.bean;
/**
* @author modev
* @date 2020/10/13 20:00
*/
public class People {
private String name;
public People() {
System.out.println("这里是无参构造!!!");
}
public void init() {
System.out.println("初始化方法!!!");
}
public void destroy() {
System.out.println("销毁方法!!!!");
}
public People(String name) {
this.name = name;
System.out.println("这里是有参构造!!!");
}
public String getName() {
System.out.println("这里是取值方法!!!");
return name;
}
public void setName(String name) {
System.out.println("这里是赋值方法!!!");
this.name = name;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
'}';
}
}
后置处理器
Spring还提供了一个后置处理器,带上后置处理器后,完整的生命周期如下:
- 通过无参构造创建对象
- 调用set方法对属性进行赋值
- 调用后置处理器的方法
postProcessBeforeInitialization
- 调用配置中的初始化方法
- 调用后置处理器的方法
postProcessAfterInitialization
- bean正式“创建”成功,可以被其他对象拿到使用
- 当容器销毁的时候,调用销毁的方法
可以看到是在初始化的前后进行调用的。
想要实现后置处理器,只需要实现一个接口:BeanPostProcessor
,重写方法对应的方法。最后在配置文件中进行注册即可。
package com.modevil.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author modev
* @date 2020/10/13 20:44
*/
public class MyBeanPost implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("前置方法" + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置方法" + beanName);
return bean;
}
}
<bean id="myBeanPost" class="com.modevil.bean.MyBeanPost"/>
需要注意的是后置处理器对于所有的bean创建的时候都会生效,并且可以在后置处理器中获得对应的bean以及beanName。
参考
- 尚硅谷的spring5课程