TypeRegister is a Java library that provides a flexible and centralized registry for type-provider links. It allows for dynamic object instantiation based on types, making it easier to manage and access providers for various types within your application.
TypeRegister library offers the following features:
- Type-Provider Registration: The TypeRegister library allows you to register type-provider links, associating specific types with their corresponding providers. This enables the system to retrieve the appropriate provider based on the type.
- Customizable Providers: You have the flexibility to create custom provider classes that implement specific functionality for different types. This allows you to define behavior tailored to each type, accommodating various implementation requirements.
- Identifier Providers:: The TypeRegister library introduces the concept of an identifier for type-provider links. An identifier allows you to associate a unique identifier with each type-provider link, providing a convenient way to retrieve providers based on their identifiers.
- Multiple Registers: The TypeRegister library supports the use of multiple registers, which act as independent containers for type-provider links. You can separate and organize type-provider links based on different contexts or modules within your application. Each register maintains its own set of type-provider links.
- Merging Registers: In situations where you need to combine type-provider links from different registers, the TypeRegister library enables you to merge registers together. This consolidation facilitates the management of providers across various contexts. When merging registers with different priorities, the resulting merged register retains the priority of the register with the highest priority among the merged registers.
- Register Priority: You can assign priorities to registers using the provided RegisterPriority enum. Register priority determines the order in which providers are retrieved for a given type. This empowers you to control the selection of providers when multiple providers are registered for the same type.
- Flexible and Extensible: The TypeRegister library provides a flexible and extensible framework for managing type-provider relationships. You can easily add, remove, or modify type-provider links based on your application's requirements, ensuring adaptability and scalability.
- Central Registry: The TypeRegister library acts as a central registry for managing type-provider links. It provides a centralized location for accessing and retrieving providers based on the registered types, promoting organization and accessibility.
- Easy Integration: The TypeRegister library seamlessly integrates into your application, offering straightforward incorporation of type-based functionality. It does not require complex configuration or setup, making it easy to integrate into new or existing projects.
The TypeRegister library empowers you to efficiently manage type-provider relationships, customize providers, control provider selection, and enhance the flexibility and extensibility of your Java applications.
You can include the TypeRegister library in your Java project by adding the following Maven dependency:
<dependency>
<groupId>nl.devoxist</groupId>
<artifactId>TypeRegister</artifactId>
<version>1.6.1</version>
</dependency>
The TypeRegister library provides a convenient way to register and manage type-provider links. This section provides an in-depth guide on how to use the library effectively.
To register a type-provider link, you can use one of the following methods:
- Method 1: Registering with Class and Provider Instances
TypeRegister.register(TypeProvider<T, P> typeProvider);
This method allows you to register a type-provider link by providing instances of the type and its corresponding provider. It is suitable when you have pre-initialized instances of the type and provider.
- Method 2: Registering with Class and Supplier Provider
TypeRegister.registerScoped(Class<T> typeCls, SerializableSupplier<P> provider);
This method is used to register a type-provider link by specifying the type class and a supplier provider. The supplier provider is responsible for lazily providing instances of the type when needed.
- Method 3: Registering with Class and Supplier Provider
TypeRegister.register(Class<T> typeCls, SerializableConsumer<X> builderConsumer);
This method allows you to register a type-provider link by specifying the type class and a builder consumer. The builder
consumer is responsible for constructing the provider using a TypeProviderBuilder
. This method provides flexibility
when you need to configure the provider using a builder pattern.
The register
method requires the (IdentifiersBuilder<Car, Cars> settings
) parameter to configure the identifiers and
resolve settings for the specified class. By passing the settings
object to the lambda expression, you can add the
scoped identifier using the addIdentifier
method.
If you no longer need a type-provider link, you can unregister it using the following methods:
- Method 1: Unregistering by Type
TypeRegister.unregister(Class<T> typeCls);
This method unregisters the type-provider link based on the type class. It removes the association between the type and
its provider from the TypeRegister
.
- Method 2: Unregistering by TypeProvider
TypeRegister.unregister(TypeProvider<T, P> typeProvider);
This method unregisters the type-provider link based on the TypeProvider instance. It removes the association between
the type and its provider from the TypeRegister
.
To check if a type has a registered provider, you can use the following method:
boolean hasProvider = TypeRegister.hasProvider(Class<T> typeCls);
This method returns true if a provider is registered for the specified type, and false otherwise.
Here are some additional usage examples to illustrate the features of the TypeRegister
:
- Example 1: Registering and Initializing Providers
// Define a type and its provider
TypeProvider<Car, CarProvider> carProvider = new ObjectProvider<>(Car.class, new CarProvider());
TypeProvider<Bus, BusProvider> busProvider = new ObjectProvider<>(Bus.class, new BusProvider());
// Register the type-provider links
TypeRegister.register(carProvider);
TypeRegister.register(busProvider);
// Initialize and retrieve the initialized instances
Car car = TypeRegister.getInitProvider(Car.class);
Bus bus = TypeRegister.getInitProvider(Bus.class);
// Use the initialized instances
car.drive();
bus.drive();
- Example 2: Retrieving Providers by Type
// Register the type-provider links
TypeRegister.register(new ObjectProvider<Rectangle, RectangleProvider>(Rectangle.class, new RectangleProvider()));
TypeRegister.register(new ObjectProvider<Circle, CircleProvider>(Circle.class, new CircleProvider()));
// Retrieve providers by type
RectangleProvider rectangleProvider = TypeRegister.getProviderByType(Rectangle.class);
CircleProvider circleProvider = TypeRegister.getProviderByType(Circle.class);
// Use the providers
Rectangle rectangle = rectangleProvider.createRectangle(10, 20);
Circle circle = circleProvider.createCircle(5.0);
rectangle.draw();
circle.draw();
- Example 3: Unregistering Type-Provider Links
// Register the type-provider links
TypeRegister.register(new ObjectProvider<Animal, AnimalProvider>(Animal.class, new AnimalProvider()));
TypeRegister.register(new ObjectProvider<Plant, PlantProvider>(Plant.class, new PlantProvider()));
// Unregister a type-provider link
TypeRegister.unregister(Animal.class);
// Retrieve providers by type
AnimalProvider animalProvider = TypeRegister.getProviderByType(Animal.class); // Returns null
PlantProvider plantProvider = TypeRegister.getProviderByType(Plant.class);
// Use the providers
Plant plant = plantProvider.createPlant("Rose");
plant.grow();
The TypeProvider
class serves as a base class for creating custom type providers in the TypeRegister library. Custom
type providers allow you to define the behavior and instantiation logic for specific types. This section provides
guidance on creating and using custom type providers.
To create a custom type provider, you need to extend the TypeProvider class and provide an implementation for the
getInitProvider()
method. The TypeProvider class has the following structure:
public abstract class TypeProvider<T, P> {
private final Class<T> typeCls;
private final P provider;
public TypeProvider(@NotNull Class<T> typeCls, @NotNull P provider) {
this.typeCls = typeCls;
this.provider = provider;
}
public Class<T> getType() {
return typeCls;
}
public P getProvider() {
return provider;
}
public abstract T getInitProvider();
}
- typeCls: The class or interface representing the type of the provider.
- provider: The provided object of the type.
You need to implement the getInitProvider()
method in your custom type provider class. This method is responsible for
returning the initialized object of the provider. You can customize the initialization logic based on your requirements.
Here's an example of a custom type provider implementation:
public class CustomTypeProvider extends TypeProvider<CustomType, CustomTypeProviderImpl> {
public CustomTypeProvider(CustomTypeProviderImpl provider) {
super(CustomType.class, provider);
}
@Override
public CustomType getInitProvider() {
// Custom initialization logic here
// ...
return null;
}
// Custom provider implementation class
public static class CustomTypeProviderImpl {
// Provider implementation details
// ...
}
}
In the example above, we define a CustomTypeProvider
class that extends the TypeProvider
class. The constructor
takes an instance of the provider class (CustomTypeProviderImpl
) and passes it to the superclass constructor using the
appropriate type class (CustomType.class
).
The getInitProvider()
method is implemented to provide the custom initialization logic for the custom type. You can
customize this method to create and return an initialized instance of the custom type.
Once you have implemented your custom type provider, you can register it using the TypeRegister.register()
method:
TypeRegister.register(new CustomTypeProvider(new CustomTypeProviderImpl()));
In the example above, we create an instance of the CustomTypeProvider
and pass an instance of the provider class ( CustomTypeProviderImpl
) to the constructor. By registering the custom type provider, you make it available for
instantiation through the TypeRegister
.
To retrieve an instance of a custom type from the TypeRegister, you can use the TypeRegister.getInitProvider()
method:
CustomType customType = TypeRegister.getInitProvider(CustomType.class);
In the example above, we use the getInitProvider()
method to retrieve an initialized instance of the custom type (
CustomType
). The TypeRegister
internally uses the registered custom type provider to instantiate the custom type and
return the initialized instance.
You can now use the customType
object in your application, leveraging the customized behavior and instantiation logic
provided by your custom type provider.
The TypeRegister library allows you to create custom registers to manage type-provider links based on your specific requirements. This section will guide you through the process of creating a custom register.
To create a custom register, you need to create a new class that extends the Register
class provided by
the TypeRegister
library. This custom class will serve as your register implementation.
Here's an example of creating a custom register called CustomRegister
:
public class CustomRegister extends Register {
// Add custom methods and logic here
}
In the above example, we create a new class CustomRegister
that extends the Register
class. You can add any custom
methods and logic specific to your register implementation in this class.
To register type-provider links in your custom register, you can utilize the methods inherited from the Register
class. These methods provide the functionality to register type-provider links based on different approaches, as
discussed earlier in the usage documentation.
You can override the register()
method to provide your custom implementation for registering type-provider links.
Here's an example:
public class CustomRegister extends Register {
@Override
public void register(TypeProvider<?, ?> typeProvider) {
// Custom logic for registering type-provider links
// ...
super.register(typeProvider);
}
}
In the above example, we override the register()
method and add our custom logic for registering type-provider links.
You can modify the implementation based on your specific requirements.
Similar to registering, you can override the unregister()
method to provide custom logic for unregistering
type-provider links. Here's an example:
public class CustomRegister extends Register {
@Override
public void unregister(TypeProvider<?, ?> typeProvider) {
// Custom logic for unregistering type-provider links
// ...
super.unregister(typeProvider);
}
}
In the above example, we override the unregister()
method and add our custom logic for unregistering type-provider
links. You can customize the implementation as per your needs.
Once you have created your custom register, you can use it in your application by instantiating the CustomRegister
class and performing register-related operations using its methods. Here's an example:
CustomRegister customRegister = new CustomRegister();
// Register type-provider links using custom register
customRegister.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Unregister type-provider links using custom register
customRegister.unregister(new ObjectProvider<>(Bus.class, new BusProvider()));
In the above example, we instantiate the CustomRegister
class and use it to register and unregister type-provider
links.
Here's an example showcasing a custom register with additional functionality:
public class CustomRegister extends Register {
private Map<String, List<String>> typeAliases;
public CustomRegister() {
typeAliases = new HashMap<>();
}
public void addTypeAlias(String type, String alias) {
List<String> aliases = typeAliases.getOrDefault(type, new ArrayList<>());
aliases.add(alias);
typeAliases.put(type, aliases);
}
public List<String> getTypeAliases(String type) {
return typeAliases.getOrDefault(type, Collections.emptyList());
}
}
In the above example, the CustomRegister
class extends the Register
class and adds additional functionality to
manage type aliases. It provides methods to add type aliases (addTypeAlias()
) and retrieve type
aliases (getTypeAliases()
).
In addition to creating a custom register, the TypeRegister library allows you to work with multiple registers simultaneously. This can be useful in scenarios where you want to separate type-provider links based on different contexts or modules within your application.
To create multiple registers, you can instantiate multiple instances of the Register
or your custom register class.
Each register will maintain its own set of type-provider links.
Here's an example of creating multiple registers:
Register register1 = new Register();
Register register2 = new Register();
In the above example, we create two separate instances of the Register
class, namely register1
and register2
. Each
register will maintain its own set of type-provider links.
When working with multiple registers, you can register and unregister type-provider links using the respective register instances. Each register will handle its own set of type-provider links independently.
Here's an example illustrating the registration and unregistration with multiple registers:
Register register1 = new Register();
Register register2 = new Register();
// Register type-provider links with register1
register1.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Register type-provider links with register2
register2.register(new ObjectProvider<>(Bus.class, new BusProvider()));
// Unregister type-provider links from register1
register1.unregister(new ObjectProvider<>(Car.class, new CarProvider()));
// Unregister type-provider links from register2
register2.unregister(new ObjectProvider<>(Bus.class, new BusProvider()));
In the above example, we register type-provider links for the Car
and Bus
types using separate register instances.
We then unregister the respective type-provider links from their respective registers.
When you have multiple registers, you can retrieve type providers from a specific register by using the respective register instance.
Here's an example demonstrating how to access type providers from multiple registers:
Register register1 = new Register();
Register register2 = new Register();
// Register type-provider links with register1
register1.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Register type-provider links with register2
register2.register(new ObjectProvider<>(Bus.class, new BusProvider()));
// Retrieve the type provider for Car from register1
TypeProvider<Car, CarProvider> carProvider1 = register1.getProviderByType(Car.class);
// Retrieve the type provider for Bus from register2
TypeProvider<Bus, BusProvider> busProvider2 = register2.getProviderByType(Bus.class);
In the above example, we register type-provider links for the Car
and Bus
types using separate register instances.
We then retrieve the respective type providers from their respective registers.
Using multiple registers can be beneficial when you want to manage type-provider links in different contexts or modules within your application. Each register can represent a specific context or module, allowing you to isolate and organize the type-provider links accordingly.
For example, you might have one register for the core functionality of your application and another register for a plugin system. This separation can help maintain modularity and prevent conflicts between different sets of type-provider links.
Here's an example showcasing the use of multiple registers with custom functionality:
public class CustomRegister extends Register {
// Custom register implementation with additional functionality
// ...
}
CustomRegister register1 = new CustomRegister();
CustomRegister register2 = new CustomRegister();
// Register type-provider links with register1
register1.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Register type-provider links with register2
register2.register(new ObjectProvider<>(Bus.class, new BusProvider()));
// Access custom functionality of register1
register1.addTypeAlias("Car", "Automobile");
List<String> aliases = register1.getTypeAliases("Car");
// Access custom functionality of register2
// ...
In the above example, we create two instances of the CustomRegister
class, register1
and register2
. Each register
has its own set of type-provider links and can provide custom functionality specific to the CustomRegister
class.
You can extend your custom register implementation with additional functionality as per your requirements and use multiple registers in different parts of your application.
In addition to working with multiple registers independently, the TypeRegister library also provides the capability to merge registers. Merging registers allows you to combine the type-provider links from multiple registers into a single register, providing a unified view of all the registered types and providers.
To merge registers, you can use the constructor of the Register
class that accepts multiple registers as arguments. By
passing the registers you want to merge, you create a new register instance that includes all the type-provider links
from the merged registers.
Here's an example of merging registers using the constructor:
Register register1 = new Register();
Register register2 = new Register();
// Register type-provider links with register1
register1.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Register type-provider links with register2
register2.register(new ObjectProvider<>(Bus.class, new BusProvider()));
// Merge register1 and register2 into a new register
Register mergedRegister = new Register(register1, register2);
In this example, we have three registers: register1
, register2
, and register3
. Each register has its own set of
type-provider links. By using the Register
constructor with the registers to merge, we create a new register called
mergedRegister
that includes all the type-provider links from the merged registers.
Once you have merged registers, you can access the type providers through the merged register instance. The merged register will provide a unified view of the registered providers, regardless of which original register they came from.
Here's an example demonstrating how to access type providers from merged registers:
Register register1 = new Register();
Register register2 = new Register();
Register register3 = new Register();
// Register type-provider links with register1
register1.register(new ObjectProvider<>(Car.class, new CarProvider()));
// Register type-provider links with register2
register2.register(new ObjectProvider<>(Bus.class, new BusProvider()));
// Register type-provider links with register3
register3.register(new ObjectProvider<>(Train.class, new TrainProvider()));
// Merge register1, register2, and register3 into a new register
Register mergedRegister = new Register(register1, register2, register3);
// Retrieve the type provider for Car from the merged register
TypeProvider<Car, CarProvider> carProvider = mergedRegister.getProviderByType(Car.class);
// Retrieve the type provider for Bus from the merged register
TypeProvider<Bus, BusProvider> busProvider = mergedRegister.getProviderByType(Bus.class);
// Retrieve the type provider for Train from the merged register
TypeProvider<Train, TrainProvider> trainProvider = mergedRegister.getProviderByType(Train.class);
In this example, we merge register1
, register2
, and register3
into mergedRegister
. We can then use the merged
register to retrieve the type providers for Car
, Bus
, and Train
by calling the getProviderByType()
method with
the respective type.
The merged register allows you to access all the type providers as if they were registered with a single register, providing a consolidated view of the registered providers.
Merging registers can be beneficial in scenarios where you have separate modules, plugins, or contexts within your application that each maintain their own set of type-provider links. By merging the registers, you create a centralized view of the providers, making it easier to manage and retrieve them.
This approach promotes modularity, encapsulation, and flexibility, allowing different parts of your application to define their own type-provider links independently. You can combine and merge registers as needed to build a comprehensive and flexible dependency injection system.
When merging registers, it's important to note that if multiple providers are registered for the same type, the selection of the provider will still be based on their priorities. The providers from the merged registers will retain their original priorities, and the provider with the highest priority will be chosen when retrieving an instance.
By merging registers and setting priorities for them, you can control the selection and precedence of providers, even when dealing with multiple registers and overlapping type-provider links.
The TypeRegister library allows you to assign a priority to registers, which determines the order in which the providers are retrieved for a given type. This can be useful when you have multiple providers registered for the same type and you want to control which provider is selected.
To set the priority of a register, you can use the RegisterPriority
enum provided by the TypeRegister library when
creating a new register instance. The enum values include LOW
, MEDIUM
, and HIGH
, representing different priority
levels.
Here's an example of creating registers with different priorities:
Register lowPriorityRegister = new Register(RegisterPriority.LOW);
Register mediumPriorityRegister = new Register(RegisterPriority.MEDIUM);
Register highPriorityRegister = new Register(RegisterPriority.HIGH);
In this example, we create three register instances with different priorities: lowPriorityRegister
with low priority,
mediumPriorityRegister
with medium priority, and highPriorityRegister
with high priority.
When merging registers with different priorities, the resulting merged register will retain the priority of the register with the highest priority among the merged registers.
Here's an example:
Register highPriorityRegister = new Register(RegisterPriority.HIGH);
Register mediumPriorityRegister = new Register(RegisterPriority.MEDIUM);
Register lowPriorityRegister = new Register(RegisterPriority.LOW);
// Register type-provider links with the high priority register
highPriorityRegister.register(new ObjectProvider<>(SomeType.class, new HighPriorityProvider()));
// Register type-provider links with the medium priority register
mediumPriorityRegister.register(new ObjectProvider<>(SomeType.class, new MediumPriorityProvider()));
// Register type-provider links with the low priority register
lowPriorityRegister.register(new ObjectProvider<>(SomeType.class, new LowPriorityProvider()));
// Merge registers with different priorities
Register mergedRegister = new Register(highPriorityRegister, mediumPriorityRegister, lowPriorityRegister);
// Retrieve the provider for SomeType from the merged register
TypeProvider<SomeType, ? extends SomeTypeProvider> provider = mergedRegister.getProviderByType(SomeType.class); // Returns HighPriorityProvider.class
In this example, we merge the registers highPriorityRegister
, mediumPriorityRegister
, and lowPriorityRegister
into
a new register called mergedRegister
. The resulting merged register will have the priority of the register with the
highest priority, which is HIGH
in this case. Therefore, when retrieving the provider for SomeType
from the merged
register, the provider from HighPriorityProvider
class will be returned, as it has the highest priority.
By utilizing register priorities and merged registers, you can control the selection of providers and ensure that the providers with the desired priority are retrieved for a given type.
Autoconstructing classes is a powerful feature provided by the ConstructorResolver class that allows you to automatically create an instance of a specified class using its constructors. This feature eliminates the need for manual instantiation and provides a convenient way to create objects with complex dependencies.
To control the autoconstruction process, ConstructorResolver
utilizes the ConstructionSettings class. This class holds
the settings that determine how the constructor parameters are resolved and which constructors are eligible for
autoconstruction.
The ConstructionSettings
class provides the following configuration options:
- Registers: The registers used to resolve the parameter types of the constructor. By default, the TypeRegister is used, but you can specify custom registers if needed.
- Identifiers: The identifier objects used by the IdentifierProvider or other key providers to choose the implementation. This allows for fine-grained control over which implementations are selected during autoconstruction.
- Need Annotation: Specifies whether the ConstructorResolving annotation is required on a constructor for it to be considered during the autoconstruction process.
By modifying these settings, you can customize the behavior of the autoconstruction process to fit your specific requirements.
The ConstructorResolver class provides a powerful and flexible way to initialize classes using the autoconstruction feature. This feature automates the process of creating an instance of a specified class by leveraging the class's constructors and resolving their parameter types. Let's explore how to use the initClass methods and see examples of their usage.
The ConstructorResolver class offers several initClass methods that handle the construction process and create instances of the specified class. These methods provide flexibility in configuring the autoconstruction process based on your specific needs. Let's explore these methods in more detail:
This method constructs the class using the default TypeRegister to resolve the types of the constructor parameters. It is suitable when you want to use the default register and do not need to modify the construction settings.
Example usage:
Example example = ConstructorResolver.initClass(Example.class);
If you require custom registers to resolve the parameter types of the constructor, you can use this method. It allows you to specify one or more registers to be used during the autoconstruction process. This is useful when you have specialized registers or want to override the default register.
Example usage:
Register customRegister = new CustomRegister();
Example example = ConstructorResolver.initClass(Example.class, customRegister);
Sometimes, you may want to enforce the presence of a specific annotation, such as ConstructorResolving
, on a
constructor to consider it for autoconstruction. This method enables you to specify whether the annotation is required.
If set to true, only constructors with the specified annotation will be considered during the autoconstruction process.
Example usage:
Example example = ConstructorResolver.initClass(Example.class, true);
This method combines the flexibility of specifying the need for an annotation and providing custom registers. You can control the presence of the annotation and use custom registers simultaneously.
Example usage:
Register customRegister = new CustomRegister();
Example example = ConstructorResolver.initClass(Example.class, true, customRegister);
When you require fine-grained control over the construction settings, this method allows you to apply custom
modifications using a Consumer
function. You can access the ConstructionSettings
object and adjust its properties
based on your specific requirements.
Example usage:
Example example = ConstructorResolver.initClass(Example.class, settings -> {
settings.setNeedAnnotation(true);
settings.setRegisters(customRegister);
});
If you prefer a more direct approach to setting the construction settings, you can pass an instance of
ConstructionSettings
directly to this method. This provides full control over the settings, including registers,
identifiers, and the need for an annotation.
Example usage:
ConstructionSettings settings = new ConstructionSettings();
settings.setNeedAnnotation(true);
settings.setRegisters(customRegister);
Example example = ConstructorResolver.initClass(Example.class, settings);
By choosing the appropriate initClass
method and adjusting the construction settings, you can tailor the
autoconstruction process to meet your specific needs and achieve the desired object initialization.
Consider the following example that demonstrates the usage of the initClass
methods:
public class AutoConstructionExample {
public static void main(String[] args) {
// Register custom types
TypeRegister.register(ResolvableType.class, ResolvableType::new);
TypeRegister.register(StaticType.class, new StaticType());
// Initialize Example class using default TypeRegister
Example example1 = ConstructorResolver.initClass(Example.class);
// Initialize Example class with custom registers
Register customRegister = new CustomRegister();
Example example2 = ConstructorResolver.initClass(Example.class, customRegister);
// Initialize Example class with annotation requirement and custom registers
Example example3 = ConstructorResolver.initClass(Example.class, true, customRegister);
// Initialize Example class using construction settings consumer
Example example4 = ConstructorResolver.initClass(Example.class, settings -> {
settings.setNeedAnnotation(true);
settings.setRegisters(customRegister);
});
// Initialize Example class using construction settings object
ConstructionSettings settings = new ConstructionSettings();
settings.setNeedAnnotation(true);
settings.setRegisters(customRegister);
Example example5 = ConstructorResolver.initClass(Example.class, settings);
// Perform operations on initialized examples
// ...
// Unregister and re-register types
TypeRegister.unregister(StaticType.class);
TypeRegister.register(StaticType.class, StaticType::new);
// Re-initialize Example class with updated types
Example example6 = ConstructorResolver.initClass(Example.class);
// ...
}
public static class Example {
@ConstructorResolving
public Example(ResolvableType type) {
// Perform operations with ResolvableType instance
}
}
public static class ResolvableType {
// Class implementation
}
public static class StaticType {
// Class implementation
}
}
In this example, we start by registering custom types ResolvableType
and StaticType
in the TypeRegister
. Then, we
demonstrate various ways to initialize the Example
class using different initClass
methods and construction
settings. Finally, we perform operations on the initialized examples and showcase how to unregister and re-register
types, followed by re-initializing the Example
class with the updated types.
The example showcases the versatility of the initClass
methods and how they can be used to automate the instantiation
process of classes with complex dependencies, while providing flexibility and customization options.
By leveraging the power of the ConstructorResolver
class and the autoconstruction feature, you can simplify object
creation and initialization, making your code more concise and maintainable.
Identifiers are objects that assist in identifying and selecting the appropriate implementation of a class during the autoconstruction process. They provide a way to specify preferences or criteria for choosing the desired implementation when multiple options are available.
To register identifiers in a Register
object, you can use the register
method. This method allows you to associate
identifiers with their corresponding implementations.
Here's an example of registering identifiers for the Car
class:
Register register = new Register();
register.register(
Car.class,
(IdentifiersBuilder<Car, Cars> settings) -> settings
.addIdentifier(Cars.ONE, new CarOne())
.addIdentifier(Cars.TWO, new CarTwo())
);
In this example, we create a Register
object and register identifiers for the Car
class. The IdentifiersBuilder
is
used to associate identifiers (Cars.ONE
and Cars.TWO
) with their corresponding implementations (CarOne
and CarTwo
).
The register
method requires the (IdentifiersBuilder<Car, Cars> settings
) parameter to configure the identifiers and
resolve settings for the specified class. By passing the settings
object to the lambda expression, you can add the
scoped identifier using the addIdentifier
method.
To retrieve a provider based on an identifier, you can use the getInitProvider
method of the Register
object. This
method allows you to specify the identifier and retrieve the initialized provider.
Here's an example:
Car car = register.getInitProvider(
Car.class,
(initProviderSettings) -> initProviderSettings.setIdentifiers(Cars.ONE)
);
In this example, the getInitProvider
method is used to retrieve the initialized provider for the Car
class. The
identifier Cars.ONE
is set using the setIdentifiers
method of the InitProviderSettings
object. This retrieves an
instance of the desired implementation (Car
) based on the provided identifier.
To utilize identifiers during the autoconstruction process, you can set the desired identifiers in the
ConstructionSettings
object. This helps the IdentifierProvider
or other key providers to determine the appropriate
implementation based on the provided identifiers.
Here's an example of using identifiers in the autoconstruction process:
ConstructionSettings settings = new ConstructionSettings();
settings.setIdentifiers(identifier1, identifier2);
In this example, we create a ConstructionSettings
object and set the identifiers identifier1
and identifier2
.
These identifiers will be used during the autoconstruction process to select the appropriate implementation.
Please note that the specific implementation and usage of identifiers may vary depending on your codebase and requirements. Identifiers can be any objects that assist in identifying a specific implementation, such as strings, enums, or custom classes.
Remember to properly configure and handle identifiers within your codebase to ensure the appropriate implementation is selected during the autoconstruction process.
To perform autoconstruction with identifiers, you can use the ConstructorResolver
class. After setting the identifiers
in the ConstructionSettings
object, you can construct the desired class by calling the initClass
method.
Here's an example:
CarExporter carExporter = ConstructorResolver.constructClass(CarExporter.class)
.setNeedAnnotation(false)
.setIdentifiers(Cars.ONE)
.setRegisters(register)
.initClass();
In this example, we use the ConstructorResolver
to construct an instance of the CarExporter
class. We disable the
requirement for the ConstructorResolving
annotation by setting the needAnnotation
flag to false. We specify the
identifier Cars.ONE
using the setIdentifiers
method and provide the Register
object using the setRegisters
method. Finally, we initialize the CarExporter
instance using the initClass
method, which automatically resolves the
appropriate implementation of Car
based on the specified identifier.
In addition to regular identifiers, the IdentifiersBuilder
class also provides a way to add scoped identifiers using
the
addScopedIdentifier
method. Scoped identifiers are associated with values that are supplied by
a SerializableSupplier
. The supplier is invoked each time the identifier is used, allowing for dynamic retrieval of
values.
Here's an example of adding a scoped identifier using a SerializableSupplier:
register.register(Car.class, (IdentifiersBuilder<Car, Cars> settings) -> {
settings.addScopedIdentifier(Cars.TWO, CarTwo::new);
});
In this example, we register a scoped identifier Cars.TWO
for the Car
class. The addScopedIdentifier
method is
used with a SerializableSupplier
lambda expression to supply the value for the scoped identifier. The lambda
expression CarTwo::new
creates a new instance of the CarTwo
class each time the scoped identifier is used.
The register
method requires the (IdentifiersBuilder<Car, Cars> settings
) parameter to configure the identifiers and
resolve settings for the specified class. By passing the settings
object to the lambda expression, you can add the
scoped identifier using the addScopedIdentifier
method.
Scoped identifiers are useful when you want to retrieve different instances or values for the same identifier at different points in time. This can be particularly handy when dealing with stateful or context-dependent objects.
Here's another example that demonstrates the usage of scoped identifiers with different supplier values:
register.register(DatabaseConnection.class, (IdentifiersBuilder<DatabaseConnection, String> settings) -> {
settings.addScopedIdentifier("local", LocalDatabaseConnection::new);
settings.addScopedIdentifier("remote", RemoteDatabaseConnection::new);
});
In this example, we register scoped identifiers "local"
and "remote"
for the DatabaseConnection
class. Depending
on the context or configuration, the appropriate connection instance will be retrieved when the corresponding scoped
identifier is used.
Scoped identifiers provide flexibility in retrieving values dynamically, allowing your code to adapt to changing requirements or conditions.
Remember to appropriately handle the scoping and lifecycle of objects associated with scoped identifiers, ensuring that they are created or retrieved when needed and properly managed to avoid resource leaks or inconsistencies.
Suppose you have different implementations of the Car
class, such as SedanCar
, SUVCar
, and SportsCar
. You can
register identifiers to select the desired implementation based on specific criteria:
register.register(Car.class, (IdentifiersBuilder<Car, String> settings) -> {
settings
.addIdentifier("sedan", new SedanCar())
.addIdentifier("suv", new SUVCar())
.addIdentifier("sports", new SportsCar());
});
In this example, identifiers "sedan", "suv", and "sports" are associated with the corresponding implementations. Later,
you can use these identifiers to retrieve the desired Car
instance.
Let's consider a scenario where you have two types of database connections: "local" and "remote". You can register identifiers to handle different connection types:
register.register(DatabaseConnection.class, (IdentifiersBuilder<DatabaseConnection, String> settings) -> {
settings
.addIdentifier("local", new LocalDatabaseConnection())
.addIdentifier("remote", new RemoteDatabaseConnection());
});
In this example, identifiers "local" and "remote" are associated with the corresponding DatabaseConnection
implementations. This allows you to choose the appropriate connection based on the identifier.
Suppose you want to support different logging levels for your application, such as "debug", "info", and "error". You can use scoped identifiers to dynamically retrieve the desired logging level:
register.register(Logger.class, (IdentifiersBuilder<Logger, LogLevel> settings) -> {
settings
.addScopedIdentifier(LogLevel.DEBUG, () -> new DebugLogger())
.addScopedIdentifier(LogLevel.INFO, () -> new InfoLogger())
.addScopedIdentifier(LogLevel.ERROR, () -> new ErrorLogger());
});
In this example, scoped identifiers are associated with different logging levels. Each time the logging level is required, the corresponding logger instance is dynamically retrieved based on the scoped identifier.
In addition to normal identifiers, you can utilize scoped identifiers to dynamically retrieve implementations based on specific conditions or context. Scoped identifiers are associated with suppliers that provide values for the identifiers at runtime.
Here's an example that demonstrates the usage of scoped identifiers in combination with normal identifiers:
register.register(DatabaseConnection.class, (IdentifiersBuilder<DatabaseConnection, String> settings) -> {
settings
.addIdentifier("local", new LocalDatabaseConnection())
.addScopedIdentifier("userScoped", () -> {
User user = getCurrentUser();
return new UserScopedDatabaseConnection(user);
});
});
In this example, we have registered two identifiers for the DatabaseConnection
class: a normal identifier "local"
and a scoped identifier "userScoped"
.
The "local"
identifier is associated with a LocalDatabaseConnection instance, which represents a general connection
used for local databases.
The "userScoped"
identifier is associated with a scoped supplier that dynamically retrieves a
UserScopedDatabaseConnection
instance based on the current user. The UserScopedDatabaseConnection
class represents a
connection that is scoped to the user and may have specific user-related configurations or permissions.
When using these identifiers, you can retrieve the appropriate connection instance based on the identifier:
// Retrieving a local database connection
DatabaseConnection localConnection = register.getInitProvider(DatabaseConnection.class, (initProviderSettings) -> {
initProviderSettings.setIdentifiers("local");
});
// Retrieving a user-scoped database connection
DatabaseConnection userScopedConnection = register.getInitProvider(DatabaseConnection.class, (initProviderSettings) -> {
initProviderSettings.setIdentifiers("userScoped");
});
In this example, we retrieve a local database connection using the "local"
identifier, which will give us the
LocalDatabaseConnection
instance. We also retrieve a user-scoped database connection using the "userScoped"
identifier. This will dynamically invoke the scoped supplier, which retrieves the UserScopedDatabaseConnection
instance based on the current user.
Scoped identifiers provide a powerful mechanism to adapt the behavior of your code based on dynamic conditions or contextual information. They allow for flexibility in selecting and configuring implementations at runtime.
Remember to handle the scoping and lifecycle of objects associated with scoped identifiers appropriately, ensuring they are created or retrieved when needed and properly managed to avoid resource leaks or inconsistencies.
These examples demonstrate different aspects of using identifiers, including registering identifiers, retrieving providers, using identifiers in autoconstruction, and autoconstructing with identifiers. You can adapt and customize these examples based on your specific class and identifier requirements in your codebase.
For the full examples go to the directory examples and find the examples.
- Dev-Bjorn