Spring2️⃣浅聊 IoC、Bean管理
相关概念:
1、控制反转(IoC)
控制反转(Inversion of Control)
OO 设计思想,目的是降低耦合度。
- 含义:将对象的创建和对象之间的依赖关系,交给 Spring 管理。
- 实现方式:
- 依赖注入
- 依赖查找
- 底层原理:XML 解析 + 工厂 + 反射
- 使用 XML 解析技术,获取配置文件中的配置信息。
- 反射 + 工厂(BeanFactory),创建对象实例。
- 需要使用对象时,从工厂获取对象实例。
1.1、Spring Bean
Spring Bean
Spring 框架的基本构建块。
简单来说,Bean 就是 Spring 容器管理的 Java 对象。
- 类型:
- Bean:一般的 Java 对象。
- FactoryBean:用于生产对象的工厂类,(👉 工厂模式)。
- 管理内容:可以基于 XML 配置、注解实现。
- 创建对象、依赖注入(IoC)
- 拦截方法调用、提供额外功能(AOP)
- 销毁
1.2、静态工厂 - BeanFactory
BeanFactory
接口:Spring 内部创建对象的工厂。IoC 容器的基本实现,通常由 Spring 内部使用
常用接口:ApplicationContext
-
FileSystemXmlApplicationContext
:使用 XML 配置,参数为系统路径。 -
ClassPathXmlApplicationContext
:使用 XML 配置,参数为类路径(推荐) -
AnnotationConfigApplicationContext
:使用 JavaConfig 配置,参数为配置类的 Class(基于注解开发使用)
BeanFactory 和 FactoryBean 是两个不同的概念,注意区分。
2、XML 开发
2.1、创建对象
创建方式
- 无参构造 + setter
- 有参构造
- BeanFactory
示例:Person
public class Person {
private String name;
private int age;
}
无参构造
注意:Person 类必须提供 setter。
<bean id="person" class="indi.jaywee.Person">
<property name="name" value="Jaywee"/>
<property name="age" value="17"/>
</bean>
有参构造 (❗)
根据参数列表,自动选择构造方法。
三种创建方式:
-
按下标:指定构造方法的参数顺序,从 0 开始。
<bean id="person1" class="indi.jaywee.Person"> <constructor-arg index="0" value="Jaywee"/> <constructor-arg index="1" value="17"/> </bean>
-
按名称:指定方法参数名(推荐使用)
<bean id="person2" class="indi.jaywee.Person"> <constructor-arg name="name" value="Jaywee"/> <constructor-arg name="age" value="17"/> </bean>
-
按类型:指定方法参数类型(不建议使用)
-
基本类型写简单类名,引用类型写全限类名(或别名)。
-
若有多个参数类型相同,无法使用。
<bean id="person3" class="indi.jaywee.Person"> <constructor-arg type="java.lang.String" value="Jaywee"/> <constructor-arg type="int" value="17"/> </bean>
-
FactoryBean
实现 FactoryBean 接口,泛型指定生产的对象类型。
-
创建 Person 工厂:
public class PersonFactory implements FactoryBean<Person> { @Override public Person getObject() throws Exception { return new Person("default", 0); } @Override public Class<?> getObjectType() { return Person.class; } @Override public boolean isSingleton() { return true; } }
-
配置元数据:配置的 Bean 类型是 PersonFactory
<bean id="personFactory" class="indi.jaywee.PersonFactory"> </bean>
2.2、依赖注入
注入方式
- 有参构造方法(即通过有参构造方法创建对象)
- setter
- 命名空间:
- p 命名空间:本质是 setter 注入
- c 命名空间:本质是有参构造注入
2.2.1、setter 注入(❗)
不同数据类型的注入基本相同,
注意区分标签和属性名。
简单类型
使用
property
标签
-
简单类型(基本类型 + 包装类 + String):value 属性。
-
引用类型:ref 属性。
<property name="参数名" value="参数值"/> <property name="参数名" ref="引用其他Bean"/>
聚合类型
使用
property
标签,内部使用子标签来区分具体类型。
-
单值类型:数组、List、Set
-
数据类型:array/list/set 标签。
-
元素:value 标签。
<property name="参数名"> <类型> <value>元素1</value> <value>元素2</value> </类型> </property>
-
-
键值对类型:Map
-
数据类型:map 标签。
-
元素:entry 标签,key/value 属性。
<property name="参数名"> <map> <entry key="key1" value="value1"/> <entry key="key2" value="value2"/> ... </map> </property>
-
-
配置文件:
-
数据类型:props 标签
-
元素:prop 标签,key 属性 + value 内容。
<property name="参数名"> <props> <prop key="prop1">value1</prop> <prop key="prop2">value2</prop> ... </props> </property>
-
-
null:自闭和标签。
<property name="参数名"> <null/> </property>
2.2.2、property 配置
在 property 标签中,可以直接填写 key 和 value 的内容。
- 缺点:硬编码,不利于统一维护。
- 对策:使用
context
标签引入配置文件,${}
引用配置。
示例:配置数据库连接池
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
2.3、自动装配(❗)
指定装配规则,Spring 根据规则进行匹配属性值,完成依赖注入。
- 实现方式:基于 XML、基于注解实现。
- 好处:自动注入和更新属性值
2.4.1、装配规则
使用 Bean 标签的
autowire
属性,有以下取值。
装配规则 | 匹配规则 | 说明 | |
---|---|---|---|
no | 不自动装配 | - | 默认 |
byName | 匹配属性名 | 对象属性名 == 待注入 Bean 的 ID 值 | 如果匹配到多个相同类型的对象,会报错 |
byType | 匹配属性类型 | 对象属性类型 == 待注入 Bean 的 class 类型 | - |
constructor | 匹配构造方法 | 按顺序匹配构造方法的参数列表 | 前提是只存在一个匹配的构造方法 |
2.4.2、示例
环境搭建
Java 类
public class Department {
private String name;
// setter、toString()
}
public class Employee {
private Department dept;
// setter、toString()、打印dept
}
先注册几个 Department
<bean id="dept" class="indi.jaywee.auto.Department">
<property name="name" value="HR"/>
</bean>
<bean id="dept1" class="indi.jaywee.auto.Department">
<property name="name" value="Dev"/>
</bean>
<bean id="dept2" class="indi.jaywee.auto.Department">
<property name="name" value="Operation"/>
</bean>
演示 byName
<bean id="employee" class="indi.jaywee.auto.Employee" autowire="byName">
</bean>
对象中的属性名为 dept,匹配到 ID 值为 dept 的 bean。
演示 byType
<bean id="employee" class="indi.jaywee.auto.Employee" autowire="byType">
</bean>
-
容器中注册了多个 Department 类型的 bean,报错。
-
假如只保留 dept1:匹配到 class 为 Department 的 bean,将其注入。
3、再谈 Spring Bean
3.1、装配时机
装配:对象创建 + 依赖注入的完整过程
- 对象创建:实例化容器时,Spring 读取元数据配置并创建所有对象。
- 依赖注入:不同方式有所区别。
- 有参构造:实例化容器时完成。
- 无参构造:从容器中获取 Bean 实例时才完成。
测试思路
- 在对象的有参、无参构造方法中调用 toString()。
- 分别使用有参、无参构造方式,配置元数据。
- 实例化容器:
- 若有输出语句,说明对象已创建。
- 若有 toString() 打印出了属性的赋值,说明已注入。
- 获取对象,调用 toString()。
3.2、生命周期 (🔥)
Bean 从创建到销毁的 5 个过程
- 创建对象:创建 Bean 实例(无参构造)
- 依赖注入:设置属性值(setter)
- 初始化:装配 bean 时调用(initMethod)
- 获取并使用对象
- 销毁:容器关闭时调用(destroy-method)
演示
定义一个类,演示 bean 的生命周期
public class LifeCycle {
private String name;
private Person person;
public LifeCycle() {
System.out.println("创建对象");
}
public void setName(String name) {
this.name = name;
System.out.println("依赖注入:name");
}
public void setPerson(Person person) {
this.person = person;
System.out.println("依赖注入:person");
}
public void initBean() {
System.out.println("初始化");
}
public void destroyBean() {
System.out.println("销毁");
}
}
配置元数据
- 初始化、销毁方法,需要使用相应的属性绑定。
- 不同属性依赖的注入顺序,由配置文件中的顺序决定。
<bean id="person" class="indi.jaywee.Person">
</bean>
<bean id="lifeCycle" class="indi.jaywee.LifeCycle"
init-method="initBean" destroy-method="destroyBean">
<property name="name" value="abc"/>
<property name="person" ref="person"/>
</bean>
3.3、后置处理器
后置处理器(Post Processor)
- 作用:处理 Bean 的生命周期。
- 类型:
- Bean 后置处理器
- BeanFactory 后置处理器
TODO...
Spring 根据 XML 配置,进行以下操作:
- 扫描
.class
文件,将包含类级别注解的 Bean 注册到 BeanFactory 中。 - 注册并实例化一个后置处理器,用于处理类内部的注解。
- 将后置处理器放到 BeanFactory 的 beanPostProcessors 列表中。
- 属性注入或者初始化 Bean 时,调用相应的处理器进行处理。
4、注解开发
- 简化 XML 配置,方便开发。
- 若同时对一个 bean 进行配置,XML 会覆盖注解的配置。
4.1、Spring 注解 (❗)
4.1.1、原始注解
创建对象
约定大于配置
@Component:Java Bean 元注解
- @Repository:只能用于 DAO 层,否则报错。
- @Controller
- @Service
依赖注入
注入
- @Value:简单类型。
- @Autowired:引用类型
微调
- @Qualifier:在待注入的属性上使用,指定要注入的 Bean 名称。
- @Primary:在对象上使用,指定当前 Bean 优先注入。
其它
- 允许空值:@Nullable
- 作用域:@Scope
- 生命周期:@PostConstruct、@PreDestory
4.1.2、新注解
用于完全注解开发,摆脱 XML 配置文件
- @Configuration:注册配置类(相当于 XML 配置文件)
- @ComponentScan:组件扫描
- @Bean:将方法返回值注册到容器中(相当于工厂 Bean)
- @PropertySource:加载 properties 文件的配置
- @Import:导入其它配置类的配置
4.1.3、相关配置
若要基于注解开发,需要导入以下依赖和配置。
-
依赖:Spring 4 + 注解需要 AOP 支持。
-
XML 配置:
-
引入 context 命名空间
-
开启组件扫描:多个包之间用逗号隔开
-
-
扫描范围:指定包内的注解生效,默认是所有注解生效。
-
include-filter:仅指定的注解生效。
-
exclude-filter:排除指定的注解。
<!-- 只扫描@Component --> <context:component-scan base-package="indi.jaywee.anno"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan> <!-- 排除@Controller --> <context:component-scan base-package="indi.jaywee.anno"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
4.2、创建对象
示例:
@Component
相当于 XML 中的
<bean>
标签。
bean 名称:
-
默认:简单类名的首字母小写形式。
-
显式设置:使用
value
属性,覆盖默认 Bean 名。// 默认:person @Component public class Person { ... } // 显式设置 @Component(value = "p") public class Person { ... } @Component("p") public class Person { ... }
假如注解的属性只有一个注解,并且是
value
。此时可以省略
value =
,直接填写字面量。
4.3、依赖注入
简单类型
@Value
// 基本类型
@Value(value = "18")
private int age;
// 包装类型
@Value("12345")
private Integer id;
// String
@Value("Jaywee")
private String name;
引用类型(❗)
@Autowired
是 Spring 注解,与 Spring 框架强耦合。建议使用 J2EE 规范的
@Resource
注解。
@Autowired | @Resource | |
---|---|---|
提供者 | Spring | J2EE(JSR-250) |
默认装配模式 | 先 byType,再 byName | 先 byName,再 byType |
搭配 | @Qualifier 、@Primary 注解 |
name、type 属性 |
@Autowired
工作机制:先 byType,再 byName
步骤:先扫描容器中是否存在类型匹配的 bean。
-
没有:抛异常。
-
有且唯一:则直接注入。
-
有多个:
-
扫描
@Qualifier
指定的 Bean 是否存在。 -
扫描是否有
@Primary
的对象。 -
匹配名称。有则注入,没有则抛异常。
-
示例:假设容器中有 UserServiceImplA 和 userServiceImplB。
-
@Qualifier
:按指定 "UserServiceImplA" 查找注入class Client { @Autowired @Qualifier("UserServiceImplA") private User user; }
-
@Primary
:优先注入 UserServiceImplBclass UserServiceImplA implements UserService{} @Primary class UserServiceImplB implements UserService{}
@Resource
注解的属性决定工作机制
- 不指定:默认 先 byName ,再 byType。
- 同时指定:按 name 和 type 唯一匹配。
- 只指定 name:有则注入,没有则抛异常。
- 只指定 type:有且其唯一则注入,否则抛异常。
5、完全注解开发
步骤
- 使用
@Configuration
注解(取代 XML 配置文件)。 - 使用
AnnotationConfigApplicationContext
容器。
示例
-
配置类:
@Configuration @ComponentScan(basePackages = "indi.jaywee.anno") public class MyConfig { @Bean public Employee getEmployee() { return new Employee(); } }
-
初始化容器:
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
6、注解 > XML ?
注解一定比配置文件好吗?
- XML:配置繁琐,但易于维护。
- 注解:使用简单,但不易于维护、需要修改类代码。
在实际开发中,仍推荐使用注解开发。