欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 社会 > 深入了解SpringIoc(续篇)

深入了解SpringIoc(续篇)

2024/12/27 16:52:13 来源:https://blog.csdn.net/2303_78378466/article/details/144702659  浏览:    关键词:深入了解SpringIoc(续篇)

目录

注入 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 进行销毁。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com