feat(ioc): add lifecycle visualization lab for class loading and bean scopes

This commit is contained in:
likingcode
2026-03-10 09:47:26 +08:00
parent 4bfb2fa250
commit 4cc94152a9
7 changed files with 404 additions and 112 deletions

View File

@@ -1,10 +1,10 @@
package com.example.scaffold.learning;
import com.example.scaffold.aop.PerformanceAspect;
import com.example.scaffold.learning.lifecycle.IocLifecycleTracker;
import com.example.scaffold.learning.lifecycle.LifecycleDemoBean;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.*;
@@ -30,6 +30,7 @@ public class IocLearningController {
private final ApplicationContext applicationContext;
private final PerformanceAspect performanceAspect;
private final IocLifecycleTracker lifecycleTracker;
// 演示字段注入(不推荐,但可以用)
// 注意session scope 的 bean 需要在 web 上下文中使用
@@ -99,10 +100,77 @@ public class IocLearningController {
"prototype", "原型 - 每次请求都创建新实例",
"request", "请求 - 每个 HTTP 请求一个实例",
"session", "会话 - 每个 HTTP 会话一个实例",
"lazySingleton", "懒加载单例 - 第一次真正获取时才创建",
"tip", "session scope 需要在 web 请求上下文中使用"
);
}
/**
* IoC 生命周期总览
*/
@GetMapping("/lifecycle/overview")
public Map<String, Object> lifecycleOverview() {
return Map.of(
"jvmClassLoading", List.of(
"类第一次被引用时才可能触发加载",
"static 代码块在类初始化阶段执行一次",
"类加载成功后JVM 里就有这个 Class 元数据"
),
"springBeanLifecycle", List.of(
"扫描 BeanDefinition",
"实例化 Beansingleton 通常在启动阶段)",
"依赖注入",
"@PostConstruct 初始化",
"放入单例池或按 scope 管理",
"容器关闭时销毁 singleton Bean"
),
"coreIdea", "类加载 ≠ Bean 创建Bean 创建 ≠ 每次请求都 new"
);
}
@GetMapping("/lifecycle/timeline")
public List<Map<String, Object>> lifecycleTimeline() {
return lifecycleTracker.snapshot();
}
@PostMapping("/lifecycle/reset")
public Map<String, String> resetLifecycleTimeline() {
lifecycleTracker.reset();
return Map.of("status", "ok", "message", "生命周期时间线已重置");
}
@GetMapping("/lifecycle/inspect/{scopeType}")
public Map<String, Object> inspectLifecycleBean(@PathVariable String scopeType,
@RequestParam(defaultValue = "manual-check") String trigger) {
String beanName = switch (scopeType) {
case "prototype" -> "iocPrototypeLifecycleBean";
case "lazy" -> "iocLazySingletonLifecycleBean";
default -> "iocSingletonLifecycleBean";
};
LifecycleDemoBean bean = (LifecycleDemoBean) applicationContext.getBean(beanName);
Map<String, Object> result = new LinkedHashMap<>(bean.inspect(trigger));
result.put("beanNameFromContext", beanName);
result.put("isSingletonInContext", applicationContext.isSingleton(beanName));
result.put("timelineSize", lifecycleTracker.snapshot().size());
return result;
}
@GetMapping("/lifecycle/compare")
public Map<String, Object> compareScopes() {
LifecycleDemoBean singleton1 = (LifecycleDemoBean) applicationContext.getBean("iocSingletonLifecycleBean");
LifecycleDemoBean singleton2 = (LifecycleDemoBean) applicationContext.getBean("iocSingletonLifecycleBean");
LifecycleDemoBean prototype1 = (LifecycleDemoBean) applicationContext.getBean("iocPrototypeLifecycleBean");
LifecycleDemoBean prototype2 = (LifecycleDemoBean) applicationContext.getBean("iocPrototypeLifecycleBean");
LifecycleDemoBean lazy1 = (LifecycleDemoBean) applicationContext.getBean("iocLazySingletonLifecycleBean");
LifecycleDemoBean lazy2 = (LifecycleDemoBean) applicationContext.getBean("iocLazySingletonLifecycleBean");
return Map.of(
"singleton", List.of(singleton1.inspect("compare-1"), singleton2.inspect("compare-2")),
"prototype", List.of(prototype1.inspect("compare-1"), prototype2.inspect("compare-2")),
"lazySingleton", List.of(lazy1.inspect("compare-1"), lazy2.inspect("compare-2"))
);
}
/**
* 性能统计
*/

View File

@@ -0,0 +1,39 @@
package com.example.scaffold.learning.lifecycle;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class IocLifecycleTracker {
private static final AtomicLong SEQ = new AtomicLong(0);
private static final CopyOnWriteArrayList<Map<String, Object>> EVENTS = new CopyOnWriteArrayList<>();
public static void record(String beanName, String scope, String phase, String detail) {
Map<String, Object> event = new LinkedHashMap<>();
event.put("seq", SEQ.incrementAndGet());
event.put("time", Instant.now().toString());
event.put("beanName", beanName);
event.put("scope", scope);
event.put("phase", phase);
event.put("detail", detail);
EVENTS.add(event);
}
public List<Map<String, Object>> snapshot() {
return new ArrayList<>(EVENTS);
}
public void reset() {
EVENTS.clear();
SEQ.set(0);
record("system", "n/a", "reset", "实验日志已重置");
}
}

View File

