반응형
Lazy
혹은 Provider
를 사용하면 의존성 주입의 시점을 늦추거나 항상 새로운 객체를 반환 받을 수 있도록 할 수있습니다.
Lazy Injection vs Provider Injection
Cpu 객체는 Provider
를 통해 주입해주고, Ram 객체는 Lazy
를 통해 주입해주도록 해주겠습니다.
ComputerComponent.kt
@Component(modules = [ComputerModule::class])
interface ComputerComponent {
fun inject(computer: Computer)
@Component.Builder
interface Builder {
fun computerModule(computerModule: ComputerModule): Builder
fun application(@BindsInstance context: Context): Builder
fun build(): ComputerComponent
}
}
ComputerModule.kt
@Module
class ComputerModule {
@Provides
fun provideCpu(): Cpu {
println("create Cpu")
return M1Cpu()
}
@Provides
fun provideRam(): Ram {
println("create Ram")
return Ram4G()
}
}
Computer.kt
class Computer {
@Inject
lateinit var cpuProvider: Provider<Cpu>
@Inject
lateinit var ramLazy: Lazy<Ram>
fun printProvider() {
println(cpuProvider.get())
println(cpuProvider.get())
println(cpuProvider.get())
}
fun printLazy() {
println(ramLazy.get())
println(ramLazy.get())
println(ramLazy.get())
}
}
Cpu.kt
interface Cpu {
val name: String
val company: String
}
class I7Cpu : Cpu {
override val name: String
get() = Name
override val company: String
get() = "Intel"
companion object {
const val Name = "I7 CPU"
}
}
class M1Cpu : Cpu {
override val name: String
get() = "M1 Chip Cpu"
override val company: String
get() = "Apple"
companion object {
const val Name = "M1 Chip Cpu"
}
}
Ram.kt
interface Ram {
val name: String
val company: String
}
class ram4G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Samsung"
companion object {
const val Name ="SSD 4G RAM"
}
}
class ram8G : Ram {
override val name: String
get() = Name
override val company: String
get() = "Hynix"
companion object {
const val Name ="8G RAM"
}
}
Test Code
@Config(manifest = Config.NONE, sdk = [28])
@RunWith(RobolectricTestRunner::class)
class ComputerUnitTest {
private lateinit var computerComponent: ComputerComponent
private lateinit var context: Context
@Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().targetContext
computerComponent = DaggerComputerComponent.builder().application(context).build() // Component Builder
}
@Test
fun testComputerProvider() {
val computer = Computer()
computerComponent.inject(computer)
computer.printProvider()
Assert.assertEquals(M1Cpu.Name, computer.cpuProvider.get().name) // 항상 새로운 객체
}
@Test
fun testComputerLazy() {
val computer = Computer()
computerComponent.inject(computer)
computer.printLazy()
Assert.assertEquals(ram4G.Name, computer.ramLazy.get().name) // get메소드가 호출되기 전까지는 객체를 생성하지 않는다.
}
}
Provider<T>로 Injection을 하면 항상 다른 객체를 반환받는 결과를 확인하실 수 있고,
Lazy<T>로 Injection을 하면 항상 같은 객체를 반환받는 결과를 확인하실 수 있습니다.
왜 이러한 결과가 나타나는지 내부적으로 코드를 들여다 보겠습니다.
Provider<T>를 통한 의존성 주입 객체 생성코드
@SuppressWarnings({
"unchecked",
"rawtypes"
})
public final class ComputerModule_ProvideCpuFactory implements Factory<Cpu> {
private final ComputerModule module;
public ComputerModule_ProvideCpuFactory(ComputerModule module) {
this.module = module;
}
@Override
public Cpu get() {
return provideCpu(module);
}
public static ComputerModule_ProvideCpuFactory create(ComputerModule module) {
return new ComputerModule_ProvideCpuFactory(module);
}
public static Cpu provideCpu(ComputerModule instance) {
return Preconditions.checkNotNullFromProvides(instance.provideCpu());
}
}
Provder<T>를 사용하면 모듈로부터 객체를 생성하여 리턴합니다.
Lazy<T>를 통한 의존성 주입 객체 생성코드
/**
* A {@link Provider} of {@link Lazy} instances that each delegate to a given {@link Provider}.
*/
public final class ProviderOfLazy<T> implements Provider<Lazy<T>> {
private final Provider<T> provider;
private ProviderOfLazy(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
/**
* Returns a new instance of {@link Lazy Lazy<T>}, which calls {@link Provider#get()} at
* most once on the {@link Provider} held by this object.
*/
@Override
public Lazy<T> get() {
return DoubleCheck.lazy(provider);
}
/**
* Creates a new {@link Provider Provider<Lazy<T>>} that decorates the given
* {@link Provider}.
*
* @see #get()
*/
public static <T> Provider<Lazy<T>> create(Provider<T> provider) {
return new ProviderOfLazy<T>(checkNotNull(provider));
}
}
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private DoubleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the provider
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
instance = reentrantCheck(instance, result);
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
}
Effective Java 71 - Double-check idiom for lazy initialization of instance fields.
Lazy<T>의 get()함수를 호출하였을 경우 내부적으로 provider를 싱글톤으로 만들어 항상 같은 객체를 반환하도록 합니다.
반응형