技术面:Spring (bean的生命周期、创建方式、注入方式、作用域)
在Java企业级开发中,Spring框架几乎是必问的技术栈。面试中,Spring Bean的相关问题经常被用来考察候选人对框架核心的掌握程度。本文将用大量代码演示,从实战角度深入解析Spring Bean的生命周期、创建方式、注入方式以及作用域。—## 一、Spring Bean 的生命周期Spring Bean的生命周期是面试中的高频考点。它描述了Bean从创建到销毁的完整过程。理解它有助于我们利用Spring提供的钩子进行自定义操作。### 1.1 生命周期流程图(简化版)1.实例化:通过构造器或工厂方法创建Bean实例。2.属性赋值:设置Bean的属性(依赖注入)。3.初始化:调用init-method或实现InitializingBean接口。4.使用:Bean就绪,供应用程序调用。5.销毁:容器关闭时,调用destroy-method或实现DisposableBean接口。### 1.2 实战代码演示下面是一个完整的示例,展示Bean生命周期中的关键点。javaimport org.springframework.beans.factory.DisposableBean;import org.springframework.beans.factory.InitializingBean;public class UserBean implements InitializingBean, DisposableBean { private String name; public UserBean() { System.out.println("1. 实例化:调用构造器 UserBean()"); } public void setName(String name) { this.name = name; System.out.println("2. 属性赋值:设置 name = " + name); } // 实现 InitializingBean 接口 @Override public void afterPropertiesSet() throws Exception { System.out.println("3. 初始化回调:afterPropertiesSet() 执行"); if (name == null) { name = "默认用户"; } } // 自定义初始化方法(通过 @Bean 的 initMethod 指定) public void customInit() { System.out.println("3.5 自定义初始化:customInit() 执行"); } // 业务方法 public void greet() { System.out.println("4. 使用Bean:Hello, " + name); } // 实现 DisposableBean 接口 @Override public void destroy() throws Exception { System.out.println("5. 销毁回调:destroy() 执行"); } // 自定义销毁方法 public void customDestroy() { System.out.println("5.5 自定义销毁:customDestroy() 执行"); }}XML配置(applicationContext.xml):xml<bean id="userBean" class="com.example.UserBean" init-method="customInit" destroy-method="customDestroy"> <property name="name" value="张三"/></bean>运行代码:javaimport org.springframework.context.support.ClassPathXmlApplicationContext;public class BeanLifecycleDemo { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserBean user = (UserBean) context.getBean("userBean"); user.greet(); context.close(); // 触发销毁 }}输出结果:1. 实例化:调用构造器 UserBean()2. 属性赋值:设置 name = 张三3. 初始化回调:afterPropertiesSet() 执行3.5 自定义初始化:customInit() 执行4. 使用Bean:Hello, 张三5. 销毁回调:destroy() 执行5.5 自定义销毁:customDestroy() 执行注意:afterPropertiesSet()在属性赋值后、自定义初始化方法之前执行。如果同时使用接口和XML配置,接口方法优先。—## 二、Bean 的创建方式Spring支持多种创建Bean的方式,常见的有三种:构造器、静态工厂和实例工厂。### 2.1 构造器方式(最常用)默认使用无参构造器,或通过<constructor-arg>传参。xml<bean id="user" class="com.example.User"> <constructor-arg name="name" value="李四"/> <constructor-arg name="age" value="25"/></bean>### 2.2 静态工厂方法适用于不需要Spring管理工厂类实例的场景。javapublic class UserFactory { // 静态方法,返回Bean public static User createUser() { User user = new User(); user.setName("王五"); user.setAge(30); return user; }}``````xml<bean id="userFromFactory" class="com.example.UserFactory" factory-method="createUser"/>### 2.3 实例工厂方法需要先创建工厂Bean,再通过它创建目标Bean。javapublic class UserInstanceFactory { // 非静态方法 public User createUser() { User user = new User(); user.setName("赵六"); user.setAge(28); return user; }}// 配置<bean id="factory" class="com.example.UserInstanceFactory"/><bean id="userFromInstance" factory-bean="factory" factory-method="createUser"/>### 2.4 注解方式(推荐)现代Spring开发更倾向于使用注解,如@Component和@Bean。java@Componentpublic class User { @Value("张三") private String name; // getter/setter...}// 或在配置类中@Configurationpublic class AppConfig { @Bean public User user() { return new User("李四", 25); }}—## 三、依赖注入方式依赖注入(DI)是Spring的核心功能,主要有三种方式:属性注入、构造器注入和setter注入。### 3.1 属性注入(Field Injection)通过@Autowired直接注入到字段上,代码最简洁,但不推荐用于生产环境,因为难以单元测试。javaimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;@Componentpublic class OrderService { @Autowired private UserRepository userRepository; // 直接注入 public void process() { userRepository.save(new User()); }}### 3.2 构造器注入(Constructor Injection)Spring官方推荐的方式,保证依赖不可变且注入时对象已完成初始化。java@Componentpublic class OrderService { private final UserRepository userRepository; // Spring自动调用构造器,注入依赖 @Autowired public OrderService(UserRepository userRepository) { this.userRepository = userRepository; } public void process() { userRepository.save(new User()); }}注意:如果类只有一个构造器,@Autowired可以省略(Spring 4.3+)。### 3.3 Setter注入通过setter方法注入,适合可选依赖。java@Componentpublic class OrderService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } public void process() { userRepository.save(new User()); }}### 3.4 注入方式对比| 方式 | 优点 | 缺点 | 适用场景 ||------|------|------|----------|| 属性注入 | 代码简洁 | 难以测试,依赖不明确 | 快速原型,不推荐生产 || 构造器注入 | 依赖明确,不可变 | 代码稍长 | 强制依赖,推荐使用 || Setter注入 | 可选依赖 | 可变,可能忘记调用 | 可选依赖或循环引用 |—## 四、Bean 的作用域作用域决定了Bean的存活范围和共享方式。Spring定义了5种作用域(Web环境有3种额外作用域)。### 4.1 常见作用域| 作用域 | 描述 | 适用场景 ||--------|------|----------|| singleton | 默认,单例,整个容器共享一个实例 | 无状态Bean(如Service) || prototype | 每次获取都创建新实例 | 有状态Bean(如User) || request | 每个HTTP请求一个实例(Web环境) | 请求级别的数据 || session | 每个HTTP Session一个实例(Web环境) | 用户会话数据 || application | 每个ServletContext一个实例(Web环境) | 全局共享数据 |### 4.2 代码演示javaimport org.springframework.context.annotation.Scope;import org.springframework.stereotype.Component;@Component@Scope("singleton") // 默认就是singleton,可省略public class SingletonBean { private int count = 0; public void increment() { count++; System.out.println("Singleton count = " + count); }}@Component@Scope("prototype") // 每次获取新实例public class PrototypeBean { private int count = 0; public void increment() { count++; System.out.println("Prototype count = " + count); }}测试代码:javaimport org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class ScopeDemo { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext("com.example"); // 获取两次单例Bean SingletonBean s1 = context.getBean(SingletonBean.class); SingletonBean s2 = context.getBean(SingletonBean.class); s1.increment(); // 输出: Singleton count = 1 s2.increment(); // 输出: Singleton count = 2 (共享同一个实例) // 获取两次原型Bean PrototypeBean p1 = context.getBean(PrototypeBean.class); PrototypeBean p2 = context.getBean(PrototypeBean.class); p1.increment(); // 输出: Prototype count = 1 p2.increment(); // 输出: Prototype count = 1 (不同实例) }}### 4.3 作用域使用注意事项-singleton与prototype混用:如果singleton Bean注入prototype Bean,prototype Bean会被固化。解决方案:使用@Lookup注解或Provider。-Web作用域:仅在Web应用(如Spring MVC)中生效,需要配置RequestContextListener或@EnableWebMvc。—## 总结本文从实战角度出发,用具体代码演示了Spring Bean的四大核心概念:1.生命周期:实例化 → 属性赋值 → 初始化 → 使用 → 销毁,提供了InitializingBean、DisposableBean接口以及init-method、destroy-method等钩子。2.创建方式:构造器(最常用)、静态工厂、实例工厂以及注解方式(@Component、@Bean)。3.注入方式:属性注入(简洁但危险)、构造器注入(推荐)、Setter注入(可选依赖),其中构造器注入是Spring官方推荐的最佳实践。4.作用域:singleton(单例,默认)、prototype(原型)、request、session、application,选择合适的作用域可以提升性能和资源利用率。面试中,不仅要记住概念,更要能结合实际项目场景解释为什么选择某种方式。例如:用构造器注入保证依赖不可变,用prototype作用域处理有状态Bean等。希望本文的代码示例能帮助你在面试中脱颖而出。