Spring Bean生命周期详解:从入门到精通

适合人群 :Java初学者、Spring框架学习者、面试准备者
关键词:Spring Bean、生命周期、作用域、依赖注入、回调方法


📚 目录

  1. 什么是Bean?
  2. Bean是否单例?
  3. Bean的生命周期详解
  4. 单例和非单例Bean的生命周期对比
  5. [Spring Bean的作用域](#Spring Bean的作用域)
  6. Bean加载/销毁前后的回调方法
  7. 完整代码示例
  8. 常见面试问题
  9. 总结

1. 什么是Bean?

1.1 Bean的基本概念

在Spring框架中,Bean是一个由Spring IoC容器管理的对象。

🔍 通俗理解

想象你开了一家咖啡店(Spring容器),店里的各种设备(咖啡机、磨豆机、收银机等)就是Bean。你不需要自己去制造这些设备,而是由供应商(Spring)统一提供和管理。

关键概念解释

  • IoC(Inversion of Control):控制反转,即对象的创建和管理权从程序员转移到Spring容器
  • Spring容器:负责创建、配置和管理Bean的容器,主要有BeanFactory和ApplicationContext两种
java 复制代码
// 传统方式:程序员自己创建对象
public class TraditionalWay {
    public static void main(String[] args) {
        // 我们自己new对象,控制权在程序员手中
        UserService userService = new UserService();
        userService.doSomething();
    }
}

// Spring方式:由容器创建和管理对象
@Component  // 告诉Spring:这是一个Bean,请帮我管理
public class UserService {
    public void doSomething() {
        System.out.println("执行业务逻辑");
    }
}

1.2 为什么需要Bean?

使用Spring Bean的优势:

  • 解耦:降低组件之间的依赖关系
  • 易于测试:可以方便地进行单元测试和Mock
  • 统一管理:对象的创建、配置、销毁都由容器统一处理
  • AOP支持:便于实现面向切面编程

2. Bean是否单例?

2.1 默认情况

答案:是的,Spring中的Bean默认是单例(Singleton)的。

🔍 通俗理解

单例就像你家里的电视机,全家人共用一台,而不是每个人都有一台自己的电视。

java 复制代码
@Component
public class SingletonBean {
    // 默认情况下,Spring容器中只会创建这个类的一个实例
    // 所有需要使用这个Bean的地方,拿到的都是同一个对象
}

2.2 验证Bean是否单例

java 复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
class MyBean {
    public MyBean() {
        System.out.println("MyBean构造方法被调用,对象创建时间:" + System.currentTimeMillis());
    }
}

public class SingletonTest {
    public static void main(String[] args) {
        // 创建Spring容器
        ApplicationContext context = new AnnotationConfigApplicationContext(MyBean.class);
        
        // 从容器中获取Bean,第一次
        MyBean bean1 = context.getBean(MyBean.class);
        System.out.println("bean1的内存地址:" + bean1);
        
        // 从容器中获取Bean,第二次
        MyBean bean2 = context.getBean(MyBean.class);
        System.out.println("bean2的内存地址:" + bean2);
        
        // 判断是否是同一个对象
        System.out.println("bean1和bean2是同一个对象吗?" + (bean1 == bean2));
    }
}

/* 运行结果:
 * MyBean构造方法被调用,对象创建时间:1673856123456
 * bean1的内存地址:com.example.MyBean@15db9742
 * bean2的内存地址:com.example.MyBean@15db9742
 * bean1和bean2是同一个对象吗?true
 * 
 * 结论:构造方法只被调用了一次,两次获取的Bean内存地址相同,证明是单例
 */

2.3 单例的优缺点

优点

  • 💡 节省内存资源(只创建一个对象)
  • 💡 提高性能(避免重复创建)
  • 💡 便于共享数据

缺点

  • ⚠️ 线程安全问题(多线程访问同一对象需要注意)
  • ⚠️ 不适合有状态的Bean(即成员变量会变化的Bean)
java 复制代码
// ❌ 错误示例:单例Bean中使用成员变量会有线程安全问题
@Component
public class UnsafeSingletonBean {
    // 这是成员变量,在单例模式下,多线程会共享这个变量,可能导致数据混乱
    private int count = 0;
    
    public void increment() {
        count++;  // 多线程访问会有并发问题
    }
}

// ✅ 正确示例1:无状态的单例Bean
@Component
public class SafeSingletonBean {
    // 没有成员变量,只有方法,是线程安全的
    public int add(int a, int b) {
        return a + b;
    }
}

// ✅ 正确示例2:使用ThreadLocal保证线程安全
@Component
public class ThreadLocalBean {
    // ThreadLocal为每个线程提供独立的变量副本
    private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
    
    public void increment() {
        count.set(count.get() + 1);  // 每个线程有自己的count
    }
}

3. Bean的生命周期详解

Bean的生命周期是指Bean从创建到销毁的整个过程,这个过程由Spring容器管理。

3.1 Bean生命周期流程图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Spring容器启动                              │
└───────────────────────┬─────────────────────────────────────┘
                        │
                        ▼
        ┌───────────────────────────────┐
        │  1. 实例化Bean(调用构造方法)    │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  2. 设置Bean的属性(依赖注入)    │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  3. 调用BeanNameAware接口      │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  4. 调用BeanFactoryAware接口   │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  5. 调用ApplicationContextAware│
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  6. BeanPostProcessor前置处理  │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  7. 调用@PostConstruct方法     │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  8. 调用InitializingBean接口   │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  9. 调用自定义init-method      │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  10. BeanPostProcessor后置处理 │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │        Bean可以使用了!         │
        │       (正常业务逻辑)           │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │     容器关闭,开始销毁Bean      │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  11. 调用@PreDestroy方法       │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  12. 调用DisposableBean接口    │
        └───────────┬───────────────────┘
                    │
                    ▼
        ┌───────────────────────────────┐
        │  13. 调用自定义destroy-method  │
        └───────────┬───────────────────┘
                    │
                    ▼
              ┌─────────┐
              │ Bean销毁 │
              └─────────┘

3.2 生命周期各阶段详解

阶段1:实例化(Instantiation)

Spring容器通过反射机制调用Bean的构造方法创建对象。

java 复制代码
@Component
public class LifeCycleBean {
    
    // 1. 实例化阶段:构造方法被调用
    public LifeCycleBean() {
        System.out.println("【1-构造方法】Bean对象被创建");
    }
}

🔍 反射机制 :Java的一种特性,可以在运行时动态地创建对象、调用方法等。

就像照镜子一样,可以看到类的内部结构(方法、属性等)并进行操作。

阶段2:属性赋值(Populate Properties)

Spring容器将配置文件或注解中定义的属性值注入到Bean中。

java 复制代码
@Component
public class LifeCycleBean {
    
    // 2. 属性赋值阶段:依赖注入
    @Value("${user.name:张三}")  // 从配置文件读取user.name,默认值为"张三"
    private String userName;
    
    @Autowired  // 自动注入其他Bean
    private UserService userService;
    
    public LifeCycleBean() {
        System.out.println("【1-构造方法】此时userName = " + userName);  // null
    }
}

🔍 依赖注入(DI - Dependency Injection)

不用自己new对象,而是让Spring自动把需要的对象"注入"进来。

就像点外卖,你不用自己做饭,外卖平台把饭送到你手上。

阶段3-5:Aware接口回调

如果Bean实现了某些Aware接口,Spring会调用相应的方法,让Bean感知到Spring容器的信息。

java 复制代码
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class LifeCycleBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
    
    // 3. BeanNameAware:让Bean知道自己在容器中的名字
    @Override
    public void setBeanName(String name) {
        System.out.println("【3-BeanNameAware】Bean的名字是:" + name);
    }
    
    // 4. BeanFactoryAware:让Bean获取到BeanFactory(Bean工厂)
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("【4-BeanFactoryAware】获取到BeanFactory:" + beanFactory);
    }
    
    // 5. ApplicationContextAware:让Bean获取到ApplicationContext(应用上下文)
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("【5-ApplicationContextAware】获取到ApplicationContext:" + applicationContext);
    }
}

