Skip to content

Handlers

ljacqu edited this page Oct 26, 2019 · 6 revisions

Introduction

To allow customizable behavior, most behavior of the injector is provided by different handlers. The injector provides some default ones which are responsible for how the injector behaves by default. However, custom handlers may be added to the injector, and default handlers may be removed.

For example, when the injector is called with injector.getSingleton(MyClass.class), it calls the methods on all configured handlers in the following order:

  1. Handler#resolve is called until the first handler returns a Resolution. This method is suitable for preliminary validation and for finding out how the object should be constructed.
  2. Once the object is instantiated it is passed to Handler#postProcess. Actions on the object can be performed here (such as invoking @PostConstruct methods) or the object to be returned from the injector can be modified (e.g. to wrap a proxy around it).

Defining the injector's handlers

If you want to use the injector with its default behavior, you can simply use the following code:

Injector injector = new InjectorBuilder()
   .addDefaultHandlers("root.package.of.project")
   .create();

To add additional handlers to the injector, you can use something like the code below:

Injector injector = new InjectorBuilder()
   .addDefaultHandlers("root.package.of.project")
   .addHandlers(new CustomHandler(), new OtherCustomHandler())
   .create();

The order is important. If you add your custom handlers before the default ones, the injector will go through them first. If you add them after (as in the example above), the injector will first go through the default ones and then through yours.

Defining handlers more flexibly

The InjectorBuilder class offers various methods e.g. if you want to remove specific default handlers while retaining the rest.

List<Handler> handlers = InjectorBuilder
    .createDefaultHandlers("root.package.of.project");

// Remove all handlers of DirectInstantiationProvider type
handlers.removeIf(handler -> handler instanceof DirectInstantiationProvider);

// Create injector with the modified handlers
Injector injector = new InjectorBuilder()
    .addHandlers(new CustomInstantiationHandler())
    .addHandlers(handlers)
    .create();

Handler methods

Notice that all methods on the Handler interface are empty default methods. This allows you to only extend the methods you need. We now look at each method separately.

resolve(ResolutionContext resolutionContext)

Called when an object is requested, before anything has been constructed yet. Handlers may remap the requested class to a child class by modifying the ResolutionContext that is passed in to it. This is also the ideal method to perform some validation that the requested class is indeed meant to pass through the injector (e.g. package validation).

If the handler is responsible for constructing the object, a Resolution<?> object is returned from the method, null otherwise (e.g. if the handler only handles a specific type or if the handler only performs validation).

Example: SavedAnnotationsHandler, matches if a dependency has an annotation for which it has a value

postProcess(Object object, ResolutionContext context, Resolution<?> resolution)

This method is called after the object has been created. PostConstruct handlers can perform actions on the fully constructed object, or return another object that will be returned from the injector instead (e.g. for wrapping it in a proxy).

Examples: PostConstructMethodInvoker, executes @PostConstruct methods; ProfilePostConstructHandler, wraps classes around a proxy when @Profile annotation is found. The latter class is in the test scope and just serves as a demo!

onAnnotation(Class<? extends Annotation> annotationType, Object object)

This method is called when Injector#provide(Class, Object) is invoked. By default it saves the given Object for the provided annotation class, cf. the previously mentioned SavedAnnotationsHandler.

onProvider(Class clazz, Provider<? extends T> provider)

When Injector#registerProvider(Class, Provider) is invoked, this method is called on the handlers. By default, the providers that are passed serve as custom instantiation type, see ProviderHandler.

onProviderClass(Class clazz, Class<Provider<? extends T>> providerClass)

This method is called on all handlers when Injector#registerProvider(Class, Class) is invoked. Example: ProviderHandler, which also implements the aforementioned onProvider(Class, Provider) method.