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:
@ -1,216 +1,261 @@
|
||||
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.*;
|
||||
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 static final Logger log = LoggerFactory.getLogger(BeanFactory.class);
|
||||
|
||||
private final BeanContext context;
|
||||
private final Set<String> creating = new HashSet<>();
|
||||
private final BeanContext context;
|
||||
private final StartupStatistics statistics;
|
||||
private final LinkedHashSet<String> creating = new LinkedHashSet<>();
|
||||
|
||||
public BeanFactory(BeanContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
public BeanFactory(BeanContext context, StartupStatistics statistics) {
|
||||
this.context = context;
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例(按名称)
|
||||
*/
|
||||
public Object getBean(String name) {
|
||||
BeanDefinition definition = context.getDefinition(name);
|
||||
if (definition == null) {
|
||||
throw new InjectException("No bean definition found for name: " + name);
|
||||
}
|
||||
/**
|
||||
* 获取 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);
|
||||
}
|
||||
|
||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||
Object singleton = context.getSingleton(name);
|
||||
if (singleton != null) {
|
||||
return singleton;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取 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));
|
||||
}
|
||||
|
||||
return createBean(definition);
|
||||
}
|
||||
/**
|
||||
* 获取 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例(按类型)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(Class<T> type) {
|
||||
List<String> candidates = context.getBeanNamesByType(type);
|
||||
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;
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
throw new InjectException("No bean found for type: " + type.getName());
|
||||
}
|
||||
} finally {
|
||||
creating.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.size() == 1) {
|
||||
return (T) getBean(candidates.get(0));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
String primaryBean = findPrimaryBean(candidates);
|
||||
if (primaryBean != null) {
|
||||
return (T) getBean(primaryBean);
|
||||
}
|
||||
private Object createBeanFromFactoryMethod(BeanDefinition definition) {
|
||||
Method factoryMethod = definition.getFactoryMethod();
|
||||
factoryMethod.setAccessible(true);
|
||||
|
||||
throw new InjectException("Multiple beans found for type " + type.getName() +
|
||||
": " + candidates + ". Use @Qualifier or @Primary to specify which one to inject");
|
||||
}
|
||||
String configBeanName = extractConfigBeanName(factoryMethod.getDeclaringClass());
|
||||
Object configInstance = getBean(configBeanName);
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例(按名称和类型)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(String name, Class<T> type) {
|
||||
Object bean = getBean(name);
|
||||
if (!type.isInstance(bean)) {
|
||||
throw new InjectException("Bean '" + name + "' is not of type " + type.getName());
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
Parameter[] parameters = factoryMethod.getParameters();
|
||||
Object[] args = new Object[parameters.length];
|
||||
|
||||
private Object createBean(BeanDefinition definition) {
|
||||
String name = definition.getName();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
args[i] = resolveDependency(parameters[i]);
|
||||
}
|
||||
|
||||
if (creating.contains(name)) {
|
||||
throw new InjectException("Circular dependency detected: " + name +
|
||||
" (creation chain: " + creating + ")");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
creating.add(name);
|
||||
try {
|
||||
Object instance;
|
||||
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);
|
||||
}
|
||||
|
||||
if (definition.isFactoryBean()) {
|
||||
instance = createBeanFromFactoryMethod(definition);
|
||||
} else {
|
||||
instance = createBeanFromConstructor(definition);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||
context.setSingleton(name, instance);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
invokePostConstruct(instance, definition);
|
||||
private String extractConfigBeanName(Class<?> configClass) {
|
||||
return Character.toLowerCase(configClass.getSimpleName().charAt(0)) + configClass.getSimpleName().substring(1);
|
||||
}
|
||||
|
||||
log.debug("Created bean: {} ({})", name, definition.getType().getName());
|
||||
return instance;
|
||||
/**
|
||||
* 初始化所有单例 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();
|
||||
}
|
||||
|
||||
} finally {
|
||||
creating.remove(name);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
|
||||
*
|
||||
* @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 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: " + 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: " + factoryMethod.getName());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to invoke @Bean method: " + 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) {
|
||||
Method postConstruct = definition.getPostConstruct();
|
||||
if (postConstruct == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
postConstruct.setAccessible(true);
|
||||
try {
|
||||
postConstruct.invoke(instance);
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to invoke @PostConstruct method: " +
|
||||
postConstruct.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
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: " + primaryBean + ", " + name);
|
||||
}
|
||||
primaryBean = name;
|
||||
}
|
||||
}
|
||||
|
||||
return primaryBean;
|
||||
}
|
||||
|
||||
private String extractConfigBeanName(Class<?> configClass) {
|
||||
String simpleName = configClass.getSimpleName();
|
||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有单例 Bean
|
||||
*/
|
||||
public void initializeSingletons() {
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||
getBean(definition.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user