🔍 Aware接口的作用

让Bean可以感知到Spring容器的存在,获取容器相关的信息。

就像给Bean装上了"感应器",能感知周围的环境。

阶段6、10:BeanPostProcessor(Bean后置处理器)

这是Spring提供的一个扩展点,可以在Bean初始化前后进行自定义处理。

java 复制代码
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * Bean后置处理器:会对容器中所有的Bean生效
 * 可以在Bean初始化前后进行自定义操作
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    
    // 6. 初始化之前调用
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【6-BeanPostProcessor前置】处理Bean:" + beanName);
        // 可以对Bean进行修改或包装,比如返回代理对象
        return bean;  // 返回原始Bean或修改后的Bean
    }
    
    // 10. 初始化之后调用
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("【10-BeanPostProcessor后置】处理Bean:" + beanName);
        // AOP就是在这个阶段实现的,返回代理对象
        return bean;
    }
}

🔍 BeanPostProcessor的应用场景

  • AOP代理对象的创建
  • Bean属性的修改或验证
  • 日志记录、性能监控等
阶段7:@PostConstruct

使用@PostConstruct注解的方法会在Bean属性赋值完成后、初始化方法之前执行。

java 复制代码
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;

@Component
public class LifeCycleBean {
    
    @Value("${user.name:张三}")
    private String userName;
    
    // 7. @PostConstruct:初始化方法(JSR-250规范)
    @PostConstruct
    public void postConstruct() {
        System.out.println("【7-@PostConstruct】Bean初始化,userName = " + userName);
        // 这里可以做一些初始化工作,比如资源加载、数据预处理等
    }
}
阶段8:InitializingBean接口

实现InitializingBean接口的Bean,会在属性设置完成后调用afterPropertiesSet()方法。

java 复制代码
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class LifeCycleBean implements InitializingBean {
    
    // 8. InitializingBean接口:Spring提供的初始化方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【8-InitializingBean】afterPropertiesSet方法被调用");
        // 可以在这里进行一些初始化操作
    }
}
阶段9:自定义init-method

可以通过配置或注解指定自定义的初始化方法。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 方式1:使用@Bean注解的initMethod属性
@Configuration
public class BeanConfig {
    
    @Bean(initMethod = "customInit")
    public LifeCycleBean lifeCycleBean() {
        return new LifeCycleBean();
    }
}

public class LifeCycleBean {
    
    // 9. 自定义初始化方法
    public void customInit() {
        System.out.println("【9-自定义init-method】customInit方法被调用");
    }
}
阶段11:@PreDestroy

使用@PreDestroy注解的方法会在Bean销毁之前执行。

