目录
反射
反射三种方式
获取反射中的Class对象
通过反射创建类对象
通过反射获取类属性、方法、构造器
IOC
概念
原理
实现方式
基于 XML 配置
基于注解配置
IOC优点
IOC的初始化过程
1. 资源定位
3. Bean 定义注册
4. BeanFactory 后置处理
5. Bean 后置处理
6. Bean 实例化与初始化
7. 容器初始化完成
2. Bean 定义加载
图解
编辑
Spring 创建对象的过程
1. Bean 定义阶段
2. Bean 实例化阶段
3. Bean 属性填充阶段
4. Bean 初始化阶段
5. Bean 使用阶段
6. Bean 销毁阶段
Spring中Bean的作用域
1. 单例(Singleton)
2. 原型(Prototype)
3. 请求(Request)
4. 会话(Session)
5. 全局会话(Global Session)
6. 应用(Application)
在 Bean 注入中涉及多种设计模式。
单例 bean 的线程安全
1. 问题根源
2. 解决方案
方案一:设计无状态 Bean
方案二:使用线程安全的类
方案三:同步方法或代码块
方案四:使用 ThreadLocal
3. 最佳实践
4. 总结
Spring创建对象的过程中,如何解决循环依赖问题
循环依赖的类型
三级缓存机制
解决循环依赖的过程
1. 创建 A Bean
2. 创建 B Bean
3. 从缓存中获取 A
4. 完成 B Bean 的创建
5. 继续完成 A Bean 的创建
代码示例
总结
Bean的初始化和销毁阶段有哪些扩展点?
初始化阶段扩展点
1. 实现 InitializingBean 接口
2. 使用 @PostConstruct 注解
3. 自定义初始化方法
4. BeanPostProcessor 接口
销毁阶段扩展点
1. 实现 DisposableBean 接口
2. 使用 @PreDestroy 注解
3. 自定义销毁方法
执行顺序总结
@Component 和 @Bean 的区别是什么?
1. 使用场景
2. 使用方式
3. 作用目标
4. 灵活性
5. 生命周期管理
@Resource VS AutoWired bean的注入
来源
注入方式
属性
依赖
使用建议
Bean生命周期
BeanFactory
基本概念
主要实现类
工作原理
使用场景
与 ApplicationContext 的比较
BeanFactory和ApplicationContext的区别
BeanFactory 与 FactoryBean的区别
概念与定位
接口定义与使用方式
应用场景
总结
反射
Java反射是一种强大的机制,允许程序在运行时动态地获取类的内部信息,并直接操作类的属性和方法。通过反射,开发者可以在运行时检查类、接口、字段和方法,并调用这些方法或访问这些字段,而无需在编译时知道它们的名称。反射主要通过java.lang.reflect包实现,提供了一系列类和接口,用于获取和操作类及其成员。反射在许多框架和库中被广泛使用,例如Spring框架的依赖注入。
反射在 Java 中主要通过 java.lang.reflect 包实现,这个包提供了一系列类和接口,用于在运行时获取和操作类及其成员。
以下是 Java 反射的一些主要功能和用法:
- 获取类的信息:
- 使用 Class.forName(String className) 动态加载类,并返回对应的 Class 对象。
- 使用 Object.getClass() 获取对象的 Class 对象。
- 使用 Class<?> clazz = MyClass.class; 获取类的 Class 对象(静态方式)。
- 获取类的成员信息:
- 使用 Class.getMethods() 获取类的所有公共方法。
- 使用 Class.getDeclaredMethods() 获取类的所有方法(包括私有方法)。
- 使用 Class.getFields() 获取类的所有公共字段。
- 使用 Class.getDeclaredFields() 获取类的所有字段(包括私有字段)。
- 创建对象实例:
- 使用 Class.newInstance() 创建类的实例(需要无参构造函数)。
- 使用 Constructor<T>.newInstance(...) 创建类的实例(可以使用带参数的构造函数)。
- 调用方法:
- 使用 Method.invoke(Object obj, Object... args) 调用对象的方法。
- 访问和修改字段:
- 使用 Field.get(Object obj) 获取对象的字段值。
- 使用 Field.set(Object obj, Object value) 设置对象的字段值。
参考:
Java的反射是什么?超详细+举例子+通俗易懂版!-CSDN博客
反射三种方式
获取反射中的Class对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:
第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
第二种,使用 .class 方法。
这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
第三种,使用类对象的 getClass() 方法。
String str = new String("Hello"); Class clz = str.getClass();
通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);
通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
price
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
输出结果是:
name
price
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
参考:大白话说Java反射:入门、使用、原理 - 陈树义 - 博客园
IOC
Spring IOC(Inversion of Control,控制反转)是 Spring 框架的核心特性之一,它极大地简化了 Java 应用程序的开发过程。
概念
在传统的 Java 开发中,对象的创建和依赖关系的管理通常由开发者手动控制。例如,一个类需要使用另一个类的实例时,会在该类内部使用
new
关键字来创建所需对象。而在 Spring IOC 中,对象的创建、初始化、销毁等生命周期管理工作不再由开发者负责,而是交给 Spring 容器来完成。这种将对象控制权从代码中转移到容器的思想就是控制反转。
原理
Spring IOC 的核心原理是基于反射机制和工厂模式。
- 反射机制:Spring 容器利用 Java 的反射机制,在运行时动态地创建对象、调用方法和访问属性,而不需要在编译时就确定具体的类。
- 工厂模式:Spring 容器充当一个工厂,它根据配置信息(如 XML 配置文件、Java 注解等)来创建和管理对象。开发者只需要告诉 Spring 容器需要哪些对象以及对象之间的依赖关系,容器就会负责创建和组装这些对象。
实现方式
Spring IOC 有两种主要的实现方式:基于 XML 配置和基于注解配置。
基于 XML 配置
在这种方式下,需要创建一个 XML 配置文件,在文件中定义对象的信息和依赖关系。
<?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">
<!-- 定义一个名为 userService 的 Bean -->
<bean id="userService" class="com.example.service.UserService">
<!-- 注入依赖的 userDao -->
<property name="userDao" ref="userDao"/>
</bean>
<!-- 定义一个名为 userDao 的 Bean -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
</beans>
在 Java 代码中加载 XML 配置文件并获取对象:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载 XML 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 userService 对象
UserService userService = (UserService) context.getBean("userService");
userService.doSomething();
}
}
基于注解配置
使用注解可以更简洁地实现 IOC。
常用的注解有
@Component
、@Service
、@Repository
、@Controller
、@Autowired
等。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
// 使用 @Service 注解将该类标记为 Spring 管理的 Bean
@Service
public class UserService {
private UserDao userDao;
// 使用 @Autowired 注解进行依赖注入
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void doSomething() {
userDao.saveUser();
}
}
import org.springframework.stereotype.Repository;
// 使用 @Repository 注解将该类标记为 Spring 管理的 Bean
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("Save user to database.");
}
}
在 Java 代码中启用注解扫描并获取对象:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
// 启用组件扫描
@ComponentScan(basePackages = "com.example")
public class AppConfig {
public static void main(String[] args) {
// 创建注解配置的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 从容器中获取 userService 对象
UserService userService = context.getBean(UserService.class);
userService.doSomething();
// 关闭上下文
context.close();
}
}
IOC优点
- 解耦:IOC 使得对象之间的依赖关系由容器来管理,降低了代码的耦合度。当需要更换依赖对象时,只需要修改配置文件或注解,而不需要修改大量的业务代码。
- 可维护性:由于对象的创建和管理集中在容器中,代码的结构更加清晰,便于维护和扩展。
- 可测试性:在进行单元测试时,可以方便地模拟对象的依赖关系,提高测试的效率和准确性。
- 灵活性:可以根据不同的环境和需求,动态地配置对象的创建和依赖关系。
- Spring的IOC容器降低了业务对象之间的复杂性,让组件之间互相解耦。
- Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,
从而提高了更好的复用性
- Spring的高度开放性,并不强制应用完全依赖于Spring,
开发者可自由选用Spring框架的部分或全部
- Spring的高度扩展性,让开发者可以轻易的让自己的框架在Spring上进行集成
- Spring的生态极其完整。
IOC的初始化过程
Spring IOC(控制反转)容器的初始化过程是一个复杂且关键的流程,它主要包含资源定位、Bean 定义加载、注册以及后续的处理等步骤,
下面为你详细阐述其初始化过程:
1. 资源定位
这是初始化的起始步骤,Spring 容器需要明确从哪里获取 Bean 的定义信息,这些定义信息可以存在于多种形式的资源中,例如 XML 文件、Java 注解或者 Groovy 脚本等。
3. Bean 定义注册
将加载得到的 BeanDefinition
对象注册到 BeanDefinitionRegistry
中,BeanDefinitionRegistry
本质上是一个存储 BeanDefinition
的注册表,Spring 可以通过 Bean 的名称从这个注册表中获取对应的 BeanDefinition
。
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
public class BeanDefinitionRegistrationExample {
public static void main(String[] args) {
// 创建 BeanFactory 作为 BeanDefinitionRegistry
DefaultListableBeanFactory registry = new DefaultListableBeanFactory();
// 创建 XML Bean 定义读取器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
// 从类路径下加载 XML 资源并注册 Bean 定义
reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
}
}
4. BeanFactory 后置处理
在 Bean 定义注册完成后,Spring 会调用 BeanFactoryPostProcessor
接口的实现类。这些后置处理器可以在 Bean 实例化之前对 BeanFactory
进行修改,例如修改 BeanDefinition
的属性。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 可以在这里修改 BeanDefinition
}
}
5. Bean 后置处理
在 Bean 实例化和属性注入之后,Spring 会调用 BeanPostProcessor
接口的实现类。BeanPostProcessor
可以在 Bean 初始化前后进行额外的处理,例如对 Bean 进行代理增强。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之前进行处理
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之后进行处理
return bean;
}
}
6. Bean 实例化与初始化
- 实例化:根据
BeanDefinition
中的信息,使用反射机制创建 Bean 的实例。 - 属性注入:将 Bean 所依赖的其他 Bean 或属性值注入到 Bean 中。可以通过构造函数注入、Setter 方法注入等方式实现。
- 初始化:调用 Bean 的初始化方法,例如实现
InitializingBean
接口的afterPropertiesSet
方法,或者使用@PostConstruct
注解标注的方法。
7. 容器初始化完成
经过上述步骤,Spring IOC 容器完成初始化,此时可以从容器中获取已经创建好的 Bean 并使用。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ContainerInitializationCompletedExample {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 Bean
Object bean = context.getBean("exampleBean");
}
}
综上所述,Spring IOC 容器的初始化是一个多步骤的过程,通过资源定位、加载、注册、后置处理、实例化和初始化等操作,最终创建并管理应用程序中的所有 Bean。
常见配置bean有 XML 配置文件的形式或者注解形式等还有一些其他的方式。
不管哪种方式,spring考虑到扩展性问题,会通过BeanDefinitionReader,
来加载bean的配置信息,
然后生成一个BeanDefinition(bean的定义信息,用来存储 bean 的所有属性方法定义)
BeanDefinitionReader 只是接口约束一些定义信息,常见的实现类
XmlBeanDefinitionReader(xml形式),
PropertiesBeanDefinitionReader(Properties配置文件),
AbstractBeanDefinitionReader (相关一些环境信息)等
BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点,其实就是在bean的实例化之前,可以获取bean的定义信息,以及修改相关信息。
比如说我们现在常见的注解方式来加载bean信息,里面其实就是也是用的BeanFactoryPostProcessor的子类实现的。
常见的 @Service、@Controller、@Repository等注解其实都是组合注解,里面里面都是包含Component注解实现的
因此当我们有自己的业务逻辑实现的时候也只需要实现BeanFactoryPostProcessor就可以了,然后加上@Component注解就可以了。
- 基于 XML 配置:当使用 XML 配置文件时,Spring 会依据配置文件的路径去定位资源。比如,若使用
ClassPathXmlApplicationContext
,它会从类路径下查找指定的 XML 文件。 -
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class XmlBasedExample { public static void main(String[] args) { // 从类路径下定位 applicationContext.xml 文件 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
- 基于注解配置:若采用注解配置,Spring 会扫描指定包及其子包下带有特定注解(如
@Component
、@Service
等)的类。使用AnnotationConfigApplicationContext
时,需要指定配置类。 -
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages = "com.example") class AppConfig {} public class AnnotationBasedExample { public static void main(String[] args) { // 指定配置类,Spring 会扫描该类所在包及其子包 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); } }
2. Bean 定义加载
在完成资源定位后,Spring 会解析这些资源,将其中定义的 Bean 信息加载到内存中,并转化为
BeanDefinition
对象。BeanDefinition
包含了 Bean 的各种元数据,如类名、作用域、依赖关系等。 - XML 解析:对于 XML 配置文件,Spring 会使用
XmlBeanDefinitionReader
来解析 XML 文件,提取其中的<bean>
标签信息,并创建对应的BeanDefinition
对象。 - 注解解析:对于注解配置,Spring 会使用
AnnotatedBeanDefinitionReader
和ClassPathBeanDefinitionScanner
来扫描带有注解的类,根据注解信息创建BeanDefinition
对象。 - 实例化:根据
BeanDefinition
中的信息,使用反射机制创建 Bean 的实例。 - 属性注入:将 Bean 所依赖的其他 Bean 或属性值注入到 Bean 中。可以通过构造函数注入、Setter 方法注入等方式实现。
- 初始化:调用 Bean 的初始化方法,例如实现
InitializingBean
接口的afterPropertiesSet
方法,或者使用@PostConstruct
注解标注的方法。
图解
Spring 创建对象的过程
Spring 创建对象的过程是一个复杂且有序的流程,主要涉及到 Bean 的定义、实例化、属性填充、初始化和销毁等阶段。下面为你详细介绍各个阶段:
1. Bean 定义阶段
在这个阶段,Spring 会读取配置信息(如 XML 文件、Java 注解等),将 Bean 的定义信息加载到内存中,并封装成 BeanDefinition
对象。BeanDefinition
包含了 Bean 的各种元数据,如类名、作用域、依赖关系等。
- 基于 XML 配置:Spring 使用
XmlBeanDefinitionReader
解析 XML 文件,提取<bean>
标签信息创建BeanDefinition
。
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
- 基于注解配置:Spring 使用
AnnotatedBeanDefinitionReader
和ClassPathBeanDefinitionScanner
扫描带有特定注解(如@Component
、@Service
等)的类,根据注解信息创建BeanDefinition
。
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 类的具体实现
}
2. Bean 实例化阶段
根据 BeanDefinition
中的信息,使用反射机制创建 Bean 的实例。Spring 提供了多种实例化方式:
- 构造函数实例化:默认情况下,Spring 使用无参构造函数创建 Bean 实例。如果 Bean 类中没有无参构造函数,Spring 会尝试使用有参构造函数进行实例化,并自动注入所需的依赖。
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 其他方法
}
- 静态工厂方法实例化:通过调用 Bean 类中的静态工厂方法来创建实例。
public class UserServiceFactory {
public static UserService createUserService() {
return new UserService();
}
}
在 XML 配置中指定静态工厂方法:
<bean id="userService" class="com.example.factory.UserServiceFactory" factory-method="createUserService"/>
- 实例工厂方法实例化:通过调用某个工厂 Bean 的实例方法来创建实例。
public class UserServiceFactory {
public UserService createUserService() {
return new UserService();
}
}
在 XML 配置中指定实例工厂方法:
<bean id="userServiceFactory" class="com.example.factory.UserServiceFactory"/>
<bean id="userService" factory-bean="userServiceFactory" factory-method="createUserService"/>
3. Bean 属性填充阶段
在 Bean 实例化完成后,Spring 会将 Bean 所依赖的其他 Bean 或属性值注入到 Bean 中。可以通过构造函数注入、Setter 方法注入等方式实现。
- 构造函数注入:在创建 Bean 实例时,通过构造函数将依赖的 Bean 注入。
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 其他方法
}
- Setter 方法注入:通过调用 Bean 的 Setter 方法将依赖的 Bean 注入。
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// 其他方法
}
在 XML 配置中进行属性注入:
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
4. Bean 初始化阶段
在 Bean 的属性填充完成后,Spring 会进行一系列的初始化操作,包括调用初始化方法、执行 BeanPostProcessor
等。
- 实现
InitializingBean
接口:实现InitializingBean
接口的afterPropertiesSet
方法,该方法会在 Bean 的属性设置完成后被调用。
import org.springframework.beans.factory.InitializingBean;
public class UserService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
// 其他方法
}
- 使用
@PostConstruct
注解:在方法上使用@PostConstruct
注解,该方法会在 Bean 初始化时被调用。
import javax.annotation.PostConstruct;
public class UserService {
@PostConstruct
public void init() {
// 初始化逻辑
}
// 其他方法
}
- 执行
BeanPostProcessor
:BeanPostProcessor
是 Spring 提供的一个扩展接口,允许在 Bean 初始化前后进行额外的处理。实现BeanPostProcessor
接口的类会在所有 Bean 初始化前后被调用。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之前进行处理
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之后进行处理
return bean;
}
}
5. Bean 使用阶段
经过上述步骤,Bean 已经创建并初始化完成,可以被应用程序使用。可以通过 ApplicationContext
或 BeanFactory
的 getBean
方法获取 Bean 实例。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.doSomething();
}
}
6. Bean 销毁阶段
当 Spring 容器关闭时,会对单例 Bean 进行销毁操作。可以通过实现 DisposableBean
接口的 destroy
方法或使用 @PreDestroy
注解来定义销毁逻辑。
- 实现
DisposableBean
接口:
import org.springframework.beans.factory.DisposableBean;
public class UserService implements DisposableBean {
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
// 其他方法
}
- 使用
@PreDestroy
注解:
import javax.annotation.PreDestroy;
public class UserService {
@PreDestroy
public void cleanup() {
// 销毁逻辑
}
// 其他方法
}
综上所述,Spring 创建对象的过程涉及多个阶段,通过这些阶段的有序执行,确保 Bean 能够正确地创建、初始化和销毁。
使用 Spring 到底省略了我们什么工作?
答:new 的过程。把 new 的过程交给第三方来创建、管理,这就是「解耦」。
Spring 也是用的 set() 方法,它只不过提供了一套更加完善的实现机制而已。
而说到底,底层的原理并没有很复杂,只是为了提高扩展性、兼容性,
Spring 提供了丰富的支持,所以才觉得源码比较难。
一分钟带你玩转 Spring IoC
Spring中Bean的作用域
- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。@Scope("singleton")
- prototype : 每次请求都会创建一个新的 bean 实例。原型 @Scope("prototype")
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 @RequestScope
session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。 @SessionScope
全局会话(Global Session)
应用(Application) @ApplicationScope
@Bean
@Scope("singleton") //@Scope声明 Spring Bean 的作用域
public Person personSingleton() {
return new Person();
}
在 Spring 框架里,Bean 的作用域定义了 Bean 实例在 Spring 容器中的生命周期和可见范围。Spring 提供了多种 Bean 作用域,以满足不同的应用场景需求。下面为你详细介绍 Spring 中常见的几种 Bean 作用域:
1. 单例(Singleton)
- 作用域描述:单例是 Spring 中默认的 Bean 作用域。当一个 Bean 的作用域为单例时,在整个 Spring 容器的生命周期里,只会创建该 Bean 的一个实例。所有对这个 Bean 的请求都会返回同一个实例。
- 使用场景:适用于无状态的 Bean,像服务层、数据访问层的 Bean 通常可设为单例,因为它们不保存特定于某个请求的状态信息。
- 示例代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
// 定义一个简单的 Bean 类
class MySingletonBean {
public MySingletonBean() {
System.out.println("MySingletonBean 实例已创建");
}
}
public class SingletonScopeExample {
public static void main(String[] args) {
// 加载 Spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 Bean 实例
MySingletonBean bean1 = context.getBean(MySingletonBean.class);
MySingletonBean bean2 = context.getBean(MySingletonBean.class);
// 比较两个 Bean 实例是否相同
System.out.println("bean1 和 bean2 是否为同一个实例: " + (bean1 == bean2));
}
}
在 XML 配置文件中配置 Bean:
xml
<bean id="mySingletonBean" class="com.example.MySingletonBean" scope="singleton"/>
或者使用注解配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MySingletonBean mySingletonBean() {
return new MySingletonBean();
}
}
2. 原型(Prototype)
- 作用域描述:当一个 Bean 的作用域为原型时,每次从 Spring 容器中请求该 Bean 时,容器都会创建一个新的实例。也就是说,对该 Bean 的每个请求都会返回一个不同的实例。
- 使用场景:适用于有状态的 Bean,例如保存用户会话信息的 Bean,因为每个用户的会话信息是不同的,需要为每个用户创建一个新的实例。
- 示例代码:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
// 定义一个简单的 Bean 类
class MyPrototypeBean {
public MyPrototypeBean() {
System.out.println("MyPrototypeBean 实例已创建");
}
}
public class PrototypeScopeExample {
public static void main(String[] args) {
// 加载 Spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 Bean 实例
MyPrototypeBean bean1 = context.getBean(MyPrototypeBean.class);
MyPrototypeBean bean2 = context.getBean(MyPrototypeBean.class);
// 比较两个 Bean 实例是否相同
System.out.println("bean1 和 bean2 是否为同一个实例: " + (bean1 == bean2));
}
}
在 XML 配置文件中配置 Bean:
<bean id="myPrototypeBean" class="com.example.MyPrototypeBean" scope="prototype"/>
或者使用注解配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public MyPrototypeBean myPrototypeBean() {
return new MyPrototypeBean();
}
}
3. 请求(Request)
- 作用域描述:该作用域仅适用于 Web 应用的 Spring 环境。当一个 Bean 的作用域为请求时,在一次 HTTP 请求的生命周期内,只会创建该 Bean 的一个实例。不同的 HTTP 请求会创建不同的实例。
- 使用场景:适用于需要在一次请求中保存状态信息的 Bean,例如处理用户请求参数的 Bean。
- 示例代码:
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
// 定义一个请求作用域的 Bean
@Component
@RequestScope
public class MyRequestBean {
// Bean 的具体实现
}
在控制器中使用该 Bean:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
private MyRequestBean myRequestBean;
@GetMapping("/test")
public String test() {
// 使用 MyRequestBean 处理请求
return "Request processed";
}
}
4. 会话(Session)
- 作用域描述:同样仅适用于 Web 应用的 Spring 环境。当一个 Bean 的作用域为会话时,在一个用户的会话生命周期内,只会创建该 Bean 的一个实例。不同用户的会话会创建不同的实例。
- 使用场景:适用于需要在用户会话期间保存状态信息的 Bean,例如保存用户登录信息的 Bean。
- 示例代码:
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;
// 定义一个会话作用域的 Bean
@Component
@SessionScope
public class MySessionBean {
// Bean 的具体实现
}
在控制器中使用该 Bean:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MySessionController {
@Autowired
private MySessionBean mySessionBean;
@GetMapping("/session-test")
public String sessionTest() {
// 使用 MySessionBean 处理会话信息
return "Session processed";
}
}
5. 全局会话(Global Session)
- 作用域描述:该作用域主要用于 Portlet 应用环境(Portlet 是一种可插拔的 Web 组件)。在全局会话中,整个 Portlet 应用的生命周期内,只会创建该 Bean 的一个实例。
- 使用场景:适用于 Portlet 应用中需要在全局会话中共享状态信息的 Bean。
6. 应用(Application)
- 作用域描述:在 Servlet 上下文的生命周期内,只会创建该 Bean 的一个实例。类似于单例作用域,但单例作用域是针对 Spring 容器,而应用作用域是针对 Servlet 上下文。
- 使用场景:适用于需要在整个 Web 应用中共享状态信息的 Bean,例如应用级别的配置信息。
- 示例代码:
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
// 定义一个应用作用域的 Bean
@Component
@ApplicationScope
public class MyApplicationBean {
// Bean 的具体实现
}
综上所述,Spring 提供的这些 Bean 作用域可以满足不同场景下对 Bean 生命周期和可见范围的需求,开发者可根据具体业务需求选择合适的作用域。
在 Bean 注入中涉及多种设计模式。
首先是工厂模式。简单工厂模式中,通过一个工厂类来创建 Bean 对象,根据传入的参数决定创建哪种具体的 Bean。工厂方法模式则是在抽象工厂类中定义创建 Bean 的抽象方法,由具体的工厂子类来实现,这样可以更灵活地创建不同类型的 Bean。抽象工厂模式可创建一系列相关的 Bean 对象,用于复杂的依赖关系。这些工厂模式将对象的创建和使用分离,方便统一管理和控制 Bean 的生成。
其次是单例模式。Spring 容器中的单例 Bean 采用了单例模式的思想,在整个容器的生命周期内,对于单例 Bean 只会创建一次。通过容器来管理单例 Bean 的生命周期,既保证了资源的有效利用,又方便了对单例 Bean 的访问和维护。
依赖注入模式本身也是一种设计模式。它将 Bean 之间的依赖关系从硬编码转变为通过容器进行管理。通过构造函数注入、 setter 注入和字段注入等方式,把一个 Bean 需要的依赖对象注入进去,使 Bean 之间的依赖关系更加清晰、可维护。
代理模式也在 Bean 注入中有应用。在 AOP(Aspect - J Auto Configuration)场景下,当需要为 Bean 添加额外的功能(如事务管理、日志记录)时,会使用代理模式。Spring 通过创建代理 Bean 来代替原 Bean 执行相关操作,在不改变原 Bean 代码的基础上实现功能扩展,其中 JDK 代理和 CGLIB 代理是常用的代理方式。
装饰器模式也存在于 Bean 注入。当需要对现有 Bean 的功能进行修饰和扩展时,可采用装饰器模式。通过创建装饰器类,在装饰器类中包含被装饰的 Bean,然后在装饰器类中对原 Bean 的功能进行补充或修改,这种模式可以灵活地改变 Bean 的功能特性。
单例 bean 的线程安全
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。
单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,
对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
- 在Bean定义局部对象,尽量避免定义可变的成员变量。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
1. 问题根源
- 单例 Bean 的特性:Spring 容器中仅存在一个实例,所有线程共享该实例。
- 线程安全隐患:如果 Bean 中存在可变状态(如成员变量),多个线程并发访问时可能导致数据不一致。
2. 解决方案
方案一:设计无状态 Bean
- 核心思想:Bean 不保存任何状态(即没有成员变量)。
- 示例:
@Component public class StatelessService { public void process() { // 仅依赖局部变量或方法参数,无共享状态 } }
- 优点:天然线程安全,无需额外处理。
- 适用场景:服务层、DAO 层等无状态组件。
方案二:使用线程安全的类
- 核心思想:依赖线程安全的类(如
ConcurrentHashMap
、AtomicInteger
)存储状态。 - 示例:
@Component public class ThreadSafeCounter { private final AtomicInteger count = new AtomicInteger(0); public int increment() { return count.incrementAndGet(); } }
- 优点:利用 JDK 提供的线程安全机制,避免同步开销。
方案三:同步方法或代码块
- 核心思想:通过
synchronized
关键字或ReentrantLock
保证线程安全。 - 示例:
@Component public class SynchronizedService { private int count = 0; public synchronized void increment() { count++; } }
- 缺点:可能成为性能瓶颈,需谨慎使用。
方案四:使用 ThreadLocal
- 核心思想:为每个线程隔离状态。
- 示例:
@Component public class ThreadLocalService { private final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); public void increment() { threadLocal.set(threadLocal.get() + 1); } }
- 适用场景:需要为每个线程独立保存状态的场景。
3. 最佳实践
- 优先设计无状态 Bean:这是最简单且高效的方式。
- 避免共享可变状态:如果必须使用状态,优先选择线程安全类或同步机制。
- 谨慎使用作用域:若无法保证线程安全,可考虑将 Bean 作用域改为
prototype
(每次请求创建新实例),但会增加资源消耗。
4. 总结
- 单例 Bean 本身并不必然不安全,只有存在可变状态时才需处理线程安全。
- Spring 框架不强制要求单例 Bean 线程安全,需开发者根据业务逻辑合理设计。
Spring创建对象的过程中,如何解决循环依赖问题
在 Spring 创建对象的过程中,循环依赖指的是两个或多个 Bean 之间相互依赖,形成一个循环引用的情况。Spring 主要通过三级缓存机制来解决单例 Bean 的循环依赖问题,下面为你详细介绍。
循环依赖的类型
- 构造器循环依赖:通过构造函数注入形成的循环依赖。例如,
A
的构造函数需要B
的实例,而B
的构造函数又需要A
的实例,这种情况 Spring 无法解决,会抛出BeanCurrentlyInCreationException
异常。 - Setter 循环依赖:通过 Setter 方法注入形成的循环依赖。Spring 可以通过三级缓存机制解决这种类型的循环依赖。
三级缓存机制
Spring 的三级缓存分别是:
- 一级缓存(singletonObjects):单例对象缓存,存储已经创建好的单例 Bean 实例。
- 二级缓存(singletonFactories):单例工厂缓存,存储创建单例 Bean 的工厂对象。
- 三级缓存(earlySingletonObjects):提前曝光的单例对象缓存,存储提前曝光的、还未完全初始化的单例 Bean 实例。
解决循环依赖的过程
假设存在
A
和B
两个 Bean,A
依赖B
,B
依赖A
,以下是 Spring 解决它们之间循环依赖的详细过程:1. 创建
A
Bean
- 实例化
A
:Spring 开始创建A
Bean,首先调用A
的构造函数实例化A
,此时A
是一个未完全初始化的对象。- 将
A
的工厂对象放入三级缓存:创建一个ObjectFactory
工厂对象,该工厂对象可以返回A
的早期引用(即未完全初始化的A
对象),并将其放入三级缓存singletonFactories
中。- 填充
A
的属性:在填充A
的属性时,发现A
依赖B
,于是开始创建B
Bean。2. 创建
B
Bean
- 实例化
B
:Spring 调用B
的构造函数实例化B
,同样B
是一个未完全初始化的对象。- 将
B
的工厂对象放入三级缓存:创建一个ObjectFactory
工厂对象,该工厂对象可以返回B
的早期引用,并将其放入三级缓存singletonFactories
中。- 填充
B
的属性:在填充B
的属性时,发现B
依赖A
,于是从缓存中查找A
。3. 从缓存中获取
A
- 检查一级缓存:发现一级缓存
singletonObjects
中没有A
。- 检查二级缓存:发现二级缓存
earlySingletonObjects
中也没有A
。- 检查三级缓存:从三级缓存
singletonFactories
中获取A
的工厂对象,调用该工厂对象的getObject()
方法得到A
的早期引用,将A
的早期引用放入二级缓存earlySingletonObjects
中,并从三级缓存singletonFactories
中移除A
的工厂对象。- 将
A
的早期引用注入到B
中:将A
的早期引用注入到B
的属性中,完成B
的属性填充。4. 完成
B
Bean 的创建
- 初始化
B
:调用B
的初始化方法,完成B
的初始化。- 将
B
放入一级缓存:将完全初始化好的B
实例放入一级缓存singletonObjects
中,并从二级缓存earlySingletonObjects
中移除B
。5. 继续完成
A
Bean 的创建
- 将
B
注入到A
中:从一级缓存singletonObjects
中获取完全初始化好的B
实例,将其注入到A
的属性中,完成A
的属性填充。- 初始化
A
:调用A
的初始化方法,完成A
的初始化。- 将
A
放入一级缓存:将完全初始化好的A
实例放入一级缓存singletonObjects
中,并从二级缓存earlySingletonObjects
中移除A
。
代码示例
以下是一个简单的代码示例,演示了 A
和 B
之间的循环依赖:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class A {
@Autowired
private B b;
public B getB() {
return b;
}
}
@Component
public class B {
@Autowired
private A a;
public A getA() {
return a;
}
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "com.example")
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
A a = context.getBean(A.class);
B b = context.getBean(B.class);
System.out.println("A 依赖的 B: " + a.getB());
System.out.println("B 依赖的 A: " + b.getA());
}
}
在上述代码中,A
和 B
通过 @Autowired
注解实现了 Setter 注入的循环依赖,Spring 会使用三级缓存机制解决这个循环依赖问题。
总结
Spring 的三级缓存机制通过提前曝光未完全初始化的 Bean 实例,解决了单例 Bean 的 Setter 循环依赖问题。但对于构造器循环依赖,Spring 无法解决,需要开发者通过调整代码结构来避免。
自己理解
解决循环依赖,一定是单默认的单例Bean中,属性互相引用的场景。
原型(Prototype)的场景是不支持循环依赖的,通常会走到AbstractBeanFactory类中下面的判断,抛出异常。
首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。
在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:
- singletonObjects 俗称“单例池”,容器,缓存创建完成单例Bean的地方。
- singletonFactories 映射创建Bean的原始工厂
- earlySingletonObjects 映射Bean的早期引用,
也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,
只是一个Instance.后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。
spring 使用三级缓存去解决循环依赖的,
其「核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中」,供另一个对象调用
「第一级缓存」:用来保存实例化、初始化都完成的对象
「第二级缓存」:用来保存实例化完成,但是未初始化完成的对象
「第三级缓存」:用来保存一个对象工厂,提供一个匿名内部类,
用于创建二级缓存中的对象
假设只设计二级缓存能否解决循环依赖?
只用二级缓存是可以解决缓存依赖的,(废弃第三级,保留第一第二)
但是会有一个问题,在配置AOP切面的时候会出错,因为无法生成代理对象。
所以三级缓存是为了处理AOP中的循环依赖。因为当配置了切面之后,在getEarlyBeanReference方法中,有可能会把之前的原始对象替换成代理对象,导致Bean的版本不是最终的版本,所以报错。
什么情况下循环依赖可以被处理?
在回答这个问题之前首先要明确一点,Spring解决循环依赖是有前置条件的
- 出现循环依赖的Bean必须要是单例
- 依赖注入的方式不能全是构造器注入的方式(很多博客上说,
只能解决setter方法的循环依赖,这是错误的)-》@lazy注解
Spring Boot 2.6及之后版本取消了循环依赖的支持
Bean的初始化和销毁阶段有哪些扩展点?
在 Spring 中,Bean 的初始化和销毁阶段提供了多个扩展点,允许开发者在特定时机执行自定义逻辑。下面将分别介绍初始化和销毁阶段的扩展点。
初始化阶段扩展点
1. 实现 InitializingBean
接口
- 原理:
InitializingBean
接口定义了afterPropertiesSet()
方法,Spring 容器在 Bean 的属性设置完成后会自动调用该方法。 - 示例代码:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class MyBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// 在这里编写初始化逻辑
System.out.println("MyBean 初始化完成,执行 afterPropertiesSet 方法");
}
}
2. 使用 @PostConstruct
注解
- 原理:
@PostConstruct
是 JSR-250 规范定义的注解,Spring 支持该注解。被@PostConstruct
注解标注的方法会在 Bean 实例化和属性注入完成后,在InitializingBean.afterPropertiesSet()
方法之前执行。 - 示例代码:
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class MyBeanWithPostConstruct {
@PostConstruct
public void init() {
// 在这里编写初始化逻辑
System.out.println("MyBeanWithPostConstruct 执行 @PostConstruct 注解标注的方法");
}
}
3. 自定义初始化方法
- 原理:在 XML 配置或 Java 配置中指定 Bean 的初始化方法,Spring 容器会在 Bean 的属性设置完成后调用该方法。
- XML 配置示例:
<bean id="myBean" class="com.example.MyBean" init-method="customInit"/>
public class MyBean {
public void customInit() {
// 在这里编写初始化逻辑
System.out.println("MyBean 执行自定义初始化方法 customInit");
}
}
- Java 配置示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(initMethod = "customInit")
public MyBean myBean() {
return new MyBean();
}
}
4. BeanPostProcessor
接口
- 原理:
BeanPostProcessor
是 Spring 提供的一个强大的扩展接口,它允许开发者在 Bean 初始化前后进行额外的处理。BeanPostProcessor
接口定义了两个方法:postProcessBeforeInitialization()
和postProcessAfterInitialization()
,分别在 Bean 初始化之前和之后被调用。 - 示例代码:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之前执行的逻辑
System.out.println("Before initializing bean: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 在 Bean 初始化之后执行的逻辑
System.out.println("After initializing bean: " + beanName);
return bean;
}
}
销毁阶段扩展点
1. 实现 DisposableBean
接口
- 原理:
DisposableBean
接口定义了destroy()
方法,Spring 容器在销毁 Bean 时会自动调用该方法。 - 示例代码:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class MyDisposableBean implements DisposableBean {
@Override
public void destroy() throws Exception {
// 在这里编写销毁逻辑
System.out.println("MyDisposableBean 执行 destroy 方法");
}
}
2. 使用 @PreDestroy
注解
- 原理:
@PreDestroy
是 JSR-250 规范定义的注解,Spring 支持该注解。被@PreDestroy
注解标注的方法会在 Bean 销毁之前执行。 - 示例代码:
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class MyBeanWithPreDestroy {
@PreDestroy
public void cleanup() {
// 在这里编写销毁逻辑
System.out.println("MyBeanWithPreDestroy 执行 @PreDestroy 注解标注的方法");
}
}
3. 自定义销毁方法
- 原理:在 XML 配置或 Java 配置中指定 Bean 的销毁方法,Spring 容器会在销毁 Bean 时调用该方法。
- XML 配置示例:
<bean id="myBean" class="com.example.MyBean" destroy-method="customDestroy"/>
public class MyBean {
public void customDestroy() {
// 在这里编写销毁逻辑
System.out.println("MyBean 执行自定义销毁方法 customDestroy");
}
}
- Java 配置示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(destroyMethod = "customDestroy")
public MyBean myBean() {
return new MyBean();
}
}
执行顺序总结
- 初始化阶段:
@PostConstruct
注解标注的方法 ->InitializingBean.afterPropertiesSet()
方法 -> 自定义初始化方法 ->BeanPostProcessor.postProcessAfterInitialization()
方法 - 销毁阶段:
@PreDestroy
注解标注的方法 ->DisposableBean.destroy()
方法 -> 自定义销毁方法
通过这些扩展点,开发者可以灵活地控制 Bean 的初始化和销毁过程,实现自定义的业务逻辑。
@Component 和 @Bean 的区别是什么?
在 Spring 框架里,
@Component
和@Bean
都能用于将对象纳入 Spring 容器的管理,但它们在使用场景、使用方式、作用目标等方面存在差异,下面为你详细介绍:
1. 使用场景
@Component
- 主要用于类级别,是一个通用的组件注解。当一个类被
@Component
注解标注时,Spring 会自动扫描并将其作为一个 Bean 注册到容器中。- 适用于那些可以自动发现并注册的组件,如服务层(
@Service
是@Component
的派生注解)、数据访问层(@Repository
是@Component
的派生注解)、控制器层(@Controller
是@Component
的派生注解)等。@Bean
- 通常用于方法级别,用于告诉 Spring 这个方法会返回一个 Bean 对象,需要将其注册到 Spring 容器中。
- 适用于以下场景:
- 当需要引入第三方库中的类作为 Bean 时,由于无法直接在第三方类上添加
@Component
注解,就可以使用@Bean
方法将其注册到 Spring 容器中。- 当创建 Bean 的过程比较复杂,需要在方法中编写额外的逻辑时,使用
@Bean
方法可以灵活控制 Bean 的创建过程。
2. 使用方式
@Component
- 直接在类上添加
@Component
注解,同时需要确保 Spring 的组件扫描功能开启。一般会在配置类上使用@ComponentScan
注解指定要扫描的包路径。
import org.springframework.stereotype.Component;
// 使用 @Component 注解标注类
@Component
public class MyComponent {
public void doSomething() {
System.out.println("Doing something...");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
// 开启组件扫描
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类的其他内容
}
@Bean
- 在配置类的方法上添加
@Bean
注解,该方法返回一个 Bean 对象。配置类需要使用@Configuration
注解标注。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 使用 @Configuration 注解标注配置类
@Configuration
public class AppConfig {
// 使用 @Bean 注解标注方法
@Bean
public MyBean myBean() {
return new MyBean();
}
}
class MyBean {
public void doSomething() {
System.out.println("MyBean is doing something...");
}
}
3. 作用目标
@Component
- 作用于类,表明这个类是一个 Spring 组件,Spring 会自动创建该类的实例并注册到容器中。
@Bean
- 作用于方法,通过调用该方法返回的对象会被注册为 Spring Bean。可以在方法中对 Bean 的创建过程进行定制,如设置 Bean 的属性、调用初始化方法等。
4. 灵活性
@Component
- 相对来说灵活性较低,因为它只是简单地将类标记为组件,Spring 会按照默认的方式创建和管理 Bean。对于 Bean 的创建过程和属性设置,开发者无法进行太多的控制。
@Bean
- 灵活性较高,开发者可以在
@Bean
方法中编写任意的逻辑来创建和配置 Bean。例如,可以根据不同的条件创建不同类型的 Bean,或者在创建 Bean 之前进行一些初始化操作。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
MyBean bean = new MyBean();
// 可以在这里对 Bean 进行额外的配置
bean.setProperty("value");
return bean;
}
}
5. 生命周期管理
@Component
- Spring 会自动管理被
@Component
注解标注的 Bean 的生命周期,包括创建、初始化和销毁。开发者可以通过实现InitializingBean
、DisposableBean
接口或使用@PostConstruct
、@PreDestroy
注解来定制 Bean 的初始化和销毁逻辑。@Bean
- 同样可以使用
@PostConstruct
、@PreDestroy
注解来定制 Bean 的初始化和销毁逻辑。此外,还可以在@Bean
注解中指定initMethod
和destroyMethod
属性来指定自定义的初始化和销毁方法。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public MyBean myBean() {
return new MyBean();
}
}
class MyBean {
public void init() {
System.out.println("MyBean is initializing...");
}
public void destroy() {
System.out.println("MyBean is destroying...");
}
}
综上所述,@Component
和 @Bean
各有其适用场景,开发者可以根据具体需求选择合适的方式来将对象注册到 Spring 容器中。
作用对象不同:
@Component 注解作用于类,
而@Bean注解作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中,
(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。
@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,
@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
@Bean 注解比 Component 注解的自定义性更强,
而且很多地方我们只能通过 @Bean 注解来注册bean。
比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
@Resource VS AutoWired bean的注入
@Resource
和@Autowired
都可用于在 Spring 框架里进行 Bean 的依赖注入,不过它们在来源、注入方式、属性等方面存在差异,下面为你详细介绍:
来源
@Autowired
:它是 Spring 框架自身定义的注解,专门用于实现依赖注入。@Resource
:属于 JSR-250 规范,是 Java 标准注解,这意味着它并非 Spring 特有,在其他支持 JSR-250 规范的框架中也能使用。
注入方式
@Autowired
:默认采用按类型(byType)注入的方式。也就是说,Spring 会依据被注入属性的类型,在容器中查找匹配的 Bean 进行注入。若容器中存在多个相同类型的 Bean,会抛出NoUniqueBeanDefinitionException
异常。不过,你可以结合@Qualifier
注解按名称(byName)来指定要注入的 Bean。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
class MyService {
// 使用 @Autowired 按类型注入
@Autowired
private MyRepository myRepository;
// 结合 @Qualifier 按名称注入
@Autowired
@Qualifier("specificRepository")
private MyRepository specificRepository;
}
@Resource
:默认按名称(byName)注入。它会先根据属性名在容器中查找同名的 Bean 进行注入。若找不到同名的 Bean,再尝试按类型(byType)注入。
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
class MyService {
// 使用 @Resource 按名称注入
@Resource
private MyRepository myRepository;
// 手动指定名称注入
@Resource(name = "specificRepository")
private MyRepository specificRepository;
}
属性
@Autowired
:它有一个required
属性,其默认值为true
,这表明注入的 Bean 必须存在,若不存在会抛出异常。若将required
属性设置为false
,当容器中不存在匹配的 Bean 时,不会抛出异常,而是将该属性值设为null
。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
class MyService {
// 设置 required 为 false
@Autowired(required = false)
private MyRepository myRepository;
}
@Resource
:具备name
和type
等属性。name
属性用于指定要注入的 Bean 的名称,type
属性用于指定要注入的 Bean 的类型。
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
class MyService {
// 指定 name 属性
@Resource(name = "myRepository")
private MyRepository myRepository;
// 指定 type 属性
@Resource(type = MyRepository.class)
private MyRepository anotherRepository;
}
依赖
@Autowired
:使用时需要依赖 Spring 框架,若项目中没有引入 Spring 相关依赖,该注解将无法使用。@Resource
:由于它是 Java 标准注解,所以不需要额外依赖 Spring 框架,只要 Java 运行环境支持 JSR-250 规范即可使用。
使用建议
- 若你的项目仅使用 Spring 框架,且注重 Spring 自身的特性和功能,推荐使用
@Autowired
注解。 - 若项目需要遵循 Java 标准规范,或者可能会在不同框架间进行迁移,使用
@Resource
注解会更合适。
综上所述,@Autowired
和 @Resource
都能实现 Bean 的依赖注入,开发者可根据项目需求和具体场景选择合适的注解。
共同点:@Resource和@Autowired都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用。
不同点:@Resource是Java自己的注解,
@Resource有两个属性是比较重要的,分是name和type;
Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。
所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
@Autowired是spring的注解,是spring2.5版本引入的,Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。
Autowired能够用在:构造器、方法、参数、成员变量和注解上,而
@Resource能用用在:类、成员变量和方法。
https://zhuanlan.zhihu.com/p/425124701
Bean生命周期
给学妹看的SpringIOC 面试题(上)
ApplicationContext.refresh()**这其中的包含了13个子方法
BeanFactory
从名字上也很好理解,生产 bean 的工厂,
它负责生产和管理各个 bean 实例。
同时也是Spring容器暴露在外获取bean的入口。
BeanFactory的生产过程其实是利用反射机制实现的。
BeanFactory
是 Spring 框架中 IOC(控制反转)容器的核心接口,它负责创建、管理和维护 Bean 对象。以下从基本概念、主要实现类、工作原理、使用场景几个方面详细介绍。
基本概念
在 Spring 里,Bean 指的是由 Spring 容器管理的对象。BeanFactory
作为 Spring 最基础的容器,提供了创建和获取 Bean 的基本功能,它定义了一套标准的 Bean 管理机制,使得开发者能够以统一的方式来管理应用中的对象。
主要实现类
XmlBeanFactory
:这是早期的一个实现类,主要用于从 XML 配置文件中读取 Bean 的定义信息。不过在 Spring 3.1 版本之后已被弃用。示例代码如下:
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
// 注意:此方式已不推荐使用
@SuppressWarnings("deprecation")
public class XmlBeanFactoryExample {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Object bean = factory.getBean("exampleBean");
}
}
DefaultListableBeanFactory
:它是BeanFactory
接口的一个重要实现类,是一个功能完整的 Bean 工厂,支持多种 Bean 定义方式,如 XML、注解等。同时,它也是ApplicationContext
内部使用的基础 Bean 工厂。ApplicationContext
:虽然ApplicationContext
是BeanFactory
的子接口,但它在BeanFactory
的基础上进行了扩展,提供了更多的企业级特性,如国际化支持、事件发布等。常见的实现类有ClassPathXmlApplicationContext
、FileSystemXmlApplicationContext
、AnnotationConfigApplicationContext
等。
工作原理
BeanFactory
的工作原理主要包含以下几个关键步骤:
- 资源定位:确定从哪里获取 Bean 的定义信息,比如 XML 文件、Java 注解等。
- Bean 定义加载:解析资源,将 Bean 的定义信息加载到内存中,转化为
BeanDefinition
对象,该对象包含了 Bean 的类名、作用域、依赖关系等元数据。 - Bean 注册:把
BeanDefinition
对象注册到BeanDefinitionRegistry
中,BeanDefinitionRegistry
是一个存储BeanDefinition
的注册表。 - Bean 创建与获取:当调用
getBean
方法获取 Bean 时,BeanFactory
会根据BeanDefinition
信息创建 Bean 实例,并进行属性注入和初始化操作。
使用场景
- 简单应用场景:对于一些简单的 Java 应用,只需要基本的 Bean 管理功能时,可以直接使用
DefaultListableBeanFactory
。示例代码如下:
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
public class DefaultListableBeanFactoryExample {
public static void main(String[] args) {
// 创建 DefaultListableBeanFactory 实例
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 创建 XML Bean 定义读取器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// 从类路径下加载 XML 资源并注册 Bean 定义
reader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
// 获取 Bean
Object bean = factory.getBean("exampleBean");
}
}
- 企业级应用场景:在企业级应用中,通常会使用
ApplicationContext
,因为它提供了更多的高级特性,如 AOP 支持、事件发布、国际化等。示例代码如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ApplicationContextExample {
public static void main(String[] args) {
// 从类路径下加载 XML 配置文件创建 ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 Bean
Object bean = context.getBean("exampleBean");
}
}
与 ApplicationContext
的比较
- 功能丰富度:
BeanFactory
是 Spring 最基础的容器,提供了基本的 Bean 管理功能;而ApplicationContext
在BeanFactory
的基础上进行了扩展,提供了更多的企业级特性。 - 加载时机:
BeanFactory
采用懒加载机制,只有在调用getBean
方法时才会创建 Bean 实例;而ApplicationContext
在容器启动时就会创建所有的单例 Bean 实例。 - 使用场景:
BeanFactory
适用于资源受限的环境或对性能要求较高的场景;ApplicationContext
适用于大多数企业级应用场景。
BeanFactory和ApplicationContext的区别
BeanFactory是一个底层的IOC容器,
而ApplicationContext是在其基础上增加了一些它的特性的,
同时增加了一些其他的整合特性比如:更好的整合SpringAOP、
国际化消息、以及事务的发布、资源访问等这些新的特性
BeanFactory 与 FactoryBean的区别
「BeanFactory 是 IOC 容器」,是用来承载对象的
「FactoryBean 是一个接口」,为 Bean 提供了更加灵活的方式,
通过代理一个Bean对象,对方法前后做一些操作。
BeanFactory
和 FactoryBean
是 Spring 框架中两个不同的概念,它们在功能和用途上有着明显的区别,以下为你详细介绍:
概念与定位
- BeanFactory
BeanFactory
是 Spring 框架中 IOC 容器的核心接口,它定义了 Spring 容器的基本功能,负责创建、管理和维护 Bean 对象。BeanFactory
是 Spring 最基础的容器,为上层应用提供了统一的 Bean 管理机制。- 简单来说,
BeanFactory
是一个工厂,负责生产和管理各种 Bean 实例,是整个 Spring IOC 容器体系的基础。
- FactoryBean
FactoryBean
是一个特殊的 Bean,它本身是一个工厂,可以用来创建其他 Bean 实例。FactoryBean
是 Spring 提供的一种扩展机制,允许开发者自定义 Bean 的创建逻辑。- 当一个类实现了
FactoryBean
接口时,Spring 会将其作为一个工厂 Bean 来处理,通过该工厂 Bean 可以创建出其他类型的 Bean 实例。
接口定义与使用方式
- BeanFactory
BeanFactory
接口定义了一系列方法,如getBean
用于获取 Bean 实例,containsBean
用于判断容器中是否包含某个 Bean 等。- 使用
BeanFactory
时,通常通过ApplicationContext
(ApplicationContext
是BeanFactory
的子接口,功能更丰富)来获取 Bean 实例,示例代码如下:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanFactoryExample {
public static void main(String[] args) {
// 创建 ApplicationContext 实例
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取 Bean 实例
Object bean = context.getBean("exampleBean");
}
}
- FactoryBean
FactoryBean
接口定义了三个方法:getObject()
用于返回由该工厂创建的 Bean 实例;getObjectType()
用于返回所创建 Bean 的类型;isSingleton()
用于指示所创建的 Bean 是否为单例。- 实现
FactoryBean
接口的类需要重写这些方法,示例代码如下:
import org.springframework.beans.factory.FactoryBean;
// 自定义 FactoryBean
public class MyFactoryBean implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
// 自定义 Bean 的创建逻辑
return new MyBean();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
class MyBean {
// Bean 类的定义
}
在配置文件中配置该 FactoryBean
:
<bean id="myFactoryBean" class="com.example.MyFactoryBean"/>
获取 FactoryBean
创建的 Bean 实例:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FactoryBeanExample {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取 FactoryBean 创建的 Bean 实例
MyBean myBean = (MyBean) context.getBean("myFactoryBean");
}
}
应用场景
- BeanFactory
- 适用于各种需要使用 Spring IOC 容器的场景,是 Spring 应用中最基础的 Bean 管理工具。无论是简单的 Java 应用还是复杂的企业级应用,都可以使用
BeanFactory
来管理 Bean 对象。 - 例如,在一个简单的 Java Web 应用中,可以使用
BeanFactory
来管理业务逻辑层和数据访问层的 Bean 实例,实现对象的解耦和统一管理。
- 适用于各种需要使用 Spring IOC 容器的场景,是 Spring 应用中最基础的 Bean 管理工具。无论是简单的 Java 应用还是复杂的企业级应用,都可以使用
- FactoryBean
- 适用于需要自定义 Bean 创建逻辑的场景。当创建 Bean 的过程比较复杂,或者需要根据不同的条件创建不同类型的 Bean 时,可以使用
FactoryBean
来封装复杂的创建逻辑。 - 例如,在创建数据源 Bean 时,可能需要根据不同的配置信息创建不同类型的数据源(如 MySQL 数据源、Oracle 数据源等),这时可以使用
FactoryBean
来封装数据源的创建逻辑。
- 适用于需要自定义 Bean 创建逻辑的场景。当创建 Bean 的过程比较复杂,或者需要根据不同的条件创建不同类型的 Bean 时,可以使用
总结
BeanFactory
是 Spring IOC 容器的核心接口,负责管理和创建各种 Bean 实例,是整个 Spring 容器体系的基础。FactoryBean
是一个特殊的 Bean,它本身是一个工厂,用于自定义 Bean 的创建逻辑,为开发者提供了更灵活的 Bean 创建方式。
欢迎收藏、点赞、一起研究