Handling Context

The Context<T> class in Infuse plays a crucial role in providing contextual information during the injection process. This class encapsulates several pieces of information that are useful when creating or providing an instance of a class. Here's a more detailed breakdown of its components and their significance:

  1. Type (Class<T>): This is the type of the class being injected. It provides the provider or injector with information about the class that requires an instance. For example, if you're injecting a DatabaseService, type would be DatabaseService.class.

  2. Object (Object): This represents the instance into which the injection is occurring. It could be the object that requires a dependency to be injected into it. This is useful for understanding the context in which the dependency is needed.

  3. Injector (Injector): The injector that is handling the injection process. This can be used to retrieve other dependencies or to create child injectors if necessary.

  4. Element (ElementType): This indicates the type of the injected member, such as FIELD, METHOD, or CONSTRUCTOR. This is important for determining how the injection should be performed.

  5. Name (String): The name of the element being injected. For fields, it would be the field name; for methods, the method name, and so on. This can be used for more fine-grained control or logging purposes.

  6. Annotations (Annotation[]): The array of annotations present on the injecting element. This can be crucial for determining specific behaviors or qualifiers for the injection.

Practical Usage of Context<T>

In practice, Context<T> is used to tailor the creation or provision of instances based on the specific circumstances of the injection. Here are a few examples of how Context<T> can be leveraged:

Example 1: Customizing Based on Annotations

Suppose you have a custom annotation @Mock and you want to provide mock instances of services when this annotation is present.

public class MockServiceProvider implements Provider<Service> {
    @Override
    public Service provide(Context<?> context) {
        if (Arrays.stream(context.getAnnotations()).anyMatch(a -> a instanceof Mock)) {
            return new MockService();
        }
        return new RealService();
    }
}

Example 2: Different Behavior for Fields and Constructors

You might want to have different behavior if a dependency is injected into a field versus a constructor.

public class LoggerProvider implements Provider<Logger> {
    @Override
    public Logger provide(Context<?> context) {
        if (context.getElement() == ElementType.FIELD) {
            return Logger.getLogger("FieldLogger");
        } else if (context.getElement() == ElementType.CONSTRUCTOR) {
            return Logger.getLogger("ConstructorLogger");
        }
        return Logger.getLogger("DefaultLogger");
    }
}

Example 3: Dependency Based on Parent Object

You can tailor the provided instance based on the class or characteristics of the object into which the dependency is being injected.

public class UserProvider implements Provider<User> {
    @Override
    public User provide(Context<?> context) {
        if (context.getObject().getClass().isAnnotationPresent(Admin.class)) {
            return new AdminUser();
        }
        return new RegularUser();
    }
}

Last updated