java 复制代码
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class LifeCycleBean {
    
    // 11. @PreDestroy:销毁前方法(JSR-250规范)
    @PreDestroy
    public void preDestroy() {
        System.out.println("【11-@PreDestroy】Bean即将销毁");
        // 这里可以做清理工作,比如关闭资源、释放连接等
    }
}
阶段12:DisposableBean接口

实现DisposableBean接口的Bean,会在销毁时调用destroy()方法。

java 复制代码
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;

@Component
public class LifeCycleBean implements DisposableBean {
    
    // 12. DisposableBean接口:Spring提供的销毁方法
    @Override
    public void destroy() throws Exception {
        System.out.println("【12-DisposableBean】destroy方法被调用");
        // 释放资源
    }
}
阶段13:自定义destroy-method

可以通过配置或注解指定自定义的销毁方法。

java 复制代码
@Configuration
public class BeanConfig {
    
    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    public LifeCycleBean lifeCycleBean() {
        return new LifeCycleBean();
    }
}

public class LifeCycleBean {
    
    // 13. 自定义销毁方法
    public void customDestroy() {
        System.out.println("【13-自定义destroy-method】customDestroy方法被调用");
    }
}

3.3 初始化和销毁方法的执行顺序

初始化方法执行顺序

  1. @PostConstruct
  2. InitializingBean.afterPropertiesSet()
  3. 自定义init-method

销毁方法执行顺序

  1. @PreDestroy
  2. DisposableBean.destroy()
  3. 自定义destroy-method

💡 最佳实践

  • 优先使用@PostConstruct@PreDestroy,因为它们是JSR-250标准,不依赖Spring
  • 避免同时使用多种初始化/销毁方法,选择一种即可

4. 单例和非单例Bean的生命周期对比

4.1 生命周期差异

对比项 单例Bean(Singleton) 原型Bean(Prototype)
创建时机 容器启动时创建 每次getBean时创建
销毁时机 容器关闭时销毁 不由容器销毁
销毁方法 会调用@PreDestroy等 不会调用销毁方法
内存占用 只有一个实例 每次创建新实例
线程安全 需要注意线程安全 天然线程安全

4.2 重点:Prototype Bean不会被销毁

这是一个很重要的知识点!

java 复制代码
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;

@Component
@Scope("prototype")  // 设置为原型作用域
public class PrototypeBean {
    
    public PrototypeBean() {
        System.out.println("PrototypeBean被创建");
    }
    
    @PreDestroy  // ⚠️ 注意:这个方法不会被调用!
    public void destroy() {
        System.out.println("PrototypeBean被销毁");
    }
}

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PrototypeBean.class);
        
        // 每次获取都会创建新对象
        PrototypeBean bean1 = context.getBean(PrototypeBean.class);  // 输出:PrototypeBean被创建
        PrototypeBean bean2 = context.getBean(PrototypeBean.class);  // 输出:PrototypeBean被创建
        
        System.out.println("bean1 == bean2? " + (bean1 == bean2));  // false
        
        // 关闭容器
        ((AnnotationConfigApplicationContext) context).close();
        // ⚠️ 注意:不会输出"PrototypeBean被销毁"
    }
}

🔍 为什么Prototype Bean不会被销毁?

因为Spring容器只负责创建Prototype Bean,创建完成后就不再管理它的生命周期了。

就像工厂生产产品,产品卖出去后,工厂就不管这个产品的后续了,由买家自己负责。

解决方案:如果需要手动管理Prototype Bean的销毁,可以这样做:

java 复制代码
import org.springframework.beans.factory.DisposableBean;

@Component
@Scope("prototype")
public class PrototypeBean implements DisposableBean {
    
    // 手动调用这个方法来销毁
    @Override
    public void destroy() throws Exception {
        System.out.println("手动销毁PrototypeBean");
        // 清理资源
    }
}

// 使用时
PrototypeBean bean = context.getBean(PrototypeBean.class);
// ... 使用bean
bean.destroy();  // 手动调用销毁方法

5. Spring Bean的作用域

5.1 什么是作用域?

**作用域(Scope)**决定了Bean的创建策略和生命周期。

🔍 通俗理解

作用域就像是Bean的"使用范围"。

  • 单例:全公司共用一台打印机
  • 原型:每个人都有自己的笔记本电脑

5.2 Spring的六种作用域

作用域 说明 使用场景
singleton 单例(默认) 无状态的Bean,如Service、DAO
prototype 原型,每次创建新实例 有状态的Bean,如临时对象
request 每个HTTP请求一个实例 Web应用,请求相关的数据
session 每个HTTP会话一个实例 Web应用,用户会话数据
application 每个ServletContext一个实例 Web应用,全局共享数据
websocket 每个WebSocket一个实例 WebSocket应用

⚠️ 注意:后四种作用域只在Web应用中有效!

5.3 各作用域的代码示例

Singleton(单例)
java 复制代码
import 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("当前count值:" + count);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SingletonBean.class);
        
        SingletonBean bean1 = context.getBean(SingletonBean.class);
        SingletonBean bean2 = context.getBean(SingletonBean.class);
        
        bean1.increment();  // 输出:当前count值:1
        bean2.increment();  // 输出:当前count值:2(因为是同一个对象)
        
        System.out.println("bean1 == bean2? " + (bean1 == bean2));  // true
    }
}
Prototype(原型)
java 复制代码
@Component
@Scope("prototype")  // 设置为原型作用域
public class PrototypeBean {
    
