implement lightweight IOC/DI framework

- Add annotation system (@Component, @Service, @Configuration, @Bean, @Inject, @Qualifier, @Primary, @Scope, @PostConstruct, @Import, @TimiInjectApplication)
- Implement BeanContext for managing bean instances and definitions
- Implement BeanScanner for component scanning (file system and jar)
- Implement BeanFactory with constructor injection and lifecycle support
- Support @Configuration and @Bean factory methods
- Support @Qualifier and @Primary for multi-implementation
- Support @Scope (singleton/prototype)
- Support @PostConstruct lifecycle callback
- Support @Import and Module extension
- Add circular dependency detection
- Add dependency graph export
- Add demo and test cases

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Timi
2026-01-11 12:26:14 +08:00
parent 05606eda74
commit 71be7c07c0
43 changed files with 1429 additions and 1129 deletions

View File

@ -0,0 +1,216 @@
package com.imyeyu.inject;
import com.imyeyu.inject.annotation.Qualifier;
import com.imyeyu.inject.annotation.ScopeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
/**
* Bean 工厂,负责创建和管理 Bean 实例
*
* @author 夜雨
*/
public class BeanFactory {
private static final Logger log = LoggerFactory.getLogger(BeanFactory.class);
private final BeanContext context;
private final Set<String> creating = new HashSet<>();
public BeanFactory(BeanContext context) {
this.context = context;
}
/**
* 获取 Bean 实例(按名称)
*/
public Object getBean(String name) {
BeanDefinition definition = context.getDefinition(name);
if (definition == null) {
throw new InjectException("No bean definition found for name: " + name);
}
if (definition.getScope() == ScopeType.SINGLETON) {
Object singleton = context.getSingleton(name);
if (singleton != null) {
return singleton;
}
}
return createBean(definition);
}
/**
* 获取 Bean 实例(按类型)
*/
@SuppressWarnings("unchecked")
public <T> T getBean(Class<T> type) {
List<String> candidates = context.getBeanNamesByType(type);
if (candidates.isEmpty()) {
throw new InjectException("No bean found for type: " + type.getName());
}
if (candidates.size() == 1) {
return (T) getBean(candidates.get(0));
}
String primaryBean = findPrimaryBean(candidates);
if (primaryBean != null) {
return (T) getBean(primaryBean);
}
throw new InjectException("Multiple beans found for type " + type.getName() +
": " + candidates + ". Use @Qualifier or @Primary to specify which one to inject");
}
/**
* 获取 Bean 实例(按名称和类型)
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String name, Class<T> type) {
Object bean = getBean(name);
if (!type.isInstance(bean)) {
throw new InjectException("Bean '" + name + "' is not of type " + type.getName());
}
return (T) bean;
}
private Object createBean(BeanDefinition definition) {
String name = definition.getName();
if (creating.contains(name)) {
throw new InjectException("Circular dependency detected: " + name +
" (creation chain: " + creating + ")");
}
creating.add(name);
try {
Object instance;
if (definition.isFactoryBean()) {
instance = createBeanFromFactoryMethod(definition);
} else {
instance = createBeanFromConstructor(definition);
}
if (definition.getScope() == ScopeType.SINGLETON) {
context.setSingleton(name, instance);
}
invokePostConstruct(instance, definition);
log.debug("Created bean: {} ({})", name, definition.getType().getName());
return instance;
} finally {
creating.remove(name);
}
}
private Object createBeanFromConstructor(BeanDefinition definition) {
Constructor<?> constructor = definition.getConstructor();
constructor.setAccessible(true);
Parameter[] parameters = constructor.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
args[i] = resolveDependency(parameters[i]);
}
try {
return constructor.newInstance(args);
} catch (Exception e) {
throw new InjectException("Failed to instantiate bean: " + definition.getName(), e);
}
}
private Object createBeanFromFactoryMethod(BeanDefinition definition) {
Method factoryMethod = definition.getFactoryMethod();
factoryMethod.setAccessible(true);
String configBeanName = extractConfigBeanName(factoryMethod.getDeclaringClass());
Object configInstance = getBean(configBeanName);
Parameter[] parameters = factoryMethod.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
args[i] = resolveDependency(parameters[i]);
}
try {
Object result = factoryMethod.invoke(configInstance, args);
if (result == null) {
throw new InjectException("@Bean method returned null: " + factoryMethod.getName());
}
return result;
} catch (Exception e) {
throw new InjectException("Failed to invoke @Bean method: " + factoryMethod.getName(), e);
}
}
private Object resolveDependency(Parameter parameter) {
Qualifier qualifier = parameter.getAnnotation(Qualifier.class);
if (qualifier != null) {
return getBean(qualifier.value());
}
Class<?> type = parameter.getType();
return getBean(type);
}
private void invokePostConstruct(Object instance, BeanDefinition definition) {
Method postConstruct = definition.getPostConstruct();
if (postConstruct == null) {
return;
}
postConstruct.setAccessible(true);
try {
postConstruct.invoke(instance);
} catch (Exception e) {
throw new InjectException("Failed to invoke @PostConstruct method: " +
postConstruct.getName(), e);
}
}
private String findPrimaryBean(List<String> candidates) {
String primaryBean = null;
for (String name : candidates) {
BeanDefinition definition = context.getDefinition(name);
if (definition.isPrimary()) {
if (primaryBean != null) {
throw new InjectException("Multiple @Primary beans found: " + primaryBean + ", " + name);
}
primaryBean = name;
}
}
return primaryBean;
}
private String extractConfigBeanName(Class<?> configClass) {
String simpleName = configClass.getSimpleName();
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
}
/**
* 初始化所有单例 Bean
*/
public void initializeSingletons() {
for (BeanDefinition definition : context.getAllDefinitions()) {
if (definition.getScope() == ScopeType.SINGLETON) {
getBean(definition.getName());
}
}
}
}