Bean¶
开闭原则(OCP)¶
开闭原则(Open/Closed Principle)是面向对象设计中的一项基本原则。它的核心思想是:
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
这意味着:
- 对扩展开放:当软件的需求变化时,可以通过扩展现有的代码来实现新的功能,而不需要修改现有的代码。
- 对修改关闭:一旦软件模块已经开发完成并投入使用,尽量避免对其进行修改,以减少引入新错误的风险。
工厂模式¶
简单工厂¶
解决的问题:
客户需要创造对象时只需要向工厂类索要即可,不需要关心具体的构造方法
缺点:
- 违背了开闭原则
- 工厂类的责任非常重大,一旦出现问题系统必然瘫痪(不要把鸡蛋放到同一个篮子里)
工厂方法¶
一个产品对应一个工厂,解决了 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() 方法
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 配置的
大体分为 5 步¶
- 实例化 Bean,需要调用无参构造方法
- 给 Bean 的属性赋值,需要调用 set 方法
- 初始化 Bean (会调用 init 方法,此方法需要自己写、自己配,方法名随意)
- 使用 Bean
- 销毁 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 生命周期七步¶
在 5 步生命周期的 Bean init 之前和之后会调用 Bean 后处理器的 before 和 after 方法。
具体来说,需要创建一个 BeanPostProcessor 的实现类,然后重写 postProcessBeforeInitialization(Object bean, String beanName)
和 after 方法
注意也需要把这个类纳入管理
这个配置将hi作用于当前xml文件中的所有 bean
完整过程¶
Bean 的创建和初始化赋值是分开的,其中创建指的是调用构造函数,赋值指的是后面一直到 Bean 后处理器的 after 方法
这里依赖注入包括 setter 注入和 构造器注入
-
通过 BeanDefinition 获取 bean 定义信息
-
调用构造函数实例化 bean(见上一节)
-
bean 的依赖注入(见下一节)
-
处理 Aware 接口。实现了 BeanNameAware, BeanFactoryAware, ApplicationContextAware 接口并重写相应的方法之后会执行这些方法
-
Bean 后处理器-前置
-
初始化方法
-
Bean 后处理器-后置
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 注解(不推荐)
如果需要通过 new 来创建一个实例,会出抛出 NullPointerException
Bean 的循环依赖问题¶
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 的管理分为清晰的两个阶段
- 在 Spring 容器加载的时候,实例化 Bean,只要其中任意一个 Bean 实例化之后,马上进行“曝光”,不会等待属性赋值(因为单例必定是唯一的)
- 曝光之后再进行属性赋值
本质上是 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 对象的创建
三级缓存¶
介绍¶
通过三级缓存解决循环依赖:
- 一级缓存 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); // 三级缓存
}
一、二级缓存¶
只能处理非代理对象的情况
三级缓存¶
假设 A 是一个代理对象
代理对象的主要作用是拦截对目标对象的方法调用,并在调用前后执行额外的逻辑(例如事务管理、日志记录等)