1. Spring Bean 基础概念
1.1 什么是Spring Bean
Spring Bean是Spring框架中的核心概念,它是由Spring IoC容器实例化、组装和管理的对象。Spring容器负责创建Bean,并通过依赖注入(DI)处理Bean之间的依赖关系。
1.2 Bean定义方式
Spring框架提供了多种方式来定义Bean:
基于注解:
@Component及其衍生注解(@Service, @Repository, @Controller)
@Bean注解(通常在@Configuration类中使用)
基于XML配置:
基于Java配置:
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
UserServiceImpl service = new UserServiceImpl();
service.setUserRepository(userRepository());
return service;
}
}
2. Spring Bean 作用域(Scope)
2.1 常用作用域
Spring框架支持以下几种常用的Bean作用域:
作用域 描述 使用场景 singleton默认作用域,每个 Spring IoC 容器只创建一个 Bean 实例无状态的服务类、工具类 prototype每次请求都会创建一个新的 Bean 实例 有状态的对象 request 每个 HTTP 请求创建一个 Bean 实例,仅在 Web 应用中有效处理 HTTP 请求的对象 session 每个 HTTP 会话创建一个 Bean 实例,仅在 Web 应用中有效用户会话相关的对象 application在 ServletContext 的生命周期内创建一个 Bean 实例,仅在 Web 应用中有效应用级别的配置对象 websocket在 WebSocket 的生命周期内创建一个 Bean 实例,仅在 Web 应用中有效WebSocket 连接相关的对象
// 使用注解设置作用域
@Component
@Scope("prototype")
public class PrototypeBean {
// ...
}
// 使用@Bean注解设置作用域
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public MyBean myBean() {
return new MyBean();
}
}
// XML配置中设置作用域
3. Bean的初始化与销毁
3.1 单例Bean的初始化时机
3.1.1 默认初始化机制
默认情况下,单例Bean是在Spring容器启动时实例化的,具体过程如下:
Spring容器启动
读取Bean定义信息
实例化单例Bean(调用构造函数)
设置Bean属性值(依赖注入)
调用Bean的初始化方法
将完全初始化好的Bean存储在IoC容器的单例缓存中
这种预初始化的机制确保了当应用程序需要使用Bean时,它已经准备好并可以立即使用,从而提高了性能。
3.1.2 延迟初始化(@Lazy注解)
当Bean上添加@Lazy注解时,Spring容器不会在启动时立即创建该Bean,而是在首次请求该Bean时才进行实例化。
@Component
@Lazy
public class LazyBean {
public LazyBean() {
System.out.println("LazyBean is being created");
}
}
延迟初始化的工作原理:
Spring容器启动时,不会立即创建标记为@Lazy的Bean
容器会创建这些Bean的代理对象
当代码首次请求该Bean时,代理会触发实际Bean的创建
创建完成后,将Bean存储在IoC容器中
使用@Lazy的优势:
减少应用启动时间
节省系统资源,特别是对于那些创建成本高但使用频率低的Bean
3.2 Bean的生命周期回调方法
Spring提供了多种方式来定义Bean的初始化和销毁方法:
3.2.1 使用@PostConstruct和@PreDestroy注解
@Component
public class LifecycleBean {
@PostConstruct
public void init() {
System.out.println("Bean is going through init");
}
@PreDestroy
public void cleanup() {
System.out.println("Bean is being destroyed");
}
}
3.2.2 实现InitializingBean和DisposableBean接口
@Component
public class LifecycleBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Bean is going through init via InitializingBean");
}
@Override
public void destroy() throws Exception {
System.out.println("Bean is being destroyed via DisposableBean");
}
}
3.2.3 在@Bean注解中指定初始化和销毁方法
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public ExampleBean exampleBean() {
return new ExampleBean();
}
}
public class ExampleBean {
public void init() {
System.out.println("Bean is going through init via custom init method");
}
public void cleanup() {
System.out.println("Bean is being destroyed via custom destroy method");
}
}
3.2.4 使用BeanPostProcessor
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TargetBean) {
System.out.println("TargetBean is going through before initialization");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TargetBean) {
System.out.println("TargetBean is going through after initialization");
}
return bean;
}
}
3.3 完整的Bean生命周期
Spring Bean的完整生命周期包括以下步骤:
实例化:创建Bean实例
属性赋值:设置Bean属性
Aware接口回调:如BeanNameAware, BeanFactoryAware, ApplicationContextAware等
BeanPostProcessor前置处理:调用BeanPostProcessor的postProcessBeforeInitialization方法
初始化:调用初始化方法(@PostConstruct, InitializingBean, 自定义init-method)
BeanPostProcessor后置处理:调用BeanPostProcessor的postProcessAfterInitialization方法
使用Bean
销毁前处理:调用DestructionAwareBeanPostProcessor的postProcessBeforeDestruction方法
销毁:调用销毁方法(@PreDestroy, DisposableBean, 自定义destroy-method)
4. 第三方Bean管理
4.1 @Bean vs @Component
4.1.1 @Component注解
@Component及其衍生注解用于标记类,使Spring自动扫描并将其注册为Bean。
@Component
public class MyComponent {
// 组件实现
}
@Service
public class UserService {
// 服务实现
}
@Repository
public class UserRepository {
// 数据访问实现
}
@Controller
public class UserController {
// 控制器实现
}
4.1.2 @Bean注解
@Bean注解用在方法上,通常位于@Configuration类中,用于显式声明一个Bean。
@Configuration
public class AppConfig {
@Bean
public ThirdPartyService thirdPartyService() {
return new ThirdPartyServiceImpl();
}
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/mydb");
dataSource.setUsername("username");
dataSource.setPassword("password");
return dataSource;
}
}
4.2 为什么要使用@Bean而不是@Component?
在以下情况下,我们需要使用@Bean而非@Component:
管理第三方库中的类:当需要将第三方库中的类注册为Bean时,由于无法直接修改其源码添加@Component注解,必须使用@Bean
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
}
定制化Bean的创建过程:当Bean的创建需要复杂的逻辑或条件判断时,@Bean方法可以包含任意Java代码
@Configuration
public class DataSourceConfig {
@Value("${app.environment}")
private String environment;
@Bean
public DataSource dataSource() {
if ("production".equals(environment)) {
return createProductionDataSource();
} else {
return createDevelopmentDataSource();
}
}
private DataSource createProductionDataSource() {
// 创建生产环境数据源
}
private DataSource createDevelopmentDataSource() {
// 创建开发环境数据源
}
}
同一个类的多个实例:当需要创建同一个类的多个不同配置的Bean实例时
@Configuration
public class CacheConfig {
@Bean
public Cache userCache() {
return new ConcurrentMapCache("users", 1000, 30);
}
@Bean
public Cache productCache() {
return new ConcurrentMapCache("products", 5000, 60);
}
}
方法注入:可以在@Bean方法中注入其他依赖
@Configuration
public class ServiceConfig {
@Bean
public UserService userService(UserRepository userRepository, EmailService emailService) {
UserServiceImpl service = new UserServiceImpl();
service.setUserRepository(userRepository);
service.setEmailService(emailService);
return service;
}
}
生命周期管理:可以在@Bean方法中精确控制初始化和销毁回调
@Configuration
public class ResourceConfig {
@Bean(initMethod = "connect", destroyMethod = "disconnect")
public RemoteService remoteService() {
RemoteService service = new RemoteService();
service.setEndpoint("https://api.example.com");
return service;
}
}
4.3 第三方Bean管理的最佳实践
创建专门的配置类:为不同模块或功能创建单独的@Configuration类
@Configuration
public class DatabaseConfig {
// 数据库相关Bean
}
@Configuration
public class SecurityConfig {
// 安全相关Bean
}
@Configuration
public class CacheConfig {
// 缓存相关Bean
}
使用条件注解:利用@Conditional、@Profile等注解根据环境选择性地创建Bean
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
// 开发环境数据源
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 生产环境数据源
}
@Bean
@ConditionalOnProperty(name = "app.metrics.enabled", havingValue = "true")
public MetricsCollector metricsCollector() {
// 指标收集器
}
}
使用属性注入:通过@Value或Environment对象注入配置属性
@Configuration
@PropertySource("classpath:app.properties")
public class ApiConfig {
@Value("${api.url}")
private String apiUrl;
@Value("${api.key}")
private String apiKey;
@Value("${api.timeout:30000}")
private int timeout;
@Bean
public ApiClient apiClient() {
ApiClient client = new ApiClient(apiUrl);
client.setApiKey(apiKey);
client.setTimeout(timeout);
return client;
}
}
使用工厂方法:对于复杂的Bean创建逻辑,可以使用工厂方法模式
@Configuration
public class ClientConfig {
@Bean
public ClientFactory clientFactory() {
return new ClientFactory();
}
@Bean
public Client client(ClientFactory factory) {
return factory.createClient();
}
}
使用@Import注解:组合多个配置类
@Configuration
@Import({DatabaseConfig.class, SecurityConfig.class, CacheConfig.class})
public class AppConfig {
// 应用级配置
}
5. Bean的线程安全性
5.1 单例Bean的线程安全问题
单例Bean的线程安全性取决于其状态:
无状态Bean:不保存任何状态信息的Bean是线程安全的
@Service
public class CalculationService {
public int add(int a, int b) {
return a + b;
}
}
有状态Bean:保存状态信息的Bean在多线程环境下可能不安全
@Service
public class CounterService {
private int count = 0;
public void increment() {
count++; // 非线程安全操作
}
public int getCount() {
return count;
}
}
5.2 解决线程安全问题的方法
使用prototype作用域:每次请求创建新实例
@Service
@Scope("prototype")
public class CounterService {
private int count = 0;
public void increment() {
count++;
}
}
使用ThreadLocal:为每个线程提供独立的变量副本
@Service
public class UserContextService {
private ThreadLocal
public void setCurrentUser(User user) {
currentUser.set(user);
}
public User getCurrentUser() {
return currentUser.get();
}
public void clear() {
currentUser.remove();
}
}
使用同步机制:确保线程安全访问
@Service
public class SynchronizedCounterService {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
使用线程安全的数据结构:如ConcurrentHashMap, AtomicInteger等
@Service
public class ConcurrentCounterService {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
6. 循环依赖问题
6.1 什么是循环依赖
循环依赖是指两个或多个Bean之间相互依赖的情况:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
6.2 Spring如何解决循环依赖
Spring主要通过三级缓存解决单例Bean的循环依赖:
一级缓存(singletonObjects):存放完全初始化好的Bean
二级缓存(earlySingletonObjects):存放原始的Bean对象(尚未填充属性)
三级缓存(singletonFactories):存放Bean工厂对象
解决过程:
当A创建时,先将A的创建工厂放入三级缓存
当A依赖B时,去创建B
如果B又依赖A,会从三级缓存中获取A的工厂,并使用工厂创建A的早期引用
将A的早期引用放入二级缓存,并删除三级缓存中的A的工厂
B使用A的早期引用完成创建,并放入一级缓存
A使用创建好的B完成创建,并放入一级缓存
6.3 循环依赖的限制
Spring无法解决以下情况的循环依赖:
构造器注入的循环依赖:因为对象还未创建完成,无法提前暴露
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
prototype作用域的循环依赖:因为prototype Bean不会被缓存
@Service
@Scope("prototype")
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Scope("prototype")
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
使用@DependsOn导致的循环依赖:显式的依赖关系检查会发现循环
@Service
@DependsOn("serviceB")
public class ServiceA {
}
@Service
@DependsOn("serviceA")
public class ServiceB {
}
7. 常见面试题及答案
7.1 Spring容器的bean是单例的还是多例的?单例的bean是什么时候被实例化的?
Spring容器中的bean默认是单例的,但可以通过设置scope属性改变作用域(如prototype、request、session等)
默认情况下,单例bean在Spring容器启动时实例化,创建完毕后存入IoC容器的单例缓存中
如果加上@Lazy注解,则会延迟初始化,即在首次请求该bean时才会被创建
7.2 Spring容器的bean是线程安全的吗?
bean的线程安全完全取决于bean的状态及其作用域
单例bean:
如果是无状态的bean(不保存任何状态信息),则是线程安全的
如果是有状态的bean(内部会保存状态信息),多个线程同时操作该bean时,可能会出现数据不一致的问题,这样的bean则是线程不安全的
对于线程安全问题的解决方案:
将有状态的bean设置为prototype作用域
在bean中使用ThreadLocal变量
使用同步机制(synchronized)
使用线程安全的集合和对象
7.3 @Autowired和@Resource有什么区别?
来源不同:
@Autowired是Spring框架提供的注解
@Resource是JDK提供的注解(javax.annotation.Resource)
注入方式不同:
@Autowired默认按类型(byType)注入,如果有多个匹配的Bean,可以配合@Qualifier按名称注入
@Resource默认按名称(byName)注入,如果没有指定name属性,则按类型注入
适用范围不同:
@Autowired可用于构造函数、字段、方法和参数
@Resource只能用于字段和方法
示例代码:
// 使用@Autowired
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
@Qualifier("primaryDataSource")
private DataSource dataSource;
}
// 使用@Resource
@Service
public class OrderService {
@Resource
private OrderRepository orderRepository;
@Resource(name = "secondaryDataSource")
private DataSource dataSource;
}
7.4 Spring如何解决循环依赖问题?
Spring主要通过三级缓存解决单例Bean的循环依赖:
一级缓存(singletonObjects):存放完全初始化好的Bean
二级缓存(earlySingletonObjects):存放原始的Bean对象(尚未填充属性)
三级缓存(singletonFactories):存放Bean工厂对象
解决过程:
当A创建时,先将A的创建工厂放入三级缓存
当A依赖B时,去创建B
如果B又依赖A,会从三级缓存中获取A的工厂,并使用工厂创建A的早期引用
将A的早期引用放入二级缓存,并删除三级缓存中的A的工厂
B使用A的早期引用完成创建,并放入一级缓存
A使用创建好的B完成创建,并放入一级缓存
需要注意的是,Spring只能解决单例作用域的setter注入的循环依赖,对于构造器注入或prototype作用域的Bean,Spring无法解决其循环依赖问题。
7.5 BeanFactory和ApplicationContext有什么区别?
继承关系:ApplicationContext是BeanFactory的子接口,提供了更多的功能
加载时机:BeanFactory是延迟加载,只有在使用到某个Bean时才会实例化,ApplicationContext在启动时就会实例化所有单例Bean
功能差异:BeanFactory:提供基本的IoC容器功能,管理Bean的生命周期ApplicationContext:除了BeanFactory的功能外,还提供:国际化支持(i18n),事件发布资源访问(如URL和文件)AOP支持,WebApplicationContext等特定应用场景的实现
使用场景:BeanFactory:适用于资源有限的环境,如移动设备;ApplicationContext:适用于大多数企业级应用
7.6 @Configuration注解的作用是什么?
@Configuration注解用于标记一个类作为Bean定义的源,相当于XML配置文件中的
主要作用:
定义Bean:通过@Bean注解方法来定义Bean
组件扫描:结合@ComponentScan注解指定要扫描的包
导入其他配置:通过@Import注解导入其他配置类
属性源:通过@PropertySource注解加载属性文件
特殊处理:
Spring会通过CGLIB增强@Configuration类,使得@Bean方法之间的调用能够保证返回同一个实例
可以使用@Configuration(proxyBeanMethods = false)禁用这种增强,提高性能(Spring 5.2+)
示例代码:
@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:app.properties")
@Import({SecurityConfig.class, DataSourceConfig.class})
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB()); // 调用另一个@Bean方法
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
7.7 Spring Boot中的@SpringBootApplication注解包含哪些注解?
@SpringBootApplication是一个组合注解,包含以下三个主要注解:
@Configuration:标记该类为配置类
@EnableAutoConfiguration:启用Spring Boot的自动配置机制
@ComponentScan:启用组件扫描,默认扫描该类所在的包及其子包
此外,还可以通过属性自定义这些注解的行为:
@SpringBootApplication(
scanBasePackages = "com.example",
exclude = {DataSourceAutoConfiguration.class}
)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}