Simple, JSR330-compatible Dependency Injection.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Paweł Płazieński 9a8197042c Add module support 4 weeks ago
.mvn Use parent 26.0.1, add jgitver 2 months ago
src Add module support 3 weeks ago
.gitignore Restore gitignore 5 years ago
LICENSE.md Add GPL license 5 years ago
README.md Add module support 3 weeks ago
pom.xml Use method signatures for factory 4 weeks ago

README.md

Injectable

Simple, JSR330-compatible Dependency Injection.

Introduction

Injectable is a extremely simple and small Dependency Injection library that offers:

  • Construction by type or Provider,
  • Providing externally constructed singletons,
  • Adding external dependency fetching mechanism,
  • Custom scopes,
  • Injected instance replacement,
  • Custom object queries.
  • Module support

Usage

Entry point for both configuration and usage is StandardRegistry, in which dependencies are registered and instances are constructed. This class implements two interfaces, Registry and Configuration.

Registration

Standard

StandardRegistry registry = StandardRegistry.create();

In registry, you can register a previously-created singleton instance:

registry.registerSingleton(new MyCustomBuiltService());

a type constructed by @Inject annotated constructor (or default one):

registry.registerType(MyStandardInjectedService.class);

Type constructed by provider:

registry.registerProvider(MyProvidedService.class, MyProvidedService::createCustom);

Also just by providing serializable functional interface:

registry.registerProvider(MyProvidedService::createCustom);

Or external dependency fetching mechanism:

registry.registerExternal((targetClass, annotationMatch) -> 
    networkResource.retrieveClassMatching(targetClass).matching(annotationMatch));

Or built from factory, where factory instance is constructed by registry:

registry.registerFactory(MyFactoryService.class, MyProductService.class, MyFactoryService::create);

Also just by providing serializable functional interface:

registry.registerFactory(MyFactoryService::create);

You can also write your own implementation of Registration or Construction and register it in registry:

registry.register(myVerySpecialRegistration);

Additionally qualified

You can mark any registration with additional qualifiers or scopes:

registry.register(Registration.type(MyDistinctService.class).with(Distinct.class));

(where Distinct is annotation meta-annotated either by javax.inject.Scope or javax.inject.Qualifier)

To quickly add javax.inject.Named qualifier, use:

registry.register(Registration.type(MyExquisiteService.class).withName("exquisite"));

Modules

Registry configuration can be split into modules to allow decoupling parts of application. Module is very simple interface that just receives Configuration to add its own elements.

The real trick with modules starts with DeclarativeModule. This is a base class for configuring registry using static and abstract methods. To use it, create an abstract class that derives from it, and declare methods in it.

To declare just a type, constructed by Inject-annotated constructor, use abstract, parameterless methods. These methods will be reflected and thier method type will be registered in registry.

To declare a factory, use static methods with any parameters. These methods will create a registration, that will call this method, fetching arguments from registry. This supports qualifiers on parameters.

Qualifier and scope annotations placed on these methods will be applied to registration. Qualifiers will be added to qualifiers placed on class, and scope will override the scope on class. Additionally, a Named qualifier with name of the method will be added to qualifiers (i.e. the product will have method name).

Example:

abstract class Configuration extends DeclarativeModule {
	// registers a type
    abstract MyStandardInjectedService standardInjectedService();
    
    // registers a factory
    static MyProvidedService providedService() {
	    return MyProvidedService.createCustom();
    }
    
    // registers a factory with dependency
    static MyFactoryService factoryService(ProvidedService providedService) {
    	return MyFactoryService.create(providedService);
    }
    
    // registers a named service 'exquisite'
    abstract MyExquisiteService exquisite(); 
    
    // registers a qualified service
	abstract @Distinct MyDistinctService distinctService();
}

To load this module, use Module#declarative wrapper:

