Compare commits
11 Commits
05606eda74
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
| fa3302d954 | |||
| 13b495dc2d | |||
| f9c1a4aeb6 | |||
| e3226f2647 | |||
| 597065badc | |||
| c408107c48 | |||
| f5c6dcd275 | |||
| 93bcc9b5c6 | |||
| 372026b716 | |||
| 13926ea24a | |||
| 71be7c07c0 |
111
.gitea/workflows/ci.yml
Normal file
111
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,111 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
build-deploy:
|
||||
runs-on: act_runner_java
|
||||
if: ${{ github.event.pull_request.merged == true }}
|
||||
env:
|
||||
JAVA_HOME: /usr/lib/jvm/java-21-openjdk
|
||||
steps:
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone ${{ github.server_url }}/${{ github.repository }}.git .
|
||||
git checkout ${{ github.sha }}
|
||||
- name: Set up environment
|
||||
run: |
|
||||
echo "PR #${{ github.event.number }} merged into master"
|
||||
echo "Source branch: ${{ github.event.pull_request.head.ref }}"
|
||||
echo "Target branch: ${{ github.event.pull_request.base.ref }}"
|
||||
- name: Run tests
|
||||
run: |
|
||||
echo "Running test suite..."
|
||||
- name: Build project
|
||||
run: |
|
||||
mvn -B -DskipTests clean package source:jar javadoc:jar
|
||||
- name: Deploy to Nexus
|
||||
if: success()
|
||||
run: |
|
||||
if [ -z "${{ secrets.NEXUS_USERNAME }}" ] || [ -z "${{ secrets.NEXUS_PASSWORD }}" ]; then
|
||||
echo "Missing secrets.NEXUS_USERNAME or secrets.NEXUS_PASSWORD"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p ~/.m2
|
||||
cat > ~/.m2/settings.xml <<EOF
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<servers>
|
||||
<server>
|
||||
<id>timi-nexus</id>
|
||||
<username>${{ secrets.NEXUS_USERNAME }}</username>
|
||||
<password>${{ secrets.NEXUS_PASSWORD }}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
EOF
|
||||
version=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version)
|
||||
artifact_id=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.artifactId)
|
||||
main_jar="target/${artifact_id}-${version}.jar"
|
||||
sources_jar="target/${artifact_id}-${version}-sources.jar"
|
||||
javadoc_jar="target/${artifact_id}-${version}-javadoc.jar"
|
||||
if [ ! -f "$main_jar" ] || [ ! -f "$sources_jar" ] || [ ! -f "$javadoc_jar" ]; then
|
||||
echo "Missing build artifacts in target"
|
||||
exit 1
|
||||
fi
|
||||
mvn -B deploy:deploy-file \
|
||||
-Dfile="$main_jar" \
|
||||
-Dsources="$sources_jar" \
|
||||
-Djavadoc="$javadoc_jar" \
|
||||
-DpomFile="./pom.xml" \
|
||||
-Durl="https://nexus.imyeyu.com/repository/maven-releases/" \
|
||||
-DrepositoryId="timi-nexus" \
|
||||
-Dhttps.protocols=TLSv1.2 \
|
||||
-Djdk.tls.client.protocols=TLSv1.2
|
||||
- name: Create release
|
||||
if: ${{ success() && startsWith(github.event.pull_request.title, 'v') }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RUNNER_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_TAG: ${{ github.event.pull_request.title }}
|
||||
RELEASE_TARGET: ${{ github.sha }}
|
||||
run: |
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "Missing secrets.RUNNER_TOKEN"
|
||||
exit 1
|
||||
fi
|
||||
api_url="$GITEA_SERVER_URL/api/v1/repos/$GITEA_REPOSITORY/releases"
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"tag_name": "$RELEASE_TAG",
|
||||
"name": "$RELEASE_TAG",
|
||||
"target_commitish": "$RELEASE_TARGET",
|
||||
"draft": false,
|
||||
"prerelease": false
|
||||
}
|
||||
EOF
|
||||
)
|
||||
response=$(curl -sS -X POST "$api_url" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload")
|
||||
release_id=$(echo "$response" | grep -o '"id":[0-9]*' | head -n 1 | grep -o '[0-9]*')
|
||||
if [ -z "$release_id" ] || echo "$response" | grep -q '"message"'; then
|
||||
echo "Create release failed: $response"
|
||||
exit 1
|
||||
fi
|
||||
echo "Release created: id=$release_id"
|
||||
for asset_path in target/*.jar; do
|
||||
asset_name=$(basename "$asset_path")
|
||||
curl -sS -X POST "$api_url/$release_id/assets?name=$asset_name" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$asset_path"
|
||||
done
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
/AGENTS.md
|
||||
/logs
|
||||
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
|
||||
198
README.md
198
README.md
@ -1,3 +1,199 @@
|
||||
# timi-inject
|
||||
|
||||
Java 轻量控制反转工具
|
||||
Java 轻量级控制反转与依赖注入框架。
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于注解的依赖注入
|
||||
- 构造器注入为核心路径
|
||||
- 支持 @Configuration + @Bean
|
||||
- 支持 @Qualifier 与 @Primary
|
||||
- 支持 @Scope(singleton / prototype)
|
||||
- 支持 @Import 与 Module 扩展
|
||||
- 支持 @PostConstruct 生命周期回调
|
||||
- 提供依赖图导出
|
||||
|
||||
## 引入依赖
|
||||
|
||||
Maven:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.inject</groupId>
|
||||
<artifactId>timi-inject</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
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
|
||||
|
||||
170
javafx-demo.md
Normal file
170
javafx-demo.md
Normal file
@ -0,0 +1,170 @@
|
||||
# JavaFX 示例说明
|
||||
|
||||
这是一个完整的 JavaFX 应用示例,展示如何在 JavaFX 中使用 Timi-Inject IOC/DI 框架。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/test/java/com/imyeyu/inject/javafxdemo/
|
||||
├── FxMain.java # JavaFX 应用入口
|
||||
├── FxConfig.java # 配置类 (@Configuration)
|
||||
├── MainController.java # 主界面控制器 (@Controller)
|
||||
├── UserService.java # 用户服务 (@Service)
|
||||
└── MessageService.java # 消息服务 (@Service)
|
||||
|
||||
src/test/resources/javafxdemo/
|
||||
└── main.fxml # 主界面 FXML 文件
|
||||
```
|
||||
|
||||
## 核心特性展示
|
||||
|
||||
### 1. IOC 容器初始化
|
||||
|
||||
```java
|
||||
@TimiInjectApplication("com.imyeyu.inject.javafxdemo")
|
||||
@Import(FxConfig.class)
|
||||
public class FxMain extends Application {
|
||||
private static TimiInject inject;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 启动 IOC 容器
|
||||
inject = TimiInject.run(FxMain.class);
|
||||
|
||||
// 启动 JavaFX 应用
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 控制器工厂集成
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/javafxdemo/main.fxml"));
|
||||
|
||||
// 使用 IOC 容器作为控制器工厂
|
||||
loader.setControllerFactory(type -> inject.di(type));
|
||||
|
||||
Parent root = loader.load();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 构造器依赖注入
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class MainController {
|
||||
private final UserService userService;
|
||||
private final MessageService messageService;
|
||||
|
||||
// 构造器注入
|
||||
public MainController(UserService userService, MessageService messageService) {
|
||||
this.userService = userService;
|
||||
this.messageService = messageService;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. @Bean 工厂方法
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class FxConfig {
|
||||
@Bean
|
||||
public DateTimeFormatter dateTimeFormatter() {
|
||||
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
@Bean("appTitle")
|
||||
public String appTitle() {
|
||||
return "Timi-Inject JavaFX Demo";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. @Qualifier 限定注入
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class MessageService {
|
||||
private final String appTitle;
|
||||
private final String version;
|
||||
|
||||
public MessageService(@Qualifier("appTitle") String appTitle,
|
||||
@Qualifier("version") String version) {
|
||||
this.appTitle = appTitle;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. @PostConstruct 生命周期
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class UserService {
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
users.add("Admin");
|
||||
users.add("User1");
|
||||
users.add("User2");
|
||||
System.out.println("UserService initialized");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 运行方式
|
||||
|
||||
### 方式一:使用 Maven 插件(推荐)
|
||||
|
||||
```bash
|
||||
mvn clean javafx:run
|
||||
```
|
||||
|
||||
### 方式二:命令行运行
|
||||
|
||||
```bash
|
||||
# 编译
|
||||
mvn clean compile test-compile
|
||||
|
||||
# 运行(需要正确配置 JavaFX 模块路径)
|
||||
java --module-path %PATH_TO_JAVAFX% \
|
||||
--add-modules javafx.controls,javafx.fxml \
|
||||
-cp target/classes;target/test-classes;... \
|
||||
com.imyeyu.inject.javafxdemo.FxMain
|
||||
```
|
||||
|
||||
## 应用功能
|
||||
|
||||
1. **用户管理**
|
||||
- 添加用户
|
||||
- 删除选中用户
|
||||
- 显示用户列表
|
||||
|
||||
2. **实时日志**
|
||||
- 操作日志记录
|
||||
- 时间戳显示
|
||||
- 清空日志功能
|
||||
|
||||
3. **依赖注入演示**
|
||||
- 控制器通过构造器注入服务
|
||||
- 服务之间相互依赖
|
||||
- @Bean 方法提供配置对象
|
||||
- @Qualifier 精确匹配依赖
|
||||
|
||||
## 技术亮点
|
||||
|
||||
1. **零配置** - 使用注解驱动,无需 XML
|
||||
2. **类型安全** - 编译时类型检查
|
||||
3. **生命周期管理** - @PostConstruct 初始化
|
||||
4. **优雅集成** - 与 JavaFX 无缝整合
|
||||
5. **依赖追踪** - 清晰的依赖关系
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. JavaFX 依赖设置为 `test` scope,仅用于演示
|
||||
2. 控制器工厂必须在加载 FXML 前设置
|
||||
3. FXML 文件路径相对于 classpath
|
||||
4. 确保 JDK 21 和 JavaFX 21.0.2 版本匹配
|
||||
59
pom.xml
59
pom.xml
@ -6,25 +6,78 @@
|
||||
|
||||
<groupId>com.imyeyu.inject</groupId>
|
||||
<artifactId>timi-inject</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<version>0.0.2</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
<javafx.version>21.0.2</javafx.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.11.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>timi_nexus</id>
|
||||
<url>https://nexus.imyeyu.com/repository/maven-releases/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>timi_nexus</id>
|
||||
<url>https://nexus.imyeyu.com/repository/maven-public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.imyeyu.io</groupId>
|
||||
<artifactId>timi-io</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<version>0.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.13</version>
|
||||
<version>1.5.24</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
17
run-javafx-demo.bat
Normal file
17
run-javafx-demo.bat
Normal file
@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
REM JavaFX Demo 启动脚本
|
||||
|
||||
echo Starting JavaFX Demo...
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM 确保已编译
|
||||
call mvn test-compile -Dmaven.test.skip=false -q
|
||||
|
||||
REM 运行应用
|
||||
java ^
|
||||
--module-path "%USERPROFILE%\.m2\repository\org\openjfx\javafx-controls\21.0.2\javafx-controls-21.0.2-win.jar;%USERPROFILE%\.m2\repository\org\openjfx\javafx-graphics\21.0.2\javafx-graphics-21.0.2-win.jar;%USERPROFILE%\.m2\repository\org\openjfx\javafx-base\21.0.2\javafx-base-21.0.2-win.jar;%USERPROFILE%\.m2\repository\org\openjfx\javafx-fxml\21.0.2\javafx-fxml-21.0.2-win.jar" ^
|
||||
--add-modules javafx.controls,javafx.fxml ^
|
||||
-cp "target\classes;target\test-classes;%USERPROFILE%\.m2\repository\com\imyeyu\io\timi-io\0.0.2\timi-io-0.0.2.jar;%USERPROFILE%\.m2\repository\ch\qos\logback\logback-classic\1.5.24\logback-classic-1.5.24.jar;%USERPROFILE%\.m2\repository\ch\qos\logback\logback-core\1.5.24\logback-core-1.5.24.jar;%USERPROFILE%\.m2\repository\org\slf4j\slf4j-api\2.0.16\slf4j-api-2.0.16.jar" ^
|
||||
com.imyeyu.inject.javafxdemo.Launcher
|
||||
|
||||
pause
|
||||
96
src/main/java/com/imyeyu/inject/BeanContext.java
Normal file
96
src/main/java/com/imyeyu/inject/BeanContext.java
Normal file
@ -0,0 +1,96 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.ScopeType;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Bean 容器上下文
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-01-12 23:37
|
||||
*/
|
||||
public class BeanContext {
|
||||
|
||||
private final Map<String, Object> singletons = new ConcurrentHashMap<>();
|
||||
private final Map<String, BeanDefinition> definitions = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, List<String>> typeIndex = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 注册 Bean 定义
|
||||
*/
|
||||
public void registerDefinition(BeanDefinition definition) {
|
||||
String name = definition.getName();
|
||||
if (definitions.containsKey(name)) {
|
||||
throw new InjectException("Bean [%s] already registered".formatted(name));
|
||||
}
|
||||
definitions.put(name, definition);
|
||||
typeIndex.computeIfAbsent(definition.getType(), k -> new ArrayList<>()).add(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单例 Bean 实例
|
||||
*/
|
||||
public void registerSingleton(String name, Object bean) {
|
||||
if (bean == null) {
|
||||
throw new InjectException("Bean [%s] cannot be null".formatted(name));
|
||||
}
|
||||
singletons.put(name, bean);
|
||||
if (!definitions.containsKey(name)) {
|
||||
BeanDefinition definition = BeanDefinition.builder().name(name).type(bean.getClass()).scope(ScopeType.SINGLETON).build();
|
||||
definitions.put(name, definition);
|
||||
typeIndex.computeIfAbsent(bean.getClass(), k -> new ArrayList<>()).add(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 定义
|
||||
*/
|
||||
public 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<String> getBeanNamesByType(Class<?> type) {
|
||||
List<String> names = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<Class<?>, List<String>> entry : typeIndex.entrySet()) {
|
||||
if (type.isAssignableFrom(entry.getKey())) {
|
||||
names.addAll(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 Bean 定义
|
||||
*/
|
||||
public Collection<BeanDefinition> getAllDefinitions() {
|
||||
return definitions.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含指定名称的 Bean
|
||||
*/
|
||||
public boolean containsBean(String name) {
|
||||
return definitions.containsKey(name);
|
||||
}
|
||||
}
|
||||
157
src/main/java/com/imyeyu/inject/BeanDefinition.java
Normal file
157
src/main/java/com/imyeyu/inject/BeanDefinition.java
Normal file
@ -0,0 +1,157 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.ScopeType;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bean 定义元数据
|
||||
*
|
||||
* @author 夜雨
|
||||
*/
|
||||
public class BeanDefinition {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
private final ScopeType scope;
|
||||
private final boolean primary;
|
||||
private final boolean lazy;
|
||||
private final Constructor<?> constructor;
|
||||
private final Method factoryMethod;
|
||||
private final Object configInstance;
|
||||
private final List<Method> postConstructs;
|
||||
|
||||
private BeanDefinition(Builder builder) {
|
||||
this.name = builder.name;
|
||||
this.type = builder.type;
|
||||
this.scope = builder.scope;
|
||||
this.primary = builder.primary;
|
||||
this.lazy = builder.lazy;
|
||||
this.constructor = builder.constructor;
|
||||
this.factoryMethod = builder.factoryMethod;
|
||||
this.configInstance = builder.configInstance;
|
||||
this.postConstructs = Collections.unmodifiableList(builder.postConstructs);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ScopeType getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public boolean isLazy() {
|
||||
return lazy;
|
||||
}
|
||||
|
||||
public Constructor<?> getConstructor() {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
public Method getFactoryMethod() {
|
||||
return factoryMethod;
|
||||
}
|
||||
|
||||
public Object getConfigInstance() {
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public List<Method> getPostConstructs() {
|
||||
return postConstructs;
|
||||
}
|
||||
|
||||
public boolean isFactoryBean() {
|
||||
return factoryMethod != null;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-01-12 23:38
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private String name;
|
||||
private Class<?> type;
|
||||
private ScopeType scope = ScopeType.SINGLETON;
|
||||
private boolean primary = false;
|
||||
private boolean lazy = false;
|
||||
private Constructor<?> constructor;
|
||||
private Method factoryMethod;
|
||||
private Object configInstance;
|
||||
private List<Method> postConstructs = Collections.emptyList();
|
||||
|
||||
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 lazy(boolean lazy) {
|
||||
this.lazy = lazy;
|
||||
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 postConstructs(List<Method> methods) {
|
||||
if (methods == null || methods.isEmpty()) {
|
||||
this.postConstructs = Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
this.postConstructs = List.copyOf(methods);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BeanDefinition build() {
|
||||
if (name == null || type == null) {
|
||||
throw new IllegalStateException("Bean name and type are required");
|
||||
}
|
||||
return new BeanDefinition(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
292
src/main/java/com/imyeyu/inject/BeanFactory.java
Normal file
292
src/main/java/com/imyeyu/inject/BeanFactory.java
Normal file
@ -0,0 +1,292 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.Inject;
|
||||
import com.imyeyu.inject.annotation.Qualifier;
|
||||
import com.imyeyu.inject.annotation.ScopeType;
|
||||
import com.imyeyu.java.ref.Ref;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bean 工厂,负责创建和管理 Bean 实例
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-01-12 23:38
|
||||
*/
|
||||
public class BeanFactory {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BeanFactory.class);
|
||||
|
||||
private final BeanContext context;
|
||||
private final StartupStatistics statistics;
|
||||
private final LinkedHashSet<String> creating = new LinkedHashSet<>();
|
||||
|
||||
public BeanFactory(BeanContext context, StartupStatistics statistics) {
|
||||
this.context = context;
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 实例
|
||||
*/
|
||||
public Object getBean(String name) {
|
||||
BeanDefinition definition = context.getDefinition(name);
|
||||
if (definition == null) {
|
||||
throw new InjectException("Bean [%s] not found".formatted(name));
|
||||
}
|
||||
if (definition.getScope() == ScopeType.SINGLETON) {
|
||||
Object singleton = context.getSingleton(name);
|
||||
if (singleton != null) {
|
||||
return singleton;
|
||||
}
|
||||
}
|
||||
return createBean(definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例
|
||||
*
|
||||
* @param type 类型
|
||||
* @return 实例
|
||||
* @param <T> 实例类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(Class<T> type) {
|
||||
List<String> candidates = context.getBeanNamesByType(type);
|
||||
if (candidates.isEmpty()) {
|
||||
throw new InjectException("Bean not found for type: %s".formatted(type.getName()));
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
return (T) getBean(candidates.getFirst());
|
||||
}
|
||||
String primaryBean = findPrimaryBean(candidates);
|
||||
if (primaryBean != null) {
|
||||
return (T) getBean(primaryBean);
|
||||
}
|
||||
throw new InjectException("Multiple beans found for type %s: %s - use @Qualifier or @Primary to specify which one to inject".formatted(type.getName(), candidates));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例
|
||||
*
|
||||
* @param name 名称
|
||||
* @param type 类型
|
||||
* @return 实例
|
||||
* @param <T> 实例类型
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getBean(String name, Class<T> type) {
|
||||
Object bean = getBean(name);
|
||||
if (!type.isInstance(bean)) {
|
||||
throw new InjectException("Bean [%s] is not of type %s".formatted(name, type.getName()));
|
||||
}
|
||||
return (T) bean;
|
||||
}
|
||||
|
||||
private Object createBean(BeanDefinition definition) {
|
||||
String name = definition.getName();
|
||||
if (creating.contains(name)) {
|
||||
StringBuilder errorMsg = new StringBuilder("Circular dependency detected:\n");
|
||||
for (String bean : creating) {
|
||||
errorMsg.append(" ").append(bean).append("\n -> ");
|
||||
}
|
||||
errorMsg.append(name).append(" (attempting to create again)");
|
||||
throw new InjectException(errorMsg.toString());
|
||||
}
|
||||
creating.add(name);
|
||||
try {
|
||||
Object instance;
|
||||
if (definition.isFactoryBean()) {
|
||||
instance = createBeanFromFactoryMethod(definition);
|
||||
} else {
|
||||
instance = createBeanFromConstructor(definition);
|
||||
}
|
||||
// 在构造器注入后执行字段注入
|
||||
injectFields(instance);
|
||||
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 [%s]".formatted(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: %s".formatted(factoryMethod.getName()));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to invoke @Bean method: %s".formatted(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();
|
||||
String paramName = parameter.getName();
|
||||
return getBeanWithFallback(type, paramName);
|
||||
}
|
||||
|
||||
private void invokePostConstruct(Object instance, BeanDefinition definition) {
|
||||
List<Method> postConstructs = definition.getPostConstructs();
|
||||
if (postConstructs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
statistics.postConstructCurrentStart = Time.now();
|
||||
for (Method postConstruct : postConstructs) {
|
||||
postConstruct.setAccessible(true);
|
||||
try {
|
||||
postConstruct.invoke(instance);
|
||||
statistics.postConstructInvocations++;
|
||||
} catch (Exception e) {
|
||||
throw new InjectException("Failed to invoke @PostConstruct method: %s".formatted(postConstruct.getName()), e);
|
||||
}
|
||||
}
|
||||
statistics.postConstructTotalTime += Time.now() - statistics.postConstructCurrentStart;
|
||||
statistics.postConstructCurrentStart = 0;
|
||||
}
|
||||
|
||||
private String findPrimaryBean(List<String> candidates) {
|
||||
String primaryBean = null;
|
||||
for (String name : candidates) {
|
||||
BeanDefinition definition = context.getDefinition(name);
|
||||
if (definition.isPrimary()) {
|
||||
if (primaryBean != null) {
|
||||
throw new InjectException("Multiple @Primary beans found: Bean [%s] and Bean [%s]".formatted(primaryBean, name));
|
||||
}
|
||||
primaryBean = name;
|
||||
}
|
||||
}
|
||||
return primaryBean;
|
||||
}
|
||||
|
||||
private String extractConfigBeanName(Class<?> configClass) {
|
||||
return Character.toLowerCase(configClass.getSimpleName().charAt(0)) + configClass.getSimpleName().substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有单例 Bean(跳过懒加载的单例)
|
||||
*/
|
||||
public void initializeSingletons() {
|
||||
statistics.iocStartTime = Time.now();
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
if (definition.getScope() == ScopeType.SINGLETON && !definition.isLazy()) {
|
||||
getBean(definition.getName());
|
||||
}
|
||||
}
|
||||
statistics.iocEndTime = Time.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
|
||||
*
|
||||
* @param target 需要注入依赖的目标对象
|
||||
*/
|
||||
public void injectFields(Object target) {
|
||||
if (target == null) {
|
||||
throw new InjectException("Cannot inject fields into null object");
|
||||
}
|
||||
statistics.injectionCurrentStart = Time.now();
|
||||
Class<?> clazz = target.getClass();
|
||||
for (Field field : Ref.listAllFields(target.getClass())) {
|
||||
if (field.isAnnotationPresent(Inject.class)) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
Object dependency = resolveDependencyForField(field);
|
||||
field.set(target, dependency);
|
||||
statistics.injectedFields++;
|
||||
log.debug("Injected field: {}.{}", clazz.getSimpleName(), field.getName());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new InjectException("Failed to inject field: %s".formatted(field.getName()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
statistics.injectionTotalTime += Time.now() - statistics.injectionCurrentStart;
|
||||
}
|
||||
|
||||
private Object resolveDependencyForField(Field field) {
|
||||
Qualifier qualifier = field.getAnnotation(Qualifier.class);
|
||||
if (qualifier != null) {
|
||||
return getBean(qualifier.value());
|
||||
}
|
||||
Class<?> type = field.getType();
|
||||
String fieldName = field.getName();
|
||||
return getBeanWithFallback(type, fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型获取 bean,当存在多个候选时按名称回退匹配
|
||||
*
|
||||
* @param type 类型
|
||||
* @param name 字段名或参数名,用于歧义时的回退匹配
|
||||
* @return bean 实例
|
||||
*/
|
||||
private Object getBeanWithFallback(Class<?> type, String name) {
|
||||
List<String> candidates = context.getBeanNamesByType(type);
|
||||
if (candidates.isEmpty()) {
|
||||
throw new InjectException("Bean not found for type: %s".formatted(type.getName()));
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
return getBean(candidates.getFirst());
|
||||
}
|
||||
// 多个候选时,先尝试按名称精确匹配
|
||||
if (candidates.contains(name)) {
|
||||
return getBean(name);
|
||||
}
|
||||
// 再尝试 @Primary
|
||||
String primaryBean = findPrimaryBean(candidates);
|
||||
if (primaryBean != null) {
|
||||
return getBean(primaryBean);
|
||||
}
|
||||
throw new InjectException("Multiple beans found for type %s: %s - use @Qualifier or @Primary to specify which one to inject".formatted(type.getName(), candidates));
|
||||
}
|
||||
}
|
||||
289
src/main/java/com/imyeyu/inject/BeanScanner.java
Normal file
289
src/main/java/com/imyeyu/inject/BeanScanner.java
Normal file
@ -0,0 +1,289 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import com.imyeyu.inject.annotation.Bean;
|
||||
import com.imyeyu.inject.annotation.Component;
|
||||
import com.imyeyu.inject.annotation.Configuration;
|
||||
import com.imyeyu.inject.annotation.Inject;
|
||||
import com.imyeyu.inject.annotation.Lazy;
|
||||
import com.imyeyu.inject.annotation.PostConstruct;
|
||||
import com.imyeyu.inject.annotation.Primary;
|
||||
import com.imyeyu.inject.annotation.Scope;
|
||||
import com.imyeyu.inject.annotation.ScopeType;
|
||||
import com.imyeyu.utils.Time;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
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;
|
||||
private final StartupStatistics statistics;
|
||||
|
||||
public BeanScanner(BeanContext context, StartupStatistics statistics) {
|
||||
this.context = context;
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描指定包路径下的所有组件
|
||||
*/
|
||||
public void scan(String... basePackages) {
|
||||
statistics.scanStartTime = Time.now();
|
||||
for (String pkg : basePackages) {
|
||||
statistics.scannedPackages.add(pkg);
|
||||
scanPackage(pkg);
|
||||
}
|
||||
statistics.scanEndTime = Time.now();
|
||||
}
|
||||
|
||||
private void scanPackage(String basePackage) {
|
||||
String path = basePackage.replace('.', '/');
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Enumeration<URL> 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: %s".formatted(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, "%s.%s".formatted(basePackage, file.getName()));
|
||||
} else if (file.getName().endsWith(".class")) {
|
||||
String className = "%s.%s".formatted(basePackage, file.getName().replace(".class", ""));
|
||||
processClass(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanJar(URL resource, String basePackage) {
|
||||
String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));
|
||||
String packagePath = basePackage.replace('.', '/');
|
||||
|
||||
try (JarFile jar = new JarFile(jarPath)) {
|
||||
Enumeration<JarEntry> 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: %s".formatted(jarPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(String className) {
|
||||
try {
|
||||
statistics.scannedClasses++;
|
||||
Class<?> clazz = Class.forName(className);
|
||||
processClass(clazz);
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.warn("Class not found: {}", className);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
log.debug("Skip class with missing dependencies: {}", className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个类
|
||||
*/
|
||||
public void processClass(Class<?> clazz) {
|
||||
if (clazz.isAnnotation() || clazz.isInterface() || clazz.isEnum()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Annotation componentAnnotation = getComponentAnnotation(clazz);
|
||||
if (componentAnnotation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String beanName = extractBeanName(componentAnnotation, clazz);
|
||||
ScopeType scope = extractScope(clazz);
|
||||
boolean primary = clazz.isAnnotationPresent(Primary.class);
|
||||
boolean lazy = extractLazy(clazz);
|
||||
Constructor<?> constructor = selectConstructor(clazz);
|
||||
List<Method> postConstructs = findPostConstructs(clazz);
|
||||
|
||||
BeanDefinition definition = BeanDefinition.builder().name(beanName).type(clazz).scope(scope).primary(primary).lazy(lazy).constructor(constructor).postConstructs(postConstructs).build();
|
||||
|
||||
context.registerDefinition(definition);
|
||||
statistics.registeredBeans++;
|
||||
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: %s".formatted(method.getName()));
|
||||
}
|
||||
|
||||
BeanDefinition definition = BeanDefinition.builder().name(beanName).type(returnType).scope(ScopeType.SINGLETON).factoryMethod(method).build();
|
||||
|
||||
context.registerDefinition(definition);
|
||||
statistics.registeredBeans++;
|
||||
log.debug("Registered bean from @Bean method: {} ({})", beanName, returnType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private Annotation getComponentAnnotation(Class<?> clazz) {
|
||||
if (clazz.isAnnotationPresent(Component.class)) {
|
||||
return clazz.getAnnotation(Component.class);
|
||||
}
|
||||
|
||||
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 boolean extractLazy(Class<?> clazz) {
|
||||
// 检查 @Lazy 注解
|
||||
if (clazz.isAnnotationPresent(Lazy.class)) {
|
||||
return true;
|
||||
}
|
||||
// 检查 @Scope 注解的 lazy 属性
|
||||
if (clazz.isAnnotationPresent(Scope.class)) {
|
||||
return clazz.getAnnotation(Scope.class).lazy();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Constructor<?> selectConstructor(Class<?> clazz) {
|
||||
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
||||
Constructor<?> injectConstructor = null;
|
||||
for (Constructor<?> constructor : constructors) {
|
||||
if (constructor.isAnnotationPresent(Inject.class)) {
|
||||
if (injectConstructor != null) {
|
||||
throw new InjectException("Multiple @Inject constructors found in %s".formatted(clazz.getName()));
|
||||
}
|
||||
injectConstructor = constructor;
|
||||
}
|
||||
}
|
||||
if (injectConstructor != null) {
|
||||
return injectConstructor;
|
||||
}
|
||||
if (constructors.length == 1) {
|
||||
return constructors[0];
|
||||
}
|
||||
try {
|
||||
return clazz.getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new InjectException("No suitable constructor found for %s. Please provide a no-arg constructor or mark one with @Inject".formatted(clazz.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Method> findPostConstructs(Class<?> clazz) {
|
||||
// 收集所有 @PostConstruct 方法及其元数据(继承深度、order 值)
|
||||
record PostConstructInfo(Method method, int depth, int order) {}
|
||||
List<PostConstructInfo> collected = new ArrayList<>();
|
||||
Class<?> currentClass = clazz;
|
||||
int depth = 0;
|
||||
|
||||
// 从当前类向上遍历到 Object,收集所有 @PostConstruct 方法
|
||||
while (currentClass != null && currentClass != Object.class) {
|
||||
int currentDepth = depth;
|
||||
for (Method method : currentClass.getDeclaredMethods()) {
|
||||
if (method.isAnnotationPresent(PostConstruct.class)) {
|
||||
if (method.getParameterCount() != 0) {
|
||||
throw new InjectException("@PostConstruct method must have no parameters: %s".formatted(method.getName()));
|
||||
}
|
||||
int order = method.getAnnotation(PostConstruct.class).value();
|
||||
collected.add(new PostConstructInfo(method, currentDepth, order));
|
||||
}
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
depth++;
|
||||
}
|
||||
|
||||
// 先按继承深度降序(父类深度大,先执行),同层级内按 order 升序
|
||||
collected.sort((a, b) -> {
|
||||
int depthCompare = Integer.compare(b.depth, a.depth);
|
||||
if (depthCompare != 0) {
|
||||
return depthCompare;
|
||||
}
|
||||
return Integer.compare(a.order, b.order);
|
||||
});
|
||||
|
||||
return collected.stream().map(PostConstructInfo::method).toList();
|
||||
}
|
||||
}
|
||||
@ -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<CallbackArg<TimiInject>> 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<TimiInject> listener) {
|
||||
afterInjectCallbackList.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全局注入后监听
|
||||
*
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public synchronized void removeAfterInjectListener(CallbackArg<TimiInject> listener) {
|
||||
afterInjectCallbackList.remove(listener);
|
||||
}
|
||||
|
||||
public boolean isEnableBanner() {
|
||||
return enableBanner;
|
||||
}
|
||||
|
||||
public void setEnableBanner(boolean enableBanner) {
|
||||
this.enableBanner = enableBanner;
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/imyeyu/inject/InjectException.java
Normal file
17
src/main/java/com/imyeyu/inject/InjectException.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
/**
|
||||
* 注入工厂,顶层接口
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-03-04 23:18
|
||||
*/
|
||||
interface InjectFactory {
|
||||
|
||||
/**
|
||||
* 根据 ID 获取反转对象
|
||||
*
|
||||
* @param id ID
|
||||
* @return 反转对象
|
||||
*/
|
||||
<T> T di(String id, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* 根据类型获取反转对象
|
||||
*
|
||||
* @param classType 类型
|
||||
* @return 反转对象
|
||||
* @param <T> 对象类型
|
||||
*/
|
||||
<T> T di(Class<T> 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);
|
||||
}
|
||||
16
src/main/java/com/imyeyu/inject/Module.java
Normal file
16
src/main/java/com/imyeyu/inject/Module.java
Normal file
@ -0,0 +1,16 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
/**
|
||||
* 模块扩展接口,用于手动配置 Bean
|
||||
*
|
||||
* @author 夜雨
|
||||
*/
|
||||
public interface Module {
|
||||
|
||||
/**
|
||||
* 配置 Bean 上下文
|
||||
*
|
||||
* @param context Bean 上下文
|
||||
*/
|
||||
void configure(BeanContext context);
|
||||
}
|
||||
28
src/main/java/com/imyeyu/inject/StartupStatistics.java
Normal file
28
src/main/java/com/imyeyu/inject/StartupStatistics.java
Normal file
@ -0,0 +1,28 @@
|
||||
package com.imyeyu.inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 启动统计信息
|
||||
*
|
||||
* @author 夜雨
|
||||
* @since 2026-01-13 01:29
|
||||
*/
|
||||
class StartupStatistics {
|
||||
|
||||
final List<String> scannedPackages = new ArrayList<>();
|
||||
int scannedClasses = 0;
|
||||
int registeredBeans = 0;
|
||||
int injectedFields = 0;
|
||||
int postConstructInvocations = 0;
|
||||
|
||||
long scanStartTime = 0;
|
||||
long scanEndTime = 0;
|
||||
long iocStartTime = 0;
|
||||
long iocEndTime = 0;
|
||||
long injectionTotalTime = 0;
|
||||
long injectionCurrentStart = 0;
|
||||
long postConstructTotalTime = 0;
|
||||
long postConstructCurrentStart = 0;
|
||||
}
|
||||
@ -1,766 +1,248 @@
|
||||
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.Text;
|
||||
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.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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 final List<String> paths;
|
||||
private final BeanContext context;
|
||||
private final BeanFactory factory;
|
||||
|
||||
/** 控制反转对象 */
|
||||
private final Map<String, Object> objects;
|
||||
|
||||
/** 依赖注入字段集 */
|
||||
private final List<Field> fields;
|
||||
|
||||
// 注入后调度 Map<对象, Map<方法, 调度顺序>>
|
||||
private final Map<Object, Map<Method, Integer>> invokeForInjected;
|
||||
|
||||
/** 控制反转监听 */
|
||||
private final Map<Class<?>, List<CallbackArg<Object>>> iocListeners;
|
||||
|
||||
/** 静态注入类列表 */
|
||||
private final List<Class<?>> staticInject;
|
||||
|
||||
/** 控制反转类对象 */
|
||||
private final List<IOCClass> classes;
|
||||
|
||||
// 异步控制反转参数
|
||||
private int iocAsyncRunning = 0;
|
||||
private boolean hasAsyncIOC = false;
|
||||
private final Object iocAsyncLock = new Object();
|
||||
|
||||
private TimiInject(InjectApp app) {
|
||||
if (TimiJava.isEmpty(app)) {
|
||||
throw new NullPointerException("empty inject app");
|
||||
private TimiInject(BeanContext context, BeanFactory factory) {
|
||||
this.context = context;
|
||||
this.factory = factory;
|
||||
}
|
||||
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"));
|
||||
|
||||
/**
|
||||
* 启动应用并初始化容器
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass) {
|
||||
return run(applicationClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动应用并初始化容器(支持初始化前注册外部 Bean)
|
||||
*
|
||||
* @param initializer 初始化前回调
|
||||
*/
|
||||
public static TimiInject run(Class<?> applicationClass, CallbackArg<TimiInject> initializer) {
|
||||
printBanner();
|
||||
|
||||
long startTime = Time.now();
|
||||
StartupStatistics statistics = new StartupStatistics();
|
||||
|
||||
log.info("Initializing TimiInject for {} ...", applicationClass.getSimpleName());
|
||||
|
||||
TimiInjectApplication annotation = applicationClass.getAnnotation(TimiInjectApplication.class);
|
||||
if (annotation == null) {
|
||||
throw new InjectException("Application class must be annotated with @TimiInjectApplication");
|
||||
}
|
||||
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<>();
|
||||
|
||||
BeanContext context = new BeanContext();
|
||||
BeanFactory factory = new BeanFactory(context, statistics);
|
||||
BeanScanner scanner = new BeanScanner(context, statistics);
|
||||
|
||||
String[] basePackages = annotation.value();
|
||||
if (basePackages.length == 0) {
|
||||
basePackages = new String[] {applicationClass.getPackage().getName()};
|
||||
}
|
||||
|
||||
log.info("Scanning packages: {}", String.join(", ", basePackages));
|
||||
scanner.scan(basePackages);
|
||||
processImports(applicationClass, scanner, context);
|
||||
log.info("Scanned {} classes, registered {} beans", statistics.scannedClasses, statistics.registeredBeans);
|
||||
|
||||
TimiInject inject = new TimiInject(context, factory);
|
||||
if (initializer != null) {
|
||||
initializer.handler(inject);
|
||||
}
|
||||
|
||||
log.info("Performing inversion of control...");
|
||||
factory.initializeSingletons();
|
||||
log.info("initialization completed!");
|
||||
log.info("\tTotal beans: {}", statistics.registeredBeans);
|
||||
log.info("\tInjected fields: {}", statistics.injectedFields);
|
||||
log.info("\tPostConstruct invocations: {}", statistics.postConstructInvocations);
|
||||
log.info("\tScan time: {} ms", statistics.scanEndTime - statistics.scanStartTime);
|
||||
log.info("\tIOC time: {} ms", statistics.iocEndTime - statistics.iocStartTime);
|
||||
log.info("\tInjection time: {} ms", statistics.injectionTotalTime);
|
||||
log.info("\tPostConstruct time: {} ms", statistics.postConstructTotalTime);
|
||||
log.info("\tTotal startup time: {} ms", Time.now() - startTime);
|
||||
return inject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例(按类型)
|
||||
*/
|
||||
public <T> T getBean(Class<T> type) {
|
||||
return factory.getBean(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bean 实例(按名称和类型)
|
||||
*/
|
||||
public <T> T getBean(String name, Class<T> type) {
|
||||
return factory.getBean(name, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 Bean 实例
|
||||
*/
|
||||
public void registerBean(String name, Object bean) {
|
||||
context.registerSingleton(name, bean);
|
||||
log.debug("Registered singleton bean: {} ({})", name, bean.getClass().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对已存在的对象执行字段注入,适用于无法通过构造器创建的对象
|
||||
*
|
||||
* @param target 需要注入依赖的目标对象
|
||||
*/
|
||||
public void inject(Object target) {
|
||||
factory.injectFields(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出依赖图
|
||||
*/
|
||||
public String exportDependencyGraph() {
|
||||
StringBuilder graph = new StringBuilder();
|
||||
graph.append("=== Dependency Graph ===\n\n");
|
||||
|
||||
for (BeanDefinition definition : context.getAllDefinitions()) {
|
||||
graph.append(formatBeanInfo(definition));
|
||||
graph.append("\n");
|
||||
}
|
||||
|
||||
return graph.toString();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
List<Method> postConstructs = definition.getPostConstructs();
|
||||
if (!postConstructs.isEmpty()) {
|
||||
info.append(" PostConstruct: ");
|
||||
for (int i = 0; i < postConstructs.size(); i++) {
|
||||
if (i > 0) {
|
||||
info.append(", ");
|
||||
}
|
||||
info.append(postConstructs.get(i).getName()).append("()");
|
||||
}
|
||||
info.append("\n");
|
||||
}
|
||||
|
||||
return info.toString();
|
||||
}
|
||||
|
||||
private static void processImports(Class<?> applicationClass, BeanScanner scanner, BeanContext context) {
|
||||
if (!applicationClass.isAnnotationPresent(Import.class)) {
|
||||
return;
|
||||
}
|
||||
Import importAnnotation = applicationClass.getAnnotation(Import.class);
|
||||
Class<?>[] importClasses = importAnnotation.value();
|
||||
for (Class<?> importClass : importClasses) {
|
||||
if (Module.class.isAssignableFrom(importClass)) {
|
||||
try {
|
||||
// 扫描
|
||||
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;
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
newInstance(iocClass);
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncRunning--;
|
||||
if (iocAsyncRunning < 1) {
|
||||
iocAsyncLock.notifyAll();
|
||||
}
|
||||
}
|
||||
Module module = (Module) importClass.getDeclaredConstructor().newInstance();
|
||||
module.configure(context);
|
||||
log.debug("Loaded module: {}", importClass.getName());
|
||||
} catch (Exception e) {
|
||||
synchronized (iocAsyncLock) {
|
||||
iocAsyncLock.notifyAll();
|
||||
throw new InjectException("Failed to instantiate module: %s".formatted(importClass.getName()), e);
|
||||
}
|
||||
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();
|
||||
// 检查是否已经通过包扫描注册
|
||||
String beanName = Text.camelCaseClassName(importClass);
|
||||
if (!context.containsBean(beanName)) {
|
||||
scanner.processClass(importClass);
|
||||
log.debug("Imported configuration: {}", importClass.getName());
|
||||
} else {
|
||||
log.debug("Skip importing already registered class: {}", importClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 依赖注入
|
||||
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));
|
||||
}
|
||||
|
||||
// 完成注入
|
||||
for (int j = 0; j < app.afterInjectCallbackList.size(); j++) {
|
||||
app.afterInjectCallbackList.get(j).handler(this);
|
||||
/**
|
||||
* 输出启动 Banner
|
||||
*/
|
||||
private static void printBanner() {
|
||||
String bannerContent = loadBanner();
|
||||
if (!bannerContent.isEmpty()) {
|
||||
System.out.println(bannerContent);
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
|
||||
/**
|
||||
* 加载 Banner 内容
|
||||
* 优先加载 banner.txt,如果不存在则加载 defBanner.txt
|
||||
*
|
||||
* @return Banner 内容
|
||||
*/
|
||||
private static String loadBanner() {
|
||||
// 优先尝试加载自定义 banner.txt
|
||||
String banner = loadBannerFromResource("banner.txt");
|
||||
if (banner != null) {
|
||||
return banner;
|
||||
}
|
||||
// 加载默认 banner
|
||||
banner = loadBannerFromResource("defBanner.txt");
|
||||
if (banner != null) {
|
||||
return banner;
|
||||
}
|
||||
// 如果都加载失败,返回空字符串
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从资源文件加载 Banner
|
||||
*
|
||||
* @param resourceName 资源文件名
|
||||
* @return Banner 内容,如果加载失败返回 null
|
||||
*/
|
||||
private static String loadBannerFromResource(String resourceName) {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描包
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描本地 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成类的控制反转封装,如果此类没有标记控制反转则忽略
|
||||
*
|
||||
* @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);
|
||||
|
||||
// 获取反转对象 ID
|
||||
if (!(isBean || isComponent || isService || isController || isResources || isUtil || isInject)) {
|
||||
// 非控制反转相关类
|
||||
if (isStaticInject) {
|
||||
// 静态注入
|
||||
staticInject.add(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化控制反转对象
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// 控制反转方法返回
|
||||
objects.putAll(iocReturn(object.getClass(), object));
|
||||
// 收集此对象需要注入后调度的方法
|
||||
invokeForInjected.put(object, invokeForInjected(object.getClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制反转方法返回对象
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
InputStream inputStream = TimiInject.class.getClassLoader().getResourceAsStream(resourceName);
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ioc(Object object) {
|
||||
ioc(object.getClass(), object);
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
return reader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@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;
|
||||
log.debug("Failed to load banner from {}: {}", resourceName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 对象名称
|
||||
* Bean 名称,默认为方法名
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@ -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 组件名称
|
||||
* Bean 名称,默认为类名首字母小写
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@ -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 "";
|
||||
}
|
||||
@ -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 控制器名称
|
||||
* Bean 名称,默认为类名首字母小写
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* <p>异步控制反转,控制反转的类或 {@link IOCReturn} 方法使用此注解将会在异步线程中实例化或调用。
|
||||
* 此注解不影响注入流程。
|
||||
* <p>此注解对实例化比较耗时的动作有明显的速度提升,有 {@link IOCPriority} 的效果,对实例化耗时较短的动作使用将会适得其反。
|
||||
* <p><u>注意:JavaFX 组件不可用,因为它只有一个 UI 线程</u>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-10-10 17:05
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface IOCAsync {
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 控制反转类使用此注解时将会优先执行实例化,对不存在异步控制反转的类使用不会提升效率。
|
||||
* <p>使用此注解后,{@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;
|
||||
}
|
||||
@ -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 中并后续执行注入
|
||||
*
|
||||
* <p><b><u>注意:注解参数为空时 {@link Inject} 注入对象的变量名需要和此方法名一致</u></b></p>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-03-04 23:16
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface IOCReturn {
|
||||
|
||||
/**
|
||||
* 对象名称,非空时注入需要明确指定
|
||||
*
|
||||
* <p><b><u>为空时注入对象的变量名需要和此方法名一致</u></b></p>
|
||||
*
|
||||
* @return 对象名称
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
21
src/main/java/com/imyeyu/inject/annotation/Import.java
Normal file
21
src/main/java/com/imyeyu/inject/annotation/Import.java
Normal file
@ -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();
|
||||
}
|
||||
@ -6,25 +6,11 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 注入,使用在类变量上,该变量的类需要在 TimiInject 控制反转中才能执行对象注入
|
||||
*
|
||||
* <p>注意
|
||||
* <ul>
|
||||
* <li><u>如果该类使用 {@link StaticInject} 静态注入,此变量必须为静态变量</u></li>
|
||||
* <li><u>如果是 {@link IOCReturn} 控制反转的对象,并且控制反转时没有指定名称,此变量名称需要和控制反转方法名一致</u></li>
|
||||
* </ul>
|
||||
* 标记用于依赖注入的构造器或字段
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-03-04 23:15
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Inject {
|
||||
|
||||
/**
|
||||
* 注入名称,控制反转明确指定名称时此属性也需要明确指定
|
||||
*
|
||||
* @return 注入名称
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@ -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} 有效,方法访问权限无限制。
|
||||
* <p>注意:{@link StaticInject} 注解类的方法使用此注解时,该方法也必须是静态方法
|
||||
*
|
||||
* <pre>
|
||||
* @Component
|
||||
* public class Demo {
|
||||
*
|
||||
* @InvokeForInjected
|
||||
* public void hello() {
|
||||
* System.out.println("hello");
|
||||
* }
|
||||
*
|
||||
* @InvokeForInjected
|
||||
* public void timiInject() {
|
||||
* System.out.println("timi-inject");
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-09-23 22:25
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface InvokeForInjected {
|
||||
|
||||
/**
|
||||
* 执行顺序,默认 0,数值小的先执行
|
||||
*
|
||||
* @return 执行顺序
|
||||
*/
|
||||
int value() default 0;
|
||||
}
|
||||
17
src/main/java/com/imyeyu/inject/annotation/Lazy.java
Normal file
17
src/main/java/com/imyeyu/inject/annotation/Lazy.java
Normal file
@ -0,0 +1,17 @@
|
||||
package com.imyeyu.inject.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标记 Bean 为懒加载
|
||||
* 等同于 @Scope(value = ScopeType.SINGLETON, lazy = true)
|
||||
*
|
||||
* @author 夜雨
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Lazy {
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 执行顺序,数值越小越先执行,默认为 0
|
||||
*
|
||||
* @return 执行顺序
|
||||
*/
|
||||
int value() default 0;
|
||||
}
|
||||
16
src/main/java/com/imyeyu/inject/annotation/Primary.java
Normal file
16
src/main/java/com/imyeyu/inject/annotation/Primary.java
Normal file
@ -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 {
|
||||
}
|
||||
21
src/main/java/com/imyeyu/inject/annotation/Qualifier.java
Normal file
21
src/main/java/com/imyeyu/inject/annotation/Qualifier.java
Normal file
@ -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, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Qualifier {
|
||||
|
||||
/**
|
||||
* Bean 名称
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@ -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 资源名称
|
||||
* Bean 名称,默认为类名首字母小写
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
28
src/main/java/com/imyeyu/inject/annotation/Scope.java
Normal file
28
src/main/java/com/imyeyu/inject/annotation/Scope.java
Normal file
@ -0,0 +1,28 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 是否懒加载(仅对 SINGLETON 作用域有效)
|
||||
* true - 首次使用时才创建
|
||||
* false - 启动时立即创建(默认)
|
||||
*/
|
||||
boolean lazy() default false;
|
||||
}
|
||||
19
src/main/java/com/imyeyu/inject/annotation/ScopeType.java
Normal file
19
src/main/java/com/imyeyu/inject/annotation/ScopeType.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.imyeyu.inject.annotation;
|
||||
|
||||
/**
|
||||
* Bean 作用域类型
|
||||
*
|
||||
* @author 夜雨
|
||||
*/
|
||||
public enum ScopeType {
|
||||
|
||||
/**
|
||||
* 单例模式(默认)
|
||||
*/
|
||||
SINGLETON,
|
||||
|
||||
/**
|
||||
* 原型模式(每次获取创建新实例)
|
||||
*/
|
||||
PROTOTYPE
|
||||
}
|
||||
@ -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 服务名称
|
||||
* Bean 名称,默认为类名首字母小写
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 静态注入,此类的<u>静态属性</u>将会在控制反转完成后进行注入,<u>此注解的类不会控制反转实例到 TimiInject 中</u>
|
||||
* <ul>
|
||||
* <li>{@link InvokeForInjected} 对本注解有效,但必须是静态方法</li>
|
||||
* </ul>
|
||||
* <pre>
|
||||
* @InvokeForInjected
|
||||
* public static void injected() {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-06-24 11:29
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface StaticInject {
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
* 父级注入,用在控制反转的类上,对控制反转的对象父级的变量也执行注入对象,父级也可以继续使用此注解
|
||||
*
|
||||
* <p>*对 {@link StaticInject} 无效
|
||||
*
|
||||
* @author 夜雨
|
||||
* @version 2022-03-04 23:16
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface SuperInject {
|
||||
}
|
||||
@ -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),此包下的所有类将检测控制反转和注入,留空则使用注解的类所在包作为扫描位置。
|
||||
* <p><u>TimiInject 的所有注解在此包扫描范围内有效</u></p>
|
||||
*
|
||||
* @return 扫描包位置
|
||||
* 组件扫描的包路径
|
||||
*/
|
||||
String[] value() default {};
|
||||
}
|
||||
|
||||
@ -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 工具名称
|
||||
* Bean 名称,默认为类名首字母小写
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
/** 控制反转和注入注解 */
|
||||
package com.imyeyu.inject.annotation;
|
||||
@ -1,2 +0,0 @@
|
||||
/** 主程序 */
|
||||
package com.imyeyu.inject;
|
||||
Reference in New Issue
Block a user