Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

VInject Framework

A lightweight and powerful dependency injection framework for Java applications, designed to simplify dependency management and improve code organization. Originally created for Minecraft plugin development, it provides seamless integration with the Bukkit/Spigot ecosystem while also supporting standalone Java applications.

Getting Started

Adding to Your Project

Add the following to your pom.xml:

<repository>
<id>vortex-repo</id>
<url>https://repo.vortexdevelopment.net/repository/maven-public/</url>
</repository>
<dependency>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Framework</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

Features

Primarily designed for Minecraft plugin development

  • Native integration with Paper plugins
  • Template dependency support for plugin frameworks
  • Automatic plugin lifecycle management
  • All @Component and @Service classes can be injected.
  • Example plugin structure with VInject and VortexCore:
packageorg.example.plugin;
@Root(
packageName = "org.example.plugin",
createInstance = false, //Do not create an instance of this class, plugin loader will handle ittemplateDependencies = {
//Used by the Intellij Plugin@TemplateDependency(groupId = "net.vortexdevelopment", artifactId = "VortexCore", version = "1.0.0-SNAPSHOT")
}
)
publicfinalclassMyPluginextendsVortexPlugin {
@OverridepublicvoidonPreComponentLoad() {
// Initialize before components are loaded
}
@OverridepublicvoidonPluginLoad() {
// Load plugin-specific resourcesConfig.load();
}
@OverrideprotectedvoidonPluginEnable() {
// Plugin enable logic
}
@OverrideprotectedvoidonPluginDisable() {
// Plugin disable logic
}
}

Extras:

  • Registering Listeners with ease

    • Use @RegisterListener to register event listeners (From VortexCore)
    • Example:
    packageorg.example.plugin.listeners;
    importorg.example.plugin.MyPlugin;
    importorg.bukkit.event.EventHandler;
    importorg.bukkit.event.Listener;
    importorg.bukkit.event.player.PlayerJoinEvent;
    importnet.vortexdevelopment.vinject.annotations.Inject; importnet.vortexdevelopment.vortexcore.vinject.annotation.RegisterListener;
    @RegisterListenerpublicclassMyListenerimplementsListener {
    @InjectprivateMyPluginmyPlugin;
    @EventHandlerpublicvoidonPlayerJoin(PlayerJoinEventevent) {
    myPlugin.getLogger().info(event.getPlayer().getName() + " joined the server!");
    }
    }
  • Create Manager classes with @Component or @Service

    • Use @Component for general-purpose classes
    • Use @Service for classes that provide business logic or services
    • Example:
    packageorg.example.plugin.services;
    importnet.vortexdevelopment.vinject.annotations.Component;
    @ComponentpublicclassMyService {
    publicvoidperformAction() {
    // Service logic
    }
    }
  • Annotation-based Dependency Injection

    • @Inject - Mark fields for dependency injection
    • @Component - Mark classes as components
    • @Service - Mark classes as services
    • @Bean - Define bean methods for dependency creation
    • @Repository - Mark classes as repositories
    • @Root - Mark the main application class
  • Database Integration

    • Built-in support for database repositories
    • Automatic entity mapping
    • CRUD operations support
  • Flexible Configuration

    • YAML-backed configuration with annotations (see YAML configuration)
    • Package scanning with inclusion/exclusion support
    • Custom annotation handlers
    • Dependency order management

Basic Usage

  1. Mark your main class with @Root:
packageorg.example.app;
@Root(packageName = "org.example.app")
publicclassYourApplication {
@InjectprivateYourServiceyourService;
privatestaticDatabasedatabase;
privatestaticRepositoryContainerrepositoryContainer;
privatestaticDependencyContainercontainer;
publicstaticvoidmain(String[] args) {
// Initialize your applicationintpoolSize = 10; // Set your desired pool sizedatabase = newDatabase("host", "port", "database", "mysql|mariadb", "username", "password", poolSize);
//Initialize the database connection if needed//database.init();// Initialize the repository containerrepositoryContainer = newRepositoryContainer(database);
// Initialize the dependency container which will load all componentsdependencyContainer = newDependencyContainer(
YourApplication.class.getAnnotation(Root.class), YourApplication.class,
null, //It will create a new instance of the classdatabase, repositoryContainer
);
//Inject static fields after components are loadeddependencyContainer.injectStatic(app);
//Inject non-static fieldsdependencyContainer.inject(app);
//Your app fully started
}
}
  1. Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}
  1. Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}
  1. Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}

Maven Transformer Plugin (Required)

The VInject-Transformer plugin is required for both database entities and YAML configurations.

Add the transformer plugin to your pom.xml:

<plugin>
<groupId>net.vortexdevelopment</groupId>
<artifactId>VInject-Transformer</artifactId>
<version>1.0.2</version>
<executions>
<execution>
<id>process-classes</id>
<phase>process-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
<execution>
<id>process-test-classes</id>
<phase>process-test-classes</phase>
<goals>
<goal>transform-classes</goal>
</goals>
</execution>
</executions>
</plugin>

What the Transformer Does

  • For @Entity classes: Adds field modification tracking for efficient database updates
  • For YAML configuration classes: Adds synthetic fields (__vinject_yaml_batch_id and __vinject_yaml_file) required for batch loading and saving

Note: Classes used in YAML batch loading (classes with fields annotated with @YamlId) must be processed by the transformer. Without it, YAML configuration features will not work correctly.

YAML configuration

VInject maps YAML files into Java objects. Paths in @YamlConfiguration.file and @YamlDirectory.dir are resolved relative to the JVM working directory unless you call ConfigurationContainer.setRootDirectory(Path) or setRootDirectory(String) before building the DependencyContainer.