Registry registry = StandardRegistry.create(Module.declarative(Configuration.class);

Configuration

Scopes

Prototype and singleton scopes are registered by default, so not using any scope annotation will create new instance each fetch request, and using javax.inject.Singleton will register single instance within StandardRegistry.

If you like to annotate every class with scope, you can use org.perfectable.injection.scope.Prototype.

To create custom scope, create an scope-annotated annotation:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadScoped {
	// marker
}

Implement a scope:

public class ThreadScope implements Scope {
    @Override
    public <T> Storage<T> requestStorage() {
        ThreadLocal<T> instanceStorage = new ThreadLocal<>();
        return (createdClass, provider) -> {
            T instance = instanceStorage.get();
            if (instance == null) {
                instance = provider.get();
                instanceStorage.set(instance);
            }
            return instance;
        };
    }
}

And register it in registry.

registry.addScope(ThreadScoped.class, threadScope);

Every object registered with this scope annotation will be placed in specified scope. and extracted from there.

Replacement

Instances can be replaced before injecting as dependency, for example with proxy:

registry.addReplacement((createdClass, provider) -> 
    proxyFactory.create(createdClass).initializedBy(provider));

Extraction

To obtain instance from registry create query for instances:

MyStandardInjectedService service = 
    registry.fetchAny(Query.typed(MyStandardInjectedService.class));

To request existing qualifiers use:

MyDistinctService service = 
    registry.fetchAny(Query.typed(MyDistinctService.class).qualifiedWith(Distinct.class));

To request instance annotated with special javax.inject.Named:

MyExquisiteService service = 
    registry.fetchAny(Query.typed(MyExquisiteService.class).named("exquisite"));

These can be chained:

MyProvidedService service = 
    registry.fetchAny(Query.typed(MyProvidedService.class).named("unbugged").qualifiedWith(Fast.class));

Quick reference

Using module

@Singleton
public class Application {
	@Inject
	@Transactional
	private DatabaseConnection databaseConnection;

	@Inject
	@Named("credit")
	private PaymentService paymentService;

	public static void main(String[] args) {
		Module argumentsModule = config -> config.register(Registration.singleton(args).named("program"));
		StandardRegistry registry = StandardRegistry.create(argumentsModule, Module.declarative(Configuration.class));
		Application configuredApplication = registry.fetchAny(Query.typed(Application.class));
		configuredApplication.execute();
	}

	private void execute() {
		// ...
	}
}

abstract class Configuration extends DeclarativeModule {
	abstract CreditPaymentService paymentService();
	abstract StandardAuthenticationService authenticationService();
	abstract Application application();
	static @Transactional DatabaseConnection databaseConnection(@Named("program") String[] arguments) {
		return DatabaseConnection.create(args);
    }
}

Procedural configuration

@Singleton
public class Application {
    @Inject
    @Transactional
    private DatabaseConnection databaseConnection;
    
    @Inject
    @Named("credit")
    private PaymentService paymentService;
    
    public static void main(String[] args) {
        DatabaseConnection databaseConnection = DatabaseConnection.create(args);
        StandardRegistry registry = StandardRegistry.create();
        registry.registerType(CreditPaymentService.class);
        registry.registerType(StandardAuthenticationService.class); // required for CreditPaymentService
        registry.register(Registration.singleton(databaseConnection).with(Transactional.class));
        registry.registerType(Application.class);
        Application configuredApplication = registry.fetchAny(Query.typed(Application.class));
        configuredApplication.execute();
    }

    private void execute() {
        // ...
    }
}

Using class scanning

If you'd also use introspectable, you can register every annotated class with your custom annotation:


public static void main(String[] args) {
    StandardRegistry registry = StandardRegistry.create();
    ClassQuery.all()
        .inPackage(Application.class.getPackage()) // Or any other prefix
        .annotatedWith(Component.class)
        .forEach(registry::registerType);
    Application configuredApplication = registry.fetchAny(Query.typed(Application.class));
    configuredApplication.execute();
}

Usage in maven

Add as dependency:

<dependency>
    <groupId>org.perfectable</groupId>
    <artifactId>injectable</artifactId>
    <version>2.0.1-SNAPSHOT</version>
</dependency>

Currently, injectable artifacts is stored on perfectable.org maven repository only, so you need to add following entry to your repositories:

<repository>
    <id>perfectable-all</id>
    <name>Perfectable</name>
    <url>https://maven.perfectable.org/repo</url>
</repository>