跳转至

Bean

开闭原则(OCP)

开闭原则(Open/Closed Principle)是面向对象设计中的一项基本原则。它的核心思想是:

软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

这意味着:

  1. 对扩展开放:当软件的需求变化时,可以通过扩展现有的代码来实现新的功能,而不需要修改现有的代码。
  2. 对修改关闭:一旦软件模块已经开发完成并投入使用,尽量避免对其进行修改,以减少引入新错误的风险。

工厂模式

简单工厂

解决的问题:

客户需要创造对象时只需要向工厂类索要即可,不需要关心具体的构造方法

缺点:

  1. 违背了开闭原则
  2. 工厂类的责任非常重大,一旦出现问题系统必然瘫痪(不要把鸡蛋放到同一个篮子里)

工厂方法

一个产品对应一个工厂,解决了 OCP 问题

工厂方法模式中的角色:

  • 抽象产品(抽象类)
  • 具体产品
  • 抽象工厂(抽象类)
  • 具体工厂

当需要扩展时,只需要添加一个具体产品类和一个具体从工厂类

DishFactory df1 = new SoupFactory();
Soup soup = df1.get();

DishFactory df2 = new RiceFactory();
Rice rice = df2.get();

缺点是类的数量增多,代码冗余

Spring 常见配置方式

XML 配置

传统的配置方式,通过 XML 文件定义 Bean 及其依赖关系。适合需要集中管理配置的场景,或者在不修改代码的情况下调整配置。

注解配置

通过在类或方法上使用注解来定义 Bean 及其依赖关系。

常用注解

  • @Component, @Service, @Repository, @Controller:用于定义 Bean。
  • @Autowired, @Resource:用于依赖注入。

Java 配置(基于 @Configuration 和 @Bean)

具体来说,Spring 容器在启动时,会扫描 @Configuration 类,并调用其中所有 @Bean 标记的方法,将这些方法的返回值注册为 Spring Bean。

@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean(anotherBean());
    }

    @Bean
    public AnotherBean anotherBean() {
        return new AnotherBean();
    }
}

Spring 实例化 Bean 的方式

Spring 容器在启动时会根据配置文件或注解的指示,自动实例化并管理 Bean。

1. 通过构造方法

spring.xml 中:

<!-- Spring 提供的实例化方式,在 spring 配置文件中直接配置类的全路径,Spring 会自动调用该类的<无参构造方法>来实例化 Bean -->
<bean id="sb" class="com.learn.SpringBean"/>

配置完成后在加载 spring 容器的时候就会调用这个无参默认构造方法

2. 通过简单工厂模式

通过简单工厂类的静态方法来创建 bean 实例(最后对象还是在程序代码中负责 new 的)

配置文件:

  • 标明工厂类
  • 标明静态工厂方法
<!-- 需要在 spring 配置文件中告诉 Spring 框架调用哪个类的哪个方法获取 Bean -->
<bean id="starBean" class="....StarFactory" factory-method="get"/>

3. 通过 factory-bean

也就是通过工厂方法模式来实例化

注意这里由于创建产品是调用了具体工厂对象的实例方法,这意味着还需要配置具体工厂类

  • 标明工厂的对象
  • 标明工厂的方法
<!-- 告诉 Spring 框架调用哪个对象的哪个方法来获取 Bean -->
<bean id="gunFactory" class="....GunFactory "/>
<bean id="gun" factory-bean="gunFactory" factory-method="get"/>

4. 通过 FactoryBean 接口

在前面的方法中,factory-bean 和 factory-method 是我们自己定义

如果编写的类直接实现 FactoryBean 接口之后,就不需要指定 factory-bean 和 factory-method 了

factory-bean 会自动指向 FactoryBean 接口的类,factory-method 会自动指向 getObject() 方法

实现类中主要考虑重写 getObject() 方法

<!-- 通过一个特殊的 Bean - 工厂 Bean,来返回一个普通的 Bean -->
<bean id="person" class="....PersonFactoryBean"/>
public class PersonFactoryBean implements FactoryBean<Person> {
    @Override 
    public Person getObject() throws Exception {
        return new Person();
    }
}

BeanFactory 和 FactoryBean 的区别

  • BeanFactory 是 SpringIoC 容器的顶级对象,即为 Bean 工厂,在 Spring 的 IoC 容器中,其负责创建 Bean 对象
  • FactoryBean 是一个用于辅助实例化其他 Bean 对象的一个 Bean
    • 普通 Bean
    • 工厂 Bean:出自于 FactoryBean 实现类的 getObject() 方法

Bean 的生命周期

BeanDefinition

Spring 容器再实例化时,会将 xml 配置的 的信息封装成一个 BeanDefinition 对象,此对象中包含多种属性用来描述 Bean,Spring 根据此对象来创建实例

大体分为 5 步

image-20250221211214063

  1. 实例化 Bean,需要调用无参构造方法
  2. 给 Bean 的属性赋值,需要调用 set 方法
  3. 初始化 Bean (会调用 init 方法,此方法需要自己写、自己配,方法名随意)
  4. 使用 Bean
  5. 销毁 Bean(会调 destroy 方法,此方法需要自己写、自己配,方法名随意)

注意:必须手动关闭 spring 容器才会销毁 Bean

<bean id="user" class="com.example.User" init-method="initBean" destroy-method="destroyBean">
    <property name="name" value="John Doe" />
</bean>
public class User {
    private String name;

    public void setName(String name) {
        System.out.println("第二步:给对象的属性赋值。");
        this.name = name;
    }

    public User() {
        System.out.println("第一步:无参数构造方法执行。");
    }

    // 这个方法需要自己写,自己配。方法名随意。
    public void initBean() {
        System.out.println("第三步:初始化Bean");
    }

