Files
timi-inject/src/main/java/com/imyeyu/inject/TimiInject.java
Timi f5c6dcd275 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>
2026-01-13 01:41:49 +08:00

249 lines
7.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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 控制反转框架
*
* @author 夜雨
*/
public class TimiInject {
private static final Logger log = LoggerFactory.getLogger(TimiInject.class);
private final BeanContext context;
private final BeanFactory factory;
private TimiInject(BeanContext context, BeanFactory factory) {
this.context = context;
this.factory = factory;
}
/**
* 启动应用并初始化容器
*/
public static TimiInject run(Class<?> applicationClass) {
return run(applicationClass, null);
}
/**
* 启动应用并初始化容器(支持初始化前注册外部 Bean
*
* @param initializer 初始化前回调
*/
public static TimiInject run(Class<?> applicationClass, CallbackArg<TimiInject> initializer) {
printBanner();
long startTime = Time.now();
StartupStatistics statistics = new StartupStatistics();
log.info("Initializing TimiInject for {} ...", applicationClass.getSimpleName());
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
if (annotation == null) {
throw new InjectException("Application class must be annotated with @TimiInjectApplication");
}
BeanContext context = new BeanContext();
BeanFactory factory = new BeanFactory(context, statistics);
BeanScanner scanner = new BeanScanner(context, statistics);
String[] basePackages = annotation.value();
if (basePackages.length == 0) {
basePackages = new String[] {applicationClass.getPackage().getName()};
}
log.info("Scanning packages: {}", String.join(", ", basePackages));
scanner.scan(basePackages);
processImports(applicationClass, scanner, context);
log.info("Scanned {} classes, registered {} beans", statistics.scannedClasses, statistics.registeredBeans);
TimiInject inject = new TimiInject(context, factory);
if (initializer != null) {
initializer.handler(inject);
}
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;
}
/**
* 获取 Bean 实例(按类型)
*/
public <T> T getBean(Class<T> type) {
return factory.getBean(type);
}
/**
* 获取 Bean 实例(按名称和类型)
*/
public <T> T getBean(String name, Class<T> type) {
return factory.getBean(name, type);
}
/**
* 注册 Bean 实例
*/
public void registerBean(String name, Object bean) {
context.registerSingleton(name, bean);
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
}
/**
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
*
* @param target 需要注入依赖的目标对象
*/
public void inject(Object target) {
factory.injectFields(target);
}
/**
* 导出依赖图
*/
public String exportDependencyGraph() {
StringBuilder graph = new StringBuilder();
graph.append("=== Dependency Graph ===\n\n");
for (BeanDefinition definition : context.getAllDefinitions()) {
graph.append(formatBeanInfo(definition));
graph.append("\n");
}
return graph.toString();
}
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");
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");
}
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");
}
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;
}
}
}