From f5c6dcd27585bbb47e67aa5ebd6eb79d34d7e919 Mon Sep 17 00:00:00 2001 From: Timi Date: Tue, 13 Jan 2026 01:41:49 +0800 Subject: [PATCH] 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 --- .gitignore | 1 + .../java/com/imyeyu/inject/BeanContext.java | 147 +++---- .../com/imyeyu/inject/BeanDefinition.java | 217 +++++----- .../java/com/imyeyu/inject/BeanFactory.java | 399 +++++++++-------- .../java/com/imyeyu/inject/BeanScanner.java | 408 +++++++++--------- .../com/imyeyu/inject/StartupStatistics.java | 28 ++ .../java/com/imyeyu/inject/TimiInject.java | 321 +++++++++----- .../com/imyeyu/inject/annotation/Inject.java | 4 +- .../com/imyeyu/inject/annotation/Lazy.java | 17 + .../com/imyeyu/inject/annotation/Scope.java | 7 + 10 files changed, 890 insertions(+), 659 deletions(-) create mode 100644 src/main/java/com/imyeyu/inject/StartupStatistics.java create mode 100644 src/main/java/com/imyeyu/inject/annotation/Lazy.java diff --git a/.gitignore b/.gitignore index 0ec825b..71249b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.claude /CLAUDE.md /AGENTS.md +/logs target/ !.mvn/wrapper/maven-wrapper.jar diff --git a/src/main/java/com/imyeyu/inject/BeanContext.java b/src/main/java/com/imyeyu/inject/BeanContext.java index fda7b1b..0cbd9bf 100644 --- a/src/main/java/com/imyeyu/inject/BeanContext.java +++ b/src/main/java/com/imyeyu/inject/BeanContext.java @@ -9,93 +9,88 @@ import java.util.concurrent.ConcurrentHashMap; * Bean 容器上下文 * * @author 夜雨 + * @since 2026-01-12 23:37 */ public class BeanContext { - private final Map definitions = new ConcurrentHashMap<>(); - private final Map singletons = new ConcurrentHashMap<>(); - private final Map, List> typeIndex = new ConcurrentHashMap<>(); + private final Map singletons = new ConcurrentHashMap<>(); + private final Map definitions = new ConcurrentHashMap<>(); + private final Map, List> typeIndex = new ConcurrentHashMap<>(); - /** - * 注册 Bean 定义 - */ - public void registerDefinition(BeanDefinition definition) { - String name = definition.getName(); - if (definitions.containsKey(name)) { - throw new InjectException("Bean '" + name + "' already registered"); - } + /** + * 注册 Bean 定义 + */ + public void registerDefinition(BeanDefinition definition) { + String name = definition.getName(); + if (definitions.containsKey(name)) { + throw new InjectException("Bean [%s] already registered".formatted(name)); + } + definitions.put(name, definition); + typeIndex.computeIfAbsent(definition.getType(), k -> new ArrayList<>()).add(name); + } - definitions.put(name, definition); - typeIndex.computeIfAbsent(definition.getType(), k -> new ArrayList<>()).add(name); - } + /** + * 注册单例 Bean 实例 + */ + public void registerSingleton(String name, Object bean) { + if (bean == null) { + throw new InjectException("Bean [%s] cannot be null".formatted(name)); + } + singletons.put(name, bean); + if (!definitions.containsKey(name)) { + BeanDefinition definition = BeanDefinition.builder().name(name).type(bean.getClass()).scope(ScopeType.SINGLETON).build(); + definitions.put(name, definition); + typeIndex.computeIfAbsent(bean.getClass(), k -> new ArrayList<>()).add(name); + } + } - /** - * 注册单例 Bean 实例 - */ - public void registerSingleton(String name, Object bean) { - if (bean == null) { - throw new InjectException("Cannot register null bean: " + name); - } - singletons.put(name, bean); + /** + * 获取 Bean 定义 + */ + public BeanDefinition getDefinition(String name) { + return definitions.get(name); + } - if (!definitions.containsKey(name)) { - BeanDefinition definition = BeanDefinition.builder() - .name(name) - .type(bean.getClass()) - .scope(ScopeType.SINGLETON) - .build(); - definitions.put(name, definition); - typeIndex.computeIfAbsent(bean.getClass(), k -> new ArrayList<>()).add(name); - } - } + /** + * 获取单例 Bean + */ + public Object getSingleton(String name) { + return singletons.get(name); + } - /** - * 获取 Bean 定义 - */ - public BeanDefinition getDefinition(String name) { - return definitions.get(name); - } + /** + * 设置单例 Bean + */ + public void setSingleton(String name, Object bean) { + singletons.put(name, bean); + } - /** - * 获取单例 Bean - */ - public Object getSingleton(String name) { - return singletons.get(name); - } + /** + * 根据类型查找 Bean 名称列表 + */ + public List getBeanNamesByType(Class type) { + List names = new ArrayList<>(); - /** - * 设置单例 Bean - */ - public void setSingleton(String name, Object bean) { - singletons.put(name, bean); - } + for (Map.Entry, List> entry : typeIndex.entrySet()) { + if (type.isAssignableFrom(entry.getKey())) { + names.addAll(entry.getValue()); + } + } - /** - * 根据类型查找 Bean 名称列表 - */ - public List getBeanNamesByType(Class type) { - List names = new ArrayList<>(); + return names; + } - for (Map.Entry, List> entry : typeIndex.entrySet()) { - if (type.isAssignableFrom(entry.getKey())) { - names.addAll(entry.getValue()); - } - } + /** + * 获取所有 Bean 定义 + */ + public Collection getAllDefinitions() { + return definitions.values(); + } - return names; - } - - /** - * 获取所有 Bean 定义 - */ - public Collection getAllDefinitions() { - return definitions.values(); - } - - /** - * 检查是否包含指定名称的 Bean - */ - public boolean containsBean(String name) { - return definitions.containsKey(name); - } + /** + * 检查是否包含指定名称的 Bean + */ + public boolean containsBean(String name) { + return definitions.containsKey(name); + } } diff --git a/src/main/java/com/imyeyu/inject/BeanDefinition.java b/src/main/java/com/imyeyu/inject/BeanDefinition.java index ccdb000..cd6b2b2 100644 --- a/src/main/java/com/imyeyu/inject/BeanDefinition.java +++ b/src/main/java/com/imyeyu/inject/BeanDefinition.java @@ -4,6 +4,8 @@ import com.imyeyu.inject.annotation.ScopeType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; /** * Bean 定义元数据 @@ -12,121 +14,144 @@ import java.lang.reflect.Method; */ public class BeanDefinition { - private final String name; - private final Class type; - private final ScopeType scope; - private final boolean primary; - private final Constructor constructor; - private final Method factoryMethod; - private final Object configInstance; - private final Method postConstruct; + private final String name; + private final Class type; + private final ScopeType scope; + private final boolean primary; + private final boolean lazy; + private final Constructor constructor; + private final Method factoryMethod; + private final Object configInstance; + private final List postConstructs; - private BeanDefinition(Builder builder) { - this.name = builder.name; - this.type = builder.type; - this.scope = builder.scope; - this.primary = builder.primary; - this.constructor = builder.constructor; - this.factoryMethod = builder.factoryMethod; - this.configInstance = builder.configInstance; - this.postConstruct = builder.postConstruct; - } + private BeanDefinition(Builder builder) { + this.name = builder.name; + this.type = builder.type; + this.scope = builder.scope; + this.primary = builder.primary; + this.lazy = builder.lazy; + this.constructor = builder.constructor; + this.factoryMethod = builder.factoryMethod; + this.configInstance = builder.configInstance; + this.postConstructs = Collections.unmodifiableList(builder.postConstructs); + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public Class getType() { - return type; - } + public Class getType() { + return type; + } - public ScopeType getScope() { - return scope; - } + public ScopeType getScope() { + return scope; + } - public boolean isPrimary() { - return primary; - } + public boolean isPrimary() { + return primary; + } - public Constructor getConstructor() { - return constructor; - } + public boolean isLazy() { + return lazy; + } - public Method getFactoryMethod() { - return factoryMethod; - } + public Constructor getConstructor() { + return constructor; + } - public Object getConfigInstance() { - return configInstance; - } + public Method getFactoryMethod() { + return factoryMethod; + } - public Method getPostConstruct() { - return postConstruct; - } + public Object getConfigInstance() { + return configInstance; + } - public boolean isFactoryBean() { - return factoryMethod != null; - } + public List getPostConstructs() { + return postConstructs; + } - public static Builder builder() { - return new Builder(); - } + public boolean isFactoryBean() { + return factoryMethod != null; + } - public static class Builder { - private String name; - private Class type; - private ScopeType scope = ScopeType.SINGLETON; - private boolean primary = false; - private Constructor constructor; - private Method factoryMethod; - private Object configInstance; - private Method postConstruct; + public static Builder builder() { + return new Builder(); + } - public Builder name(String name) { - this.name = name; - return this; - } + /** + * + * + * @author 夜雨 + * @since 2026-01-12 23:38 + */ + public static class Builder { - public Builder type(Class type) { - this.type = type; - return this; - } + private String name; + private Class type; + private ScopeType scope = ScopeType.SINGLETON; + private boolean primary = false; + private boolean lazy = false; + private Constructor constructor; + private Method factoryMethod; + private Object configInstance; + private List postConstructs = Collections.emptyList(); - public Builder scope(ScopeType scope) { - this.scope = scope; - return this; - } + public Builder name(String name) { + this.name = name; + return this; + } - public Builder primary(boolean primary) { - this.primary = primary; - return this; - } + public Builder type(Class type) { + this.type = type; + return this; + } - public Builder constructor(Constructor constructor) { - this.constructor = constructor; - return this; - } + public Builder scope(ScopeType scope) { + this.scope = scope; + return this; + } - public Builder factoryMethod(Method method) { - this.factoryMethod = method; - return this; - } + public Builder primary(boolean primary) { + this.primary = primary; + return this; + } - public Builder configInstance(Object instance) { - this.configInstance = instance; - return this; - } + public Builder lazy(boolean lazy) { + this.lazy = lazy; + return this; + } - public Builder postConstruct(Method method) { - this.postConstruct = method; - return this; - } + public Builder constructor(Constructor constructor) { + this.constructor = constructor; + return this; + } - public BeanDefinition build() { - if (name == null || type == null) { - throw new IllegalStateException("Bean name and type are required"); - } - return new BeanDefinition(this); - } - } + public Builder factoryMethod(Method method) { + this.factoryMethod = method; + return this; + } + + public Builder configInstance(Object instance) { + this.configInstance = instance; + return this; + } + + public Builder postConstructs(List methods) { + if (methods == null || methods.isEmpty()) { + this.postConstructs = Collections.emptyList(); + return this; + } + this.postConstructs = List.copyOf(methods); + return this; + } + + public BeanDefinition build() { + if (name == null || type == null) { + throw new IllegalStateException("Bean name and type are required"); + } + return new BeanDefinition(this); + } + } } diff --git a/src/main/java/com/imyeyu/inject/BeanFactory.java b/src/main/java/com/imyeyu/inject/BeanFactory.java index d9122d8..fe1898a 100644 --- a/src/main/java/com/imyeyu/inject/BeanFactory.java +++ b/src/main/java/com/imyeyu/inject/BeanFactory.java @@ -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 creating = new HashSet<>(); + private final BeanContext context; + private final StartupStatistics statistics; + private final LinkedHashSet 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 实例类型 + */ + @SuppressWarnings("unchecked") + public T getBean(Class type) { + List 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 实例类型 + */ + @SuppressWarnings("unchecked") + public T getBean(String name, Class 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 getBean(Class type) { - List 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 getBean(String name, Class 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 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 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 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); + } } diff --git a/src/main/java/com/imyeyu/inject/BeanScanner.java b/src/main/java/com/imyeyu/inject/BeanScanner.java index c8b8873..509b36e 100644 --- a/src/main/java/com/imyeyu/inject/BeanScanner.java +++ b/src/main/java/com/imyeyu/inject/BeanScanner.java @@ -1,6 +1,15 @@ 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.LoggerFactory; @@ -10,7 +19,9 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; 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.JarFile; @@ -21,231 +32,236 @@ import java.util.jar.JarFile; */ 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) { - this.context = context; - } + public BeanScanner(BeanContext context, StartupStatistics statistics) { + this.context = context; + this.statistics = statistics; + } - /** - * 扫描指定包路径下的所有组件 - */ - public void scan(String... basePackages) { - for (String pkg : basePackages) { - scanPackage(pkg); - } - } + /** + * 扫描指定包路径下的所有组件 + */ + public void scan(String... basePackages) { + statistics.scanStartTime = Time.now(); + for (String pkg : basePackages) { + statistics.scannedPackages.add(pkg); + scanPackage(pkg); + } + statistics.scanEndTime = Time.now(); + } - private void scanPackage(String basePackage) { - String path = basePackage.replace('.', '/'); - try { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - Enumeration resources = classLoader.getResources(path); + private void scanPackage(String basePackage) { + String path = basePackage.replace('.', '/'); + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Enumeration resources = classLoader.getResources(path); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - String protocol = resource.getProtocol(); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + String protocol = resource.getProtocol(); - if ("file".equals(protocol)) { - scanFileSystem(new File(resource.getFile()), basePackage); - } else if ("jar".equals(protocol)) { - scanJar(resource, basePackage); - } - } - } catch (IOException e) { - throw new InjectException("Failed to scan package: " + basePackage, e); - } - } + if ("file".equals(protocol)) { + scanFileSystem(new File(resource.getFile()), basePackage); + } else if ("jar".equals(protocol)) { + scanJar(resource, basePackage); + } + } + } catch (IOException e) { + throw new InjectException("Failed to scan package: %s".formatted(basePackage), e); + } + } - private void scanFileSystem(File directory, String basePackage) { - if (!directory.exists() || !directory.isDirectory()) { - return; - } + private void scanFileSystem(File directory, String basePackage) { + if (!directory.exists() || !directory.isDirectory()) { + return; + } - File[] files = directory.listFiles(); - if (files == null) { - return; - } + File[] files = directory.listFiles(); + if (files == null) { + return; + } - for (File file : files) { - if (file.isDirectory()) { - scanFileSystem(file, basePackage + "." + file.getName()); - } else if (file.getName().endsWith(".class")) { - String className = basePackage + "." + file.getName().replace(".class", ""); - processClass(className); - } - } - } + for (File file : files) { + if (file.isDirectory()) { + scanFileSystem(file, "%s.%s".formatted(basePackage, file.getName())); + } else if (file.getName().endsWith(".class")) { + String className = "%s.%s".formatted(basePackage, file.getName().replace(".class", "")); + processClass(className); + } + } + } - private void scanJar(URL resource, String basePackage) { - String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!")); - String packagePath = basePackage.replace('.', '/'); + private void scanJar(URL resource, String basePackage) { + String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!")); + String packagePath = basePackage.replace('.', '/'); - try (JarFile jar = new JarFile(jarPath)) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); + try (JarFile jar = new JarFile(jarPath)) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); - if (name.startsWith(packagePath) && name.endsWith(".class")) { - String className = name.replace('/', '.').replace(".class", ""); - processClass(className); - } - } - } catch (IOException e) { - throw new InjectException("Failed to scan jar: " + jarPath, e); - } - } + if (name.startsWith(packagePath) && name.endsWith(".class")) { + String className = name.replace('/', '.').replace(".class", ""); + processClass(className); + } + } + } catch (IOException e) { + throw new InjectException("Failed to scan jar: %s".formatted(jarPath), e); + } + } - private void processClass(String className) { - try { - Class clazz = Class.forName(className); - processClass(clazz); - } catch (ClassNotFoundException e) { - log.warn("Class not found: {}", className); - } catch (NoClassDefFoundError e) { - log.debug("Skip class with missing dependencies: {}", className); - } - } + private void processClass(String className) { + try { + statistics.scannedClasses++; + Class clazz = Class.forName(className); + processClass(clazz); + } catch (ClassNotFoundException e) { + log.warn("Class not found: {}", className); + } catch (NoClassDefFoundError e) { + log.debug("Skip class with missing dependencies: {}", className); + } + } - /** - * 处理单个类 - */ - public void processClass(Class clazz) { - if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum()) { - return; - } + /** + * 处理单个类 + */ + public void processClass(Class clazz) { + if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum()) { + return; + } - Annotation componentAnnotation = getComponentAnnotation(clazz); - if (componentAnnotation == null) { - return; - } + Annotation componentAnnotation = getComponentAnnotation(clazz); + if (componentAnnotation == null) { + return; + } - String beanName = extractBeanName(componentAnnotation, clazz); - ScopeType scope = extractScope(clazz); - boolean primary = clazz.isAnnotationPresent(Primary.class); - Constructor constructor = selectConstructor(clazz); - Method postConstruct = findPostConstruct(clazz); + String beanName = extractBeanName(componentAnnotation, clazz); + ScopeType scope = extractScope(clazz); + boolean primary = clazz.isAnnotationPresent(Primary.class); + boolean lazy = extractLazy(clazz); + Constructor constructor = selectConstructor(clazz); + List postConstructs = findPostConstructs(clazz); - BeanDefinition definition = BeanDefinition.builder() - .name(beanName) - .type(clazz) - .scope(scope) - .primary(primary) - .constructor(constructor) - .postConstruct(postConstruct) - .build(); + BeanDefinition definition = BeanDefinition.builder().name(beanName).type(clazz).scope(scope).primary(primary).lazy(lazy).constructor(constructor).postConstructs(postConstructs).build(); - context.registerDefinition(definition); - log.debug("Registered bean: {} ({})", beanName, clazz.getName()); + context.registerDefinition(definition); + statistics.registeredBeans++; + log.debug("Registered bean: {} ({})", beanName, clazz.getName()); - if (clazz.isAnnotationPresent(Configuration.class)) { - processBeanMethods(clazz); - } - } + if (clazz.isAnnotationPresent(Configuration.class)) { + processBeanMethods(clazz); + } + } - private void processBeanMethods(Class configClass) { - for (Method method : configClass.getDeclaredMethods()) { - if (!method.isAnnotationPresent(Bean.class)) { - continue; - } + private void processBeanMethods(Class configClass) { + for (Method method : configClass.getDeclaredMethods()) { + if (!method.isAnnotationPresent(Bean.class)) { + continue; + } - Bean beanAnnotation = method.getAnnotation(Bean.class); - String beanName = beanAnnotation.value().isEmpty() ? method.getName() : beanAnnotation.value(); - Class returnType = method.getReturnType(); + Bean beanAnnotation = method.getAnnotation(Bean.class); + String beanName = beanAnnotation.value().isEmpty() ? method.getName() : beanAnnotation.value(); + Class returnType = method.getReturnType(); - if (returnType == void.class || returnType == Void.class) { - throw new InjectException("@Bean method cannot return void: " + method.getName()); - } + if (returnType == void.class || returnType == Void.class) { + throw new InjectException("@Bean method cannot return void: %s".formatted(method.getName())); + } - BeanDefinition definition = BeanDefinition.builder() - .name(beanName) - .type(returnType) - .scope(ScopeType.SINGLETON) - .factoryMethod(method) - .build(); + BeanDefinition definition = BeanDefinition.builder().name(beanName).type(returnType).scope(ScopeType.SINGLETON).factoryMethod(method).build(); - context.registerDefinition(definition); - log.debug("Registered bean from @Bean method: {} ({})", beanName, returnType.getName()); - } - } + context.registerDefinition(definition); + statistics.registeredBeans++; + log.debug("Registered bean from @Bean method: {} ({})", beanName, returnType.getName()); + } + } - private Annotation getComponentAnnotation(Class clazz) { - if (clazz.isAnnotationPresent(Component.class)) { - return clazz.getAnnotation(Component.class); - } + private Annotation getComponentAnnotation(Class clazz) { + if (clazz.isAnnotationPresent(Component.class)) { + return clazz.getAnnotation(Component.class); + } - for (Annotation annotation : clazz.getAnnotations()) { - if (annotation.annotationType().isAnnotationPresent(Component.class)) { - return annotation; - } - } + for (Annotation annotation : clazz.getAnnotations()) { + if (annotation.annotationType().isAnnotationPresent(Component.class)) { + return annotation; + } + } - return null; - } + return null; + } - private String extractBeanName(Annotation annotation, Class clazz) { - try { - Method valueMethod = annotation.annotationType().getMethod("value"); - String value = (String) valueMethod.invoke(annotation); - if (!value.isEmpty()) { - return value; - } - } catch (Exception e) { - // ignore - } + private String extractBeanName(Annotation annotation, Class clazz) { + try { + Method valueMethod = annotation.annotationType().getMethod("value"); + String value = (String) valueMethod.invoke(annotation); + if (!value.isEmpty()) { + return value; + } + } catch (Exception e) { + // ignore + } - String simpleName = clazz.getSimpleName(); - return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); - } + String simpleName = clazz.getSimpleName(); + return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); + } - private ScopeType extractScope(Class clazz) { - if (clazz.isAnnotationPresent(Scope.class)) { - return clazz.getAnnotation(Scope.class).value(); - } - return ScopeType.SINGLETON; - } + private ScopeType extractScope(Class clazz) { + if (clazz.isAnnotationPresent(Scope.class)) { + return clazz.getAnnotation(Scope.class).value(); + } + return ScopeType.SINGLETON; + } - private Constructor selectConstructor(Class clazz) { - Constructor[] constructors = clazz.getDeclaredConstructors(); + 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; + } - Constructor injectConstructor = null; - for (Constructor constructor : constructors) { - if (constructor.isAnnotationPresent(Inject.class)) { - if (injectConstructor != null) { - throw new InjectException("Multiple @Inject constructors found in " + clazz.getName()); - } - injectConstructor = constructor; - } - } + private Constructor selectConstructor(Class clazz) { + Constructor[] constructors = clazz.getDeclaredConstructors(); + Constructor injectConstructor = null; + for (Constructor constructor : constructors) { + if (constructor.isAnnotationPresent(Inject.class)) { + if (injectConstructor != null) { + throw new InjectException("Multiple @Inject constructors found in %s".formatted(clazz.getName())); + } + injectConstructor = constructor; + } + } + if (injectConstructor != null) { + return injectConstructor; + } + if (constructors.length == 1) { + return constructors[0]; + } + try { + return clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new InjectException("No suitable constructor found for %s. Please provide a no-arg constructor or mark one with @Inject".formatted(clazz.getName()), e); + } + } - if (injectConstructor != null) { - return injectConstructor; - } - - if (constructors.length == 1) { - return constructors[0]; - } - - try { - return clazz.getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new InjectException("No suitable constructor found for " + clazz.getName() + - ". Please provide a no-arg constructor or mark one with @Inject", e); - } - } - - private Method findPostConstruct(Class clazz) { - for (Method method : clazz.getDeclaredMethods()) { - if (method.isAnnotationPresent(PostConstruct.class)) { - if (method.getParameterCount() != 0) { - throw new InjectException("@PostConstruct method must have no parameters: " + method.getName()); - } - return method; - } - } - return null; - } + private List findPostConstructs(Class clazz) { + List methods = new ArrayList<>(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(PostConstruct.class)) { + if (method.getParameterCount() != 0) { + throw new InjectException("@PostConstruct method must have no parameters: %s".formatted(method.getName())); + } + methods.add(method); + } + } + return methods; + } } diff --git a/src/main/java/com/imyeyu/inject/StartupStatistics.java b/src/main/java/com/imyeyu/inject/StartupStatistics.java new file mode 100644 index 0000000..1deec6d --- /dev/null +++ b/src/main/java/com/imyeyu/inject/StartupStatistics.java @@ -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 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; +} diff --git a/src/main/java/com/imyeyu/inject/TimiInject.java b/src/main/java/com/imyeyu/inject/TimiInject.java index 0ee766b..f66d732 100644 --- a/src/main/java/com/imyeyu/inject/TimiInject.java +++ b/src/main/java/com/imyeyu/inject/TimiInject.java @@ -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 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 di(Class 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 di(String name, Class 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 getBean(Class type) { + return factory.getBean(type); + } - for (BeanDefinition definition : context.getAllDefinitions()) { - graph.append(formatBeanInfo(definition)); - graph.append("\n"); - } + /** + * 获取 Bean 实例(按名称和类型) + */ + public T getBean(String name, Class 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 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; + } + } } diff --git a/src/main/java/com/imyeyu/inject/annotation/Inject.java b/src/main/java/com/imyeyu/inject/annotation/Inject.java index 4241a5b..590428a 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Inject.java +++ b/src/main/java/com/imyeyu/inject/annotation/Inject.java @@ -6,11 +6,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 标记用于依赖注入的构造器 + * 标记用于依赖注入的构造器或字段 * * @author 夜雨 */ -@Target(ElementType.CONSTRUCTOR) +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Inject { } diff --git a/src/main/java/com/imyeyu/inject/annotation/Lazy.java b/src/main/java/com/imyeyu/inject/annotation/Lazy.java new file mode 100644 index 0000000..de7b3a5 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/Lazy.java @@ -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 { +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Scope.java b/src/main/java/com/imyeyu/inject/annotation/Scope.java index f8022c3..4628939 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Scope.java +++ b/src/main/java/com/imyeyu/inject/annotation/Scope.java @@ -18,4 +18,11 @@ public @interface Scope { * 作用域类型 */ ScopeType value() default ScopeType.SINGLETON; + + /** + * 是否懒加载(仅对 SINGLETON 作用域有效) + * true - 首次使用时才创建 + * false - 启动时立即创建(默认) + */ + boolean lazy() default false; }