    public void destroyBean() {
        System.out.println("第五步:销毁Bean。");
    }
}
public class BeanLifecycleTest {

    @Test
    public void testBeanLifecycleFive() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("第四步:使用Bean: " + user);

        // 注意:必须手动关闭Spring容器,这样Spring容器才会销毁Bean。
        ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
        context.close();
    }
}

Bean 生命周期七步

image-20250221215113130

在 5 步生命周期的 Bean init 之前和之后会调用 Bean 后处理器的 before 和 after 方法。

具体来说,需要创建一个 BeanPostProcessor 的实现类,然后重写 postProcessBeforeInitialization(Object bean, String beanName) 和 after 方法

注意也需要把这个类纳入管理

<bean class="....LogBeanPostProcessor"/>

这个配置将hi作用于当前xml文件中的所有 bean

完整过程

Bean 的创建和初始化赋值是分开的,其中创建指的是调用构造函数,赋值指的是后面一直到 Bean 后处理器的 after 方法

这里依赖注入包括 setter 注入和 构造器注入

image-20250221231303767

  1. 通过 BeanDefinition 获取 bean 定义信息

  2. 调用构造函数实例化 bean(见上一节)

  3. bean 的依赖注入(见下一节)

  4. 处理 Aware 接口。实现了 BeanNameAware, BeanFactoryAware, ApplicationContextAware 接口并重写相应的方法之后会执行这些方法

  5. Bean 后处理器-前置

  6. 初始化方法

  7. Bean 后处理器-后置

Bean 的作用域

<bean id="demo" class="....Demo" scope="singleton"></bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");

SpringBean sb = applicationContext.getBean("sb", SpringBean.class); // SpringBean 是自己写的一个类

Spring 项目

默认为单例。

scope 配置有两个选项:

  • singleton:在 Spring 上下文初始化的时候实例化,每次调用 ApplicationContext 对象的 getBean() 方法时返回那个单例的对象
  • prototype:在 Spring 上下文初始化的时候不会实例化,每次调用 getBean() 时都会拿到一个新的对象

引入 Web 框架

要求项目是一个 Web 应用。引入 springMVC 依赖之后,scope 可以多出两个选项:

  • request:一次请求中一个 bean
  • session:一次会话中一个 bean

依赖注入

主要分为 setter 注入,构造器注入,以及字段注入(不推荐)

  • 如果没有使用依赖注入(如 @Autowired@Resource 或构造函数注入),Spring 容器不会自动将依赖的对象注入到目标类中。
  • 目标类中的依赖对象会是 null,导致在运行时抛出 NullPointerException

setter 注入

在 setter 方法上通过 @Autowired 进行注入

  • 可以通过 @Autowired(required = false) 显式标记某个依赖为可选的,即使不注入匹配的 Bean 也不会抛出异常
@Service
public class UserService {
    private UserRepository userRepository;

    @Autowired // 在<依赖注入>时自动执行
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

构造器注入

通过构造函数进行注入,确保对象创建时所有依赖都已注入。

  • 在只有一个构造函数的情况下不需要显式声明
  • 在有多个构造函数的情况下需要使用 @Autowired 指明具体使用的构造函数
@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Field 注入

直接在类的属性上加上 Autowired 注解(不推荐)

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository; 
}

如果需要通过 new 来创建一个实例,会出抛出 NullPointerException

Bean 的循环依赖问题

image-20250222130733920

singleton + setter注入

<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singelton">
    <property name="name" value="张三"/>
    <property name="wife" ref="wifeBean"/>
</bean>

<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singelton">
    <property name="name" value="小花"/>
    <property name="husband" ref="husbandBean"/>
</bean>

循环依赖不会出问题,原因:

此模式下 Spring 对 Bean 的管理分为清晰的两个阶段

  1. 在 Spring 容器加载的时候,实例化 Bean,只要其中任意一个 Bean 实例化之后,马上进行“曝光”,不会等待属性赋值(因为单例必定是唯一的)
  2. 曝光之后再进行属性赋值

本质上是 setter 方法可以再对象创建完之后再执行

prototype + setter注入

<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
    <property name="name" value="张三"/>
    <property name="wife" ref="wifeBean"/>
</bean>

<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
    <property name="name" value="小花"/>
    <property name="husband" ref="husbandBean"/>
</bean>

抛出异常:BeanCurrentlyInCreationException

scope 是 prototype 时每次请求该 bean 时,都会创建一个新的实例,

但是如果其中一个是 singleton 时,不会出现循环依赖

构造注入

三级缓存本身无法解决构造注入情况下发生的循环依赖

bean 的生命周期中构造函数需要被首先执行,即使是 earlySingletonObjects 也无法缓存构造函数没有执行完的 bean

@component
public class A {
    private B b;
    public A(B b) { // 自动注入。如果 b 不是 Spring Bean ,异常
        this.b = B;
    }
}

解决方案:

使用 @Lazy 进行懒加载,只有需要对象的时候再去进行 bean 对象的创建

// 构造方法
public A(@Lazy B b) {
    this.b = b;
}

三级缓存

介绍

通过三级缓存解决循环依赖:

  • 一级缓存 singletonObjects:单例池(存放的都是 singleton 对象),缓存已经完成初始化的对象
  • 二级缓存 earlySingletonObjects:缓存早期的 bean 对象,即已经执行了构造函数,但是后续初始化工作还没有完成
  • 三级缓存 singletonFactories:缓存 ObjectFactory,即创建对象的对象工厂
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 二级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
}

一、二级缓存

只能处理非代理对象的情况

image-20250222134844072

三级缓存

假设 A 是一个代理对象

代理对象的主要作用是拦截对目标对象的方法调用,并在调用前后执行额外的逻辑(例如事务管理、日志记录等)

image-20250222135242671