For batch item types that use @YamlId, keep the VInject-Transformer enabled as described in Maven Transformer Plugin (Required).

Single-file configuration (@YamlConfiguration)

Annotate one class with @YamlConfiguration to bind a single YAML file. Values are written into fields directly (setters are not required for loading).

  • file: path to the .yml file (relative to the configuration root unless absolute).
  • path: optional base prefix for every field on this class. Each field maps to path + . + key.
  • @Key("segment"): overrides the key segment for that field. When path is set, @Key is appended under that base (for example path = "app" and @Key("display-name")app.display-name).
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlConfiguration;
@YamlConfiguration(file = "config.yml", path = "app")
publicclassAppConfig {
@Key("port")
privateintport;
@Key("display-name")
privateStringname;
}
app:
port: 8080display-name: "My App"

Optional attributes: autoSave, asyncSave, and encoding (default UTF-8).

Nested sections, maps, and lists

Nested POJO fields and parameterized Map / List types are filled from nested YAML. Use ConfigurationSection as a field type when you want the raw subsection.

To map any ConfigurationSection to a new instance outside @YamlConfiguration, use ConfigurationContainer.mapSection(Class<T>, ConfigurationSection).

Layout and comments (@YamlItem, @Comment, newlines)

  • @YamlItem on a class marks a compact YAML object (a single subtree when saving, with tighter field layout).
  • @Comment on a type or field adds comment lines above that entry when saving.
  • @NewLineBefore and @NewLineAfter on fields control blank lines when YAML is rendered.

Directory batch loading (@YamlDirectory, @YamlId, @YamlCollection)

A holder class loads many YAML files from one directory into typed items.

importnet.vortexdevelopment.vinject.annotation.component.Component;
importnet.vortexdevelopment.vinject.annotation.yaml.Key;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlCollection;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlDirectory;
importnet.vortexdevelopment.vinject.annotation.yaml.YamlId;
importjava.util.HashMap;
importjava.util.Map;
@Component@YamlDirectory(dir = "rewards", target = Reward.class)
publicclassRewardDirectory {
@YamlCollectionprivateMap<String, Reward> rewards = newHashMap<>();
publicMap<String, Reward> getRewards() {
returnrewards;
}
}
@YamlItempublicclassReward {
@YamlIdprivateStringid;
@Key("amount")
privateintamount;
}

On disk: under rewards/, every .yml / .yaml file is read. recursive (default true) controls subfolders; copyDefaults copies matching resources from the JAR when the folder is missing or empty.

YAML shape when rootKey is empty (default): top-level keys are item IDs; each key’s value is a section mapped onto target.

gold:
amount: 100diamond:
amount: 5

When rootKey is set (for example rootKey = "items"), that section is taken first and each key under it is an item ID.

@YamlId: the item’s map key is stored in the annotated String field. This is what enables batch save and file tracking together with the transformer.

Holder collections: after load, every Map or Collection field on the holder is filled with the loaded items. @YamlCollection marks the batch field explicitly. The batch id is holderClass.getName() + "::" + dir.

Mapping: each target class is filled from YAML by field mapping, like @YamlConfiguration. Register a YamlSerializerBase when the type cannot be represented as a simple set of fields (see below).

Custom serializers (YamlSerializerBase, @YamlSerializer)

Implement YamlSerializerBase<T> with getTargetType(), serialize(T), and deserialize(Map<String, Object>) to control how a type is read and written.

  • Discovery: classes annotated with @YamlSerializer under your @Root scan package are instantiated and registered when ConfigurationContainer starts (no-arg or injectable constructor).
  • Manual:ConfigurationContainer.registerSerializer(...) or YamlSerializerRegistry.registerSerializer(...).
importnet.vortexdevelopment.vinject.annotation.yaml.YamlSerializer;
importnet.vortexdevelopment.vinject.config.serializer.YamlSerializerBase;
importjava.util.HashMap;
importjava.util.Map;
publicclassCoords {
privatefinalintx, y;
publicCoords(intx, inty) { this.x = x; this.y = y; }
publicintgetX() { returnx; }
publicintgetY() { returny; }
}
@YamlSerializerpublicclassCoordsSerializerimplementsYamlSerializerBase<Coords> {
@OverridepublicClass<Coords> getTargetType() {
returnCoords.class;
}
@OverridepublicMap<String, Object> serialize(Coordsc) {
Map<String, Object> m = newHashMap<>();
m.put("cx", c.getX());
m.put("cy", c.getY());
returnm;
}
@OverridepublicCoordsdeserialize(Map<String, Object> map) {
intx = ((Number) map.get("cx")).intValue();
inty = ((Number) map.get("cy")).intValue();
returnnewCoords(x, y);
}
}

Fields of type Coords in YAML configs then round-trip through this serializer on load and save.

Conditional components (@YamlConditional)

@YamlConditional on a class skips registering that component unless a value in a @YamlConfiguration class matches. Example: configuration = MyConfig.class, path = "features.vouchers", value = "true". Use operator when you need a comparison other than equality.

Performance Optimization

For optimal performance with VInject-Transformer, ensure your entity classes have:

  • Getters and setters for all fields, or
  • Lombok's @Data annotation

Example:

@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}

Advanced Features

Custom Annotation Handlers

Create custom annotation handlers by extending AnnotationHandler:

@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}

Package Scanning Configuration

Configure package scanning in your @Root annotation:

@Root(
packageName = "com.your.package",
ignoredPackages = {"com.your.package.excluded"},
includedPackages = {"com.your.package.included"}
)
publicclassYourApplication {
// Your application code
}

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

For third-party dependencies and their licenses, please see the NOTICE file.

About

Java dependency injection framework

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages