深入了解SpringIoc(续篇)

devtools/2025/1/1 5:35:31/

目录

注入 Bean 的方式有哪些?

构造函数注入还是 Setter 注入?

Bean 的作用域有哪些?

Bean 是线程安全的吗?

Bean 的生命周期了解么?


注入 Bean 的方式有哪些?

依赖注入 (Dependency Injection, DI) 的常见方式:

  1. 构造函数注入:通过类的构造函数来注入依赖项。
  2. Setter 注入:通过类的 Setter 方法来注入依赖项。
  3. Field(字段) 注入:直接在类的字段上使用注解(如 @Autowired@Resource)来注入依赖项。

构造函数注入示例:

@Service
public class UserService {private final UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository = userRepository;}//...
}

Setter 注入示例:

@Service
public class UserService {private UserRepository userRepository;// 在 Spring 4.3 及以后的版本,特定情况下 @Autowired 可以省略不写@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}//...
}

Field 注入示例:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;//...
}

构造函数注入还是 Setter 注入?

Spring 官方推荐构造函数注入,这种注入方式的优势如下:

  1. 依赖完整性:确保所有必需依赖在对象创建时就被注入,避免了空指针异常的风险。
  2. 不可变性:有助于创建不可变对象,提高了线程安全性。
  3. 初始化保证:组件在使用前已完全初始化,减少了潜在的错误。
  4. 测试便利性:在单元测试中,可以直接通过构造函数传入模拟的依赖项,而不必依赖 Spring 容器进行注入。

构造函数注入适合处理必需的依赖项,而 Setter 注入 则更适合可选的依赖项,这些依赖项可以有默认值或在对象生命周期中动态设置。虽然 @Autowired 可以用于 Setter 方法来处理必需的依赖项,但构造函数注入仍然是更好的选择。

在某些情况下(例如第三方类不提供 Setter 方法),构造函数注入可能是唯一的选择

Bean 的作用域有哪些?

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

如何配置 bean 的作用域呢?

xml 方式:

<bean id="..." class="..." scope="singleton"></bean>

注解方式:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {return new Person();
}

Bean 是线程安全的吗?

Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。

我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton ,重点关注 singleton 作用域即可。

prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

有状态 Bean 示例:

// 定义了一个购物车类,其中包含一个保存用户的购物车里商品的 List
@Component
public class ShoppingCart {private List<String> items = new ArrayList<>();public void addItem(String item) {items.add(item);}public List<String> getItems() {return items;}
}

不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

无状态 Bean 示例:

// 定义了一个用户服务,它仅包含业务逻辑而不保存任何状态。
@Component
public class UserService {public User findUserById(Long id) {//...}//...
}

对于有状态单例 Bean 的线程安全问题,常见的三种解决办法是:

  1. 避免可变成员变量: 尽量设计 Bean 为无状态。
  2. 使用ThreadLocal: 将可变成员变量保存在 ThreadLocal 中,确保线程独立。
  3. 使用同步机制: 利用 synchronizedReentrantLock 来进行同步控制,确保线程安全。

这里以 ThreadLocal为例,演示一下ThreadLocal 保存用户登录信息的场景:

public class UserThreadLocal {private UserThreadLocal() {}private static final ThreadLocal<SysUser> LOCAL = ThreadLocal.withInitial(() -> null);public static void put(SysUser sysUser) {LOCAL.set(sysUser);}public static SysUser get() {return LOCAL.get();}public static void remove() {LOCAL.remove();}
}

Bean 的生命周期了解么?

  • 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
  • Bean 属性赋值/填充:为 Bean 设置相关属性和依赖,例如@Autowired 等注解注入的对象、@Value 注入的值、setter方法或构造函数注入依赖和值、@Resource注入的各种资源。
  • Bean 初始化
    • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
    • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
    • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入 BeanFactory对象的实例。
    • 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
    • 如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
    • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
    • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法。
  • 销毁 Bean:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
  • 如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  • 如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的 Bean 销毁方法。或者,也可以直接通过@PreDestroy 注解标记 Bean 销毁之前执行的方法。

AbstractAutowireCapableBeanFactory 的 doCreateBean() 方法中能看到依次执行了这 4 个阶段:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException {// 1. 创建 Bean 的实例BeanWrapper instanceWrapper = null;if (instanceWrapper == null) {instanceWrapper = createBeanInstance(beanName, mbd, args);}Object exposedObject = bean;try {// 2. Bean 属性赋值/填充populateBean(beanName, mbd, instanceWrapper);// 3. Bean 初始化exposedObject = initializeBean(beanName, exposedObject, mbd);}// 4. 销毁 Bean-注册回调接口try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}return exposedObject;
}

Aware 接口能让 Bean 能拿到 Spring 容器资源。

Spring 中提供的 Aware 接口主要有:

BeanNameAware:注入当前 bean 对应 beanName; BeanClassLoaderAware:注入加载当前 bean 的 ClassLoader; BeanFactoryAware:注入当前 BeanFactory 容器的引用。

public interface BeanPostProcessor {// 初始化前置处理default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}// 初始化后置处理default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}}
  • postProcessBeforeInitialization:Bean 实例化、属性注入完成后,InitializingBean#afterPropertiesSet方法以及自定义的 init-method 方法之前执行;
  • postProcessAfterInitialization:类似于上面,不过是在 InitializingBean#afterPropertiesSet方法以及自定义的 init-method 方法之后执行。

InitializingBeaninit-method 是 Spring 为 Bean 初始化提供的扩展点。

public interface InitializingBean {// 初始化逻辑void afterPropertiesSet() throws Exception;
}

指定 init-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"><bean id="demo" class="com.chaycao.Demo" init-method="init()"/></beans>

如何记忆呢?

