feat(ioc): add lifecycle visualization lab for class loading and bean scopes
This commit is contained in:
@@ -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",
|
||||
"实例化 Bean(singleton 通常在启动阶段)",
|
||||
"依赖注入",
|
||||
"@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"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能统计
|
||||
*/
|
||||
|
||||
@@ -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", "实验日志已重置");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.example.scaffold.learning.lifecycle;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface LifecycleDemoBean {
|
||||
Map<String, Object> inspect(String trigger);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user