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.
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>- Native integration with Paper plugins
- Template dependency support for plugin frameworks
- Automatic plugin lifecycle management
- All
@Componentand@Serviceclasses 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
}
}Registering Listeners with ease
- Use
@RegisterListenerto 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!"); } }
- Use
Create Manager classes with
@Componentor@Service- Use
@Componentfor general-purpose classes - Use
@Servicefor classes that provide business logic or services - Example:
packageorg.example.plugin.services; importnet.vortexdevelopment.vinject.annotations.Component; @ComponentpublicclassMyService { publicvoidperformAction() { // Service logic } }
- Use
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
- 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
}
}- Create a service:
@ServicepublicclassYourService {
@InjectprivateDatabasedatabase;
publicvoiddoSomething() {
// Your service logic
}
}- Create a component:
@ComponentpublicclassYourComponent {
@InjectprivateYourServiceyourService;
publicvoiddoSomething() {
yourService.doSomething();
}
}- Create a repository:
@RepositorypublicinterfaceUserRepositoryextendsCrudRepository<User, Long> {
// Your repository methods
}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>- For
@Entityclasses: Adds field modification tracking for efficient database updates - For YAML configuration classes: Adds synthetic fields (
__vinject_yaml_batch_idand__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.
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).
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.ymlfile (relative to the configuration root unless absolute).path: optional base prefix for every field on this class. Each field maps topath+.+ key.@Key("segment"): overrides the key segment for that field. Whenpathis set,@Keyis appended under that base (for examplepath = "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 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).
@YamlItemon a class marks a compact YAML object (a single subtree when saving, with tighter field layout).@Commenton a type or field adds comment lines above that entry when saving.@NewLineBeforeand@NewLineAfteron fields control blank lines when YAML is rendered.
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: 5When 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).
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
@YamlSerializerunder your@Rootscan package are instantiated and registered whenConfigurationContainerstarts (no-arg or injectable constructor). - Manual:
ConfigurationContainer.registerSerializer(...)orYamlSerializerRegistry.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.
@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.
For optimal performance with VInject-Transformer, ensure your entity classes have:
- Getters and setters for all fields, or
- Lombok's
@Dataannotation
Example:
@Data@EntitypublicclassUser {
privateLongid;
privateStringname;
privateStringemail;
}Create custom annotation handlers by extending AnnotationHandler:
@Registry(annotation = CustomAnnotation.class, order = RegistryOrder.COMPONENTS)
publicclassCustomAnnotationHandlerextendsAnnotationHandler {
@Overridepublicvoidhandle(Class<?> clazz, Objectinstance, DependencyContainercontainer) {
// Your custom handling logic
}
}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
}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.