spring-ioc-bean

devtools/2025/3/6 17:26:12/

本文重点在于充分应用 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>
java">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);
}
  1. 通过bean的id查找 getBean(String name),需要强转
  2. 通过类型查找 getBean(Class<T> requiredType),不需要强转

依赖注入

创建一个带有属性的 Bean,并注入属性值。

基础类型注入

java">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>

引用类型注入

java">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

java">Map<String, Object> beans = ctx.getBeansOfType(SomeInterface.class);

根据被注解标注的类

java">Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);

获取容器中所有 bean

java">String[] beanNames = ctx.getBeanDefinitionNames();

延迟查找(缺省加载)

java">ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
Dog dog = dogProvider.getIfAvailable(Dog::new);

ApplicationContext 与 BeanFactory

  • ApplicationContextBeanFactory 的子接口,提供了更多功能。
  • AOP 支持、配置元信息、资源管理、事件驱动机制、国际化、Environment 抽象等。

注解驱动Ioc和组件扫描

xml驱动的ioc使用的是ClassPathXmlApplicationContext,注解驱动的ioc需要使用AnnotationConfigApplicationContext

配置类(对应之前的xml)

java">@Configuration
public class QuickstartConfiguration {@Beanpublic Person person() {Person person = new Person();person.setName("person");person.setAge(123);return person;}@Beanpublic Cat cat() {Cat cat = new Cat();cat.setName("test-cat-anno");// 直接拿上面的person()方法作为返回值即可,相当于refcat.setMaster(person());return cat;}
}
  • 配置类使用 @Configuration 标注
  • 每一个需要创建出来的bean,使用一个 @Bean 注解标注的方法,方法返回值对应需要创建出来Bean的类型,默认方法名是bean的id(也可以在 @Bean 注解中再次指定)
  • 方法体中可以具体去实现bean的创建逻辑包括依赖注入
  • 复杂类型注入使用方法的返回值

组件扫描

除了使用 @Bean 来手动指定 Bean 的注册以外,还可以使用组件扫描来根据类路径批量加载 Bean

java">@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>

注解

java">@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>

注解

java">@Bean
public Person person() {return new Person("test-person-anno-byconstructor", 18);
}

组件扫描简单类型注入

java">@Value("0")
private Integer order;

我们在 @Value 中引用的值不仅可以来自常量,还可以使用配置文件中的值,或者使用 SpEL 表达式来引用更复杂的情况(其他 Bean 的值或计算属性)。

PropertySource配置文件注入

@PropertySource({properties配置文件的位置}) 可引入配置文件中的键值对

java">@PropertySource("classpath:basic_di/value/red.properties")
public class InjectValueConfiguration {@Value("${red.name}")private String name;
}

properties 文件(先转换成一个一个map)等配置项信息会读取到 Environment 对象中

补充-> SpEL表达式

在注入时可以使用 #{} 来引用其他bean的属性值,或者使用复杂的计算属性

自动注入机制-@Autowired

@Autowired

java">
@Component
public class Dog {// 1.属性上标注@Autowiredprivate Person person;// 2.构造器上标注@Autowiredpublic Dog(Person person) {this.person = person;}// 3.setter 方法上标注@Autowiredpublic void setPerson(Person person) {this.person = person;}
}

注入的bean不存在

java">@Autowired(required = false)
private Person person;

解决多个相同类型的bean注入冲突

java">@Autowired
@Qualifier("cat")
private Animal animal;@Primary
@Bean
public Animal cat() {return new Cat();
}

@Autowired 注入逻辑分析

  1. 优先按照类型查找
  2. 如果存在多个相同类型的bean,再按照名称查找
  3. 如果存在多个相同类型的bean,且没有指定名称,报错

注入多个相同类型的bean

使用 List 或者 Set 注入

java">@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可以执行一些初始化操作

java">public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {private static final Logger log = LoggerFactory.getLogger(MyBean.class);@Overridepublic void setBeanName(String name) {log.debug("当前bean " + this + " 名字叫:" + name);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.debug("当前bean " + this + " 容器是:" + applicationContext);}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("当前bean " + this + " 初始化");}@Autowiredpublic void aaa(ApplicationContext applicationContext) { // 没有后处理器无法执行log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);}@PostConstructpublic void init() { // 没有后处理器无法执行log.debug("当前bean " + this + " 使用@PostConstruct 初始化");}
}

在 Bean 初始化的时候会把对应的对象注入进去(注入到重写的 setApplicationContextsetBeanName 形参中,然后我们就可以在这个函数中执行手动赋值的操作,这个函数会在 Bean 生命周期中自动调用)。

:::color1
@Autowired 失效场景

config 配置类中创建 BeanFactoryPostProcessor ,此时,config 配置类的创建实际提前了,BeanPostProcessor 还未创建,所以依赖于BeanPostProcessor的 @Autowired 自动注入无法生效

:::

bean 的类型

分为普通bean和FactoryBean

工厂bean

FactoryBean 用来创建一些初始化流程比较复杂的bean

java">public interface FactoryBean<T> {// 返回创建的对象@NullableT getObject() throws Exception;// 返回创建的对象的类型(即泛型类型)@NullableClass<?> getObjectType();// 创建的对象是单实例Bean还是原型Bean,默认单实例default boolean isSingleton() {return true;}
}

我们创建一个工厂bean,用于指定某一个类型的bean的生产逻辑,后续就可以直接从spring容器中按照我们顶底好的生产逻辑,直接拿到我们想要的类型的bean了

java">public class ToyFactoryBean implements FactoryBean<Toy> {public ToyFactoryBean() {System.out.println("ToyFactoryBean 初始化了。。。");}private Child child;@Overridepublic Toy getObject() throws Exception {switch (child.getWantToy()) {case "ball":return new Ball("ball");case "car":return new Car("car");default:return null;}}@Overridepublic Class<Toy> getObjectType() {return Toy.class;}public void setChild(Child child) {this.child = child;}
}@Configuration
public class BeanTypeConfiguration {@Beanpublic Child child() {return new Child();}@Beanpublic 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 的使用要点:

  1. 作用是用于创建较为复杂的产品如 SqlSessionFactory ,但 @Bean 已具备等价功能。
  2. 使用较为古怪,一不留神就会用错:
    a. 被 Factory 创建的 Bean
    • 会认为创建、依赖注入、 Aware 接口回调、前初始化这些都是 FactoryBean 的职责,这些流程都不会走。
    • 唯有后初始化的流程会走,也就是产品可以被代理增强。
    • 单例的产品不会存储于 BeanFactorysingletonObjects 成员中,而是另一个 factoryBeanObjectCache 成员中。
  • b. 按名字获取时,拿到的是产品对象,名字前加 & 获取的是工厂对象。
    工厂管理的 Bean 在初始化后可以被增强,其他阶段不会。

:::

bean 的作用域

  • 单例 -> 默认 -> 每次获取的或者注入的都是同一个对象 -> ApplicationContext 被初始化就会被创建。
  • 原型 Bean -> 指定 @Scope("prototype") -> 每次获取就会创建一个新的对象 -> 延迟了 Bean 创建的时机。

bean 实例化方式

spring对bean的多种对象创建模式的支持

普通 Bean

通过 <bean> 标签或 @Bean 注解创建。

FactoryBean 方式

通过实现 FactoryBean 接口创建。

静态工厂

这里初始化对象的方法是静态的

java">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"/>

注解驱动创建的写法

java">@Bean
public Car car2() {return CarStaticFactory.getCar();
}

静态工厂不会注册到spring容器,只是需要用到这一点静态方法的逻辑

实例工厂

创建bean的方式不是静态的

java">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的实例化方法

注解驱动写法

java">@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {return carInstanceFactory.getCar();
}

先注册工厂,再用工厂指定创建bean逻辑

bean 的生命周期

bean 生命周期时序图
在这里插入图片描述

生命周期回调机制

  1. init-methoddestroy-method
  2. @PostConstruct@PreDestroy
  3. InitializingBeanDisposableBean 接口。

http://www.ppmy.cn/devtools/165042.html

相关文章

Unix Domain Socket和eventfd

在Linux开发中&#xff0c;Unix Domain Socket和eventfd是两种不同的通信机制&#xff0c;它们的设计目标和适用场景有显著差异。以下分点解释并配合示例说明&#xff1a; 一、Unix Domain Socket&#xff08;UDS&#xff09; 1. 是什么&#xff1f; 一种**本地进程间通信&am…

【数据库】关系代数

关系代数 一、关系代数的概念二、关系代数的运算2.1 并、差、交2.2 投影、选择2.3 笛卡尔积2.4 连接2.5 重命名2.6 优先级 一、关系代数的概念 关系代数是一种抽象的数据查询语言用对关系的运算来表达查询 运算对象&#xff1a;关系运算符&#xff1a;4类运算结果&#xff1a;…

大白话html第五章HTML5 新增表单元素和属性

大白话html第五章HTML5 新增表单元素和属性 HTML5 给表单带来了很多新的小伙伴&#xff0c;让我们收集用户信息变得更方便、更智能。 新增表单元素 <input type"date">&#xff1a;这个就像一个自带日历的小框框&#xff0c;用户可以直接在里面选择日期&…

11 Oracle Golden Gate 高可用解决方案:Golden Gate 助力企业保障业务连续性

文章目录 Oracle Golden Gate 高可用解决方案&#xff1a;Golden Gate 助力企业保障业务连续性一、Oracle Golden Gate基本概念二、设计异地灾备策略2.1 需求分析2.2 网络规划2.3 部署架构 三、实施异地灾备策略3.1 环境准备3.2 配置Golden Gate3.3 验证与测试 四、数据保护策略…

Mac mini M4安装nvm 和node

先要安装Homebrew&#xff08;如果尚未安装&#xff09;。在终端中输入以下命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 根据提示操作完成Homebrew的安装。 安装nvm。在终端中输入以下命令&#xf…

Docker 部署开源项目HivisionIDPhotos详细教程

本章教程主要介绍如何在Docker上部署开源项目HivisionIDPhotos。 一、HivisionIDPhotos简介 HivisionIDPhoto 旨在开发一种实用、系统性的证件照智能制作算法。 它利用一套完善的AI模型工作流程,实现对多种用户拍照场景的识别、抠图与证件照生成。 HivisionIDPhoto 可以做到:…

安全见闻5,6

人工智能篇 人工智能目前处于高数发展阶段,所涉及的安全问题也很多 ai所收集的数据有泄露的风险(数据安全) ai进行工作的时候可能因为收集的恶意信息而产生错误(对抗攻击) ai模型被逆向窃取的风险,涉及到知识产权被侵犯的问题 ai被用作与恶意网络攻击的风险 同时要搞好ai…

iOS实现一个强大的本地状态记录容器

我们开发中经常会遇到这样的场景&#xff0c;就是我们客户端用户进行了某个操作&#xff0c;这个操作影响了数据的状态&#xff0c;但是我们又不方便重新请求一次数据&#xff0c; 这个时候&#xff0c;就需要我们记录一下本地状态在内存中&#xff0c;随着业务越来越复杂&…