-
Notifications
You must be signed in to change notification settings - Fork 1
java_provide
To use objects from everywhere, you need to use it in DI. In order for DI to provide objects, their generation must first be described.
As described above, all objects must be declared in a module in order to provide them.
@Module
public abstract class SevenPlanetModule {
public Earth earth() {
return new Earth();
}
}In many cases, a simple constructor call can be ignored. The annotation processor will independently find and call the constructor for the object, taking into account the parameters used.
@Module
public abstract class SevenPlanetModule {
public abstract SolarSystem solarSystem(Sun sun, Earth earth);
}Note that the order of the arguments is important to the object providing function. If the argument set does not match the constructor, the library will throw an error.
If method arguments can specify dependencies for creating an object. Dependencies must be specified via arguments so that they are also cached in DI. Dependent objects must also be provided by the DI component.
You can also provide parameterized types, but you cannot provide primitives and their object implementations.
@Module
public abstract class SevenPlanetModule {
public abstract Wire<Usb, MiniUsb> usb_miniusb();
}DI has tools for differentiating the provision of objects of the same type among themselves.
The easiest way to do this is either by using the existing @Named qualifier or by declaring your own.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyQualifier {
}You can also declare qualifiers with additional properties and attributes. This will allow you to combine qualifiers into groups.
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ThreadQualifier {
ThreadType type() default ThreadType.Main;
enum ThreadType {
Main,
Default,
IO
}
}Next, it is enough to indicate the qualification when presenting the object.
@Module
public class ThreadsModule {
@ThreadQualifier(type = ThreadQualifier.ThreadType.Main)
@Provide(cache = Provide.CacheType.Strong)
public ThreadPoolExecutor mainThreadPoolExecutor() {
return new ThreadPoolExecutor(0, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(), runnable -> new Thread(runnable));
}
}You can now choose which dependencies to use for your component using an argument qualifier.
@Module
public abstract class PresentersModule {
@MyQualifier
public abstract FeaturePresenter provideFeaturePresenter(
@ThreadQualifier(type = ThreadQualifier.ThreadType.Main) ThreadPoolExecutor executor
);
}Qualifiers can also be used for injection fields.
In some cases, there may not be enough qualifiers to separate objects, for example, the number of instances of the same class may be undefined. For such cases, stone offers a provider ID tool. Identifiers allow you to generate objects of the same type several times, as well as cache them using a unique key - identifier.
@Module
public abstract class PresentersModule {
public abstract FeaturePresenter provideFeaturePresenter(
ScreenId screenId,
LoginId loginId
);
}The code above, for example, allows you to reuse FeaturePresenter with the same values
screenId and loginId and distinguish between different instances based on these keys.
To use classifiers, you must declare them in the @Component annotation.
@Component(identifiers = {ScreenId.class, LoginId.class})
public interface AppComponent {
PresentersModule presentersModule();
}In this case, the DI component independently determines what is a dependency and what is an identifier when resolving dependencies.
Qualifiers cannot be provided.
Additionally, they must override hashCode and equals to be used as a key.
public class LoginId {
public String accountName = "";
public LoginId(String tag) {
this.accountName = tag;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoginId screenId = (LoginId) o;
return Objects.equals(accountName, screenId.accountName);
}
@Override
public int hashCode() {
return Objects.hash(accountName);
}
}Once you have defined all the identifiers for your DI component, you can inject all your objects with an identifier. Moreover, the number of identifiers during injection is not limited in any way; for each matching dependency, its own identifier will be transferred.
@Component(identifiers = {ScreenId.class, LoginId.class})
public interface AppComponent {
void inject(FeatureScreen screen, LoginId loginId, ScreenId screenId);
}Identifiers can also be explicitly specified and used in methods for providing objects with dependencies.
@Component(identifiers = {ScreenId.class, LoginId.class})
public interface AppComponent {
FeaturePresenter featurePresenter(LoginId loginId, ScreenId screenId);
}Be careful, if there is no identifier during injection, it will simply be replaced with null.
You cannot use multiple identifiers of the same type in one injection location.
Each identifier must have its own type.
When providing with dependencies, the identifier is also passed to the dependent classes to provide a unique instance of it.
Some objects cannot be created in the application; they may be provided by external libraries. They can be linked in the Library and provided in the project via DI.
You can declare a binding object using a providing method in a module using the @BindInstance annotation.
@Module
public interface SunSystemModule {
@BindInstance(cache = BindInstance.CacheType.Weak)
Sun sun(Sun sun);
}An annotation can indicate the following caching methods
- Weak - caching weak links
- Soft - caching soft link
- Strong - caching strong link
For ease of use, you can also expose the object representation in the component directly.
@Component
public interface GodWorkspaceComponent {
SunSystemModule sunSystem();
Sun sun();
}Then you can use DI.sunSystem().sun(null) instead of the full entry
shorthand DI.sun().
Binding is initialized by calling DI.sunSystem().sun( new Sun() ).
However, calling an identical method with a null parameter does not nullify the binding, but only provides the value.
The next entry will be working
GodWorkspaceComponent DI = Stone.createComponent(GodWorkspaceComponent.class);
Sun sun = new Sun();
DI.sunSystem().sun(sun);
Sun sunFromDI = DI.sunSystem().sun(null);
System.out.println("sunFromDI " + sunFromDI);For a more abbreviated notation, you can declare the binding without declaring it in the module. It is enough to make the same entry completely in the component.
@Component
public interface SunSystemComponent {
@BindInstance
Sun sun(Sun sun);
}The rule for using the null parameter is also preserved here
SunSystemComponent DI = Stone.createComponent(SunSystemComponent.class);
Sun sun = new Sun();
DI.sun(sun);
Sun sunFromDI = DI.sun(null);
System.out.println("sunFromDI " + sunFromDI);Resetting object binding can be done through cache contol.
As of version 1.0.3, binding does not support either qualifiers or identifiers.
In many cases, constantly turning to DI to get some object becomes inconvenient, especially if there are several such objects in the class. A good way in this case would be to use injecting objects from the component. By declaring an injection method in a component without any annotations.
@Component
public interface AppComponent {
void inject(FeatureScreen screen);
}You can inject variables and methods inside this class. The methods are called once, when the injection is called.
public class FeatureScreen {
@Inject
public FeaturePresenter presenter;
@Inject
void init(FeaturePresenter presenter) {
}
void start() {
AppComponent DI = Stone.createComponent(AppComponent.class);
DI.inject(this);
}
}You can also use identifiers as arguments for injection. This allows you to build your own unique chain of objects for each new screen according to the identifier key.
@Component(identifiers = {ScreenId.class, LoginId.class})
public interface AppComponent {
void inject(FeatureScreen featureScreen, StoneLifeCycleOwner owner, LoginId loginId, ScreenId screenId);
}In the example above, we injected objects into featureScreen, taking into account the identifiers loginId and screenId, and also set up protection against the destruction of objects when re-creating featureScreen using the life cycle control parameter lifeCycleOwner.