Files
timi-inject/src/main/java/com/imyeyu/inject/BeanFactory.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

262 lines
8.0 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.Inject;
import com.imyeyu.inject.annotation.Qualifier;
import com.imyeyu.inject.annotation.ScopeType;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.utils.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.LinkedHashSet;
import java.util.List;
/**
* Bean 工厂,负责创建和管理 Bean 实例
*
* @author 夜雨
* @since 2026-01-12 23:38
*/
public class BeanFactory {
private static final Logger log = LoggerFactory.getLogger(BeanFactory.class);
private final BeanContext context;
private final StartupStatistics statistics;
private final LinkedHashSet<String> creating = new LinkedHashSet<>();
public BeanFactory(BeanContext context, StartupStatistics statistics) {
this.context = context;
this.statistics = statistics;
}
/**
* 获取 Bean 实例
*
* @param name 名称
* @return 实例
*/
public Object getBean(String name) {
BeanDefinition definition = context.getDefinition(name);
if (definition == null) {
throw new InjectException("Bean [%s] not found".formatted(name));
}
if (definition.getScope() == ScopeType.SINGLETON) {
Object singleton = context.getSingleton(name);
if (singleton != null) {
return singleton;
}
}
return createBean(definition);
}
/**
* 获取 Bean 实例
*
* @param type 类型
* @return 实例
* @param <T> 实例类型
*/
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> type) {
List<String> candidates = context.getBeanNamesByType(type);
if (candidates.isEmpty()) {
throw new InjectException("Bean not found for type: %s".formatted(type.getName()));
}
if (candidates.size() == 1) {
return (T) getBean(candidates.getFirst());
}
String primaryBean = findPrimaryBean(candidates);
if (primaryBean != null) {
return (T) getBean(primaryBean);
}
throw new InjectException("Multiple beans found for type %s: %s - use @Qualifier or @Primary to specify which one to inject".formatted(type.getName(), candidates));
}
/**
* 获取 Bean 实例
*
* @param name 名称
* @param type 类型
* @return 实例
* @param <T> 实例类型
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String name, Class<T> type) {
Object bean = getBean(name);
if (!type.isInstance(bean)) {
throw new InjectException("Bean [%s] is not of type %s".formatted(name, type.getName()));
}
return (T) bean;
}
private Object createBean(BeanDefinition definition) {
String name = definition.getName();
if (creating.contains(name)) {
StringBuilder errorMsg = new StringBuilder("Circular dependency detected:\n");
for (String bean : creating) {
errorMsg.append(" ").append(bean).append("\n -> ");
}
errorMsg.append(name).append(" (attempting to create again)");
throw new InjectException(errorMsg.toString());
}
creating.add(name);
try {
Object instance;
if (definition.isFactoryBean()) {
instance = createBeanFromFactoryMethod(definition);
} else {
instance = createBeanFromConstructor(definition);
}
if (definition.getScope() == ScopeType.SINGLETON) {
context.setSingleton(name, instance);
}
invokePostConstruct(instance, definition);
log.debug("Created bean: {} ({})", name, definition.getType().getName());
return instance;
} finally {
creating.remove(name);
}
}
private Object createBeanFromConstructor(BeanDefinition definition) {
Constructor<?> constructor = definition.getConstructor();
constructor.setAccessible(true);
Parameter[] parameters = constructor.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
args[i] = resolveDependency(parameters[i]);
}
try {
return constructor.newInstance(args);
} catch (Exception e) {
throw new InjectException("Failed to instantiate Bean [%s]".formatted(definition.getName()), e);
}
}
private Object createBeanFromFactoryMethod(BeanDefinition definition) {
Method factoryMethod = definition.getFactoryMethod();
factoryMethod.setAccessible(true);
String configBeanName = extractConfigBeanName(factoryMethod.getDeclaringClass());
Object configInstance = getBean(configBeanName);
Parameter[] parameters = factoryMethod.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
args[i] = resolveDependency(parameters[i]);
}
try {
Object result = factoryMethod.invoke(configInstance, args);
if (result == null) {
throw new InjectException("@Bean method returned null: %s".formatted(factoryMethod.getName()));
}
return result;
} catch (Exception e) {
throw new InjectException("Failed to invoke @Bean method: %s".formatted(factoryMethod.getName()), e);
}
}
private Object resolveDependency(Parameter parameter) {
Qualifier qualifier = parameter.getAnnotation(Qualifier.class);
if (qualifier != null) {
return getBean(qualifier.value());
}
Class<?> type = parameter.getType();
return getBean(type);
}
private void invokePostConstruct(Object instance, BeanDefinition definition) {
List<Method> postConstructs = definition.getPostConstructs();
if (postConstructs.isEmpty()) {
return;
}
statistics.postConstructCurrentStart = Time.now();
for (Method postConstruct : postConstructs) {
postConstruct.setAccessible(true);
try {
postConstruct.invoke(instance);
statistics.postConstructInvocations++;
} catch (Exception e) {
throw new InjectException("Failed to invoke @PostConstruct method: %s".formatted(postConstruct.getName()), e);
}
}
statistics.postConstructTotalTime += Time.now() - statistics.postConstructCurrentStart;
statistics.postConstructCurrentStart = 0;
}
private String findPrimaryBean(List<String> candidates) {
String primaryBean = null;
for (String name : candidates) {
BeanDefinition definition = context.getDefinition(name);
if (definition.isPrimary()) {
if (primaryBean != null) {
throw new InjectException("Multiple @Primary beans found: Bean [%s] and Bean [%s]".formatted(primaryBean, name));
}
primaryBean = name;
}
}
return primaryBean;
}
private String extractConfigBeanName(Class<?> configClass) {
return Character.toLowerCase(configClass.getSimpleName().charAt(0)) + configClass.getSimpleName().substring(1);
}
/**
* 初始化所有单例 Bean跳过懒加载的单例
*/
public void initializeSingletons() {
statistics.iocStartTime = Time.now();
for (BeanDefinition definition : context.getAllDefinitions()) {
if (definition.getScope() == ScopeType.SINGLETON && !definition.isLazy()) {
getBean(definition.getName());
}
}
statistics.iocEndTime = Time.now();
}
/**
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
*
* @param target 需要注入依赖的目标对象
*/
public void injectFields(Object target) {
if (target == null) {
throw new InjectException("Cannot inject fields into null object");
}
statistics.injectionCurrentStart = Time.now();
Class<?> clazz = target.getClass();
for (Field field : Ref.listAllFields(target.getClass())) {
if (field.isAnnotationPresent(Inject.class)) {
field.setAccessible(true);
try {
Object dependency = resolveDependencyForField(field);
field.set(target, dependency);
statistics.injectedFields++;
log.debug("Injected field: {}.{}", clazz.getSimpleName(), field.getName());
} catch (IllegalAccessException e) {
throw new InjectException("Failed to inject field: %s".formatted(field.getName()), e);
}
}
}
statistics.injectionTotalTime += Time.now() - statistics.injectionCurrentStart;
}
private Object resolveDependencyForField(Field field) {
Qualifier qualifier = field.getAnnotation(Qualifier.class);
if (qualifier != null) {
return getBean(qualifier.value());
}
Class<?> type = field.getType();
return getBean(type);
}
}