英文:
Creating an indigenous dependency injection framework
问题
我正在创建一个分析库。其中有数十个类,我需要通过依赖注入来使用,以避免编写样板代码。我本来可以使用 Dagger,但我想保持库独立于第三方 SDK。有人能指导我编写我们自己的本土依赖注入框架,它能高效地处理代码中所有类的声明和按需实例化吗?
我尝试使用 HashMap 作为类注册表来存储我所有类的实例,但这意味着我们需要一次性声明所有类,其中很多在运行时将不被使用。
英文:
I am creating an analytics library. I have dozens of classes in it which i need to use via dependency injection to avoid writing boilerplate code. I would have used dagger but i want to keep the library independent of third party SDKs . Can someone guide me through writing our own indigenous dependency injection framework which would efficiently take care of all of class declarations and instatiations on demand in the code?
I tried using Hashmap for class registry for storing instances of all of my classes, but that would mean we would declare all of the classes at once where many of them would be left unused in runtime
答案1
得分: 1
以下是翻译好的部分:
你在使用哈希映射作为类注册表时方向正确,只是需要传递消费者而不是实例作为值,当调用时它们会进行实例化。
这里是实现这一目标的步骤:
第一目标:存储类的注册表:
步骤1:创建一个类,其中你将保持哈希映射的注册表。让我们将这个类命名为DiConstructor
,在这个类中声明哈希映射如下:
private final Map<Class<T>, ClassFactory> holder = new HashMap<>();
这里的ClassFactory
是用于创建类型T
的新实例的工厂的接口。
步骤2:编写一个函数,用于为所有的类创建注册表。
public <T> void registerFactory(Class<T> clazz, ClassFactory<T> factory) {
holder.put(clazz, factory);
}
步骤3:创建一个声明类,在这个类中为所有的类添加注册表,并使用这个方法:
registerFactory(MyClassNameToDi.class, new ClassFactory<MyClassNameToDi>() {
@NonNull
@Override
public MyClassNameToDi get(DiConstructor di) {
return new MyClassNameToDi();
}
});
注意:这段代码用于创建和声明注册表,应该在初始化库时完成,以便在运行时后期能够解析这些类。
第二目标:从注册表中检索值
有多种方法可以做到这一点,因为现在你已经将类工厂存储为值。
选项1:当你拥有DiConstructor
引用时,你可以编写一个getter方法,当其引用作为参数传递时,它将返回类实例。它看起来会像这样:
public <T> T get(Class<T> clazz) {
ClassFactory classFactory = holder.get(clazz);
Object instance = null;
if (classFactory != null) {
instance = classFactory.get(this);
}
return clazz.cast(instance);
}
注意:你需要尝试捕获这段代码,因为在哈希映射中找不到记录时可能会抛出异常。现在,使用这个方法将非常简单,只需使用DiConstructor
实例,将实例名为di
。
di.get(MyClassNameToDi.class);
选项2:使用注解,就像Dagger一样。
在源代码中使用@Inject
注解声明依赖。
@Inject
private YourClassToDi classInstance;
现在创建一个类,该类将接收具有@Inject
注解的所有字段,以解决依赖关系。让我们将其命名为MyAndroidInjector
。在这个类中编写一个inject
函数,它将包含活动上下文作为参数。
让我们看看在这个inject
方法中需要做什么。
收集所有带有@Inject
注解的字段。你需要使用Java反射概念来检索这些字段。代码看起来会像这样:
Set<Field> result = new HashSet<>();
Class<?> c = classs;
String canonicalName = c.getCanonicalName();
while (c != null && canonicalName != null && canonicalName.startsWith("com.yourpackagename")) {
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(Named.class)) {
result.add(field);
}
}
c = c.getSuperclass();
canonicalName = c.getCanonicalName();
}
现在,选取每个字段实例,检查其注解,并使用field.set()
解决其依赖关系,不要忘记将setAccessible
设置为true
。在这里,你需要在getDiConstructor()
方法中获取你的Di
注册表类实例并解决依赖关系。
for (Field field : result) {
String named = null;
if (field.isAnnotationPresent(Named.class)) {
named = field.getAnnotation(Named.class).value();
}
field.setAccessible(true);
field.set(targetObject, getDiConstructor().get(named, field.getType()));
}
最后,在包含@Inject
注解的活动的onCreate
中调用这个方法将完成任务。
MyAndroidInjector.inject(this);
希望这对你有帮助。如果你需要更多信息,请随时告诉我。
英文:
You are going in correct direction when using a hash-map for using it as a registry of classes. Just that instead of instance as values you need to pass consumers which would instantiate when called.
Here are steps to implement the same :
Objective 1st : Storing the classes in registry :
Step 1: Create a class where you would keep your Hash-map of registries. Let's name this class as DiConstructor where you would declare the hash-map like one below :
private final Map<Class<T>, ClassFactory> holder = new HashMap<>();
here ClassFactory being Interface to Factories of new instances of type T
Step 2: Write a function that would create registry for all of your classes.
public <T> void registerFactory(Class<T> clazz, ClassFactory<T> factory) {
holder.put(clazz, factory);
}
Step 3: Create a declaration class where you would add registries for all of your classes and use this method :
registerFactory(MyClassNameToDi.class,
new ClassFactory<MyClassNameToDi>() {
@NonNull
@Override
public MyClassNameToDi get(DiConstructor di) {
return new MyClassNameToDi();
}
});
Note : This code to create and declare registries should be done while initialising your library so that the classes will be resolvable on run time later.
Objective 2nd: Retrieving values from registry
There are multiple ways to do so, now that you have the class factory stored as value.
Option 1: When you have DiConstructor reference with you, you can write a getter method which would return the class instance when its reference is passed as argument. It would look something like this :
public <T> T get(Class<T> clazz) {
ClassFactory classFactory = holder.get(clazz);
Object instance = null;
if (classFactory != null) {
instance = classFactory.get(this);
}
return clazz.cast(instance);
}
Note: You will need to try-catch this code as it might throw exception when record is not found in HashMap
Now using this would be pretty much straightforward with DiConstructor instance . Keeping instance name di here.
di.get(MyClassNameToDi.class);
Option 2: Using annotations, just like dagger
Example of usage in source code :
Declare the dependency with inject annotation .
@Inject
private YourClassToDi classInstance;
Now create a class which would receive all fields with inject annotations from this class to resolve the dependencies. Let's name it as MyAndroidInjector. Inside this class write a function inject which would contain activity context as argument.
Let's see what we need to do in this inject method .
Collect all fields annotated with inject. You will need to use some of the Java reflection concepts here to retrieve the fields here. The code would look something like one below :
Set<Field> result = new HashSet<>();
Class<?> c = classs;
String canonicalName = c.getCanonicalName();
while (c != null && canonicalName != null && canonicalName.startsWith("com.yourpackagename")) {
for (Field field : c.getDeclaredFields())
{
if (field.isAnnotationPresent(Named.class))
{
result.add(field);
}
}
c = c.getSuperclass();canonicalName = c.getCanonicalName();
}
Now pick every field instance , check its annotation and resolve its dependency using field.set() and do not forget to apply setAccessible as true. Here you would need to get your Di registry class instance in getDiConstructor() method and resolve the dependencies.
for (Field field : result){
String named = null;
if (field.isAnnotationPresent(Named.class)) {
named = field.getAnnotation(Named.class).value();
}
field.setAccessible(true);
field.set(targetObject, getDiConstructor().get(named,
field.getType()));
}
At last calling this in your onCreate of activity containing the inject annotations would do the job.
MyAndroidInjector.inject(this);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论