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:
@ -1,766 +1,140 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.Bean;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
import com.imyeyu.inject.annotation.Controller;
|
||||
import com.imyeyu.inject.annotation.IOCAsync;
|
||||
import com.imyeyu.inject.annotation.IOCPriority;
|
||||
import com.imyeyu.inject.annotation.IOCReturn;
|
||||
import com.imyeyu.inject.annotation.Inject;
|
||||
import com.imyeyu.inject.annotation.InvokeForInjected;
|
||||
import com.imyeyu.inject.annotation.Resources;
|
||||
import com.imyeyu.inject.annotation.Service;
|
||||
import com.imyeyu.inject.annotation.StaticInject;
|
||||
import com.imyeyu.inject.annotation.SuperIOC;
|
||||
import com.imyeyu.inject.annotation.SuperInject;
|
||||
import com.imyeyu.inject.annotation.Import;
|
||||
import com.imyeyu.inject.annotation.TimiInjectApplication;
|
||||
import com.imyeyu.inject.annotation.Util;
|
||||
import com.imyeyu.io.IO;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.CallbackArg;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Timi 控制反转框架
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-03-04 23:14
|
||||
*/
|
||||
public class TimiInject implements InjectFactory {
|
||||
public class TimiInject {
|
||||
|
||||
/** 日志 */
|
||||
private static final Logger log = LoggerFactory.getLogger(TimiInject.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(TimiInject.class);
|
||||
|
||||
/** 类路径 */
|
||||
private final List<String> paths;
|
||||
private final BeanContext context;
|
||||
private final BeanFactory factory;
|
||||
|
||||
/** 控制反转对象 */
|
||||
private final Map<String, Object> objects;
|
||||
private TimiInject(BeanContext context, BeanFactory factory) {
|
||||
this.context = context;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
/** 依赖注入字段集 */
|
||||
private final List<Field> fields;
|
||||
/**
|
||||
* 启动应用并初始化容器
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass, String... args) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("Starting {} ...", applicationClass.getSimpleName());
|
||||
|
||||
// 注入后调度 Map<对象, Map<方法, 调度顺序>>
|
||||
private final Map<Object, Map<Method, Integer>> invokeForInjected;
|
||||
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
|
||||
if (annotation == null) {
|
||||
throw new InjectException("Application class must be annotated with @TimiInjectApplication");
|
||||
}
|
||||
|
||||
/** 控制反转监听 */
|
||||
private final Map<Class<?>, List<CallbackArg<Object>>> iocListeners;
|
||||
BeanContext context = new BeanContext();
|
||||
BeanFactory factory = new BeanFactory(context);
|
||||
BeanScanner scanner = new BeanScanner(context);
|
||||
|
||||
/** 静态注入类列表 */
|
||||
private final List<Class<?>> staticInject;
|
||||
String[] basePackages = annotation.value();
|
||||
if (basePackages.length == 0) {
|
||||
basePackages = new String[]{applicationClass.getPackage().getName()};
|
||||
}
|
||||
|
||||
/** 控制反转类对象 */
|
||||
private final List<IOCClass> classes;
|
||||
scanner.scan(basePackages);
|
||||
processImports(applicationClass, scanner, context);
|
||||
factory.initializeSingletons();
|
||||
|
||||
// 异步控制反转参数
|
||||
private int iocAsyncRunning = 0;
|
||||
private boolean hasAsyncIOC = false;
|
||||
private final Object iocAsyncLock = new Object();
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.info("Started {} in {} ms ({} beans)", applicationClass.getSimpleName(), elapsed,
|
||||
context.getAllDefinitions().size());
|
||||
|
||||
private TimiInject(InjectApp app) {
|
||||
if (TimiJava.isEmpty(app)) {
|
||||
throw new NullPointerException("empty inject app");
|
||||
}
|
||||
app.injector = this;
|
||||
// 初始化
|
||||
long initAt = Time.now();
|
||||
// Logo
|
||||
if (app.enableBanner) {
|
||||
if (IO.resourceExist(getClass(), "banner.txt")) {
|
||||
log.info(IO.resourceToString(getClass(), "banner.txt"));
|
||||
} else {
|
||||
log.info(IO.resourceToString(getClass(), "defBanner.txt"));
|
||||
}
|
||||
}
|
||||
log.info("Starting TimiInject {} ..", app.clazz);
|
||||
List<String> scanPathList = new ArrayList<>();
|
||||
objects = new HashMap<>();
|
||||
iocListeners = new HashMap<>();
|
||||
paths = new ArrayList<>();
|
||||
fields = new ArrayList<>();
|
||||
invokeForInjected = new LinkedHashMap<>();
|
||||
staticInject = new ArrayList<>();
|
||||
classes = new ArrayList<>();
|
||||
try {
|
||||
// 扫描
|
||||
long scanAt = Time.now();
|
||||
{
|
||||
// 扫描包
|
||||
log.info("Scanning..");
|
||||
TimiInjectApplication appAnnotation = app.clazz.getAnnotation(TimiInjectApplication.class);
|
||||
if (appAnnotation == null) {
|
||||
throw new NullPointerException("TimiInjectApplication can not be null, used annotation for scanner class");
|
||||
}
|
||||
if (TimiJava.isEmpty(appAnnotation.value())) {
|
||||
scanPathList.add(app.clazz.getPackageName());
|
||||
} else {
|
||||
scanPathList.addAll(List.of(appAnnotation.value()));
|
||||
}
|
||||
// 扫描类
|
||||
URI baseURI = app.clazz.getProtectionDomain().getCodeSource().getLocation().toURI();
|
||||
// baseURI: 可执行 jar 或编译 classes 路径
|
||||
for (int j = 0; j < scanPathList.size(); j++) {
|
||||
scan(new File(baseURI).getPath(), scanPathList.get(j));
|
||||
}
|
||||
}
|
||||
// 控制反转
|
||||
log.info("Running IOC..");
|
||||
long iocAt = Time.now();
|
||||
{
|
||||
// Application IOC
|
||||
if (app.obj != null) {
|
||||
objects.put(app.clazz.getName(), app.obj);
|
||||
// 控制反转方法返回
|
||||
objects.putAll(iocReturn(app.clazz, app.obj));
|
||||
// 收集此对象需要注入后调度的方法
|
||||
invokeForInjected.put(app.obj, invokeForInjected(app.clazz));
|
||||
}
|
||||
// 自 IOC
|
||||
objects.put(getClass().getName(), this);
|
||||
// 其他注解 IOC
|
||||
for (int j = 0; j < paths.size(); j++) {
|
||||
generateIOCClass(Class.forName(paths.get(j)));
|
||||
}
|
||||
classes.sort(Comparator.comparingInt(o -> o.level));
|
||||
for (int j = 0; j < classes.size(); j++) {
|
||||
final IOCClass iocClass = classes.get(j);
|
||||
try {
|
||||
// 控制反转
|
||||
if (iocClass.clazz.isAnnotationPresent(IOCAsync.class)) {
|
||||
// 异步实例化
|
||||
iocAsyncRunning++;
|
||||
hasAsyncIOC = true;
|
||||
return new TimiInject(context, factory);
|
||||
}
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
newInstance(iocClass);
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncRunning--;
|
||||
if (iocAsyncRunning < 1) {
|
||||
iocAsyncLock.notifyAll();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncLock.notifyAll();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
// 同步实例化
|
||||
newInstance(iocClass);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("IOC fail id: %s, class: %s".formatted(iocClass.id, iocClass.clazz), e);
|
||||
}
|
||||
}
|
||||
if (hasAsyncIOC && iocAsyncRunning != 0) {
|
||||
// 存在异步控制反转,且至少有一个类正在进行
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncLock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 依赖注入
|
||||
log.info("Running inject..");
|
||||
long injectAt = Time.now();
|
||||
{
|
||||
Collection<Object> objects = this.objects.values();
|
||||
for (Object object : objects) {
|
||||
inject(object, object.getClass());
|
||||
}
|
||||
// 静态注入
|
||||
for (int j = 0; j < staticInject.size(); j++) {
|
||||
inject(null, staticInject.get(j));
|
||||
}
|
||||
/**
|
||||
* 依赖注入 - 获取 Bean 实例(按类型)
|
||||
*/
|
||||
public <T> T di(Class<T> type) {
|
||||
return factory.getBean(type);
|
||||
}
|
||||
|
||||
// 完成注入
|
||||
for (int j = 0; j < app.afterInjectCallbackList.size(); j++) {
|
||||
app.afterInjectCallbackList.get(j).handler(this);
|
||||
}
|
||||
}
|
||||
log.info("Invoking injected callback..");
|
||||
// 注入调度
|
||||
long invokeForInjectedAt = Time.now();
|
||||
{
|
||||
for (Map.Entry<Object, Map<Method, Integer>> item : invokeForInjected.entrySet()) {
|
||||
for (Method method : item.getValue().keySet()) {
|
||||
try {
|
||||
long start = -1;
|
||||
if (log.isDebugEnabled()) {
|
||||
start = Time.now();
|
||||
}
|
||||
method.invoke(item.getKey());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("invoke injected event {} ms for {}#{}", "%4s".formatted(Time.now() - start), item.getKey().getClass().getName(), method.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("invoke method fail: " + item.getKey().getClass().getName() + "#" + method.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 静态
|
||||
for (int j = 0; j < staticInject.size(); j++) {
|
||||
Method[] methods = staticInject.get(j).getDeclaredMethods();
|
||||
for (int k = 0; k < methods.length; k++) {
|
||||
if (Modifier.isStatic(methods[k].getModifiers())) {
|
||||
methods[k].setAccessible(true);
|
||||
if (methods[k].getDeclaredAnnotation(InvokeForInjected.class) != null) {
|
||||
try {
|
||||
methods[k].invoke(null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("invoke method fail: " + methods[k].getDeclaringClass().getName() + "#" + methods[k].getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 依赖注入 - 获取 Bean 实例(按名称和类型)
|
||||
*/
|
||||
public <T> T di(String name, Class<T> type) {
|
||||
return factory.getBean(name, type);
|
||||
}
|
||||
|
||||
long now = Time.now();
|
||||
log.info("Completed!");
|
||||
log.info("IOC objects: " + objects.size());
|
||||
log.info("Inject fields: " + fields.size());
|
||||
log.info("Build Time: ");
|
||||
log.info("{} ms for Initialization", "%8s".formatted(scanAt - initAt));
|
||||
log.info("{} ms for Scan Classes", "%8s".formatted(iocAt - scanAt));
|
||||
log.info("{} ms for IOC", "%8s".formatted(injectAt - iocAt));
|
||||
log.info("{} ms for Inject", "%8s".formatted(invokeForInjectedAt - injectAt));
|
||||
log.info("{} ms for Invoke Callback", "%8s".formatted(now - invokeForInjectedAt));
|
||||
log.info("{} ms for Total", "%8s".formatted(now - initAt));
|
||||
} catch (Exception e) {
|
||||
log.error("TimiInject run error", e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 控制反转 - 手动注册 Bean 实例
|
||||
*/
|
||||
public void ioc(String name, Object bean) {
|
||||
context.registerSingleton(name, bean);
|
||||
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描包
|
||||
*
|
||||
* @param basePath 基本文件位置,可执行 jar 或编译 classes 路径。路径为系统分隔符
|
||||
* @param scanPath 扫描位置,源码包路径,分隔符为 '.'
|
||||
*/
|
||||
private void scan(String basePath, String scanPath) throws IOException {
|
||||
if (basePath.endsWith(".jar")) {
|
||||
// 扫描 jar class
|
||||
JarFile jarFile = new JarFile(basePath);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
scanPath = scanPath.replace('.', '/'); // jar 内路径始终为 '/' 分隔符
|
||||
while (entries.hasMoreElements()) {
|
||||
String name = entries.nextElement().getName();
|
||||
if (name.startsWith(scanPath) && name.endsWith(".class") && !name.contains("$")) {
|
||||
paths.add(name.substring(0, name.length() - 6).replace('/', '.'));
|
||||
}
|
||||
}
|
||||
jarFile.close();
|
||||
} else {
|
||||
// 扫描本地文件 class
|
||||
scanPath = scanPath.replace('.', File.separatorChar);
|
||||
scanFile(new File(basePath + File.separator + scanPath), basePath.length());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 导出依赖图
|
||||
*/
|
||||
public String exportDependencyGraph() {
|
||||
StringBuilder graph = new StringBuilder();
|
||||
graph.append("=== Dependency Graph ===\n\n");
|
||||
|
||||
/**
|
||||
* 扫描本地 class 文件
|
||||
*
|
||||
* @param directory 文件夹
|
||||
* @param clip 剪切位置
|
||||
*/
|
||||
private void scanFile(File directory, int clip) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isFile()) {
|
||||
String path = files[i].getPath();
|
||||
if (path.endsWith(".class") && !path.contains("$")) {
|
||||
paths.add(path.substring(clip + 1, path.length() - 6).replace(File.separatorChar, '.'));
|
||||
}
|
||||
} else {
|
||||
scanFile(files[i], clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
graph.append(formatBeanInfo(definition));
|
||||
graph.append("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成类的控制反转封装,如果此类没有标记控制反转则忽略
|
||||
*
|
||||
* @param clazz 类对象
|
||||
*/
|
||||
private void generateIOCClass(Class<?> clazz) {
|
||||
boolean isBean = clazz.isAnnotationPresent(Bean.class);
|
||||
boolean isComponent = clazz.isAnnotationPresent(Component.class);
|
||||
boolean isService = clazz.isAnnotationPresent(Service.class);
|
||||
boolean isController = clazz.isAnnotationPresent(Controller.class);
|
||||
boolean isResources = clazz.isAnnotationPresent(Resources.class);
|
||||
boolean isUtil = clazz.isAnnotationPresent(Util.class);
|
||||
// 注入
|
||||
boolean isInject = clazz.isAnnotationPresent(Inject.class);
|
||||
boolean isStaticInject = clazz.isAnnotationPresent(StaticInject.class);
|
||||
return graph.toString();
|
||||
}
|
||||
|
||||
// 获取反转对象 ID
|
||||
if (!(isBean || isComponent || isService || isController || isResources || isUtil || isInject)) {
|
||||
// 非控制反转相关类
|
||||
if (isStaticInject) {
|
||||
// 静态注入
|
||||
staticInject.add(clazz);
|
||||
}
|
||||
}
|
||||
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");
|
||||
|
||||
String id = null;
|
||||
if (isBean) {
|
||||
id = clazz.getAnnotation(Bean.class).value();
|
||||
}
|
||||
if (isComponent) {
|
||||
id = clazz.getAnnotation(Component.class).value();
|
||||
}
|
||||
if (isService) {
|
||||
id = clazz.getAnnotation(Service.class).value();
|
||||
}
|
||||
if (isController) {
|
||||
id = clazz.getAnnotation(Controller.class).value();
|
||||
}
|
||||
if (isResources) {
|
||||
id = clazz.getAnnotation(Resources.class).value();
|
||||
}
|
||||
if (isUtil) {
|
||||
id = clazz.getAnnotation(Util.class).value();
|
||||
}
|
||||
if (isInject) {
|
||||
id = clazz.getAnnotation(Inject.class).value();
|
||||
}
|
||||
if (id != null) {
|
||||
IOCClass iocClass = new IOCClass(id, clazz.getName(), clazz.isAnnotationPresent(IOCAsync.class), clazz);
|
||||
if (clazz.isAnnotationPresent(IOCPriority.class)) {
|
||||
// IOCPriority 注解类和异步控制反转优先实例化
|
||||
iocClass.level = clazz.getAnnotation(IOCPriority.class).value();
|
||||
}
|
||||
classes.add(iocClass);
|
||||
// 静态子类和父级静态子类控制反转
|
||||
Class<?> superClass = clazz;
|
||||
do {
|
||||
Class<?>[] declaredClasses = superClass.getDeclaredClasses();
|
||||
for (int i = 0; i < declaredClasses.length; i++) {
|
||||
if (Modifier.isStatic(declaredClasses[i].getModifiers())) {
|
||||
generateIOCClass(declaredClasses[i]);
|
||||
}
|
||||
}
|
||||
} while (superClass.isAnnotationPresent(SuperIOC.class) && (superClass = superClass.getSuperclass()) != null);
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化控制反转对象
|
||||
*
|
||||
* @param iocClass 控制反转类
|
||||
* @throws NoSuchMethodException 不存在构造方法异常
|
||||
* @throws InvocationTargetException 调用异常
|
||||
* @throws InstantiationException 实例化异常
|
||||
* @throws IllegalAccessException 访问异常
|
||||
*/
|
||||
private void newInstance(IOCClass iocClass) throws Exception {
|
||||
long start = -1;
|
||||
if (log.isDebugEnabled()) {
|
||||
start = Time.now();
|
||||
}
|
||||
if (definition.getPostConstruct() != null) {
|
||||
info.append(" PostConstruct: ").append(definition.getPostConstruct().getName()).append("()\n");
|
||||
}
|
||||
|
||||
Constructor<?> constructor = iocClass.clazz.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
Object object = constructor.newInstance();
|
||||
if (TimiJava.isEmpty(iocClass.id)) {
|
||||
objects.put(iocClass.path, object);
|
||||
} else {
|
||||
objects.put(iocClass.id, object);
|
||||
}
|
||||
return info.toString();
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
if (iocClass.isAsync) {
|
||||
log.debug("IOC {} ms(async) for {}: {}", "%4s".formatted(Time.now() - start), iocClass.id, iocClass.clazz.getName());
|
||||
} else {
|
||||
log.debug("IOC {} ms for {}: {}", "%4s".formatted(Time.now() - start), iocClass.id, iocClass.clazz.getName());
|
||||
}
|
||||
}
|
||||
private static void processImports(Class<?> applicationClass, BeanScanner scanner, BeanContext context) {
|
||||
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 控制反转方法返回
|
||||
objects.putAll(iocReturn(object.getClass(), object));
|
||||
// 收集此对象需要注入后调度的方法
|
||||
invokeForInjected.put(object, invokeForInjected(object.getClass()));
|
||||
}
|
||||
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
||||
Class<?>[] importClasses = importAnnotation.value();
|
||||
|
||||
/**
|
||||
* 控制反转方法返回对象
|
||||
*
|
||||
* @param clazz 所属类
|
||||
* @param object 类对象
|
||||
* @return 控制反转对象
|
||||
*/
|
||||
private Map<String, Object> iocReturn(Class<?> clazz, Object object) {
|
||||
Map<String, Object> iocObjects = new HashMap<>();
|
||||
// 控制反转方法返回
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
try {
|
||||
methods[i].setAccessible(true);
|
||||
IOCReturn iocReturn = methods[i].getAnnotation(IOCReturn.class);
|
||||
if (iocReturn != null) {
|
||||
if (methods[i].isAnnotationPresent(IOCAsync.class)) {
|
||||
iocAsyncRunning++;
|
||||
hasAsyncIOC = true;
|
||||
|
||||
final int j = i;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
long start = -1;
|
||||
if (log.isDebugEnabled()) {
|
||||
start = Time.now();
|
||||
}
|
||||
|
||||
Object returnObject = methods[j].invoke(object);
|
||||
if (returnObject == null) {
|
||||
throw new NullPointerException("return IOC object can not be null: " + clazz.getSimpleName() + "#" + methods[j].getName());
|
||||
}
|
||||
String id = iocReturn.value();
|
||||
if (TimiJava.isEmpty(id)) {
|
||||
id = returnObject.getClass().getName() + "#" + methods[j].getName();
|
||||
}
|
||||
objects.put(id, returnObject);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("IOC {} ms(async) for {}: {}", "%4s".formatted(Time.now() - start), id, returnObject.getClass().getName());
|
||||
}
|
||||
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncRunning--;
|
||||
if (iocAsyncRunning < 1) {
|
||||
iocAsyncLock.notifyAll();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncLock.notifyAll();
|
||||
}
|
||||
log.error("IOC fail class: %s#%s".formatted(clazz.getName(), methods[j].getName()), e);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
long start = -1;
|
||||
if (log.isDebugEnabled()) {
|
||||
start = Time.now();
|
||||
}
|
||||
|
||||
Object returnObject = methods[i].invoke(object);
|
||||
if (returnObject == null) {
|
||||
throw new NullPointerException("return IOC object can not be null: " + clazz.getSimpleName() + "#" + methods[i].getName());
|
||||
}
|
||||
String id = iocReturn.value();
|
||||
if (TimiJava.isEmpty(id)) {
|
||||
id = returnObject.getClass().getName() + "#" + methods[i].getName();
|
||||
}
|
||||
iocObjects.put(id, returnObject);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("IOC {} ms for {}: {}", "%4s".formatted(Time.now() - start), id, returnObject.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
log.error("IOC fail class: %s#%s".formatted(clazz.getName(), methods[i].getName()), e);
|
||||
}
|
||||
}
|
||||
return iocObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向变量注入对象
|
||||
*
|
||||
* @param object 控制反转对象
|
||||
* @param clazz 该变量类型(存在父级注入时递归调用)
|
||||
*/
|
||||
private void inject(Object object, Class<?> clazz) throws IllegalAccessException {
|
||||
if (clazz.isAnnotationPresent(SuperInject.class)) {
|
||||
// 父级注入
|
||||
inject(object, clazz.getSuperclass());
|
||||
}
|
||||
injectFields(object, clazz.getDeclaredFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* 向变量字段注入对象
|
||||
*
|
||||
* @param object 目标
|
||||
* @param fields 字段
|
||||
*/
|
||||
private void injectFields(Object object, Field... fields) throws IllegalAccessException {
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (object == null) {
|
||||
if (!Modifier.isStatic(fields[i].getModifiers())) {
|
||||
// 静态注入,忽略非静态属性
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Inject inject = fields[i].getAnnotation(Inject.class);
|
||||
if (inject != null) {
|
||||
fields[i].setAccessible(true);
|
||||
if (fields[i].getType().equals(TimiInject.class)) {
|
||||
// 注入注射器
|
||||
fields[i].set(object, this);
|
||||
} else {
|
||||
String value = inject.value();
|
||||
// 执行注入
|
||||
if (TimiJava.isEmpty(value)) {
|
||||
String id = fields[i].getType().getName() + "#" + fields[i].getName();
|
||||
|
||||
log.debug("Injecting {}", id);
|
||||
Object diObject = di(id, Object.class);
|
||||
try {
|
||||
if (diObject == null) {
|
||||
fields[i].set(object, di(fields[i].getType()));
|
||||
} else {
|
||||
fields[i].set(object, diObject);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (object == null) {
|
||||
throw new RuntimeException("inject field fail: null#" + fields[i].getName(), e);
|
||||
} else {
|
||||
throw new RuntimeException("inject field fail: " + object.getClass().getName() + "#" + fields[i].getName(), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fields[i].set(object, objects.get(value));
|
||||
}
|
||||
// 添加到集合
|
||||
this.fields.add(fields[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集此对象需要注入后调度的方法
|
||||
*
|
||||
* @param clazz 对象类
|
||||
*/
|
||||
private Map<Method, Integer> invokeForInjected(Class<?> clazz) {
|
||||
Map<Method, Integer> map = new HashMap<>();
|
||||
if (clazz.isAnnotationPresent(SuperInject.class)) {
|
||||
// 父级调度
|
||||
map.putAll(invokeForInjected(clazz.getSuperclass()));
|
||||
}
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
methods[i].setAccessible(true);
|
||||
InvokeForInjected invokeForInjected = methods[i].getAnnotation(InvokeForInjected.class);
|
||||
if (invokeForInjected != null) {
|
||||
map.put(methods[i], invokeForInjected.value());
|
||||
}
|
||||
}
|
||||
|
||||
// 排序
|
||||
List<Map.Entry<Method, Integer>> list = new ArrayList<>(map.entrySet());
|
||||
list.sort(Comparator.comparingDouble(o -> o.getValue().doubleValue()));
|
||||
|
||||
LinkedHashMap<Method, Integer> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Method, Integer> entry : list) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 TimiInject 注入框架,此方法应运行且只运行一次
|
||||
*
|
||||
* @param clazz TimiInjectApplication 注解的类
|
||||
*/
|
||||
public static synchronized TimiInject run(Class<?> clazz) {
|
||||
return run(new InjectApp(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 TimiInject 注入框架,此方法应运行且只运行一次
|
||||
*
|
||||
* @param obj TimiInjectApplication 注解的对象
|
||||
*/
|
||||
public static synchronized TimiInject run(Object obj) {
|
||||
return run(new InjectApp(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行 TimiInject 注入框架,此方法应运行且只运行一次
|
||||
*
|
||||
* @param app 控制反转应用
|
||||
*/
|
||||
public static synchronized TimiInject run(InjectApp app) {
|
||||
return new TimiInject(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 获取依赖注入对象
|
||||
*
|
||||
* @param id ID
|
||||
* @return 控制反转的对象
|
||||
*/
|
||||
@Override
|
||||
public <T> T di(String id, Class<T> clazz) {
|
||||
return clazz.cast(objects.get(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取依赖注入对象
|
||||
*
|
||||
* @param classType 类型
|
||||
* @param <T> 控制反转类型
|
||||
* @return 控制反转对象
|
||||
*/
|
||||
@Override
|
||||
public <T> T di(Class<T> classType) {
|
||||
for (Object next : objects.values()) {
|
||||
if (next.getClass().getName().equals(classType.getName()) || classType.isAssignableFrom(next.getClass())) {
|
||||
return classType.cast(next);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ioc(Object object) {
|
||||
ioc(object.getClass(), object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ioc(Class<?> clazz, Object object) {
|
||||
ioc(clazz.getName(), object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ioc(String id, Object object) {
|
||||
try {
|
||||
log.info("Call IOC: " + id);
|
||||
// ---------- 控制反转 ----------
|
||||
long iocAt = Time.now();
|
||||
// 本类对象
|
||||
objects.put(id, object);
|
||||
// 方法返回对象
|
||||
Map<String, Object> iocReturnObjects = iocReturn(object.getClass(), object);
|
||||
objects.putAll(iocReturnObjects);
|
||||
|
||||
// ---------- 注入 ----------
|
||||
long injectAt = Time.now();
|
||||
// 本类对象字段
|
||||
inject(object, object.getClass());
|
||||
// 其他类的本类对象注入
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (fields.get(i).getType().equals(object.getClass())) {
|
||||
fields.get(i).set(di(fields.get(i).getDeclaringClass()), object);
|
||||
}
|
||||
}
|
||||
// 方法返回对象注入
|
||||
for (Map.Entry<String, Object> iocObject : iocReturnObjects.entrySet()) {
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (fields.get(i).getType().equals(iocObject.getValue().getClass())) {
|
||||
fields.get(i).set(di(fields.get(i).getDeclaringClass()), iocObject.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
long afterInjectingAt = Time.now();
|
||||
// 一般注入后调度
|
||||
Map<Method, Integer> methods = invokeForInjected(object.getClass());
|
||||
for (Map.Entry<Method, Integer> method : methods.entrySet()) {
|
||||
method.getKey().invoke(object);
|
||||
}
|
||||
|
||||
long callIOCListenersAt = Time.now();
|
||||
// 控制反转监听
|
||||
if (iocListeners.get(object.getClass()) != null) {
|
||||
List<CallbackArg<Object>> listeners = iocListeners.get(object.getClass());
|
||||
for (int i = 0; i < listeners.size(); i++) {
|
||||
listeners.get(i).handler(object);
|
||||
}
|
||||
}
|
||||
long now = Time.now();
|
||||
log.info("Completed for {} IOC object in {} ms", (iocReturnObjects.size() + 1), now - iocAt);
|
||||
|
||||
log.debug("Build Time Detail: ");
|
||||
log.debug("{} ms for IOC", "%8s".formatted(injectAt - iocAt));
|
||||
log.debug("{} ms for Inject", "%8s".formatted(afterInjectingAt - injectAt));
|
||||
log.debug("{} ms for Injected Event", "%8s".formatted(callIOCListenersAt - afterInjectingAt));
|
||||
log.debug("{} ms for IOC Listener Event", "%8s".formatted(now - callIOCListenersAt));
|
||||
log.debug("{} ms for Total", "%8s".formatted(now - iocAt));
|
||||
} catch (Exception e) {
|
||||
log.error("call ioc fail: " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加控制反转监听,这通常是为强制控制反转的类监听,因为第一轮控制反转是监听不到的
|
||||
*
|
||||
* @param clazz 控制反转类
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public synchronized void addIOCListener(Class<?> clazz, CallbackArg<Object> listener) {
|
||||
iocListeners.computeIfAbsent(clazz, k -> new ArrayList<>()).add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除控制反转监听
|
||||
*
|
||||
* @param clazz 控制反转类
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public synchronized void removeIOCListener(Class<?> clazz, CallbackArg<Object> listener) {
|
||||
iocListeners.computeIfAbsent(clazz, k -> new ArrayList<>()).remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制反转类
|
||||
*
|
||||
* <p>夜雨 创建于 2022-10-11 09:42
|
||||
*/
|
||||
private static class IOCClass {
|
||||
|
||||
/** id */
|
||||
String id;
|
||||
|
||||
/** 类路径 */
|
||||
String path;
|
||||
|
||||
/** true 为异步实例化 */
|
||||
boolean isAsync;
|
||||
|
||||
/** 类对象 */
|
||||
Class<?> clazz;
|
||||
|
||||
/** 优先级 */
|
||||
int level;
|
||||
|
||||
public IOCClass(String id, String path, boolean isAsync, Class<?> clazz) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.isAsync = isAsync;
|
||||
this.clazz = clazz;
|
||||
this.level = Integer.MAX_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: " + importClass.getName(), e);
|
||||
}
|
||||
} else {
|
||||
scanner.processClass(importClass);
|
||||
log.debug("Imported configuration: {}", importClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user