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
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
/.claude
|
/.claude
|
||||||
/CLAUDE.md
|
/CLAUDE.md
|
||||||
/AGENTS.md
|
/AGENTS.md
|
||||||
|
/logs
|
||||||
|
|
||||||
target/
|
target/
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|||||||
@ -9,11 +9,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
* Bean 容器上下文
|
* Bean 容器上下文
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
|
* @since 2026-01-12 23:37
|
||||||
*/
|
*/
|
||||||
public class BeanContext {
|
public class BeanContext {
|
||||||
|
|
||||||
private final Map<String, BeanDefinition> definitions = new ConcurrentHashMap<>();
|
|
||||||
private final Map<String, Object> singletons = new ConcurrentHashMap<>();
|
private final Map<String, Object> singletons = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, BeanDefinition> definitions = new ConcurrentHashMap<>();
|
||||||
private final Map<Class<?>, List<String>> typeIndex = new ConcurrentHashMap<>();
|
private final Map<Class<?>, List<String>> typeIndex = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,9 +23,8 @@ public class BeanContext {
|
|||||||
public void registerDefinition(BeanDefinition definition) {
|
public void registerDefinition(BeanDefinition definition) {
|
||||||
String name = definition.getName();
|
String name = definition.getName();
|
||||||
if (definitions.containsKey(name)) {
|
if (definitions.containsKey(name)) {
|
||||||
throw new InjectException("Bean '" + name + "' already registered");
|
throw new InjectException("Bean [%s] already registered".formatted(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
definitions.put(name, definition);
|
definitions.put(name, definition);
|
||||||
typeIndex.computeIfAbsent(definition.getType(), k -> new ArrayList<>()).add(name);
|
typeIndex.computeIfAbsent(definition.getType(), k -> new ArrayList<>()).add(name);
|
||||||
}
|
}
|
||||||
@ -34,16 +34,11 @@ public class BeanContext {
|
|||||||
*/
|
*/
|
||||||
public void registerSingleton(String name, Object bean) {
|
public void registerSingleton(String name, Object bean) {
|
||||||
if (bean == null) {
|
if (bean == null) {
|
||||||
throw new InjectException("Cannot register null bean: " + name);
|
throw new InjectException("Bean [%s] cannot be null".formatted(name));
|
||||||
}
|
}
|
||||||
singletons.put(name, bean);
|
singletons.put(name, bean);
|
||||||
|
|
||||||
if (!definitions.containsKey(name)) {
|
if (!definitions.containsKey(name)) {
|
||||||
BeanDefinition definition = BeanDefinition.builder()
|
BeanDefinition definition = BeanDefinition.builder().name(name).type(bean.getClass()).scope(ScopeType.SINGLETON).build();
|
||||||
.name(name)
|
|
||||||
.type(bean.getClass())
|
|
||||||
.scope(ScopeType.SINGLETON)
|
|
||||||
.build();
|
|
||||||
definitions.put(name, definition);
|
definitions.put(name, definition);
|
||||||
typeIndex.computeIfAbsent(bean.getClass(), k -> new ArrayList<>()).add(name);
|
typeIndex.computeIfAbsent(bean.getClass(), k -> new ArrayList<>()).add(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import com.imyeyu.inject.annotation.ScopeType;
|
|||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean 定义元数据
|
* Bean 定义元数据
|
||||||
@ -16,20 +18,22 @@ public class BeanDefinition {
|
|||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
private final ScopeType scope;
|
private final ScopeType scope;
|
||||||
private final boolean primary;
|
private final boolean primary;
|
||||||
|
private final boolean lazy;
|
||||||
private final Constructor<?> constructor;
|
private final Constructor<?> constructor;
|
||||||
private final Method factoryMethod;
|
private final Method factoryMethod;
|
||||||
private final Object configInstance;
|
private final Object configInstance;
|
||||||
private final Method postConstruct;
|
private final List<Method> postConstructs;
|
||||||
|
|
||||||
private BeanDefinition(Builder builder) {
|
private BeanDefinition(Builder builder) {
|
||||||
this.name = builder.name;
|
this.name = builder.name;
|
||||||
this.type = builder.type;
|
this.type = builder.type;
|
||||||
this.scope = builder.scope;
|
this.scope = builder.scope;
|
||||||
this.primary = builder.primary;
|
this.primary = builder.primary;
|
||||||
|
this.lazy = builder.lazy;
|
||||||
this.constructor = builder.constructor;
|
this.constructor = builder.constructor;
|
||||||
this.factoryMethod = builder.factoryMethod;
|
this.factoryMethod = builder.factoryMethod;
|
||||||
this.configInstance = builder.configInstance;
|
this.configInstance = builder.configInstance;
|
||||||
this.postConstruct = builder.postConstruct;
|
this.postConstructs = Collections.unmodifiableList(builder.postConstructs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -48,6 +52,10 @@ public class BeanDefinition {
|
|||||||
return primary;
|
return primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLazy() {
|
||||||
|
return lazy;
|
||||||
|
}
|
||||||
|
|
||||||
public Constructor<?> getConstructor() {
|
public Constructor<?> getConstructor() {
|
||||||
return constructor;
|
return constructor;
|
||||||
}
|
}
|
||||||
@ -60,8 +68,8 @@ public class BeanDefinition {
|
|||||||
return configInstance;
|
return configInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Method getPostConstruct() {
|
public List<Method> getPostConstructs() {
|
||||||
return postConstruct;
|
return postConstructs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFactoryBean() {
|
public boolean isFactoryBean() {
|
||||||
@ -72,15 +80,23 @@ public class BeanDefinition {
|
|||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-12 23:38
|
||||||
|
*/
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private Class<?> type;
|
private Class<?> type;
|
||||||
private ScopeType scope = ScopeType.SINGLETON;
|
private ScopeType scope = ScopeType.SINGLETON;
|
||||||
private boolean primary = false;
|
private boolean primary = false;
|
||||||
|
private boolean lazy = false;
|
||||||
private Constructor<?> constructor;
|
private Constructor<?> constructor;
|
||||||
private Method factoryMethod;
|
private Method factoryMethod;
|
||||||
private Object configInstance;
|
private Object configInstance;
|
||||||
private Method postConstruct;
|
private List<Method> postConstructs = Collections.emptyList();
|
||||||
|
|
||||||
public Builder name(String name) {
|
public Builder name(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -102,6 +118,11 @@ public class BeanDefinition {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder lazy(boolean lazy) {
|
||||||
|
this.lazy = lazy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder constructor(Constructor<?> constructor) {
|
public Builder constructor(Constructor<?> constructor) {
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
return this;
|
return this;
|
||||||
@ -117,8 +138,12 @@ public class BeanDefinition {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder postConstruct(Method method) {
|
public Builder postConstructs(List<Method> methods) {
|
||||||
this.postConstruct = method;
|
if (methods == null || methods.isEmpty()) {
|
||||||
|
this.postConstructs = Collections.emptyList();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.postConstructs = List.copyOf(methods);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,110 +1,121 @@
|
|||||||
package com.imyeyu.inject;
|
package com.imyeyu.inject;
|
||||||
|
|
||||||
|
import com.imyeyu.inject.annotation.Inject;
|
||||||
import com.imyeyu.inject.annotation.Qualifier;
|
import com.imyeyu.inject.annotation.Qualifier;
|
||||||
import com.imyeyu.inject.annotation.ScopeType;
|
import com.imyeyu.inject.annotation.ScopeType;
|
||||||
|
import com.imyeyu.java.ref.Ref;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
import java.util.*;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean 工厂,负责创建和管理 Bean 实例
|
* Bean 工厂,负责创建和管理 Bean 实例
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
|
* @since 2026-01-12 23:38
|
||||||
*/
|
*/
|
||||||
public class BeanFactory {
|
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 BeanContext context;
|
||||||
private final Set<String> creating = new HashSet<>();
|
private final StartupStatistics statistics;
|
||||||
|
private final LinkedHashSet<String> creating = new LinkedHashSet<>();
|
||||||
|
|
||||||
public BeanFactory(BeanContext context) {
|
public BeanFactory(BeanContext context, StartupStatistics statistics) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.statistics = statistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 Bean 实例(按名称)
|
* 获取 Bean 实例
|
||||||
|
*
|
||||||
|
* @param name 名称
|
||||||
|
* @return 实例
|
||||||
*/
|
*/
|
||||||
public Object getBean(String name) {
|
public Object getBean(String name) {
|
||||||
BeanDefinition definition = context.getDefinition(name);
|
BeanDefinition definition = context.getDefinition(name);
|
||||||
if (definition == null) {
|
if (definition == null) {
|
||||||
throw new InjectException("No bean definition found for name: " + name);
|
throw new InjectException("Bean [%s] not found".formatted(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||||
Object singleton = context.getSingleton(name);
|
Object singleton = context.getSingleton(name);
|
||||||
if (singleton != null) {
|
if (singleton != null) {
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return createBean(definition);
|
return createBean(definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 Bean 实例(按类型)
|
* 获取 Bean 实例
|
||||||
|
*
|
||||||
|
* @param type 类型
|
||||||
|
* @return 实例
|
||||||
|
* @param <T> 实例类型
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getBean(Class<T> type) {
|
public <T> T getBean(Class<T> type) {
|
||||||
List<String> candidates = context.getBeanNamesByType(type);
|
List<String> candidates = context.getBeanNamesByType(type);
|
||||||
|
|
||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
throw new InjectException("No bean found for type: " + type.getName());
|
throw new InjectException("Bean not found for type: %s".formatted(type.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (candidates.size() == 1) {
|
if (candidates.size() == 1) {
|
||||||
return (T) getBean(candidates.get(0));
|
return (T) getBean(candidates.getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
String primaryBean = findPrimaryBean(candidates);
|
String primaryBean = findPrimaryBean(candidates);
|
||||||
if (primaryBean != null) {
|
if (primaryBean != null) {
|
||||||
return (T) getBean(primaryBean);
|
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));
|
||||||
throw new InjectException("Multiple beans found for type " + type.getName() +
|
|
||||||
": " + candidates + ". Use @Qualifier or @Primary to specify which one to inject");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 Bean 实例(按名称和类型)
|
* 获取 Bean 实例
|
||||||
|
*
|
||||||
|
* @param name 名称
|
||||||
|
* @param type 类型
|
||||||
|
* @return 实例
|
||||||
|
* @param <T> 实例类型
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getBean(String name, Class<T> type) {
|
public <T> T getBean(String name, Class<T> type) {
|
||||||
Object bean = getBean(name);
|
Object bean = getBean(name);
|
||||||
if (!type.isInstance(bean)) {
|
if (!type.isInstance(bean)) {
|
||||||
throw new InjectException("Bean '" + name + "' is not of type " + type.getName());
|
throw new InjectException("Bean [%s] is not of type %s".formatted(name, type.getName()));
|
||||||
}
|
}
|
||||||
return (T) bean;
|
return (T) bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object createBean(BeanDefinition definition) {
|
private Object createBean(BeanDefinition definition) {
|
||||||
String name = definition.getName();
|
String name = definition.getName();
|
||||||
|
|
||||||
if (creating.contains(name)) {
|
if (creating.contains(name)) {
|
||||||
throw new InjectException("Circular dependency detected: " + name +
|
StringBuilder errorMsg = new StringBuilder("Circular dependency detected:\n");
|
||||||
" (creation chain: " + creating + ")");
|
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);
|
creating.add(name);
|
||||||
try {
|
try {
|
||||||
Object instance;
|
Object instance;
|
||||||
|
|
||||||
if (definition.isFactoryBean()) {
|
if (definition.isFactoryBean()) {
|
||||||
instance = createBeanFromFactoryMethod(definition);
|
instance = createBeanFromFactoryMethod(definition);
|
||||||
} else {
|
} else {
|
||||||
instance = createBeanFromConstructor(definition);
|
instance = createBeanFromConstructor(definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||||
context.setSingleton(name, instance);
|
context.setSingleton(name, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
invokePostConstruct(instance, definition);
|
invokePostConstruct(instance, definition);
|
||||||
|
|
||||||
log.debug("Created bean: {} ({})", name, definition.getType().getName());
|
log.debug("Created bean: {} ({})", name, definition.getType().getName());
|
||||||
return instance;
|
return instance;
|
||||||
|
|
||||||
@ -116,18 +127,15 @@ public class BeanFactory {
|
|||||||
private Object createBeanFromConstructor(BeanDefinition definition) {
|
private Object createBeanFromConstructor(BeanDefinition definition) {
|
||||||
Constructor<?> constructor = definition.getConstructor();
|
Constructor<?> constructor = definition.getConstructor();
|
||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
|
|
||||||
Parameter[] parameters = constructor.getParameters();
|
Parameter[] parameters = constructor.getParameters();
|
||||||
Object[] args = new Object[parameters.length];
|
Object[] args = new Object[parameters.length];
|
||||||
|
|
||||||
for (int i = 0; i < parameters.length; i++) {
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
args[i] = resolveDependency(parameters[i]);
|
args[i] = resolveDependency(parameters[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return constructor.newInstance(args);
|
return constructor.newInstance(args);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InjectException("Failed to instantiate bean: " + definition.getName(), e);
|
throw new InjectException("Failed to instantiate Bean [%s]".formatted(definition.getName()), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,69 +156,106 @@ public class BeanFactory {
|
|||||||
try {
|
try {
|
||||||
Object result = factoryMethod.invoke(configInstance, args);
|
Object result = factoryMethod.invoke(configInstance, args);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
throw new InjectException("@Bean method returned null: " + factoryMethod.getName());
|
throw new InjectException("@Bean method returned null: %s".formatted(factoryMethod.getName()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InjectException("Failed to invoke @Bean method: " + factoryMethod.getName(), e);
|
throw new InjectException("Failed to invoke @Bean method: %s".formatted(factoryMethod.getName()), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object resolveDependency(Parameter parameter) {
|
private Object resolveDependency(Parameter parameter) {
|
||||||
Qualifier qualifier = parameter.getAnnotation(Qualifier.class);
|
Qualifier qualifier = parameter.getAnnotation(Qualifier.class);
|
||||||
|
|
||||||
if (qualifier != null) {
|
if (qualifier != null) {
|
||||||
return getBean(qualifier.value());
|
return getBean(qualifier.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> type = parameter.getType();
|
Class<?> type = parameter.getType();
|
||||||
return getBean(type);
|
return getBean(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invokePostConstruct(Object instance, BeanDefinition definition) {
|
private void invokePostConstruct(Object instance, BeanDefinition definition) {
|
||||||
Method postConstruct = definition.getPostConstruct();
|
List<Method> postConstructs = definition.getPostConstructs();
|
||||||
if (postConstruct == null) {
|
if (postConstructs.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
statistics.postConstructCurrentStart = Time.now();
|
||||||
|
for (Method postConstruct : postConstructs) {
|
||||||
postConstruct.setAccessible(true);
|
postConstruct.setAccessible(true);
|
||||||
try {
|
try {
|
||||||
postConstruct.invoke(instance);
|
postConstruct.invoke(instance);
|
||||||
|
statistics.postConstructInvocations++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InjectException("Failed to invoke @PostConstruct method: " +
|
throw new InjectException("Failed to invoke @PostConstruct method: %s".formatted(postConstruct.getName()), e);
|
||||||
postConstruct.getName(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
statistics.postConstructTotalTime += Time.now() - statistics.postConstructCurrentStart;
|
||||||
|
statistics.postConstructCurrentStart = 0;
|
||||||
|
}
|
||||||
|
|
||||||
private String findPrimaryBean(List<String> candidates) {
|
private String findPrimaryBean(List<String> candidates) {
|
||||||
String primaryBean = null;
|
String primaryBean = null;
|
||||||
|
|
||||||
for (String name : candidates) {
|
for (String name : candidates) {
|
||||||
BeanDefinition definition = context.getDefinition(name);
|
BeanDefinition definition = context.getDefinition(name);
|
||||||
if (definition.isPrimary()) {
|
if (definition.isPrimary()) {
|
||||||
if (primaryBean != null) {
|
if (primaryBean != null) {
|
||||||
throw new InjectException("Multiple @Primary beans found: " + primaryBean + ", " + name);
|
throw new InjectException("Multiple @Primary beans found: Bean [%s] and Bean [%s]".formatted(primaryBean, name));
|
||||||
}
|
}
|
||||||
primaryBean = name;
|
primaryBean = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return primaryBean;
|
return primaryBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractConfigBeanName(Class<?> configClass) {
|
private String extractConfigBeanName(Class<?> configClass) {
|
||||||
String simpleName = configClass.getSimpleName();
|
return Character.toLowerCase(configClass.getSimpleName().charAt(0)) + configClass.getSimpleName().substring(1);
|
||||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化所有单例 Bean
|
* 初始化所有单例 Bean(跳过懒加载的单例)
|
||||||
*/
|
*/
|
||||||
public void initializeSingletons() {
|
public void initializeSingletons() {
|
||||||
|
statistics.iocStartTime = Time.now();
|
||||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
if (definition.getScope() == ScopeType.SINGLETON && !definition.isLazy()) {
|
||||||
getBean(definition.getName());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
package com.imyeyu.inject;
|
package com.imyeyu.inject;
|
||||||
|
|
||||||
import com.imyeyu.inject.annotation.*;
|
import com.imyeyu.inject.annotation.Bean;
|
||||||
|
import com.imyeyu.inject.annotation.Component;
|
||||||
|
import com.imyeyu.inject.annotation.Configuration;
|
||||||
|
import com.imyeyu.inject.annotation.Inject;
|
||||||
|
import com.imyeyu.inject.annotation.Lazy;
|
||||||
|
import com.imyeyu.inject.annotation.PostConstruct;
|
||||||
|
import com.imyeyu.inject.annotation.Primary;
|
||||||
|
import com.imyeyu.inject.annotation.Scope;
|
||||||
|
import com.imyeyu.inject.annotation.ScopeType;
|
||||||
|
import com.imyeyu.utils.Time;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -10,7 +19,9 @@ import java.lang.annotation.Annotation;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
@ -24,18 +35,23 @@ public class BeanScanner {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(BeanScanner.class);
|
private static final Logger log = LoggerFactory.getLogger(BeanScanner.class);
|
||||||
|
|
||||||
private final BeanContext context;
|
private final BeanContext context;
|
||||||
|
private final StartupStatistics statistics;
|
||||||
|
|
||||||
public BeanScanner(BeanContext context) {
|
public BeanScanner(BeanContext context, StartupStatistics statistics) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.statistics = statistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描指定包路径下的所有组件
|
* 扫描指定包路径下的所有组件
|
||||||
*/
|
*/
|
||||||
public void scan(String... basePackages) {
|
public void scan(String... basePackages) {
|
||||||
|
statistics.scanStartTime = Time.now();
|
||||||
for (String pkg : basePackages) {
|
for (String pkg : basePackages) {
|
||||||
|
statistics.scannedPackages.add(pkg);
|
||||||
scanPackage(pkg);
|
scanPackage(pkg);
|
||||||
}
|
}
|
||||||
|
statistics.scanEndTime = Time.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scanPackage(String basePackage) {
|
private void scanPackage(String basePackage) {
|
||||||
@ -55,7 +71,7 @@ public class BeanScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InjectException("Failed to scan package: " + basePackage, e);
|
throw new InjectException("Failed to scan package: %s".formatted(basePackage), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +87,9 @@ public class BeanScanner {
|
|||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
scanFileSystem(file, basePackage + "." + file.getName());
|
scanFileSystem(file, "%s.%s".formatted(basePackage, file.getName()));
|
||||||
} else if (file.getName().endsWith(".class")) {
|
} else if (file.getName().endsWith(".class")) {
|
||||||
String className = basePackage + "." + file.getName().replace(".class", "");
|
String className = "%s.%s".formatted(basePackage, file.getName().replace(".class", ""));
|
||||||
processClass(className);
|
processClass(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,12 +111,13 @@ public class BeanScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InjectException("Failed to scan jar: " + jarPath, e);
|
throw new InjectException("Failed to scan jar: %s".formatted(jarPath), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processClass(String className) {
|
private void processClass(String className) {
|
||||||
try {
|
try {
|
||||||
|
statistics.scannedClasses++;
|
||||||
Class<?> clazz = Class.forName(className);
|
Class<?> clazz = Class.forName(className);
|
||||||
processClass(clazz);
|
processClass(clazz);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
@ -126,19 +143,14 @@ public class BeanScanner {
|
|||||||
String beanName = extractBeanName(componentAnnotation, clazz);
|
String beanName = extractBeanName(componentAnnotation, clazz);
|
||||||
ScopeType scope = extractScope(clazz);
|
ScopeType scope = extractScope(clazz);
|
||||||
boolean primary = clazz.isAnnotationPresent(Primary.class);
|
boolean primary = clazz.isAnnotationPresent(Primary.class);
|
||||||
|
boolean lazy = extractLazy(clazz);
|
||||||
Constructor<?> constructor = selectConstructor(clazz);
|
Constructor<?> constructor = selectConstructor(clazz);
|
||||||
Method postConstruct = findPostConstruct(clazz);
|
List<Method> postConstructs = findPostConstructs(clazz);
|
||||||
|
|
||||||
BeanDefinition definition = BeanDefinition.builder()
|
BeanDefinition definition = BeanDefinition.builder().name(beanName).type(clazz).scope(scope).primary(primary).lazy(lazy).constructor(constructor).postConstructs(postConstructs).build();
|
||||||
.name(beanName)
|
|
||||||
.type(clazz)
|
|
||||||
.scope(scope)
|
|
||||||
.primary(primary)
|
|
||||||
.constructor(constructor)
|
|
||||||
.postConstruct(postConstruct)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
context.registerDefinition(definition);
|
context.registerDefinition(definition);
|
||||||
|
statistics.registeredBeans++;
|
||||||
log.debug("Registered bean: {} ({})", beanName, clazz.getName());
|
log.debug("Registered bean: {} ({})", beanName, clazz.getName());
|
||||||
|
|
||||||
if (clazz.isAnnotationPresent(Configuration.class)) {
|
if (clazz.isAnnotationPresent(Configuration.class)) {
|
||||||
@ -157,17 +169,13 @@ public class BeanScanner {
|
|||||||
Class<?> returnType = method.getReturnType();
|
Class<?> returnType = method.getReturnType();
|
||||||
|
|
||||||
if (returnType == void.class || returnType == Void.class) {
|
if (returnType == void.class || returnType == Void.class) {
|
||||||
throw new InjectException("@Bean method cannot return void: " + method.getName());
|
throw new InjectException("@Bean method cannot return void: %s".formatted(method.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinition definition = BeanDefinition.builder()
|
BeanDefinition definition = BeanDefinition.builder().name(beanName).type(returnType).scope(ScopeType.SINGLETON).factoryMethod(method).build();
|
||||||
.name(beanName)
|
|
||||||
.type(returnType)
|
|
||||||
.scope(ScopeType.SINGLETON)
|
|
||||||
.factoryMethod(method)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
context.registerDefinition(definition);
|
context.registerDefinition(definition);
|
||||||
|
statistics.registeredBeans++;
|
||||||
log.debug("Registered bean from @Bean method: {} ({})", beanName, returnType.getName());
|
log.debug("Registered bean from @Bean method: {} ({})", beanName, returnType.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,44 +216,52 @@ public class BeanScanner {
|
|||||||
return ScopeType.SINGLETON;
|
return ScopeType.SINGLETON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean extractLazy(Class<?> clazz) {
|
||||||
|
// 检查 @Lazy 注解
|
||||||
|
if (clazz.isAnnotationPresent(Lazy.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 检查 @Scope 注解的 lazy 属性
|
||||||
|
if (clazz.isAnnotationPresent(Scope.class)) {
|
||||||
|
return clazz.getAnnotation(Scope.class).lazy();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private Constructor<?> selectConstructor(Class<?> clazz) {
|
private Constructor<?> selectConstructor(Class<?> clazz) {
|
||||||
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
||||||
|
|
||||||
Constructor<?> injectConstructor = null;
|
Constructor<?> injectConstructor = null;
|
||||||
for (Constructor<?> constructor : constructors) {
|
for (Constructor<?> constructor : constructors) {
|
||||||
if (constructor.isAnnotationPresent(Inject.class)) {
|
if (constructor.isAnnotationPresent(Inject.class)) {
|
||||||
if (injectConstructor != null) {
|
if (injectConstructor != null) {
|
||||||
throw new InjectException("Multiple @Inject constructors found in " + clazz.getName());
|
throw new InjectException("Multiple @Inject constructors found in %s".formatted(clazz.getName()));
|
||||||
}
|
}
|
||||||
injectConstructor = constructor;
|
injectConstructor = constructor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (injectConstructor != null) {
|
if (injectConstructor != null) {
|
||||||
return injectConstructor;
|
return injectConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constructors.length == 1) {
|
if (constructors.length == 1) {
|
||||||
return constructors[0];
|
return constructors[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return clazz.getDeclaredConstructor();
|
return clazz.getDeclaredConstructor();
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
throw new InjectException("No suitable constructor found for " + clazz.getName() +
|
throw new InjectException("No suitable constructor found for %s. Please provide a no-arg constructor or mark one with @Inject".formatted(clazz.getName()), e);
|
||||||
". Please provide a no-arg constructor or mark one with @Inject", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Method findPostConstruct(Class<?> clazz) {
|
private List<Method> findPostConstructs(Class<?> clazz) {
|
||||||
|
List<Method> methods = new ArrayList<>();
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
if (method.isAnnotationPresent(PostConstruct.class)) {
|
if (method.isAnnotationPresent(PostConstruct.class)) {
|
||||||
if (method.getParameterCount() != 0) {
|
if (method.getParameterCount() != 0) {
|
||||||
throw new InjectException("@PostConstruct method must have no parameters: " + method.getName());
|
throw new InjectException("@PostConstruct method must have no parameters: %s".formatted(method.getName()));
|
||||||
}
|
}
|
||||||
return method;
|
methods.add(method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return methods;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/main/java/com/imyeyu/inject/StartupStatistics.java
Normal file
28
src/main/java/com/imyeyu/inject/StartupStatistics.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package com.imyeyu.inject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动统计信息
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
* @since 2026-01-13 01:29
|
||||||
|
*/
|
||||||
|
class StartupStatistics {
|
||||||
|
|
||||||
|
final List<String> scannedPackages = new ArrayList<>();
|
||||||
|
int scannedClasses = 0;
|
||||||
|
int registeredBeans = 0;
|
||||||
|
int injectedFields = 0;
|
||||||
|
int postConstructInvocations = 0;
|
||||||
|
|
||||||
|
long scanStartTime = 0;
|
||||||
|
long scanEndTime = 0;
|
||||||
|
long iocStartTime = 0;
|
||||||
|
long iocEndTime = 0;
|
||||||
|
long injectionTotalTime = 0;
|
||||||
|
long injectionCurrentStart = 0;
|
||||||
|
long postConstructTotalTime = 0;
|
||||||
|
long postConstructCurrentStart = 0;
|
||||||
|
}
|
||||||
@ -2,10 +2,19 @@ package com.imyeyu.inject;
|
|||||||
|
|
||||||
import com.imyeyu.inject.annotation.Import;
|
import com.imyeyu.inject.annotation.Import;
|
||||||
import com.imyeyu.inject.annotation.TimiInjectApplication;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 控制反转框架
|
* Timi 控制反转框架
|
||||||
@ -27,9 +36,22 @@ public class TimiInject {
|
|||||||
/**
|
/**
|
||||||
* 启动应用并初始化容器
|
* 启动应用并初始化容器
|
||||||
*/
|
*/
|
||||||
public static TimiInject run(Class<?> applicationClass, String... args) {
|
public static TimiInject run(Class<?> applicationClass) {
|
||||||
long startTime = System.currentTimeMillis();
|
return run(applicationClass, null);
|
||||||
log.info("Starting {} ...", applicationClass.getSimpleName());
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动应用并初始化容器(支持初始化前注册外部 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);
|
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
@ -37,47 +59,69 @@ public class TimiInject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BeanContext context = new BeanContext();
|
BeanContext context = new BeanContext();
|
||||||
BeanFactory factory = new BeanFactory(context);
|
BeanFactory factory = new BeanFactory(context, statistics);
|
||||||
BeanScanner scanner = new BeanScanner(context);
|
BeanScanner scanner = new BeanScanner(context, statistics);
|
||||||
|
|
||||||
String[] basePackages = annotation.value();
|
String[] basePackages = annotation.value();
|
||||||
if (basePackages.length == 0) {
|
if (basePackages.length == 0) {
|
||||||
basePackages = new String[]{applicationClass.getPackage().getName()};
|
basePackages = new String[] {applicationClass.getPackage().getName()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("Scanning packages: {}", String.join(", ", basePackages));
|
||||||
scanner.scan(basePackages);
|
scanner.scan(basePackages);
|
||||||
processImports(applicationClass, scanner, context);
|
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();
|
factory.initializeSingletons();
|
||||||
|
log.info("initialization completed!");
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
log.info("\tTotal beans: {}", statistics.registeredBeans);
|
||||||
log.info("Started {} in {} ms ({} beans)", applicationClass.getSimpleName(), elapsed,
|
log.info("\tInjected fields: {}", statistics.injectedFields);
|
||||||
context.getAllDefinitions().size());
|
log.info("\tPostConstruct invocations: {}", statistics.postConstructInvocations);
|
||||||
|
log.info("\tScan time: {} ms", statistics.scanEndTime - statistics.scanStartTime);
|
||||||
return new TimiInject(context, factory);
|
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 实例(按类型)
|
* 获取 Bean 实例(按类型)
|
||||||
*/
|
*/
|
||||||
public <T> T di(Class<T> type) {
|
public <T> T getBean(Class<T> type) {
|
||||||
return factory.getBean(type);
|
return factory.getBean(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 依赖注入 - 获取 Bean 实例(按名称和类型)
|
* 获取 Bean 实例(按名称和类型)
|
||||||
*/
|
*/
|
||||||
public <T> T di(String name, Class<T> type) {
|
public <T> T getBean(String name, Class<T> type) {
|
||||||
return factory.getBean(name, type);
|
return factory.getBean(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制反转 - 手动注册 Bean 实例
|
* 注册 Bean 实例
|
||||||
*/
|
*/
|
||||||
public void ioc(String name, Object bean) {
|
public void registerBean(String name, Object bean) {
|
||||||
context.registerSingleton(name, bean);
|
context.registerSingleton(name, bean);
|
||||||
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
|
||||||
|
*
|
||||||
|
* @param target 需要注入依赖的目标对象
|
||||||
|
*/
|
||||||
|
public void inject(Object target) {
|
||||||
|
factory.injectFields(target);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出依赖图
|
* 导出依赖图
|
||||||
*/
|
*/
|
||||||
@ -103,12 +147,19 @@ public class TimiInject {
|
|||||||
if (definition.isFactoryBean()) {
|
if (definition.isFactoryBean()) {
|
||||||
info.append(" Factory: @Bean ").append(definition.getFactoryMethod().getName()).append("()\n");
|
info.append(" Factory: @Bean ").append(definition.getFactoryMethod().getName()).append("()\n");
|
||||||
} else if (definition.getConstructor() != null) {
|
} else if (definition.getConstructor() != null) {
|
||||||
info.append(" Constructor: ").append(definition.getConstructor().getParameterCount())
|
info.append(" Constructor: ").append(definition.getConstructor().getParameterCount()).append(" parameters\n");
|
||||||
.append(" parameters\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.getPostConstruct() != null) {
|
List<Method> postConstructs = definition.getPostConstructs();
|
||||||
info.append(" PostConstruct: ").append(definition.getPostConstruct().getName()).append("()\n");
|
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();
|
return info.toString();
|
||||||
@ -118,10 +169,8 @@ public class TimiInject {
|
|||||||
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
||||||
Class<?>[] importClasses = importAnnotation.value();
|
Class<?>[] importClasses = importAnnotation.value();
|
||||||
|
|
||||||
for (Class<?> importClass : importClasses) {
|
for (Class<?> importClass : importClasses) {
|
||||||
if (Module.class.isAssignableFrom(importClass)) {
|
if (Module.class.isAssignableFrom(importClass)) {
|
||||||
try {
|
try {
|
||||||
@ -129,11 +178,11 @@ public class TimiInject {
|
|||||||
module.configure(context);
|
module.configure(context);
|
||||||
log.debug("Loaded module: {}", importClass.getName());
|
log.debug("Loaded module: {}", importClass.getName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InjectException("Failed to instantiate module: " + importClass.getName(), e);
|
throw new InjectException("Failed to instantiate module: %s".formatted(importClass.getName()), e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 检查是否已经通过包扫描注册
|
// 检查是否已经通过包扫描注册
|
||||||
String beanName = extractBeanName(importClass);
|
String beanName = Text.camelCaseClassName(importClass);
|
||||||
if (!context.containsBean(beanName)) {
|
if (!context.containsBean(beanName)) {
|
||||||
scanner.processClass(importClass);
|
scanner.processClass(importClass);
|
||||||
log.debug("Imported configuration: {}", importClass.getName());
|
log.debug("Imported configuration: {}", importClass.getName());
|
||||||
@ -144,8 +193,56 @@ public class TimiInject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractBeanName(Class<?> clazz) {
|
/**
|
||||||
String simpleName = clazz.getSimpleName();
|
* 输出启动 Banner
|
||||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记用于依赖注入的构造器
|
* 标记用于依赖注入的构造器或字段
|
||||||
*
|
*
|
||||||
* @author 夜雨
|
* @author 夜雨
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.CONSTRUCTOR)
|
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface Inject {
|
public @interface Inject {
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/main/java/com/imyeyu/inject/annotation/Lazy.java
Normal file
17
src/main/java/com/imyeyu/inject/annotation/Lazy.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.imyeyu.inject.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记 Bean 为懒加载
|
||||||
|
* 等同于 @Scope(value = ScopeType.SINGLETON, lazy = true)
|
||||||
|
*
|
||||||
|
* @author 夜雨
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Lazy {
|
||||||
|
}
|
||||||
@ -18,4 +18,11 @@ public @interface Scope {
|
|||||||
* 作用域类型
|
* 作用域类型
|
||||||
*/
|
*/
|
||||||
ScopeType value() default ScopeType.SINGLETON;
|
ScopeType value() default ScopeType.SINGLETON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否懒加载(仅对 SINGLETON 作用域有效)
|
||||||
|
* true - 首次使用时才创建
|
||||||
|
* false - 启动时立即创建(默认)
|
||||||
|
*/
|
||||||
|
boolean lazy() default false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user