    private int count = 0;
    
    public void increment() {
        count++;
        System.out.println("当前count值:" + count);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(PrototypeBean.class);
        
        PrototypeBean bean1 = context.getBean(PrototypeBean.class);
        PrototypeBean bean2 = context.getBean(PrototypeBean.class);
        
        bean1.increment();  // 输出:当前count值:1
        bean2.increment();  // 输出:当前count值:1(因为是不同对象)
        
        System.out.println("bean1 == bean2? " + (bean1 == bean2));  // false
    }
}
Request(请求作用域)
java 复制代码
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST)  // 或者 @Scope("request")
public class RequestBean {
    
    private String requestId;
    
    public RequestBean() {
        // 每个HTTP请求都会创建一个新的RequestBean实例
        this.requestId = "Request-" + System.currentTimeMillis();
        System.out.println("创建RequestBean:" + requestId);
    }
    
    public String getRequestId() {
        return requestId;
    }
}

// 在Controller中使用
@RestController
public class TestController {
    
    @Autowired
    private RequestBean requestBean;  // 每个请求注入的都是新实例
    
    @GetMapping("/test")
    public String test() {
        return "Request ID: " + requestBean.getRequestId();
    }
}

📝 Request作用域的典型应用

  • 存储当前请求的上下文信息
  • 记录请求的日志信息
  • 临时存储请求参数
Session(会话作用域)
java 复制代码
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION)  // 或者 @Scope("session")
public class SessionBean {
    
    private String sessionId;
    private String username;
    
    public SessionBean() {
        // 每个用户会话创建一个SessionBean实例
        this.sessionId = "Session-" + System.currentTimeMillis();
        System.out.println("创建SessionBean:" + sessionId);
    }
    
    // getter和setter
}

// 在Controller中使用
@RestController
public class UserController {
    
    @Autowired
    private SessionBean sessionBean;  // 同一个会话中获取的是同一个实例
    
    @PostMapping("/login")
    public String login(@RequestParam String username) {
        sessionBean.setUsername(username);
        return "登录成功,Session ID: " + sessionBean.getSessionId();
    }
    
    @GetMapping("/userInfo")
    public String getUserInfo() {
        return "用户名:" + sessionBean.getUsername() + 
               ", Session ID: " + sessionBean.getSessionId();
    }
}

📝 Session作用域的典型应用

  • 用户登录信息
  • 购物车数据
  • 用户偏好设置

5.4 如何选择合适的作用域?

复制代码
选择作用域的决策树:

是否是Web应用?
├─ 否 → 是否需要共享实例?
│       ├─ 是 → singleton
│       └─ 否 → prototype
│
└─ 是 → 数据的生命周期是什么?
        ├─ 整个应用生命周期 → singleton 或 application
        ├─ 用户会话期间 → session
        ├─ 单个HTTP请求 → request
        └─ 每次使用都要新建 → prototype

选择建议

  • Service层、DAO层:使用singleton(默认)
  • Controller层:使用singleton(默认),Spring MVC会处理线程安全
  • 有状态的临时对象:使用prototype
  • 用户相关数据:使用session
  • 请求相关数据:使用request

6. Bean加载/销毁前后的回调方法

在Spring中,有多种方式可以在Bean加载和销毁前后执行自定义逻辑。

6.1 回调方法汇总

方式 类型 优先级 是否依赖Spring
@PostConstruct 初始化 1(最先执行) 否(JSR-250标准)
InitializingBean 初始化 2
init-method 初始化 3
@PreDestroy 销毁 1(最先执行) 否(JSR-250标准)
DisposableBean 销毁 2
destroy-method 销毁 3

6.2 方式1:使用@PostConstruct和@PreDestroy(推荐)

java 复制代码
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

/**
 * 使用JSR-250标准注解
 * 优点:不依赖Spring,代码更通用
 */
@Component
public class AnnotationBean {
    
    public AnnotationBean() {
        System.out.println("【构造方法】AnnotationBean被创建");
    }
    
    /**
     * 初始化方法:在Bean属性赋值完成后调用
     * 适合做:资源加载、数据初始化、连接建立等
     */
    @PostConstruct
    public void init() {
        System.out.println("【@PostConstruct】执行初始化逻辑");
        // 示例:加载配置文件
        loadConfiguration();
        // 示例:建立数据库连接
        connectDatabase();
    }
    
    /**
     * 销毁方法:在Bean销毁前调用
     * 适合做:资源释放、连接关闭、数据保存等
     */
    @PreDestroy
    public void cleanup() {
        System.out.println("【@PreDestroy】执行清理逻辑");
        // 示例:关闭数据库连接
        closeDatabase();
        // 示例:保存数据
        saveData();
    }
    
    private void loadConfiguration() {
        System.out.println("  - 加载配置文件");
    }
    
    private void connectDatabase() {
        System.out.println("  - 建立数据库连接");
    }
    
    private void closeDatabase() {
        System.out.println("  - 关闭数据库连接");
    }
    
    private void saveData() {
        System.out.println("  - 保存数据");
    }
}

6.3 方式2:实现InitializingBean和DisposableBean接口

java 复制代码
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

/**
 * 实现Spring提供的接口
 * 缺点:代码与Spring框架耦合
 */
@Component
public class InterfaceBean implements InitializingBean, DisposableBean {
    
    public InterfaceBean() {
        System.out.println("【构造方法】InterfaceBean被创建");
    }
    