@@ -0,0 +1,51 @@
package com.example.scaffold.learning.lifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Component("iocLazySingletonLifecycleBean")
@Lazy
public class LazySingletonLifecycleBean implements LifecycleDemoBean {
static {
IocLifecycleTracker.record("iocLazySingletonLifecycleBean", "lazy-singleton", "class-load", "JVM 初始化类static block");
}
private final String instanceId = UUID.randomUUID().toString().substring(0, 8);
private final AtomicInteger accessCount = new AtomicInteger(0);
public LazySingletonLifecycleBean() {
IocLifecycleTracker.record("iocLazySingletonLifecycleBean", "lazy-singleton", "constructor", "构造器执行,说明第一次真正获取才创建实例");
}
@PostConstruct
public void init() {
IocLifecycleTracker.record("iocLazySingletonLifecycleBean", "lazy-singleton", "post-construct", "Lazy 单例初始化完成,并被放入单例缓存");
}
@PreDestroy
public void destroy() {
IocLifecycleTracker.record("iocLazySingletonLifecycleBean", "lazy-singleton", "pre-destroy", "容器关闭时销毁 lazy singleton");
}
@Override
public Map<String, Object> inspect(String trigger) {
int count = accessCount.incrementAndGet();
IocLifecycleTracker.record("iocLazySingletonLifecycleBean", "lazy-singleton", "method-call", "trigger=" + trigger + ", accessCount=" + count);
Map<String, Object> result = new LinkedHashMap<>();
result.put("beanName", "iocLazySingletonLifecycleBean");
result.put("scope", "lazy-singleton");
result.put("instanceId", instanceId);
result.put("identityHashCode", System.identityHashCode(this));
result.put("accessCount", count);
result.put("explanation", "Lazy 单例不会在容器启动时创建,而是在第一次 getBean 时才真正实例化");
return result;
}
}

View File

@@ -0,0 +1,7 @@
package com.example.scaffold.learning.lifecycle;
import java.util.Map;
public interface LifecycleDemoBean {
Map<String, Object> inspect(String trigger);
}

View File

@@ -0,0 +1,51 @@
package com.example.scaffold.learning.lifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Component("iocPrototypeLifecycleBean")
@Scope("prototype")
public class PrototypeLifecycleBean implements LifecycleDemoBean {
static {
IocLifecycleTracker.record("iocPrototypeLifecycleBean", "prototype", "class-load", "JVM 初始化类static block仅第一次");
}
private final String instanceId = UUID.randomUUID().toString().substring(0, 8);
private final AtomicInteger accessCount = new AtomicInteger(0);
public PrototypeLifecycleBean() {
IocLifecycleTracker.record("iocPrototypeLifecycleBean", "prototype", "constructor", "构造器执行实例ID=" + instanceId);
}
@PostConstruct
public void init() {
IocLifecycleTracker.record("iocPrototypeLifecycleBean", "prototype", "post-construct", "原型 Bean 初始化完成");
}
@PreDestroy
public void destroy() {
IocLifecycleTracker.record("iocPrototypeLifecycleBean", "prototype", "pre-destroy", "注意prototype Bean 默认不会由容器统一销毁");
}
@Override
public Map<String, Object> inspect(String trigger) {
int count = accessCount.incrementAndGet();
IocLifecycleTracker.record("iocPrototypeLifecycleBean", "prototype", "method-call", "trigger=" + trigger + ", accessCount=" + count);
Map<String, Object> result = new LinkedHashMap<>();
result.put("beanName", "iocPrototypeLifecycleBean");
result.put("scope", "prototype");
result.put("instanceId", instanceId);
result.put("identityHashCode", System.identityHashCode(this));
result.put("accessCount", count);
result.put("explanation", "原型 Bean 每次获取都会创建新实例,因此实例 ID / hashCode 会变化");
return result;
}
}

View File

@@ -0,0 +1,49 @@
package com.example.scaffold.learning.lifecycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Component("iocSingletonLifecycleBean")
public class SingletonLifecycleBean implements LifecycleDemoBean {
static {
IocLifecycleTracker.record("iocSingletonLifecycleBean", "singleton", "class-load", "JVM 初始化类static block");
}
private final String instanceId = UUID.randomUUID().toString().substring(0, 8);
private final AtomicInteger accessCount = new AtomicInteger(0);
public SingletonLifecycleBean() {
IocLifecycleTracker.record("iocSingletonLifecycleBean", "singleton", "constructor", "构造器执行实例ID=" + instanceId);
}
@PostConstruct
public void init() {
IocLifecycleTracker.record("iocSingletonLifecycleBean", "singleton", "post-construct", "Bean 初始化完成,已进入单例生命周期");
}
@PreDestroy
public void destroy() {
IocLifecycleTracker.record("iocSingletonLifecycleBean", "singleton", "pre-destroy", "容器关闭时销毁单例 Bean");
}
@Override
public Map<String, Object> inspect(String trigger) {
int count = accessCount.incrementAndGet();
IocLifecycleTracker.record("iocSingletonLifecycleBean", "singleton", "method-call", "trigger=" + trigger + ", accessCount=" + count);
Map<String, Object> result = new LinkedHashMap<>();
result.put("beanName", "iocSingletonLifecycleBean");
result.put("scope", "singleton");
result.put("instanceId", instanceId);
result.put("identityHashCode", System.identityHashCode(this));
result.put("accessCount", count);
result.put("explanation", "单例 Bean 在容器中通常只创建一次,后续每次获取都是同一个实例");
return result;
}
}