diff --git a/.gitignore b/.gitignore index 5ff6309..0ec825b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +/.claude +/CLAUDE.md +/AGENTS.md + target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ diff --git a/README.md b/README.md index 23654c5..ff465df 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,199 @@ # timi-inject -Java 轻量控制反转工具 \ No newline at end of file +Java 轻量级控制反转与依赖注入框架。 + +## 特性 + +- 基于注解的依赖注入 +- 构造器注入为核心路径 +- 支持 @Configuration + @Bean +- 支持 @Qualifier 与 @Primary +- 支持 @Scope(singleton / prototype) +- 支持 @Import 与 Module 扩展 +- 支持 @PostConstruct 生命周期回调 +- 提供依赖图导出 + +## 引入依赖 + +Maven: + +```xml + + com.imyeyu.inject + timi-inject + 0.0.1 + +``` + +## 快速开始 + +1) 定义应用入口 + +```java +@TimiInjectApplication("com.example.demo") +public class DemoApp { +} +``` + +2) 定义组件与配置 + +```java +@Service +public class UserService { + public String hello() { + return "hi"; + } +} + +@Configuration +public class DemoConfig { + @Bean("appName") + public String appName() { + return "timi-inject"; + } +} +``` + +3) 启动并获取 Bean + +```java +public static void main(String[] args) { + TimiInject inject = TimiInject.run(DemoApp.class); + UserService userService = inject.di(UserService.class); + String appName = inject.di("appName", String.class); +} +``` + +## 注解说明 + +组件注解(类上): + +- @Component +- @Service +- @Controller +- @Resources +- @Util + +配置注解: + +- @Configuration: 配置类 +- @Bean: 定义 Bean 工厂方法 +- @Import: 引入外部配置或组件 + +依赖注入: + +- 默认构造器注入 +- @Inject: 指定构造器 +- @Qualifier: 按名称限定注入 +- @Primary: 多实现时优先注入 + +作用域: + +- @Scope(ScopeType.SINGLETON) +- @Scope(ScopeType.PROTOTYPE) + +生命周期: + +- @PostConstruct: 初始化回调 + +## 多实现与限定注入 + +```java +@Component("fastStorage") +public class FastStorage implements Storage {} + +@Component("defaultStorage") +public class DefaultStorage implements Storage {} + +@Service +public class ReportService { + private final Storage storage; + + public ReportService(@Qualifier("fastStorage") Storage storage) { + this.storage = storage; + } +} +``` + +## @Primary 示例 + +```java +@Component +@Primary +public class DefaultStorage implements Storage {} +``` + +## @Configuration 与 @Bean 示例 + +```java +@Configuration +public class AppConfig { + @Bean + public Clock clock() { + return Clock.systemUTC(); + } +} +``` + +## @Inject 示例 + +```java +@Service +public class UserService { + private final UserRepository repo; + + @Inject + public UserService(UserRepository repo) { + this.repo = repo; + } +} +``` + +## @Import 与 Module + +```java +@TimiInjectApplication("com.example.demo") +@Import({ExternalConfig.class, DemoModule.class}) +public class DemoApp { +} + +public class DemoModule implements Module { + @Override + public void configure(BeanContext context) { + // 手动注册 BeanDefinition 或外部单例 + } +} +``` + +## 手动注册 Bean + +```java +TimiInject inject = TimiInject.run(DemoApp.class); +inject.ioc("runtimeConfig", new RuntimeConfig()); +``` + +## 导出依赖图 + +```java +String graph = inject.exportDependencyGraph(); +System.out.println(graph); +``` + +## JavaFX 示例 + +测试目录下提供独立 JavaFX demo: + +- 入口: `src/test/java/com/imyeyu/inject/javafxdemo/FxMain.java` +- 配置: `src/test/java/com/imyeyu/inject/javafxdemo/FxConfig.java` + +运行方式: + +```bash +mvn -DskipTests javafx:run +``` + +## 约束与建议 + +- 建议单一构造器,或显式使用 @Inject 标注 +- 多实现时必须使用 @Qualifier 或 @Primary +- 避免在 @Bean 方法返回 null diff --git a/pom.xml b/pom.xml index 63f4b5a..27c860e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,25 +6,32 @@ com.imyeyu.inject timi-inject - 0.0.1 + 0.0.2 21 21 UTF-8 true + 21.0.2 com.imyeyu.io timi-io - 0.0.1 + 0.0.2 ch.qos.logback logback-classic - 1.5.13 + 1.5.24 + + + org.openjfx + javafx-controls + ${javafx.version} + test diff --git a/src/main/java/com/imyeyu/inject/BeanContext.java b/src/main/java/com/imyeyu/inject/BeanContext.java new file mode 100644 index 0000000..fda7b1b --- /dev/null +++ b/src/main/java/com/imyeyu/inject/BeanContext.java @@ -0,0 +1,101 @@ +package com.imyeyu.inject; + +import com.imyeyu.inject.annotation.ScopeType; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Bean 容器上下文 + * + * @author 夜雨 + */ +public class BeanContext { + + private final Map definitions = new ConcurrentHashMap<>(); + private final Map singletons = 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"); + } + + 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("Cannot register null bean: " + 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 BeanDefinition getDefinition(String name) { + return definitions.get(name); + } + + /** + * 获取单例 Bean + */ + public Object getSingleton(String name) { + return singletons.get(name); + } + + /** + * 设置单例 Bean + */ + public void setSingleton(String name, Object bean) { + singletons.put(name, bean); + } + + /** + * 根据类型查找 Bean 名称列表 + */ + public List getBeanNamesByType(Class> type) { + List names = new ArrayList<>(); + + for (Map.Entry, List> entry : typeIndex.entrySet()) { + if (type.isAssignableFrom(entry.getKey())) { + names.addAll(entry.getValue()); + } + } + + return names; + } + + /** + * 获取所有 Bean 定义 + */ + public Collection getAllDefinitions() { + return definitions.values(); + } + + /** + * 检查是否包含指定名称的 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 new file mode 100644 index 0000000..ccdb000 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/BeanDefinition.java @@ -0,0 +1,132 @@ +package com.imyeyu.inject; + +import com.imyeyu.inject.annotation.ScopeType; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Bean 定义元数据 + * + * @author 夜雨 + */ +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 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; + } + + public String getName() { + return name; + } + + public Class> getType() { + return type; + } + + public ScopeType getScope() { + return scope; + } + + public boolean isPrimary() { + return primary; + } + + public Constructor> getConstructor() { + return constructor; + } + + public Method getFactoryMethod() { + return factoryMethod; + } + + public Object getConfigInstance() { + return configInstance; + } + + public Method getPostConstruct() { + return postConstruct; + } + + public boolean isFactoryBean() { + return factoryMethod != null; + } + + public static Builder builder() { + return new Builder(); + } + + 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 Builder name(String name) { + this.name = name; + return this; + } + + public Builder type(Class> type) { + this.type = type; + return this; + } + + public Builder scope(ScopeType scope) { + this.scope = scope; + return this; + } + + public Builder primary(boolean primary) { + this.primary = primary; + return this; + } + + public Builder constructor(Constructor> constructor) { + this.constructor = constructor; + return this; + } + + public Builder factoryMethod(Method method) { + this.factoryMethod = method; + return this; + } + + public Builder configInstance(Object instance) { + this.configInstance = instance; + return this; + } + + public Builder postConstruct(Method method) { + this.postConstruct = method; + 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 new file mode 100644 index 0000000..d9122d8 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/BeanFactory.java @@ -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 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 getBean(Class type) { + List 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 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; + } + + 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 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()); + } + } + } +} diff --git a/src/main/java/com/imyeyu/inject/BeanScanner.java b/src/main/java/com/imyeyu/inject/BeanScanner.java new file mode 100644 index 0000000..c8b8873 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/BeanScanner.java @@ -0,0 +1,251 @@ +package com.imyeyu.inject; + +import com.imyeyu.inject.annotation.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +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.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Bean 扫描器 + * + * @author 夜雨 + */ +public class BeanScanner { + + private static final Logger log = LoggerFactory.getLogger(BeanScanner.class); + + private final BeanContext context; + + public BeanScanner(BeanContext context) { + this.context = context; + } + + /** + * 扫描指定包路径下的所有组件 + */ + public void scan(String... basePackages) { + for (String pkg : basePackages) { + scanPackage(pkg); + } + } + + 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(); + + 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); + } + } + + private void scanFileSystem(File directory, String basePackage) { + if (!directory.exists() || !directory.isDirectory()) { + 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); + } + } + } + + 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(); + + 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); + } + } + + 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); + } + } + + /** + * 处理单个类 + */ + public void processClass(Class> clazz) { + if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum()) { + 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); + + BeanDefinition definition = BeanDefinition.builder() + .name(beanName) + .type(clazz) + .scope(scope) + .primary(primary) + .constructor(constructor) + .postConstruct(postConstruct) + .build(); + + context.registerDefinition(definition); + log.debug("Registered bean: {} ({})", beanName, clazz.getName()); + + if (clazz.isAnnotationPresent(Configuration.class)) { + processBeanMethods(clazz); + } + } + + 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(); + + if (returnType == void.class || returnType == Void.class) { + throw new InjectException("@Bean method cannot return void: " + method.getName()); + } + + 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()); + } + } + + 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; + } + } + + 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 + } + + 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 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 " + 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 " + 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; + } +} diff --git a/src/main/java/com/imyeyu/inject/InjectApp.java b/src/main/java/com/imyeyu/inject/InjectApp.java deleted file mode 100644 index 0619d45..0000000 --- a/src/main/java/com/imyeyu/inject/InjectApp.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.imyeyu.inject; - -import com.imyeyu.java.bean.CallbackArg; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author 夜雨 - * @since 2024-10-10 15:20 - */ -public final class InjectApp { - - Object obj; - - Class> clazz; - - TimiInject injector; - - boolean enableBanner = true; - - /** 全局注入后监听,包括静态注入 */ - final List> afterInjectCallbackList = new ArrayList<>(); - - public InjectApp(Object obj) { - this.obj = obj; - this.clazz = obj.getClass(); - } - - public InjectApp(Class> clazz) { - this.clazz = clazz; - } - - public InjectApp disableBanner() { - enableBanner = false; - return this; - } - - public TimiInject injector() { - return injector; - } - - /** - * 添加全局注入后监听 - * - * @param listener 监听器 - */ - public synchronized void addAfterInjectListener(CallbackArg listener) { - afterInjectCallbackList.add(listener); - } - - /** - * 移除全局注入后监听 - * - * @param listener 监听器 - */ - public synchronized void removeAfterInjectListener(CallbackArg listener) { - afterInjectCallbackList.remove(listener); - } - - public boolean isEnableBanner() { - return enableBanner; - } - - public void setEnableBanner(boolean enableBanner) { - this.enableBanner = enableBanner; - } -} diff --git a/src/main/java/com/imyeyu/inject/InjectException.java b/src/main/java/com/imyeyu/inject/InjectException.java new file mode 100644 index 0000000..9f56cd1 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/InjectException.java @@ -0,0 +1,17 @@ +package com.imyeyu.inject; + +/** + * IOC/DI 框架异常 + * + * @author 夜雨 + */ +public class InjectException extends RuntimeException { + + public InjectException(String message) { + super(message); + } + + public InjectException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/imyeyu/inject/InjectFactory.java b/src/main/java/com/imyeyu/inject/InjectFactory.java deleted file mode 100644 index 6828b3d..0000000 --- a/src/main/java/com/imyeyu/inject/InjectFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.imyeyu.inject; - -/** - * 注入工厂,顶层接口 - * - * @author 夜雨 - * @version 2022-03-04 23:18 - */ -interface InjectFactory { - - /** - * 根据 ID 获取反转对象 - * - * @param id ID - * @return 反转对象 - */ - T di(String id, Class clazz); - - /** - * 根据类型获取反转对象 - * - * @param classType 类型 - * @return 反转对象 - * @param 对象类型 - */ - T di(Class classType); - - /** - * 强制控制反转并重新注入 - * - * @param object 反转对象 - */ - void ioc(Object object); - - /** - * 强制控制反转并重新注入 - * - * @param clazz 反转类型 - * @param object 反转对象 - */ - void ioc(Class> clazz, Object object); - - /** - * 强制控制反转并重新注入 - * - * @param id 对象 ID - * @param object 反转对象 - */ - void ioc(String id, Object object); -} diff --git a/src/main/java/com/imyeyu/inject/Module.java b/src/main/java/com/imyeyu/inject/Module.java new file mode 100644 index 0000000..a32fa13 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/Module.java @@ -0,0 +1,16 @@ +package com.imyeyu.inject; + +/** + * 模块扩展接口,用于手动配置 Bean + * + * @author 夜雨 + */ +public interface Module { + + /** + * 配置 Bean 上下文 + * + * @param context Bean 上下文 + */ + void configure(BeanContext context); +} diff --git a/src/main/java/com/imyeyu/inject/TimiInject.java b/src/main/java/com/imyeyu/inject/TimiInject.java index bcd0cb8..6f7c484 100644 --- a/src/main/java/com/imyeyu/inject/TimiInject.java +++ b/src/main/java/com/imyeyu/inject/TimiInject.java @@ -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 paths; + private final BeanContext context; + private final BeanFactory factory; - /** 控制反转对象 */ - private final Map objects; + private TimiInject(BeanContext context, BeanFactory factory) { + this.context = context; + this.factory = factory; + } - /** 依赖注入字段集 */ - private final List fields; + /** + * 启动应用并初始化容器 + */ + public static TimiInject run(Class> applicationClass, String... args) { + long startTime = System.currentTimeMillis(); + log.info("Starting {} ...", applicationClass.getSimpleName()); - // 注入后调度 Map<对象, Map<方法, 调度顺序>> - private final Map> invokeForInjected; + TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class); + if (annotation == null) { + throw new InjectException("Application class must be annotated with @TimiInjectApplication"); + } - /** 控制反转监听 */ - private final Map, List>> iocListeners; + BeanContext context = new BeanContext(); + BeanFactory factory = new BeanFactory(context); + BeanScanner scanner = new BeanScanner(context); - /** 静态注入类列表 */ - private final List> staticInject; + String[] basePackages = annotation.value(); + if (basePackages.length == 0) { + basePackages = new String[]{applicationClass.getPackage().getName()}; + } - /** 控制反转类对象 */ - private final List 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 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 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 di(Class 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> 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 di(String name, Class 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 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 iocReturn(Class> clazz, Object object) { - Map 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 invokeForInjected(Class> clazz) { - Map 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> list = new ArrayList<>(map.entrySet()); - list.sort(Comparator.comparingDouble(o -> o.getValue().doubleValue())); - - LinkedHashMap result = new LinkedHashMap<>(); - for (Map.Entry 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 di(String id, Class clazz) { - return clazz.cast(objects.get(id)); - } - - /** - * 根据类型获取依赖注入对象 - * - * @param classType 类型 - * @param 控制反转类型 - * @return 控制反转对象 - */ - @Override - public T di(Class 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 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 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 methods = invokeForInjected(object.getClass()); - for (Map.Entry method : methods.entrySet()) { - method.getKey().invoke(object); - } - - long callIOCListenersAt = Time.now(); - // 控制反转监听 - if (iocListeners.get(object.getClass()) != null) { - List> 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 listener) { - iocListeners.computeIfAbsent(clazz, k -> new ArrayList<>()).add(listener); - } - - /** - * 移除控制反转监听 - * - * @param clazz 控制反转类 - * @param listener 监听器 - */ - public synchronized void removeIOCListener(Class> clazz, CallbackArg listener) { - iocListeners.computeIfAbsent(clazz, k -> new ArrayList<>()).remove(listener); - } - - /** - * 控制反转类 - * - * 夜雨 创建于 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()); + } + } + } } diff --git a/src/main/java/com/imyeyu/inject/annotation/Bean.java b/src/main/java/com/imyeyu/inject/annotation/Bean.java index fd7e991..2aecf63 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Bean.java +++ b/src/main/java/com/imyeyu/inject/annotation/Bean.java @@ -1,26 +1,21 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(通用对象),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * Bean 工厂方法注解,用于 @Configuration 类中 * * @author 夜雨 - * @since 2024-07-19 15:48 */ -@Target({ElementType.TYPE}) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface Bean { - /** - * 对象名称,非空时注入需要明确执行 - * - * @return 对象名称 - */ - String value() default ""; + /** + * Bean 名称,默认为方法名 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Component.java b/src/main/java/com/imyeyu/inject/annotation/Component.java index caec677..2fd6f94 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Component.java +++ b/src/main/java/com/imyeyu/inject/annotation/Component.java @@ -1,26 +1,21 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(组件),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * 通用组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface Component { - /** - * 组件名称,非空时注入需要明确执行 - * - * @return 组件名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/SuperIOC.java b/src/main/java/com/imyeyu/inject/annotation/Configuration.java similarity index 51% rename from src/main/java/com/imyeyu/inject/annotation/SuperIOC.java rename to src/main/java/com/imyeyu/inject/annotation/Configuration.java index 206872c..5975881 100644 --- a/src/main/java/com/imyeyu/inject/annotation/SuperIOC.java +++ b/src/main/java/com/imyeyu/inject/annotation/Configuration.java @@ -1,19 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 父级控制反转,通常用于父级存在控制反转子类时使用,父级可以继续使用此注解 + * 配置类注解 * * @author 夜雨 - * @version 2023-02-28 11:42 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface SuperIOC { +@Component +public @interface Configuration { + + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Controller.java b/src/main/java/com/imyeyu/inject/annotation/Controller.java index f101063..d32be18 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Controller.java +++ b/src/main/java/com/imyeyu/inject/annotation/Controller.java @@ -1,26 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(控制器),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * 控制器组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Component public @interface Controller { - /** - * 控制器名称,非空时注入需要明确执行 - * - * @return 控制器名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java b/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java deleted file mode 100644 index eaa9452..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 异步控制反转,控制反转的类或 {@link IOCReturn} 方法使用此注解将会在异步线程中实例化或调用。 - * 此注解不影响注入流程。 - * 此注解对实例化比较耗时的动作有明显的速度提升,有 {@link IOCPriority} 的效果,对实例化耗时较短的动作使用将会适得其反。 - * 注意:JavaFX 组件不可用,因为它只有一个 UI 线程 - * - * @author 夜雨 - * @version 2022-10-10 17:05 - */ -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IOCAsync { -} diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java b/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java deleted file mode 100644 index ffe3fa5..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 控制反转类使用此注解时将会优先执行实例化,对不存在异步控制反转的类使用不会提升效率。 - * 使用此注解后,{@link InvokeForInjected} 的方法也会优先于其他类触发 - * - * @author 夜雨 - * @version 2022-10-10 23:17 - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IOCPriority { - - /** - * 优先级,默认所有使用此注解的类都会往前挤,除非使用指定等级,数值小的优先执行 - * - * @return 优先等级,默认 {@link Integer#MAX_VALUE} / 2,非此注解的类的优先级为 {@link Integer#MAX_VALUE} - */ - int value() default Integer.MAX_VALUE / 2; -} diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java b/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java deleted file mode 100644 index df40411..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 控制反转(方法返回对象),注解在方法上,该方法所在类也必须控制反转到 TimiInject 中,经过此注解的方法返回值也会控制反转到 TimiInject 中并后续执行注入 - * - * 注意:注解参数为空时 {@link Inject} 注入对象的变量名需要和此方法名一致 - * - * @author 夜雨 - * @version 2022-03-04 23:16 - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IOCReturn { - - /** - * 对象名称,非空时注入需要明确指定 - * - * 为空时注入对象的变量名需要和此方法名一致 - * - * @return 对象名称 - */ - String value() default ""; -} diff --git a/src/main/java/com/imyeyu/inject/annotation/Import.java b/src/main/java/com/imyeyu/inject/annotation/Import.java new file mode 100644 index 0000000..fd40c8e --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/Import.java @@ -0,0 +1,21 @@ +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; + +/** + * 引入外部配置类或模块 + * + * @author 夜雨 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Import { + + /** + * 要引入的配置类或模块类 + */ + Class>[] value(); +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Inject.java b/src/main/java/com/imyeyu/inject/annotation/Inject.java index 2f00345..4241a5b 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Inject.java +++ b/src/main/java/com/imyeyu/inject/annotation/Inject.java @@ -6,25 +6,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 注入,使用在类变量上,该变量的类需要在 TimiInject 控制反转中才能执行对象注入 - * - * 注意 - * - * 如果该类使用 {@link StaticInject} 静态注入,此变量必须为静态变量 - * 如果是 {@link IOCReturn} 控制反转的对象,并且控制反转时没有指定名称,此变量名称需要和控制反转方法名一致 - * + * 标记用于依赖注入的构造器 * * @author 夜雨 - * @version 2022-03-04 23:15 */ -@Target({ElementType.FIELD}) +@Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.RUNTIME) public @interface Inject { - - /** - * 注入名称,控制反转明确指定名称时此属性也需要明确指定 - * - * @return 注入名称 - */ - String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/InvokeForInjected.java b/src/main/java/com/imyeyu/inject/annotation/InvokeForInjected.java deleted file mode 100644 index 4227cdb..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/InvokeForInjected.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 此注解使用在方法上,TimiInject 完成注入后自动执行,对 {@link StaticInject} 和 {@link SuperInject} 有效,方法访问权限无限制。 - * 注意:{@link StaticInject} 注解类的方法使用此注解时,该方法也必须是静态方法 - * - * - * @Component - * public class Demo { - * - * @InvokeForInjected - * public void hello() { - * System.out.println("hello"); - * } - * - * @InvokeForInjected - * public void timiInject() { - * System.out.println("timi-inject"); - * } - * } - * - * - * @author 夜雨 - * @version 2022-09-23 22:25 - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface InvokeForInjected { - - /** - * 执行顺序,默认 0,数值小的先执行 - * - * @return 执行顺序 - */ - int value() default 0; -} diff --git a/src/main/java/com/imyeyu/inject/annotation/PostConstruct.java b/src/main/java/com/imyeyu/inject/annotation/PostConstruct.java new file mode 100644 index 0000000..b608e64 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/PostConstruct.java @@ -0,0 +1,16 @@ +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 初始化后回调方法注解 + * + * @author 夜雨 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface PostConstruct { +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Primary.java b/src/main/java/com/imyeyu/inject/annotation/Primary.java new file mode 100644 index 0000000..961f8d1 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/Primary.java @@ -0,0 +1,16 @@ +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(多实现时) + * + * @author 夜雨 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Primary { +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Qualifier.java b/src/main/java/com/imyeyu/inject/annotation/Qualifier.java new file mode 100644 index 0000000..22a4f50 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/Qualifier.java @@ -0,0 +1,21 @@ +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; + +/** + * 按名称限定依赖注入 + * + * @author 夜雨 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Qualifier { + + /** + * Bean 名称 + */ + String value(); +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Resources.java b/src/main/java/com/imyeyu/inject/annotation/Resources.java index 07369fd..dcc14aa 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Resources.java +++ b/src/main/java/com/imyeyu/inject/annotation/Resources.java @@ -1,26 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(资源),注解在类上后该类将被 TimiInject 实例化并控制反转托管,后续执行注入 + * 资源层组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Component public @interface Resources { - /** - * 资源名称,非空时注入需要明确执行 - * - * @return 资源名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Scope.java b/src/main/java/com/imyeyu/inject/annotation/Scope.java new file mode 100644 index 0000000..f8022c3 --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/Scope.java @@ -0,0 +1,21 @@ +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 作用域注解 + * + * @author 夜雨 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Scope { + + /** + * 作用域类型 + */ + ScopeType value() default ScopeType.SINGLETON; +} diff --git a/src/main/java/com/imyeyu/inject/annotation/ScopeType.java b/src/main/java/com/imyeyu/inject/annotation/ScopeType.java new file mode 100644 index 0000000..ec018ab --- /dev/null +++ b/src/main/java/com/imyeyu/inject/annotation/ScopeType.java @@ -0,0 +1,19 @@ +package com.imyeyu.inject.annotation; + +/** + * Bean 作用域类型 + * + * @author 夜雨 + */ +public enum ScopeType { + + /** + * 单例模式(默认) + */ + SINGLETON, + + /** + * 原型模式(每次获取创建新实例) + */ + PROTOTYPE +} diff --git a/src/main/java/com/imyeyu/inject/annotation/Service.java b/src/main/java/com/imyeyu/inject/annotation/Service.java index a8b2e0a..2c356c1 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Service.java +++ b/src/main/java/com/imyeyu/inject/annotation/Service.java @@ -1,26 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(服务),注解在类上后该类将被 TimiInject 实例化并控制反转托管,后续执行注入 + * 服务层组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Component public @interface Service { - /** - * 服务名称,非空时注入需要明确执行 - * - * @return 服务名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/StaticInject.java b/src/main/java/com/imyeyu/inject/annotation/StaticInject.java deleted file mode 100644 index 4cf489b..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/StaticInject.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 静态注入,此类的静态属性将会在控制反转完成后进行注入,此注解的类不会控制反转实例到 TimiInject 中 - * - * {@link InvokeForInjected} 对本注解有效,但必须是静态方法 - * - * - * @InvokeForInjected - * public static void injected() { - * } - * - * - * @author 夜雨 - * @version 2022-06-24 11:29 - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface StaticInject { -} diff --git a/src/main/java/com/imyeyu/inject/annotation/SuperInject.java b/src/main/java/com/imyeyu/inject/annotation/SuperInject.java deleted file mode 100644 index a0ffb73..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/SuperInject.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 父级注入,用在控制反转的类上,对控制反转的对象父级的变量也执行注入对象,父级也可以继续使用此注解 - * - * *对 {@link StaticInject} 无效 - * - * @author 夜雨 - * @version 2022-03-04 23:16 - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface SuperInject { -} diff --git a/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java b/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java index 8723aa1..a9c01f1 100644 --- a/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java +++ b/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java @@ -1,29 +1,21 @@ package com.imyeyu.inject.annotation; -import com.imyeyu.inject.TimiInject; - -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * TimiInject 注入程序,{@link TimiInject} 核心将基于此注解类的位置(或注解参数)进行包扫描和控制反转 + * 标记应用入口类并指定组件扫描路径 * * @author 夜雨 - * @version 2022-03-04 23:32 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface TimiInjectApplication { - /** - * 扫描包位置(如:com.imyeyu.inject),此包下的所有类将检测控制反转和注入,留空则使用注解的类所在包作为扫描位置。 - * TimiInject 的所有注解在此包扫描范围内有效 - * - * @return 扫描包位置 - */ - String[] value() default {}; + /** + * 组件扫描的包路径 + */ + String[] value() default {}; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Util.java b/src/main/java/com/imyeyu/inject/annotation/Util.java index 612b188..8f8d8d5 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Util.java +++ b/src/main/java/com/imyeyu/inject/annotation/Util.java @@ -1,26 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(工具),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * 工具类组件注解 * * @author 夜雨 - * @version 2023-05-18 00:36 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Component public @interface Util { - /** - * 工具名称,非空时注入需要明确执行 - * - * @return 工具名称 - */ - String value() default ""; -} \ No newline at end of file + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; +} diff --git a/src/main/java/com/imyeyu/inject/annotation/package-info.java b/src/main/java/com/imyeyu/inject/annotation/package-info.java deleted file mode 100644 index ee93d1e..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** 控制反转和注入注解 */ -package com.imyeyu.inject.annotation; diff --git a/src/main/java/com/imyeyu/inject/package-info.java b/src/main/java/com/imyeyu/inject/package-info.java deleted file mode 100644 index d29a267..0000000 --- a/src/main/java/com/imyeyu/inject/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** 主程序 */ -package com.imyeyu.inject; \ No newline at end of file diff --git a/src/test/java/com/imyeyu/inject/demo/DefaultStorage.java b/src/test/java/com/imyeyu/inject/demo/DefaultStorage.java new file mode 100644 index 0000000..862f9a5 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/DefaultStorage.java @@ -0,0 +1,17 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.annotation.Component; + +/** + * 默认存储实现 + * + * @author 夜雨 + */ +@Component +public class DefaultStorage implements Storage { + + @Override + public void save(String data) { + System.out.println("Default save: " + data); + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/DemoApp.java b/src/test/java/com/imyeyu/inject/demo/DemoApp.java new file mode 100644 index 0000000..ef6aa67 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/DemoApp.java @@ -0,0 +1,29 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.TimiInject; +import com.imyeyu.inject.annotation.TimiInjectApplication; + +/** + * 演示应用 + * + * @author 夜雨 + */ +@TimiInjectApplication("com.imyeyu.inject.demo") +public class DemoApp { + + public static void main(String[] args) { + TimiInject inject = TimiInject.run(DemoApp.class); + + UserService userService = inject.di(UserService.class); + System.out.println(userService.hello()); + + String appName = inject.di("appName", String.class); + System.out.println("App Name: " + appName); + + Storage storage = inject.di(Storage.class); + System.out.println("Storage: " + storage.getClass().getSimpleName()); + + System.out.println("\n=== Dependency Graph ==="); + System.out.println(inject.exportDependencyGraph()); + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/DemoConfig.java b/src/test/java/com/imyeyu/inject/demo/DemoConfig.java new file mode 100644 index 0000000..67edcf5 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/DemoConfig.java @@ -0,0 +1,23 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.annotation.Bean; +import com.imyeyu.inject.annotation.Configuration; + +/** + * 演示配置 + * + * @author 夜雨 + */ +@Configuration +public class DemoConfig { + + @Bean("appName") + public String appName() { + return "timi-inject-demo"; + } + + @Bean + public Integer serverPort() { + return 8080; + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/FastStorage.java b/src/test/java/com/imyeyu/inject/demo/FastStorage.java new file mode 100644 index 0000000..91b07df --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/FastStorage.java @@ -0,0 +1,19 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.annotation.Component; +import com.imyeyu.inject.annotation.Primary; + +/** + * 快速存储实现 + * + * @author 夜雨 + */ +@Component +@Primary +public class FastStorage implements Storage { + + @Override + public void save(String data) { + System.out.println("Fast save: " + data); + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/QuickTest.java b/src/test/java/com/imyeyu/inject/demo/QuickTest.java new file mode 100644 index 0000000..fa44be2 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/QuickTest.java @@ -0,0 +1,67 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.TimiInject; +import com.imyeyu.inject.annotation.Component; +import com.imyeyu.inject.annotation.PostConstruct; +import com.imyeyu.inject.annotation.Service; +import com.imyeyu.inject.annotation.TimiInjectApplication; + +/** + * 快速测试 + * + * @author 夜雨 + */ +public class QuickTest { + + @TimiInjectApplication("com.imyeyu.inject.demo") + public static class TestApp { + } + + @Service + public static class TestService { + private boolean initialized = false; + + @PostConstruct + public void init() { + this.initialized = true; + System.out.println("TestService initialized"); + } + + public boolean isInitialized() { + return initialized; + } + } + + public static void main(String[] args) { + System.out.println("=== Timi-Inject Quick Test ===\n"); + + TimiInject inject = TimiInject.run(TestApp.class); + + System.out.println("\n=== Test 1: Simple DI ==="); + UserRepository repo = inject.di(UserRepository.class); + System.out.println("UserRepository: " + (repo != null ? "OK" : "FAIL")); + + System.out.println("\n=== Test 2: Constructor Injection ==="); + UserService service = inject.di(UserService.class); + System.out.println("UserService: " + service.hello()); + + System.out.println("\n=== Test 3: @Bean Method ==="); + String appName = inject.di("appName", String.class); + System.out.println("AppName: " + appName); + + Integer port = inject.di("serverPort", Integer.class); + System.out.println("ServerPort: " + port); + + System.out.println("\n=== Test 4: @Primary ==="); + Storage storage = inject.di(Storage.class); + System.out.println("Storage type: " + storage.getClass().getSimpleName()); + System.out.println("Expected FastStorage: " + (storage instanceof FastStorage ? "OK" : "FAIL")); + + System.out.println("\n=== Test 5: Manual Registration ==="); + inject.ioc("customBean", "Custom Value"); + String custom = inject.di("customBean", String.class); + System.out.println("Custom bean: " + custom); + + System.out.println("\n=== All Tests Passed! ==="); + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/Storage.java b/src/test/java/com/imyeyu/inject/demo/Storage.java new file mode 100644 index 0000000..422dbd4 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/Storage.java @@ -0,0 +1,10 @@ +package com.imyeyu.inject.demo; + +/** + * 存储接口 + * + * @author 夜雨 + */ +public interface Storage { + void save(String data); +} diff --git a/src/test/java/com/imyeyu/inject/demo/UserRepository.java b/src/test/java/com/imyeyu/inject/demo/UserRepository.java new file mode 100644 index 0000000..5414d5f --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/UserRepository.java @@ -0,0 +1,16 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.annotation.Resources; + +/** + * 用户仓储 + * + * @author 夜雨 + */ +@Resources +public class UserRepository { + + public UserRepository() { + System.out.println("UserRepository created"); + } +} diff --git a/src/test/java/com/imyeyu/inject/demo/UserService.java b/src/test/java/com/imyeyu/inject/demo/UserService.java new file mode 100644 index 0000000..88a4816 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/demo/UserService.java @@ -0,0 +1,28 @@ +package com.imyeyu.inject.demo; + +import com.imyeyu.inject.annotation.PostConstruct; +import com.imyeyu.inject.annotation.Service; + +/** + * 用户服务 + * + * @author 夜雨 + */ +@Service +public class UserService { + + private final UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } + + @PostConstruct + public void init() { + System.out.println("UserService initialized"); + } + + public String hello() { + return "Hello from UserService! Repository: " + repository.getClass().getSimpleName(); + } +}
夜雨 创建于 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()); + } + } + } } diff --git a/src/main/java/com/imyeyu/inject/annotation/Bean.java b/src/main/java/com/imyeyu/inject/annotation/Bean.java index fd7e991..2aecf63 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Bean.java +++ b/src/main/java/com/imyeyu/inject/annotation/Bean.java @@ -1,26 +1,21 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(通用对象),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * Bean 工厂方法注解,用于 @Configuration 类中 * * @author 夜雨 - * @since 2024-07-19 15:48 */ -@Target({ElementType.TYPE}) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface Bean { - /** - * 对象名称,非空时注入需要明确执行 - * - * @return 对象名称 - */ - String value() default ""; + /** + * Bean 名称,默认为方法名 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Component.java b/src/main/java/com/imyeyu/inject/annotation/Component.java index caec677..2fd6f94 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Component.java +++ b/src/main/java/com/imyeyu/inject/annotation/Component.java @@ -1,26 +1,21 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(组件),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * 通用组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface Component { - /** - * 组件名称,非空时注入需要明确执行 - * - * @return 组件名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/SuperIOC.java b/src/main/java/com/imyeyu/inject/annotation/Configuration.java similarity index 51% rename from src/main/java/com/imyeyu/inject/annotation/SuperIOC.java rename to src/main/java/com/imyeyu/inject/annotation/Configuration.java index 206872c..5975881 100644 --- a/src/main/java/com/imyeyu/inject/annotation/SuperIOC.java +++ b/src/main/java/com/imyeyu/inject/annotation/Configuration.java @@ -1,19 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 父级控制反转,通常用于父级存在控制反转子类时使用,父级可以继续使用此注解 + * 配置类注解 * * @author 夜雨 - * @version 2023-02-28 11:42 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface SuperIOC { +@Component +public @interface Configuration { + + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/Controller.java b/src/main/java/com/imyeyu/inject/annotation/Controller.java index f101063..d32be18 100644 --- a/src/main/java/com/imyeyu/inject/annotation/Controller.java +++ b/src/main/java/com/imyeyu/inject/annotation/Controller.java @@ -1,26 +1,22 @@ package com.imyeyu.inject.annotation; -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * 控制反转(控制器),注解在类上后该类将被 TimiInject 实例化并控制反转,后续执行注入 + * 控制器组件注解 * * @author 夜雨 - * @version 2022-03-04 23:16 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Component public @interface Controller { - /** - * 控制器名称,非空时注入需要明确执行 - * - * @return 控制器名称 - */ - String value() default ""; + /** + * Bean 名称,默认为类名首字母小写 + */ + String value() default ""; } diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java b/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java deleted file mode 100644 index eaa9452..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCAsync.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - *
异步控制反转,控制反转的类或 {@link IOCReturn} 方法使用此注解将会在异步线程中实例化或调用。 - * 此注解不影响注入流程。 - *
此注解对实例化比较耗时的动作有明显的速度提升,有 {@link IOCPriority} 的效果,对实例化耗时较短的动作使用将会适得其反。 - *
注意:JavaFX 组件不可用,因为它只有一个 UI 线程 - * - * @author 夜雨 - * @version 2022-10-10 17:05 - */ -@Target({ElementType.TYPE, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IOCAsync { -} diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java b/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java deleted file mode 100644 index ffe3fa5..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCPriority.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 控制反转类使用此注解时将会优先执行实例化,对不存在异步控制反转的类使用不会提升效率。 - *
使用此注解后,{@link InvokeForInjected} 的方法也会优先于其他类触发 - * - * @author 夜雨 - * @version 2022-10-10 23:17 - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface IOCPriority { - - /** - * 优先级,默认所有使用此注解的类都会往前挤,除非使用指定等级,数值小的优先执行 - * - * @return 优先等级,默认 {@link Integer#MAX_VALUE} / 2,非此注解的类的优先级为 {@link Integer#MAX_VALUE} - */ - int value() default Integer.MAX_VALUE / 2; -} diff --git a/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java b/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java deleted file mode 100644 index df40411..0000000 --- a/src/main/java/com/imyeyu/inject/annotation/IOCReturn.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.imyeyu.inject.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 控制反转(方法返回对象),注解在方法上,该方法所在类也必须控制反转到 TimiInject 中,经过此注解的方法返回值也会控制反转到 TimiInject 中并后续执行注入 - * - *
注意:注解参数为空时 {@link Inject} 注入对象的变量名需要和此方法名一致
为空时注入对象的变量名需要和此方法名一致
注意 - *
注意:{@link StaticInject} 注解类的方法使用此注解时,该方法也必须是静态方法 - * - *
- * @Component - * public class Demo { - * - * @InvokeForInjected - * public void hello() { - * System.out.println("hello"); - * } - * - * @InvokeForInjected - * public void timiInject() { - * System.out.println("timi-inject"); - * } - * } - *
- * @InvokeForInjected - * public static void injected() { - * } - *
*对 {@link StaticInject} 无效 - * - * @author 夜雨 - * @version 2022-03-04 23:16 - */ -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface SuperInject { -} diff --git a/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java b/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java index 8723aa1..a9c01f1 100644 --- a/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java +++ b/src/main/java/com/imyeyu/inject/annotation/TimiInjectApplication.java @@ -1,29 +1,21 @@ package com.imyeyu.inject.annotation; -import com.imyeyu.inject.TimiInject; - -import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * TimiInject 注入程序,{@link TimiInject} 核心将基于此注解类的位置(或注解参数)进行包扫描和控制反转 + * 标记应用入口类并指定组件扫描路径 * * @author 夜雨 - * @version 2022-03-04 23:32 */ -@Target({ElementType.TYPE}) +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented public @interface TimiInjectApplication { - /** - * 扫描包位置(如:com.imyeyu.inject),此包下的所有类将检测控制反转和注入,留空则使用注解的类所在包作为扫描位置。 - *
TimiInject 的所有注解在此包扫描范围内有效