refactor: optimize string concatenation and add startup statistics
- Replace string concatenation with .formatted() method for better readability - Add StartupStatistics class to track initialization metrics - Add detailed startup logging with timing information - Scan time - IOC time - Injection time - PostConstruct time - Total startup time - Add banner output support (banner.txt or defBanner.txt) - Add @Lazy annotation support for lazy initialization Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -2,10 +2,19 @@ package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.Import;
|
||||
import com.imyeyu.inject.annotation.TimiInjectApplication;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
import com.imyeyu.utils.Text;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Timi 控制反转框架
|
||||
@ -14,138 +23,226 @@ import java.util.*;
|
||||
*/
|
||||
public class TimiInject {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TimiInject.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(TimiInject.class);
|
||||
|
||||
private final BeanContext context;
|
||||
private final BeanFactory factory;
|
||||
private final BeanContext context;
|
||||
private final BeanFactory factory;
|
||||
|
||||
private TimiInject(BeanContext context, BeanFactory factory) {
|
||||
this.context = context;
|
||||
this.factory = factory;
|
||||
}
|
||||
private TimiInject(BeanContext context, BeanFactory factory) {
|
||||
this.context = context;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动应用并初始化容器
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass, String... args) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("Starting {} ...", applicationClass.getSimpleName());
|
||||
/**
|
||||
* 启动应用并初始化容器
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass) {
|
||||
return run(applicationClass, null);
|
||||
}
|
||||
|
||||
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
|
||||
if (annotation == null) {
|
||||
throw new InjectException("Application class must be annotated with @TimiInjectApplication");
|
||||
}
|
||||
/**
|
||||
* 启动应用并初始化容器(支持初始化前注册外部 Bean)
|
||||
*
|
||||
* @param initializer 初始化前回调
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass, CallbackArg<TimiInject> initializer) {
|
||||
printBanner();
|
||||
|
||||
BeanContext context = new BeanContext();
|
||||
BeanFactory factory = new BeanFactory(context);
|
||||
BeanScanner scanner = new BeanScanner(context);
|
||||
long startTime = Time.now();
|
||||
StartupStatistics statistics = new StartupStatistics();
|
||||
|
||||
String[] basePackages = annotation.value();
|
||||
if (basePackages.length == 0) {
|
||||
basePackages = new String[]{applicationClass.getPackage().getName()};
|
||||
}
|
||||
log.info("Initializing TimiInject for {} ...", applicationClass.getSimpleName());
|
||||
|
||||
scanner.scan(basePackages);
|
||||
processImports(applicationClass, scanner, context);
|
||||
factory.initializeSingletons();
|
||||
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
|
||||
if (annotation == null) {
|
||||
throw new InjectException("Application class must be annotated with @TimiInjectApplication");
|
||||
}
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.info("Started {} in {} ms ({} beans)", applicationClass.getSimpleName(), elapsed,
|
||||
context.getAllDefinitions().size());
|
||||
BeanContext context = new BeanContext();
|
||||
BeanFactory factory = new BeanFactory(context, statistics);
|
||||
BeanScanner scanner = new BeanScanner(context, statistics);
|
||||
|
||||
return new TimiInject(context, factory);
|
||||
}
|
||||
String[] basePackages = annotation.value();
|
||||
if (basePackages.length == 0) {
|
||||
basePackages = new String[] {applicationClass.getPackage().getName()};
|
||||
}
|
||||
|
||||
/**
|
||||
* 依赖注入 - 获取 Bean 实例(按类型)
|
||||
*/
|
||||
public <T> T di(Class<T> type) {
|
||||
return factory.getBean(type);
|
||||
}
|
||||
log.info("Scanning packages: {}", String.join(", ", basePackages));
|
||||
scanner.scan(basePackages);
|
||||
processImports(applicationClass, scanner, context);
|
||||
log.info("Scanned {} classes, registered {} beans", statistics.scannedClasses, statistics.registeredBeans);
|
||||
|
||||
/**
|
||||
* 依赖注入 - 获取 Bean 实例(按名称和类型)
|
||||
*/
|
||||
public <T> T di(String name, Class<T> type) {
|
||||
return factory.getBean(name, type);
|
||||
}
|
||||
TimiInject inject = new TimiInject(context, factory);
|
||||
if (initializer != null) {
|
||||
initializer.handler(inject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制反转 - 手动注册 Bean 实例
|
||||
*/
|
||||
public void ioc(String name, Object bean) {
|
||||
context.registerSingleton(name, bean);
|
||||
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
||||
}
|
||||
log.info("Performing inversion of control...");
|
||||
factory.initializeSingletons();
|
||||
log.info("initialization completed!");
|
||||
log.info("\tTotal beans: {}", statistics.registeredBeans);
|
||||
log.info("\tInjected fields: {}", statistics.injectedFields);
|
||||
log.info("\tPostConstruct invocations: {}", statistics.postConstructInvocations);
|
||||
log.info("\tScan time: {} ms", statistics.scanEndTime - statistics.scanStartTime);
|
||||
log.info("\tIOC time: {} ms", statistics.iocEndTime - statistics.iocStartTime);
|
||||
log.info("\tInjection time: {} ms", statistics.injectionTotalTime);
|
||||
log.info("\tPostConstruct time: {} ms", statistics.postConstructTotalTime);
|
||||
log.info("\tTotal startup time: {} ms", Time.now() - startTime);
|
||||
return inject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出依赖图
|
||||
*/
|
||||
public String exportDependencyGraph() {
|
||||
StringBuilder graph = new StringBuilder();
|
||||
graph.append("=== Dependency Graph ===\n\n");
|
||||
/**
|
||||
* 获取 Bean 实例(按类型)
|
||||
*/
|
||||
public <T> T getBean(Class<T> type) {
|
||||
return factory.getBean(type);
|
||||
}
|
||||
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
graph.append(formatBeanInfo(definition));
|
||||
graph.append("\n");
|
||||
}
|
||||
/**
|
||||
* 获取 Bean 实例(按名称和类型)
|
||||
*/
|
||||
public <T> T getBean(String name, Class<T> type) {
|
||||
return factory.getBean(name, type);
|
||||
}
|
||||
|
||||
return graph.toString();
|
||||
}
|
||||
/**
|
||||
* 注册 Bean 实例
|
||||
*/
|
||||
public void registerBean(String name, Object bean) {
|
||||
context.registerSingleton(name, bean);
|
||||
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
||||
}
|
||||
|
||||
private String formatBeanInfo(BeanDefinition definition) {
|
||||
StringBuilder info = new StringBuilder();
|
||||
info.append("Bean: ").append(definition.getName()).append("\n");
|
||||
info.append(" Type: ").append(definition.getType().getName()).append("\n");
|
||||
info.append(" Scope: ").append(definition.getScope()).append("\n");
|
||||
info.append(" Primary: ").append(definition.isPrimary()).append("\n");
|
||||
/**
|
||||
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
|
||||
*
|
||||
* @param target 需要注入依赖的目标对象
|
||||
*/
|
||||
public void inject(Object target) {
|
||||
factory.injectFields(target);
|
||||
}
|
||||
|
||||
if (definition.isFactoryBean()) {
|
||||
info.append(" Factory: @Bean ").append(definition.getFactoryMethod().getName()).append("()\n");
|
||||
} else if (definition.getConstructor() != null) {
|
||||
info.append(" Constructor: ").append(definition.getConstructor().getParameterCount())
|
||||
.append(" parameters\n");
|
||||
}
|
||||
/**
|
||||
* 导出依赖图
|
||||
*/
|
||||
public String exportDependencyGraph() {
|
||||
StringBuilder graph = new StringBuilder();
|
||||
graph.append("=== Dependency Graph ===\n\n");
|
||||
|
||||
if (definition.getPostConstruct() != null) {
|
||||
info.append(" PostConstruct: ").append(definition.getPostConstruct().getName()).append("()\n");
|
||||
}
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
graph.append(formatBeanInfo(definition));
|
||||
graph.append("\n");
|
||||
}
|
||||
|
||||
return info.toString();
|
||||
}
|
||||
return graph.toString();
|
||||
}
|
||||
|
||||
private static void processImports(Class<?> applicationClass, BeanScanner scanner, BeanContext context) {
|
||||
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
||||
return;
|
||||
}
|
||||
private String formatBeanInfo(BeanDefinition definition) {
|
||||
StringBuilder info = new StringBuilder();
|
||||
info.append("Bean: ").append(definition.getName()).append("\n");
|
||||
info.append(" Type: ").append(definition.getType().getName()).append("\n");
|
||||
info.append(" Scope: ").append(definition.getScope()).append("\n");
|
||||
info.append(" Primary: ").append(definition.isPrimary()).append("\n");
|
||||
|
||||
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
||||
Class<?>[] importClasses = importAnnotation.value();
|
||||
if (definition.isFactoryBean()) {
|
||||
info.append(" Factory: @Bean ").append(definition.getFactoryMethod().getName()).append("()\n");
|
||||
} else if (definition.getConstructor() != null) {
|
||||
info.append(" Constructor: ").append(definition.getConstructor().getParameterCount()).append(" parameters\n");
|
||||
}
|
||||
|
||||
for (Class<?> importClass : importClasses) {
|
||||
if (Module.class.isAssignableFrom(importClass)) {
|
||||
try {
|
||||
Module module = (Module) importClass.getDeclaredConstructor().newInstance();
|
||||
module.configure(context);
|
||||
log.debug("Loaded module: {}", importClass.getName());
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to instantiate module: " + importClass.getName(), e);
|
||||
}
|
||||
} else {
|
||||
// 检查是否已经通过包扫描注册
|
||||
String beanName = extractBeanName(importClass);
|
||||
if (!context.containsBean(beanName)) {
|
||||
scanner.processClass(importClass);
|
||||
log.debug("Imported configuration: {}", importClass.getName());
|
||||
} else {
|
||||
log.debug("Skip importing already registered class: {}", importClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Method> postConstructs = definition.getPostConstructs();
|
||||
if (!postConstructs.isEmpty()) {
|
||||
info.append(" PostConstruct: ");
|
||||
for (int i = 0; i < postConstructs.size(); i++) {
|
||||
if (i > 0) {
|
||||
info.append(", ");
|
||||
}
|
||||
info.append(postConstructs.get(i).getName()).append("()");
|
||||
}
|
||||
info.append("\n");
|
||||
}
|
||||
|
||||
private static String extractBeanName(Class<?> clazz) {
|
||||
String simpleName = clazz.getSimpleName();
|
||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
||||
}
|
||||
return info.toString();
|
||||
}
|
||||
|
||||
private static void processImports(Class<?> applicationClass, BeanScanner scanner, BeanContext context) {
|
||||
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
||||
return;
|
||||
}
|
||||
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
||||
Class<?>[] importClasses = importAnnotation.value();
|
||||
for (Class<?> importClass : importClasses) {
|
||||
if (Module.class.isAssignableFrom(importClass)) {
|
||||
try {
|
||||
Module module = (Module) importClass.getDeclaredConstructor().newInstance();
|
||||
module.configure(context);
|
||||
log.debug("Loaded module: {}", importClass.getName());
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to instantiate module: %s".formatted(importClass.getName()), e);
|
||||
}
|
||||
} else {
|
||||
// 检查是否已经通过包扫描注册
|
||||
String beanName = Text.camelCaseClassName(importClass);
|
||||
if (!context.containsBean(beanName)) {
|
||||
scanner.processClass(importClass);
|
||||
log.debug("Imported configuration: {}", importClass.getName());
|
||||
} else {
|
||||
log.debug("Skip importing already registered class: {}", importClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出启动 Banner
|
||||
*/
|
||||
private static void printBanner() {
|
||||
String bannerContent = loadBanner();
|
||||
if (!bannerContent.isEmpty()) {
|
||||
System.out.println(bannerContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 Banner 内容
|
||||
* 优先加载 banner.txt,如果不存在则加载 defBanner.txt
|
||||
*
|
||||
* @return Banner 内容
|
||||
*/
|
||||
private static String loadBanner() {
|
||||
// 优先尝试加载自定义 banner.txt
|
||||
String banner = loadBannerFromResource("banner.txt");
|
||||
if (banner != null) {
|
||||
return banner;
|
||||
}
|
||||
// 加载默认 banner
|
||||
banner = loadBannerFromResource("defBanner.txt");
|
||||
if (banner != null) {
|
||||
return banner;
|
||||
}
|
||||
// 如果都加载失败,返回空字符串
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从资源文件加载 Banner
|
||||
*
|
||||
* @param resourceName 资源文件名
|
||||
* @return Banner 内容,如果加载失败返回 null
|
||||
*/
|
||||
private static String loadBannerFromResource(String resourceName) {
|
||||
try {
|
||||
InputStream inputStream = TimiInject.class.getClassLoader().getResourceAsStream(resourceName);
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
return reader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to load banner from {}: {}", resourceName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user