    /**
     * InitializingBean接口的方法
     * 在属性设置完成后调用
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【InitializingBean】afterPropertiesSet方法执行");
        // 执行初始化逻辑
    }
    
    /**
     * DisposableBean接口的方法
     * 在Bean销毁时调用
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("【DisposableBean】destroy方法执行");
        // 执行清理逻辑
    }
}

6.4 方式3:使用init-method和destroy-method

java 复制代码
// Bean类
public class XmlBean {
    
    public XmlBean() {
        System.out.println("【构造方法】XmlBean被创建");
    }
    
    // 自定义初始化方法(方法名可以自定义)
    public void customInit() {
        System.out.println("【init-method】customInit方法执行");
    }
    
    // 自定义销毁方法(方法名可以自定义)
    public void customDestroy() {
        System.out.println("【destroy-method】customDestroy方法执行");
    }
}

// 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    
    /**
     * 通过@Bean注解的initMethod和destroyMethod属性指定初始化和销毁方法
     */
    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    public XmlBean xmlBean() {
        return new XmlBean();
    }
}

6.5 三种方式的混合使用

如果同时使用多种方式,执行顺序如下:

java 复制代码
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class MixedBean implements InitializingBean, DisposableBean {
    
    public MixedBean() {
        System.out.println("0. 构造方法执行");
    }
    
    @PostConstruct
    public void postConstruct() {
        System.out.println("1. @PostConstruct执行");
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println("2. InitializingBean.afterPropertiesSet执行");
    }
    
    public void customInit() {
        System.out.println("3. init-method执行");
    }
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("4. @PreDestroy执行");
    }
    
    @Override
    public void destroy() {
        System.out.println("5. DisposableBean.destroy执行");
    }
    
    public void customDestroy() {
        System.out.println("6. destroy-method执行");
    }
}

@Configuration
class MixedBeanConfig {
    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    public MixedBean mixedBean() {
        return new MixedBean();
    }
}

/* 运行结果:
 * 0. 构造方法执行
 * 1. @PostConstruct执行
 * 2. InitializingBean.afterPropertiesSet执行
 * 3. init-method执行
 * (Bean使用中...)
 * 4. @PreDestroy执行
 * 5. DisposableBean.destroy执行
 * 6. destroy-method执行
 */

6.6 实际应用场景

场景1:数据库连接池初始化
java 复制代码
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class DatabaseConnectionPool {
    
    @Value("${db.url}")
    private String dbUrl;
    
    @Value("${db.username}")
    private String username;
    
    @Value("${db.password}")
    private String password;
    
    private HikariDataSource dataSource;
    
    /**
     * 初始化:创建数据库连接池
     */
    @PostConstruct
    public void init() {
        System.out.println("【初始化】创建数据库连接池");
        dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(dbUrl);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMaximumPoolSize(10);  // 最大连接数
        System.out.println("【初始化完成】数据库连接池已就绪");
    }
    
    /**
     * 销毁:关闭数据库连接池
     */
    @PreDestroy
    public void cleanup() {
        System.out.println("【清理】关闭数据库连接池");
        if (dataSource != null && !dataSource.isClosed()) {
            dataSource.close();
        }
        System.out.println("【清理完成】数据库连接池已关闭");
    }
    
    public HikariDataSource getDataSource() {
        return dataSource;
    }
}
场景2:缓存预加载
java 复制代码
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Component
public class CacheManager {
    
    // 模拟缓存
    private Map<String, Object> cache = new HashMap<>();
    
    /**
     * 初始化:预加载热点数据到缓存
     */
    @PostConstruct
    public void preloadCache() {
        System.out.println("【初始化】开始预加载缓存数据");
        
        // 模拟从数据库加载热点数据
        cache.put("hotData1", "这是热点数据1");
        cache.put("hotData2", "这是热点数据2");
        cache.put("hotData3", "这是热点数据3");
        
        System.out.println("【初始化完成】已加载 " + cache.size() + " 条缓存数据");
    }
    
    public Object get(String key) {
        return cache.get(key);
    }
}
场景3:定时任务启动和停止
java 复制代码
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Component
public class ScheduledTaskManager {
    
    private ScheduledExecutorService scheduler;
    
    /**
     * 初始化:启动定时任务
     */
    @PostConstruct
    public void startScheduledTask() {
        System.out.println("【初始化】启动定时任务");
        
        scheduler = Executors.newScheduledThreadPool(1);
        
        // 每10秒执行一次任务
        scheduler.scheduleAtFixedRate(() -> {
            System.out.println("【定时任务】执行清理操作:" + System.currentTimeMillis());
        }, 0, 10, TimeUnit.SECONDS);
        
        System.out.println("【初始化完成】定时任务已启动");
    }
    
    /**
     * 销毁:停止定时任务
     */
    @PreDestroy
    public void stopScheduledTask() {
        System.out.println("【清理】停止定时任务");
        
        if (scheduler != null && !scheduler.isShutdown()) {
            scheduler.shutdown();
            try {
                // 等待任务完成,最多等待5秒
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    scheduler.shutdownNow();  // 强制停止
                }
            } catch (InterruptedException e) {
                scheduler.shutdownNow();
            }
        }
        
        System.out.println("【清理完成】定时任务已停止");
    }
}

7. 完整代码示例

7.1 演示所有生命周期阶段的完整Bean

java 复制代码
package com.example.demo.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * 完整的Bean生命周期演示
 * 这个Bean实现了所有的生命周期接口和回调方法
 * 
 * @author Demo
 */
@Component  // 标记为Spring组件,由容器管理
public class CompleteLifeCycleBean implements 
        BeanNameAware,           // 获取Bean名称
        BeanFactoryAware,        // 获取BeanFactory
        ApplicationContextAware, // 获取ApplicationContext
        InitializingBean,        // 初始化Bean
        DisposableBean {         // 销毁Bean

    // ========== 属性 ==========
    
    @Value("${user.name:DefaultUser}")  // 从配置文件读取,默认值为DefaultUser
    private String userName;
    
    @Autowired(required = false)  // 自动注入(可选)
    private UserService userService;
    
    // 用于保存Bean名称
    private String beanName;
    
    // ========== 第1阶段:实例化 ==========
    
    /**
     * 构造方法:Bean被创建的第一步
     * 此时还没有进行属性注入,所以userName和userService都是null
     */
    public CompleteLifeCycleBean() {
        System.out.println("\n========== Bean生命周期开始 ==========");
        System.out.println("【阶段1-构造方法】Bean对象被创建");
        System.out.println("  当前userName的值: " + userName);  // null
        System.out.println("  当前userService的值: " + userService);  // null
    }
    
    // ========== 第2阶段:属性赋值 ==========
    // Spring自动完成,不需要我们写代码
    // 这个阶段会给userName和userService赋值
    
    // ========== 第3阶段:BeanNameAware接口 ==========
    
    /**
     * 设置Bean的名字
     * Spring会调用这个方法,告诉Bean它在容器中的名字
     */
    @Override
    public void setBeanName(String name) {
        System.out.println("\n【阶段3-BeanNameAware】设置Bean名称");
        System.out.println("  Bean的名字是: " + name);
        this.beanName = name;
    }
    
    // ========== 第4阶段:BeanFactoryAware接口 ==========
    
    /**
     * 设置BeanFactory
     * 通过这个接口,Bean可以获取到创建它的工厂
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("\n【阶段4-BeanFactoryAware】设置BeanFactory");
        System.out.println("  BeanFactory类型: " + beanFactory.getClass().getSimpleName());
        // 可以使用beanFactory获取其他Bean
    }
    
    // ========== 第5阶段:ApplicationContextAware接口 ==========
    
    /**
     * 设置ApplicationContext
     * 这是最强大的Aware接口,可以获取应用上下文的所有功能
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("\n【阶段5-ApplicationContextAware】设置ApplicationContext");
        System.out.println("  ApplicationContext类型: " + applicationContext.getClass().getSimpleName());
        System.out.println("  容器中的Bean数量: " + applicationContext.getBeanDefinitionCount());
    }
    
    // ========== 第6阶段:BeanPostProcessor前置处理 ==========
    // 这个方法在MyBeanPostProcessor类中实现
    
    // ========== 第7阶段:@PostConstruct注解 ==========
    
    /**
     * 初始化方法1:@PostConstruct注解
     * 这是推荐的初始化方法,因为它是JSR-250标准,不依赖Spring
     * 此时属性已经注入完成,可以安全使用
     */
    @PostConstruct
    public void postConstructMethod() {
        System.out.println("\n【阶段7-@PostConstruct】执行初始化方法");
        System.out.println("  此时userName的值: " + userName);  // 已经有值了
        System.out.println("  执行初始化逻辑,例如:加载配置、建立连接等");
    }
    
    // ========== 第8阶段:InitializingBean接口 ==========
    
    /**
     * 初始化方法2:InitializingBean接口的afterPropertiesSet方法
     * 这个方法在@PostConstruct之后执行
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("\n【阶段8-InitializingBean】afterPropertiesSet方法执行");
        System.out.println("  执行初始化逻辑");
    }
    
    // ========== 第9阶段:自定义init-method ==========
    // 在配置类中通过@Bean(initMethod="customInit")指定
    
    /**
     * 初始化方法3:自定义初始化方法
     * 需要在@Bean注解中通过initMethod属性指定
     */
    public void customInit() {
        System.out.println("\n【阶段9-自定义init-method】customInit方法执行");
        System.out.println("  执行初始化逻辑");
    }
    
    // ========== 第10阶段:BeanPostProcessor后置处理 ==========
    // 这个方法在MyBeanPostProcessor类中实现
    
    // ========== Bean可以使用了!==========
    
    /**
     * 业务方法:Bean初始化完成后,可以执行正常的业务逻辑
     */
    public void doSomething() {
        System.out.println("\n【Bean使用中】执行业务方法");
        System.out.println("  Bean名称: " + beanName);
        System.out.println("  用户名: " + userName);
    }
    
    // ========== 销毁阶段开始 ==========
    
    // ========== 第11阶段:@PreDestroy注解 ==========
    
    /**
     * 销毁方法1:@PreDestroy注解
     * 这是推荐的销毁方法,因为它是JSR-250标准
     * 在容器关闭时自动调用
     */
    @PreDestroy
    public void preDestroyMethod() {
        System.out.println("\n========== Bean销毁阶段开始 ==========");
        System.out.println("【阶段11-@PreDestroy】执行销毁前的清理工作");
        System.out.println("  例如:关闭连接、释放资源、保存数据等");
    }
    
    // ========== 第12阶段:DisposableBean接口 ==========
    
    /**
     * 销毁方法2:DisposableBean接口的destroy方法
     * 在@PreDestroy之后执行
     */
    @Override
    public void destroy() throws Exception {
        System.out.println("\n【阶段12-DisposableBean】destroy方法执行");
        System.out.println("  执行清理逻辑");
    }
    
    // ========== 第13阶段:自定义destroy-method ==========
    // 在配置类中通过@Bean(destroyMethod="customDestroy")指定
    
    /**
     * 销毁方法3:自定义销毁方法
     * 需要在@Bean注解中通过destroyMethod属性指定
     */
    public void customDestroy() {
        System.out.println("\n【阶段13-自定义destroy-method】customDestroy方法执行");
        System.out.println("  执行清理逻辑");
        System.out.println("========== Bean生命周期结束 ==========\n");
    }
}

