Created
April 3, 2018 10:23
-
-
Save Ekalips/e7d71bf5d41de9c703de692b0ca09c01 to your computer and use it in GitHub Desktop.
Simple DI/ServiceLocator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.ekalips.p2pchat.di.modules; | |
public interface IDependencyProviderModule<T> { | |
Class<T> getProvidedClass(); | |
T provideDependency(); | |
/** | |
* Specifies is provided dependency should be re-created each time or cached can be used | |
* | |
* @return true if dependency should be re-created each time, false if cached version can be used | |
*/ | |
boolean isUnique(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.ekalips.p2pchat.di; | |
import android.annotation.SuppressLint; | |
import android.content.Context; | |
import android.support.annotation.NonNull; | |
import com.ekalips.p2pchat.di.modules.IDependencyProviderModule; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationTargetException; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
public class ServiceLocator { | |
private static final Object lock = new Object(); | |
@SuppressLint("StaticFieldLeak") | |
private static ServiceLocator instance; | |
private final Context context; | |
private final Map<Class, IDependencyProviderModule> readyProviderModules = new HashMap<>(); | |
private final Map<Class, Class<? extends IDependencyProviderModule>> rawProviderModules = new HashMap<>(); | |
private final Set<Class> providedWithDefault = new HashSet<>(); | |
private final Map<Class, Object> cachedDependencies = new HashMap<>(); | |
private ServiceLocator(Context context) { | |
this.context = context.getApplicationContext(); | |
} | |
public static ServiceLocator init(Context context) { | |
synchronized (lock) { | |
if (instance == null) { | |
instance = new ServiceLocator(context); | |
} | |
return instance; | |
} | |
} | |
public ServiceLocator registerProviderModule(@NonNull IDependencyProviderModule module) { | |
readyProviderModules.put(module.getProvidedClass(), module); | |
return this; | |
} | |
public <T> ServiceLocator registerRawProviderModule(@NonNull Class<T> dependencyClass, @NonNull Class<? extends IDependencyProviderModule<T>> moduleClass) { | |
rawProviderModules.put(dependencyClass, moduleClass); | |
return this; | |
} | |
/** | |
* It'll register dependency with default module (provided dependency will be used as singleton | |
*/ | |
public <T> ServiceLocator registerWithDefaultModule(@NonNull Class<T> dependencyClass) { | |
providedWithDefault.add(dependencyClass); | |
return this; | |
} | |
@SuppressWarnings("unchecked") | |
public <T> T get(Class<T> classInstance) { | |
if (classInstance.equals(Context.class)) { | |
return (T) context; | |
} | |
if (cachedDependencies.containsKey(classInstance)) { | |
return (T) cachedDependencies.get(classInstance); | |
} | |
if (readyProviderModules.containsKey(classInstance)) { | |
return cacheIfNeeded((IDependencyProviderModule<T>) readyProviderModules.get(classInstance)); | |
} | |
if (rawProviderModules.containsKey(classInstance)) { | |
Class<IDependencyProviderModule<T>> rawClass = (Class<IDependencyProviderModule<T>>) rawProviderModules.get(classInstance); | |
try { | |
return cacheIfNeeded(instantiateClass(rawClass)); | |
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { | |
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance, e); | |
} | |
} | |
if (providedWithDefault.contains(classInstance)) { | |
try { | |
T item = instantiateClass(classInstance); | |
cachedDependencies.put(classInstance, item); | |
return item; | |
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { | |
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance, e); | |
} | |
} | |
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance); | |
} | |
@SuppressWarnings("unchecked") | |
private <R> R instantiateClass(Class<R> clazz) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException { | |
Constructor<R>[] constructors = (Constructor<R>[]) clazz.getConstructors(); | |
if (constructors.length == 0) { | |
throw new IllegalArgumentException("Raw dependency has no available constructors: " + clazz); | |
} | |
Constructor<R> rawConstructor = constructors[0]; | |
Object[] params = new Object[rawConstructor.getParameterTypes().length]; | |
Class<?>[] parameterTypes = rawConstructor.getParameterTypes(); | |
for (int i = 0; i < parameterTypes.length; i++) { | |
Class<?> constructorParam = parameterTypes[i]; | |
if (constructorParam == clazz) { | |
throw new IllegalArgumentException("Found dependency cycle. Class constructor cannot require same class object: " + clazz); | |
} | |
params[i] = get(constructorParam); | |
} | |
return rawConstructor.newInstance(params); | |
} | |
private <T> T cacheIfNeeded(IDependencyProviderModule<T> module) { | |
T dep = module.provideDependency(); | |
if (!module.isUnique()) { | |
cachedDependencies.put(module.getProvidedClass(), dep); | |
} | |
return dep; | |
} | |
public static ServiceLocator getInstance() { | |
if (instance == null) { | |
throw new IllegalStateException("You should initialize service locator first"); | |
} else { | |
return instance; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment