spring-ioc-bean
本文重点在于充分应用 Spring 提供的 IoC 特性,介绍如何创建一个好用的 Bean。基础篇不涉及后置处理器、 BeanDefinition 以及 Spring 加载原理相关的知识。
引入
ioc 的起源
- ** 接口与实现类的需求变更 ** :最初的静态工厂模式。
- ** 反射机制 ** :延迟加载,避免编译时依赖。
- ** 外部化配置 ** :将 Bean 的全限定名存储在配置文件中。
- ** 缓存机制 ** :存储已创建的 Bean 对象。
入门
使用 Spring根据xml配置文件来创建一个对象,并且完成属性的注入
依赖查找
使用 ClassPathXmlApplicationContext 创建 Spring 容器,并根据配置文件中的 Bean ID 或类型获取 Bean 对象。
<bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"></bean>
public static void main(String[] args) throws Exception {
BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");
Person person = (Person) factory.getBean("person");
System.out.println(person);
}
- 通过bean的id查找
getBean(String name),需要强转 - 通过类型查找
getBean(Class<T> requiredType),不需要强转
依赖注入
创建一个带有属性的 Bean,并注入属性值。
基础类型注入
public class Person {
private String name;
private Integer age;
// getter and setter ......
}
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
<property name="name" value="test-person-byset"/>
<property name="age" value="18"/>
</bean>
引用类型注入
public class Cat {
private String name;
private Person master;
// getter and setter ......
}
<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
<property name="name" value="test-cat"/>
<property name="master" ref="person"/>
</bean>
:::info
基础类型 <property name="name" value="xxx"/>
引用类型 <property name="name" ref="xxx"/>
:::
基础
依赖查找高级
根据接口类型获取所有实现bean
Map<String, Object> beans = ctx.getBeansOfType(SomeInterface.class);
根据被注解标注的类
Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);
获取容器中所有 bean
String[] beanNames = ctx.getBeanDefinitionNames();
延迟查找(缺省加载)
ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
Dog dog = dogProvider.getIfAvailable(Dog::new);
ApplicationContext 与 BeanFactory
ApplicationContext是BeanFactory的子接口,提供了更多功能。- AOP 支持、配置元信息、资源管理、事件驱动机制、国际化、Environment 抽象等。
注解驱动Ioc和组件扫描
xml驱动的ioc使用的是ClassPathXmlApplicationContext,注解驱动的ioc需要使用AnnotationConfigApplicationContext
配置类(对应之前的xml)
@Configuration
public class QuickstartConfiguration {
@Bean
public Person person() {
Person person = new Person();
person.setName("person");
person.setAge(123);
return person;
}
@Bean
public Cat cat() {
Cat cat = new Cat();
cat.setName("test-cat-anno");
// 直接拿上面的person()方法作为返回值即可,相当于ref
cat.setMaster(person());
return cat;
}
}
- 配置类使用
@Configuration标注 - 每一个需要创建出来的bean,使用一个 @Bean 注解标注的方法,方法返回值对应需要创建出来Bean的类型,默认方法名是bean的id(也可以在 @Bean 注解中再次指定)
- 方法体中可以具体去实现bean的创建逻辑包括依赖注入
- 复杂类型注入使用方法的返回值
组件扫描
除了使用 @Bean 来手动指定 Bean 的注册以外,还可以使用组件扫描来根据类路径批量加载 Bean
@Configuration
@ComponentScan("com.linkedbear.spring.annotation.c_scan")
public class ComponentScanConfiguration {
}
@Component
public class Cat {
private final String name;
private final Person master;
@Autowired // spring 4.3 后可省略
public Cat(Person master, @Value("test-cat") String name) {
this.master = master;
this.name = name;
}
}
组件扫描 @ComponentScan -> 扫描所有 @Componnet 及其派生注解(比如@Service …)的类
组件扫描的方式的依赖注入有多种方式,我们目前使用构造器注入
简单类型使用 @Value,复杂类型,默认按照类型注入,如果存在多个@Qualifier指定bean名称
依赖注入
setter 属性注入
无参构造器 + set 方法
xml -> 之前用的最多的方式
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
<property name="name" value="test-person-byset"/>
<property name="age" value="18"/>
</bean>
注解
@Bean
public Person person() {
Person person = new Person();
person.setName("test-person-anno-byset");
person.setAge(18);
return person;
}
构造器注入
需要添加全参构造器
xml
<bean id="person" class="com.linkedbear.spring.basic_di.b_constructor.bean.Person">
<constructor-arg index="0" value="test-person-byconstructor"/>
<constructor-arg index="1" value="18"/>
</bean>
注解
@Bean
public Person person() {
return new Person("test-person-anno-byconstructor", 18);
}
组件扫描简单类型注入
@Value("0")
private Integer order;
我们在 @Value 中引用的值不仅可以来自常量,还可以使用配置文件中的值,或者使用 SpEL 表达式来引用更复杂的情况(其他 Bean 的值或计算属性)。
PropertySource配置文件注入
@PropertySource({properties配置文件的位置}) 可引入配置文件中的键值对
@PropertySource("classpath:basic_di/value/red.properties")
public class InjectValueConfiguration {
@Value("${red.name}")
private String name;
}
properties 文件(先转换成一个一个map)等配置项信息会读取到 Environment 对象中
补充-> SpEL表达式
在注入时可以使用 #{} 来引用其他bean的属性值,或者使用复杂的计算属性
自动注入机制-@Autowired
@Autowired
@Component
public class Dog {
// 1.属性上标注
@Autowired
private Person person;
// 2.构造器上标注
@Autowired
public Dog(Person person) {
this.person = person;
}
// 3.setter 方法上标注
@Autowired
public void setPerson(Person person) {
this.person = person;
}
}
注入的bean不存在
@Autowired(required = false)
private Person person;
解决多个相同类型的bean注入冲突
@Autowired
@Qualifier("cat")
private Animal animal;
@Primary
@Bean
public Animal cat() {
return new Cat();
}
@Autowired 注入逻辑分析
- 优先按照类型查找
- 如果存在多个相同类型的bean,再按照名称查找
- 如果存在多个相同类型的bean,且没有指定名称,报错
注入多个相同类型的bean
使用 List 或者 Set 注入
@Autowired
private List<Animal> animals; // 会注入所有类型为Animal的bean
@Resource 和 @Inject 注入
@Resource根据名称注入。@Inject根据类型注 入,需要额外导入 JSR330 依赖。
回调注入-Aware接口&InitializingBean回调