// ========== 模拟的UserService ==========

@Component
class UserService {
    public UserService() {
        System.out.println("  [依赖Bean] UserService被创建");
    }
}

7.2 BeanPostProcessor实现类

java 复制代码
package com.example.demo.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

/**
 * Bean后置处理器
 * 会对容器中的所有Bean生效
 * 
 * @author Demo
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    
    /**
     * 初始化之前调用
     * 在@PostConstruct之前执行
     * 
     * @param bean Bean实例
     * @param beanName Bean名称
     * @return 返回的Bean(可以是原始Bean,也可以是包装后的Bean)
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 只处理我们关心的Bean,避免影响其他Bean
        if (bean instanceof CompleteLifeCycleBean) {
            System.out.println("\n【阶段6-BeanPostProcessor前置】处理Bean: " + beanName);
            System.out.println("  可以在这里修改Bean或返回代理对象");
        }
        return bean;  // 返回原始Bean或修改后的Bean
    }
    
    /**
     * 初始化之后调用
     * 在所有初始化方法(@PostConstruct、afterPropertiesSet、init-method)之后执行
     * 
     * @param bean Bean实例
     * @param beanName Bean名称
     * @return 返回的Bean(可以是原始Bean,也可以是代理对象)
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 只处理我们关心的Bean
        if (bean instanceof CompleteLifeCycleBean) {
            System.out.println("\n【阶段10-BeanPostProcessor后置】处理Bean: " + beanName);
            System.out.println("  可以在这里返回代理对象(AOP就是在这里实现的)");
            System.out.println("\n========== Bean初始化完成,可以使用了!==========");
        }
        return bean;  // 返回原始Bean或代理对象
    }
}

7.3 测试类

java 复制代码
package com.example.demo;

import com.example.demo.bean.CompleteLifeCycleBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 测试类:演示Bean的完整生命周期
 * 
 * @author Demo
 */
@Configuration
@ComponentScan("com.example.demo")  // 扫描包
public class BeanLifeCycleTest {
    
    public static void main(String[] args) {
        System.out.println("==================== 开始启动Spring容器 ====================\n");
        
        // 1. 创建Spring容器
        AnnotationConfigApplicationContext context = 
            new AnnotationConfigApplicationContext(BeanLifeCycleTest.class);
        
        System.out.println("\n==================== Spring容器启动完成 ====================");
        
        // 2. 从容器中获取Bean
        CompleteLifeCycleBean bean = context.getBean(CompleteLifeCycleBean.class);
        
        // 3. 使用Bean
        bean.doSomething();
        
        System.out.println("\n==================== 准备关闭Spring容器 ====================");
        
        // 4. 关闭容器(会触发Bean的销毁)
        context.close();
        
        System.out.println("\n==================== Spring容器已关闭 ====================");
    }
}

8. 常见面试问题及答案 💡

8.1 基础问题

❓ Q1:什么是Spring Bean?

答案

Spring Bean是由Spring IoC容器管理的对象。具有以下特点:

  1. 由容器创建:不需要程序员手动new对象
  2. 由容器管理:生命周期由Spring管理
  3. 支持依赖注入:自动注入所需依赖
  4. 支持AOP:可方便地应用切面编程
java 复制代码
// 传统方式
UserService userService = new UserService();

// Spring方式
@Component
public class UserService { }

❓ Q2:Bean是否单例?单例和原型的区别?

答案是的,默认是单例(Singleton)

对比表

对比项 Singleton Prototype
创建时机 容器启动时 getBean时
实例数量 一个 多个
销毁管理 容器销毁 不销毁
线程安全 需注意 天然安全

❓ Q3:Bean生命周期有哪些阶段?

答案13个阶段

记忆口诀:创建→赋值→感知→前置→初始化→后置→使用→销毁

1-2. 实例化、属性赋值

3-5. Aware接口回调

  1. BeanPostProcessor前置

7-9. 三种初始化方法

  1. BeanPostProcessor后置

11-13. 三种销毁方法


❓ Q4:Prototype Bean为什么不会被销毁?

答案

Spring对Prototype Bean只负责创建,不负责销毁

原因

  • Prototype Bean可能有多个实例
  • 容器无法追踪所有实例
  • 生命周期由使用者管理

手动销毁

java 复制代码
@Component
@Scope("prototype")
public class PrototypeBean implements DisposableBean {
    public void destroy() {
        // 清理资源
    }
}

// 使用时手动调用
bean.destroy();

8.2 进阶问题

❓ Q5:Spring Bean有哪些作用域?

答案6种作用域

作用域 说明 适用
singleton 单例(默认) 所有
prototype 每次新建 所有
request 每请求一个 Web
session 每会话一个 Web
application 每应用一个 Web
websocket 每连接一个 WS

❓ Q6:如何在Bean加载/销毁时执行自定义逻辑?

答案3种方式

方式1(推荐)

java 复制代码
@Component
public class MyBean {
    @PostConstruct
    public void init() { }
    
    @PreDestroy
    public void cleanup() { }
}

方式2

java 复制代码
public class MyBean implements InitializingBean, DisposableBean {
    public void afterPropertiesSet() { }
    public void destroy() { }
}

方式3

java 复制代码
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyBean myBean() { return new MyBean(); }

执行顺序:@PostConstruct → InitializingBean → init-method


❓ Q7:什么是BeanPostProcessor?

答案

Bean后置处理器,在Bean初始化前后进行处理。

作用

  • 修改Bean属性
  • 返回代理对象(AOP实现原理)
  • 添加日志、监控等
java 复制代码
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name) {
        // 初始化前处理
        return bean;
    }
    
    public Object postProcessAfterInitialization(Object bean, String name) {
        // 初始化后处理(AOP在这里返回代理)
        return bean;
    }
}

❓ Q8:Aware接口有什么作用?

答案

让Bean感知Spring容器,获取容器资源。

常用Aware接口

接口 作用
BeanNameAware 获取Bean名称
BeanFactoryAware 获取BeanFactory
ApplicationContextAware 获取ApplicationContext
EnvironmentAware 获取环境变量
java 复制代码
@Component
public class MyBean implements ApplicationContextAware {
    private ApplicationContext context;
    
    public void setApplicationContext(ApplicationContext ctx) {
        this.context = ctx;
    }
}

8.3 高级问题

❓ Q9:Spring如何解决循环依赖?

答案

通过三级缓存解决单例Bean的循环依赖。

三级缓存

  1. 一级缓存(singletonObjects):完整Bean
  2. 二级缓存(earlySingletonObjects):早期Bean
  3. 三级缓存(singletonFactories):Bean工厂

解决过程

复制代码
1. 创建A,放入三级缓存
2. A注入B,开始创建B
3. B注入A,从三级缓存获取A(半成品)
4. B初始化完成,放入一级缓存
5. A获得B,完成初始化,放入一级缓存

限制

  • ❌ 无法解决:Prototype作用域、构造器注入
  • ✅ 可以解决:Singleton的属性注入

解决方案

java 复制代码
@Lazy  // 延迟加载
@Autowired
private BeanB beanB;

❓ Q10:如何保证单例Bean线程安全?

答案

5种方案

1. 无状态Bean(推荐)

java 复制代码
@Component
public class SafeBean {
    // 没有成员变量,天然线程安全
    public int add(int a, int b) {
        return a + b;
    }
}

2. ThreadLocal

java 复制代码
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);

3. synchronized

java 复制代码
public synchronized void increment() { count++; }

4. 原子类

java 复制代码
private AtomicInteger count = new AtomicInteger(0);

5. Prototype作用域

java 复制代码
@Scope("prototype")

最佳实践

  • Service/DAO层:无状态单例
  • Controller层:不用成员变量存请求数据
  • 有状态Bean:使用Prototype或ThreadLocal

9. 总结 📝

9.1 核心要点

Bean是Spring IoC容器管理的对象,默认单例

生命周期13个阶段:创建→赋值→感知→前置→初始化→后置→使用→销毁

推荐使用@PostConstruct/@PreDestroy做初始化/销毁

Prototype Bean不会被容器销毁,需手动管理

单例Bean要注意线程安全,优先设计无状态

9.2 最佳实践

  1. 初始化/销毁:优先@PostConstruct/@PreDestroy
  2. Service层:无状态单例Bean
  3. 有状态Bean:Prototype作用域
  4. 避免循环依赖:使用@Lazy或重新设计
  5. 线程安全:无状态设计或ThreadLocal

9.3 常见陷阱

⚠️ Prototype Bean不会调用销毁方法

⚠️ 单例Bean的成员变量线程不安全

⚠️ 构造器注入的循环依赖无法解决

⚠️ 构造方法中依赖尚未注入


📚 参考资料


🎉 恭喜你完成学习!

通过本文,你已经掌握了Spring Bean生命周期的完整知识体系,包括基础概念、详细流程、实战代码和面试要点。

如果这篇文章对你有帮助,请点赞👍、收藏⭐、分享📤!


相关推荐
计算机程序设计小李同学2 小时前
基于JavaServer Pages(JSP)技术开发的食谱分享平台
java·开发语言
阿达King哥2 小时前
hotspot中的Java类对象如何保存虚函数
java·jvm
啦啦啦_99992 小时前
SSE(Server-Sent Events)
java
源代码•宸2 小时前
大厂技术岗面试之一面(准备自我介绍、反问)
经验分享·后端·算法·面试·职场和发展·golang·反问
我是一只小青蛙8882 小时前
C++模板进阶技巧全解析
java·开发语言
组合缺一2 小时前
FastJson2 与 SnackJson4 有什么区别?
java·json·fastjson·snackjson
卓怡学长3 小时前
m111基于MVC的舞蹈网站的设计与实现
java·前端·数据库·spring boot·spring·mvc
存在的五月雨3 小时前
Redis的一些使用
java·数据库·redis
Elias不吃糖10 小时前
Java Lambda 表达式
java·开发语言·学习