  1. 整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。
  2. 初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBeaninit-method 的初始化操作。
  3. 销毁这一步会注册相关销毁回调接口,最后通过DisposableBeandestory-method 进行销毁。

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

相关文章

数学建模 绘图 图表 可视化(2)

文章目录 前言柱形图条形图克利夫兰点图系列坡度图南丁格尔玫瑰图径向柱图极坐标图词云图总结参考资料 前言 承接上期 数学建模 绘图 图表 可视化&#xff08;1&#xff09;的总体描述&#xff0c;这期我们继续跟随《Python 数据可视化之美 专业图表绘制指南》步伐来学习其中l…

从虚拟到现实:AI与AR/VR技术如何改变体验经济?

引言&#xff1a;体验经济的崛起 在当今消费环境中&#xff0c;产品与服务早已不再是市场竞争的唯一焦点&#xff0c;能够提供深刻感知和独特体验的品牌&#xff0c;往往更能赢得消费者的青睐。这种转变标志着体验经济的崛起。体验经济不仅仅是简单的买卖行为&#xff0c;而是通…

VLM和VLAM(VLA)相关介绍和发展历程

目录 一、个人感想二、相关介绍2.1 视觉语言模型 (VLM) 的发展历程2.2 视觉语言动作模型 (VLA) 的发展历程2.3 一些关键的研究工作&#xff1a;一些架构图 三、发展历程3.1 视觉语言模型 (VLM) 的发展时间线3.2 视觉语言动作模型 (VLA) 的发展时间线 四、参考资料 一、个人感想…

github codespaces推送镜像时unauthorized: access token has insufficient scopes 解决方法

最近有镜像构建的需求&#xff0c;于是就使用了github提供的这个codespace来构建docker镜像&#xff0c;但是在构建后进行镜像推送时提示 unauthorized: access token has insufficient scopes &#xff0c;如下图 这个一看就知道是权限问题&#xff0c; 这里的access token h…

PDF书籍《手写调用链监控APM系统-Java版》第2章 第一个Agent应用

本人阅读了 Skywalking 的大部分核心代码&#xff0c;也了解了相关的文献&#xff0c;对此深有感悟&#xff0c;特此借助巨人的思想自己手动用JAVA语言实现了一个 “调用链监控APM” 系统。本书采用边讲解实现原理边编写代码的方式&#xff0c;看本书时一定要跟着敲代码。 作者…

【从0带做】基于Springboot3+Vue3的场馆预约系统

大家好&#xff0c;我是武哥&#xff0c;最近给大家手撸了一个基于SpringBoot3Vue3的场馆预约系统&#xff0c;可用于毕业设计、课程设计、练手学习&#xff0c;系统全部原创&#xff0c;如有遇到网上抄袭站长的&#xff0c;欢迎联系博主~ 项目演示视频和教程视频 https://ww…

FlaskAPI-路径参数、查询参数

1 路径参数 在 Flask 中&#xff0c;路径传参是一种常见的传递数据的方式。通过在 URL 路径中指定参数&#xff0c;可以让视图函数根据不同的参数值来返回不同的内容。这种方式可以用于根据用户请求的不同资源&#xff08;如用户 ID、产品编号等&#xff09;来提供定制化的响应…

【SpringMVC】REST 风格

REST&#xff08;Representational State Transfer&#xff0c;表现形式状态转换&#xff09;是一种访问网络资源的格式。传统的资源描述方式通常如下&#xff1a; http://localhost/user/getById?id1http://localhost/user/saveUser 而 REST 风格的描述则更简洁&#xff1a…