使用 Aware 接口实现回调注入。 回调注入不依赖于框架的扩展,属于内置功能,如果我们需要一个 不依赖于后处理器 就可以注入的场景,可以选它。@Autowired(其他依赖后处理器的注入机制)依赖于后处理器,某些情况下会失效。
| 接口名 | 用途 |
|---|---|
| BeanFactoryAware | 回调注入 BeanFactory |
| ApplicationContextAware | 回调注入 ApplicationContext(与上面不同,后续 IOC 高级讲解) |
| EnvironmentAware | 回调注入 Environment(后续IOC高级讲解) |
| ApplicationEventPublisherAware | 回调注入事件发布器 |
| ResourceLoaderAware | 回调注入资源加载器(xml驱动可用) |
| BeanClassLoaderAware | 回调注入加载当前 Bean 的 ClassLoader |
| BeanNameAware | 回调注入当前 Bean 的名称 |
InitializingBean可以执行一些初始化操作
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
private static final Logger log = LoggerFactory.getLogger(MyBean.class);
@Override
public void setBeanName(String name) {
log.debug("当前bean " + this + " 名字叫:" + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.debug("当前bean " + this + " 容器是:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("当前bean " + this + " 初始化");
}
@Autowired
public void aaa(ApplicationContext applicationContext) { // 没有后处理器无法执行
log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);
}
@PostConstruct
public void init() { // 没有后处理器无法执行
log.debug("当前bean " + this + " 使用@PostConstruct 初始化");
}
}
在 Bean 初始化的时候会把对应的对象注入进去(注入到重写的 setApplicationContext 和 setBeanName 形参中,然后我们就可以在这个函数中执行手动赋值的操作,这个函数会在 Bean 生命周期中自动调用)。
:::color1
@Autowired 失效场景
config 配置类中创建 BeanFactoryPostProcessor ,此时,config 配置类的创建实际提前了,BeanPostProcessor 还未创建,所以依赖于BeanPostProcessor的 @Autowired 自动注入无法生效
:::
bean 的类型
分为普通bean和FactoryBean
工厂bean
FactoryBean 用来创建一些初始化流程比较复杂的bean
public interface FactoryBean<T> {
// 返回创建的对象
@Nullable
T getObject() throws Exception;
// 返回创建的对象的类型(即泛型类型)
@Nullable
Class<?> getObjectType();
// 创建的对象是单实例Bean还是原型Bean,默认单实例
default boolean isSingleton() {
return true;
}
}
我们创建一个工厂bean,用于指定某一个类型的bean的生产逻辑,后续就可以直接从spring容器中按照我们顶底好的生产逻辑,直接拿到我们想要的类型的bean了
public class ToyFactoryBean implements FactoryBean<Toy> {
public ToyFactoryBean() {
System.out.println("ToyFactoryBean 初始化了。。。");
}
private Child child;
@Override
public Toy getObject() throws Exception {
switch (child.getWantToy()) {
case "ball":
return new Ball("ball");
case "car":
return new Car("car");
default:
return null;
}
}
@Override
public Class<Toy> getObjectType() {
return Toy.class;
}
public void setChild(Child child) {
this.child = child;
}
}
@Configuration
public class BeanTypeConfiguration {
@Bean
public Child child() {
return new Child();
}
@Bean
public ToyFactoryBean toyFactory() {
ToyFactoryBean toyFactory = new ToyFactoryBean();
/// 给工厂bean注入属性
toyFactory.setChild(child());
return toyFactory;
}
}
public class BeanTypeAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);
// 可以直接拿目标生产的bean,工厂bean本身对用户是不可见的
Map<String, Toy> toys = ctx.getBeansOfType(Toy.class);
toys.forEach((name, toy) -> {
System.out.println("toy name : " + name + ", " + toy.toString());
});
// 测试工厂bean创建出来bean的作用域
Toy toy1 = ctx.getBean(Toy.class);
Toy toy2 = ctx.getBean(Toy.class);
System.out.println(toy1 == toy2);
ToyFactoryBean factoryBean = ctx.getBean(ToyFactoryBean.class);
System.out.println(factoryBean);
System.out.println(ctx.getBean("toyFactory"));
// 拿工厂bean本身需要加 &
System.out.println(ctx.getBean("&toyFactory"));
}
}
- FactoryBean 加载时机 -> 伴随 ApplicationContext 被加载
- FactoryBean 创建bean的时机 -> 延迟生产,等到getBean的时候再去加载
- 生产出来的bean是否单例 -> 默认单例,可重写 isSingleton() 来改变生产的bean的作用域
- 可通过 & 取出 BeanFactory 本体
:::color1
在 Spring 发展阶段中重要,但目前已经很鸡肋的接口 FactoryBean 的使用要点:
- 作用是用于创建较为复杂的产品如
SqlSessionFactory,但@Bean已具备等价功能。 - 使用较为古怪,一不留神就会用错:
a. 被Factory创建的Bean:- 会认为创建、依赖注入、
Aware接口回调、前初始化这些都是FactoryBean的职责,这些流程都不会走。 - 唯有后初始化的流程会走,也就是产品可以被代理增强。
- 单例的产品不会存储于
BeanFactory的singletonObjects成员中,而是另一个factoryBeanObjectCache成员中。
- 会认为创建、依赖注入、
- b. 按名字获取时,拿到的是产品对象,名字前加
&获取的是工厂对象。
工厂管理的Bean在初始化后可以被增强,其他阶段不会。
:::
bean 的作用域
- 单例 -> 默认 -> 每次获取的或者注入的都是同一个对象 ->
ApplicationContext被初始化就会被创建。 - 原型 Bean -> 指定
@Scope("prototype")-> 每次获取就会创建一个新的对象 -> 延迟了 Bean 创建的时机。
bean 实例化方式
spring对bean的多种对象创建模式的支持
普通 Bean
通过 <bean> 标签或 @Bean 注解创建。
FactoryBean 方式
通过实现 FactoryBean 接口创建。
静态工厂
这里初始化对象的方法是静态的
public class CarStaticFactory {
public static Car getCar() {
return new Car();
}
}
xml 中指定工厂类,以及静态初始化方法即可,自动根据静态初始化方法判断生产出来bean的类型
<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>
注解驱动创建的写法
@Bean
public Car car2() {
return CarStaticFactory.getCar();
}
静态工厂不会注册到spring容器,只是需要用到这一点静态方法的逻辑
实例工厂
创建bean的方式不是静态的
public class CarInstanceFactory {
public Car getCar() {
return new Car();
}
}
<bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
<bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>
需要先把实例工厂类注册到spring容器,再去指定这个bean是实例工厂bean,并且这个实例工厂bean的实例化方法
注解驱动写法
@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {
return carInstanceFactory.getCar();
}
先注册工厂,再用工厂指定创建bean逻辑
bean 的生命周期
bean 生命周期时序图

生命周期回调机制
init-method和destroy-method。@PostConstruct和@PreDestroy。InitializingBean和DisposableBean接口。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)