Skip to content

3week_AOS_tech_post_4

sey2 edited this page Jan 9, 2024 · 6 revisions

๋ ˆํŠธ๋กœํ•์„ ๋„์ž…ํ•˜๊ธฐ ์ „์— ์ด๋Ÿฐ ๊ถ๊ธˆ์ฆ์ด ์ƒ๊ฒผ๋‹ค.

์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฑธ๊นŒ? interface ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ๋Š” ๊ฑฐ ๊ฐ™์€๋ฐ ์ด๊ฒŒ ์ž๋ฐ”์—์„œ ๊ฐ€๋Šฅํ•œ๊ฐ€?

Retrofit์ด๋ž€?

  • REST API ํ†ต์‹ ์„ ์œ„ํ•ด ๊ตฌํ˜„๋œ ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.
  • AsyncTask ์—†์ด Background Thread์—์„œ ์‹คํ–‰๋˜๋ฉด callback์„ ํ†ตํ•ด Main Thread์—์„œ UI ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณต.
  • ๋‹ค๋ฅธ ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์กด์žฌํ•˜์ง€๋งŒ Retrofit์˜ ์„ฑ๋Šฅ๊ณผ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์ด ์‰ฝ๋‹ค.

AsyncTask๋ž€?

AsyncTask๋Š” Android์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ๊ฒŒ์‹œํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ๊ณผ๊ฑฐ์—๋Š” Android ๊ฐœ๋ฐœ์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์„ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ๊ฒƒ์ด์ง€๋งŒ, ์˜ค๋Š˜๋‚ ์—๋Š” Android์˜ WorkManager, Coroutines, RxJava์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋“ค์ด ๋” ๋งŽ์ด ์‚ฌ์šฉ๋˜๋ฉฐ, AsyncTask ์ž์ฒด๋Š” deprecated(์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š”) ์ƒํƒœ์ž…๋‹ˆ๋‹ค.


์‚ฌ์šฉ ๋ฐฉ๋ฒ•?

Step1

โ€ข @GET, @POST, @DELETE, @UPDATE ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์„ฑ

interface TrimApiService {
    @GET("/trims")
    suspend fun getTrimList(): CaArtResponse<List<Trim>>
}

Step2

Retrofit.Builder ๋งŒ๋“ค๊ธฐ

fun getRetrofit(): Retrofit = retrofit ?: Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()

Step3

DTO ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

data class Trim(
    val description: String,
    val exteriorColors: List<TrimItemColor>,
    val interiorColors: List<TrimItemColor>,
    val mainOptions: List<TrimMainItem>,
    val trimImage: String,
    val trimName: String,
    val trimPrice: Long,
    var isChecked: Boolean = false
)

์œ„์˜ ๊ณผ์ •์„ ํ†ตํ•ด API Interface์˜ ๋ฉ”์„œ๋“œ ๊ฐ์ฒด๋ฅผ ์„ ์–ธํ•˜๊ณ  ๋™๊ธฐ/๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•˜์—ฌ ์„œ๋ฒ„์—์„œ Response๋ฅผ ๋ฐ›์•„์˜จ ๋’ค ์ž‘์—…์„ ์ˆ˜ํ–‰

var service: TrimApiService = retrofit.create(TrimApiService::class.java)
service.getTrimList()

// ์ž๋ฐ”๋Š” ์ธํ„ฐํŽ˜์ด์ด์Šค๋ฅผ ๊ฐ์ฒดํ™”ํ•˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋ฐ ์—ฌ๊ธฐ์„œ๋Š” ์–ด๋–ป๊ฒŒ ๋งŒ๋“œ๋Š”๊ฑธ๊นŒ?

๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ API Interface๋งŒ ๊ตฌํ˜„ํ•ด์„œ request๋ฅผ ์ˆ˜ํ–‰ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ฑธ๊นŒ ?

์šฐ์„  retrofit.create() ๋‚ด๋ถ€ ์ฝ”๋“œ ๊ตฌํ˜„๋ถ€๋ฅผ ํ™•์ธํ•ด๋ณด์ž

@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
  public <T> T create(final Class<T> service) {

		// ์˜ฌ๋ฐ”๋ฅธ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์ธ์ง€ ๊ฒ€์‚ฌ
		// Class ํด๋ž˜์Šค์˜ ๋‚ด๋ถ€ ํ•จ์ˆ˜์˜ isInterface()๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ฒ€์‚ฌ
    // Retrofit์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Œ (๋‹ค์Œ ๊ธ€ ์ฐธ๊ณ )
    validateServiceInterface(service);
    return (T)
       //Proxy ํด๋ž˜์Šค์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ”„๋ก์‹œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
        Proxy.newProxyInstance(
            service.getClassLoader(),

	// ํ”„๋ก์‹œ๊ฐ€ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ์ธํ„ฐํŽ˜์ด์Šค ๋ชฉ๋ก์„ ๋ฐฐ์—ด๋กœ ์ œ๊ณต, ์—ฌ๊ธฐ์„œ๋Š” service๋งŒ์„ ๊ตฌํ˜„ํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ์Œ
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                  throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                  return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

์šฐ์„  ์ฝ”๋“œ ๋ถ€๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ”„๋ก์‹œ ํŒจํ„ด, ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์ฝ”๋“œ ๊ตฌํ˜„๋ถ€๋ฅผ ๋ณด๋ฉด ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

ํ”„๋ก์‹œ ํŒจํ„ด์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด


ํ”„๋ก์‹œ ํŒจํ„ด์ด๋ž€?

  • ํ”„๋ก์‹œ๋ž€ "๋Œ€๋ฆฌ์ธ"์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
  • ์ž๋ฐ”์—์„œ ํ”„๋ก์‹œ๋Š” ํƒ€๊ฒŸ์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ํƒ€๊นƒ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค

์˜ˆ์ œ

1) ๊ธฐ๋ณธ ์ธํ„ฐํŽ˜์ด์Šค ๋ฐ ๊ตฌํ˜„์ฒด

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋  ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ทธ ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค.
interface OnlineStore {
    fun purchaseProduct(productName: String): String
    fun refundProduct(productName: String): String
    fun checkInventory(productName: String): Int
}
class ActualStore : OnlineStore {
    override fun purchaseProduct(productName: String) = "$productName purchased!"
    override fun refundProduct(productName: String) = "$productName refunded!"
    override fun checkInventory(productName: String) = 100  // ์žฌ๊ณ ๋Š” 100๊ฐœ๋กœ ๊ฐ€์ •
}

2) ํ”„๋ก์‹œ ๊ตฌํ˜„์ฒด

  • ํ”„๋ก์‹œ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋˜, purchaseProduct() ํ•จ์ˆ˜์— ๋กœ๊ทธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค
class LoggedStore(private val store: OnlineStore) : OnlineStore {
    override fun purchaseProduct(productName: String): String {
        println("Attempting to purchase $productName")
        val result = store.purchaseProduct(productName)
        println("$productName purchase complete.")
        return result
    }

    override fun refundProduct(productName: String) 
				  = store.refundProduct(productName)

    override fun checkInventory(productName: String)
				 = store.checkInventory(productName)
}

4. ์žฅ์ 

  • ์‹ค์ œ ๊ฐ์ฒด์˜ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ , ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ํ™œ์šฉ

ํ”„๋ก์‹œ ํŒจํ„ด์€ ํƒ€๊ฒŸ์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ํƒ€๊ฒŸ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ์–ดํ•  ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.


ํ”„๋ก์‹œ ๊ตฌํ˜„์˜ ๋ฌธ์ œ์ 

  1. ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค.
interface OnlineStore {
    fun purchaseProduct(productName: String): String
    fun refundProduct(productName: String): String
    fun checkInventory(productName: String): Int
}

OnlineStore์˜ ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•œ ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด OnlineStore๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

์ฆ‰, OnlineStore์˜ ๋ชจ๋“  ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

๋งŒ์•ฝ purchaseProduct() ๋ฉ”์†Œ๋“œ๋งŒ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ํ•˜๋”๋ผ๋„ refundProduct()์™€ checkInventory()๋ฅผ ๋ชจ๋‘ overrideํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค

override ํ•จ์ˆ˜๊ฐ€ ํ•˜๋Š” ์ผ์ด๋ผ๊ณค ํƒ€๊ฒŸ์˜ ๋ฉ”์†Œ๋“œ์— ์œ„์ž„ํ•˜๋Š” ๊ฒƒ ๋ฟ์ธ๋ฐ๋„ ๋ง์ด๋‹ค.

  1. ํด๋ž˜์Šค์˜ ์ค‘๋ณต์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ

Java์˜ Reflection API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์œ„์˜ ๋ฌธ์ œ์ ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

์ž๋ฐ” ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค

image

์ฝ”ํ‹€๋ฆฐ ํ™•์žฅ ํ•จ์ˆ˜ ๊ธฐ๋Šฅ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉด

import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy

interface OnlineStore {
    fun purchaseProduct(productName: String): String
    fun refundProduct(productName: String): String
    fun checkInventory(productName: String): Int
}

class ActualStore : OnlineStore {
    override fun purchaseProduct(productName: String) = "$productName purchased!"
    override fun refundProduct(productName: String) = "$productName refunded!"
    override fun checkInventory(productName: String) = 100  
}

class StoreInvocationHandler(private val store: OnlineStore) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method, args: Array<Any?>): Any? {
        println("Method ${method.name} is being invoked with arguments: ${args.joinToString()}")
        val result = method.invoke(store, *args)
        println("Method ${method.name} invocation completed. Result: $result")
        return result
    }
}

inline fun <reified T> createProxy(store: OnlineStore): T {
    return Proxy.newProxyInstance(
        T::class.java.classLoader,
        arrayOf(T::class.java),
        StoreInvocationHandler(store)
    ) as T
}

fun main() {
    val storeProxy: OnlineStore = createProxy(ActualStore())
    println(storeProxy.purchaseProduct("Shoes"))
    println(storeProxy.checkInventory("Shoes"))
}

๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ์˜ ์žฅ์ 

  1. ํŠน์ • ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ๋ชจ๋‘ ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  2. ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ํŠน์ • ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋งŒ ๊ทธ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ๋กœ์ง์„ InvocationHandler ๋‚ด๋ถ€์— ๊ตฌํ˜„ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ

โ†’ Retrofit์€ Java์˜ Proxy ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. โ†’ ํ”„๋ก์‹œ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•œ ์ •๋ณด (๋ฉ”์†Œ๋“œ ์ด๋ฆ„, ๋งค๊ฐœ๋ณ€์ˆ˜, ์–ด๋…ธํ…Œ์ด์…˜ ๋“ฑ)๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
โ†’ ์ด๋ฅผ ํ†ตํ•ด Retrofit์€ ์–ด๋–ค HTTP ์š”์ฒญ์„ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š”์ง€ ์•Œ์•„๋ƒ…๋‹ˆ๋‹ค.

์ดํ•ด๊ฐ€ ์ž˜ ์•ˆ๊ฐ€ ..

์‰ฝ๊ฒŒ ๋น„์œ ๋ฅผ ํ•˜์ž๋ฉด ๋ ˆ์Šคํ† ๋ž‘์—์„œ ๋ฉ”๋‰ด๋ฅผ ์ฃผ๋ฌธํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋ ˆ์Šคํ† ๋ž‘์€ Retrofit, ๋Œ€์ƒ์€ ๊ฐœ๋ฐœ์ž, ๋ฉ”๋‰ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค(API), ๊ฐ ๋ฉ”๋‰ด ํ•ญ๋ชฉ์€ API์˜ ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.

  1. ๋ฉ”๋‰ด ์„ ํƒ (API ์ •์˜): ๋‹น์‹ ์€ ์Œ์‹์„ ์ฃผ๋ฌธํ•˜๊ธฐ ์ „์— ๋ฉ”๋‰ด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฉ”๋‰ด ํ•ญ๋ชฉ์€ ํŠน์ • ์Œ์‹์„ ์˜๋ฏธํ•˜๋ฉฐ, ์–ด๋–ป๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”์ง€์˜ ์ง€์‹œ์‚ฌํ•ญ(์–ด๋…ธํ…Œ์ด์…˜)์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ฃผ๋ฌธ ์ „๋‹ฌ (๋ฉ”์†Œ๋“œ ํ˜ธ์ถœ): ๋‹น์‹ ์€ ์›จ์ดํ„ฐ์—๊ฒŒ ๋ฉ”๋‰ด ํ•ญ๋ชฉ์„ ์ง€๋ชฉํ•˜๋ฉฐ ์ฃผ๋ฌธํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์›จ์ดํ„ฐ๋Š” ๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ์˜ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  3. ์›จ์ดํ„ฐ์˜ ์—ญํ•  (๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ): ์›จ์ดํ„ฐ๋Š” ๋‹น์‹ ์ด ์ง€์ •ํ•œ ๋ฉ”๋‰ด ํ•ญ๋ชฉ(๋ฉ”์†Œ๋“œ)๊ณผ ๊ทธ์— ๋Œ€ํ•œ ์ง€์‹œ์‚ฌํ•ญ(์–ด๋…ธํ…Œ์ด์…˜)์„ ํ™•์ธํ•˜๊ณ  ์ด๋ฅผ ์ฃผ๋ฐฉ์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ฃผ๋ฐฉ์—์„œ๋Š” ์ด ์ง€์‹œ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์Œ์‹์„ ์ค€๋น„ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด ๋น„์œ ์—์„œ ์›จ์ดํ„ฐ์˜ ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์€ ๋Ÿฐํƒ€์ž„์— ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์ฆ‰, ์›จ์ดํ„ฐ(๋‹ค์ด๋‚˜๋ฏน ํ”„๋ก์‹œ)๋Š” ์‚ฌ์ „์— ์–ด๋–ค ์ฃผ๋ฌธ์ด ์˜ฌ์ง€ ์•Œ ์ˆ˜ ์—†์œผ๋ฉฐ, ์‹ค์ œ ์ฃผ๋ฌธ์ด ๋“ค์–ด์™”์„ ๋•Œ ํ•ด๋‹น ์ฃผ๋ฌธ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋™์ ์œผ๋กœ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

Retrofit์—์„œ์˜ ์ ์šฉ

๊ฐœ๋ฐœ์ž๊ฐ€ Retrofit ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ์‹ค์ œ๋กœ ํ•ด๋‹น ๋ฉ”์†Œ๋“œ์˜ ๊ตฌํ˜„์ฒด๋Š” ์—†์Šต๋‹ˆ๋‹ค

๋Œ€์‹ , Java์˜ Proxy ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ํ”„๋ก์‹œ๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์ด ํ”„๋ก์‹œ๋Š” ๋ฉ”์†Œ๋“œ์˜ ์ด๋ฆ„, ๋งค๊ฐœ๋ณ€์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ์–ด๋…ธํ…Œ์ด์…˜ ๋“ฑ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•˜๊ณ  ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‹ค์ œ HTTP ์š”์ฒญ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, @GET("users/{user}/repos") ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด, ํ”„๋ก์‹œ๋Š” GET ์š”์ฒญ์„ /users/{user}/repos ๊ฒฝ๋กœ๋กœ ์ƒ์„ฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ, Retrofit์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ œ๊ณตํ•œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ์–ด๋…ธํ…Œ์ด์…˜ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋„คํŠธ์›Œํ‚น ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœ๊ทธ๋žจ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

image

ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ Service ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ  Service์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Call ๊ฐ์ฒด๋ฅผ ๋ฐ›๊ณ  Call ๊ฐ์ฒด์—๊ฒŒ ์ผ์„ ์‹œํ‚ค๋ฉด ๋œ๋‹ค.


Retrofit์€ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด์œ 

validateServiceInterface ๋ฉ”์†Œ๋“œ์˜ ๊ตฌํ˜„ ๋ถ€์ธ๋ฐ

์ œ๊ณต๋œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ทธ ์ƒ์œ„ ์ธํ„ฐํŽ˜์ด์Šค๋“ค์ด ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜(์ฆ‰, ์ œ๋„ค๋ฆญ)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ Retrofit์€ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

private void validateServiceInterface(Class<?> service) {
    if (!service.isInterface()) {
      throw new IllegalArgumentException("API declarations must be interfaces.");
    }

    Deque<Class<?>> check = new ArrayDeque<>(1);
    check.add(service);
    while (!check.isEmpty()) {
      Class<?> candidate = check.removeFirst();
      if (candidate.getTypeParameters().length != 0) {
        StringBuilder message =
            new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
        if (candidate != service) {
          message.append(" which is an interface of ").append(service.getName());
        }
        throw new IllegalArgumentException(message.toString());
      }
      Collections.addAll(check, candidate.getInterfaces());
    }
		
		// ์กฐ๊ฑด๋ฌธ์€ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค์— ์„ ์–ธ๋œ ๋ชจ๋“  ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฒ€์‚ฌํ•˜์—ฌ ๊ทธ๊ฒƒ๋“ค์ด ์œ ํšจํ•œ์ง€ ๋ฏธ๋ฆฌ ํ™•์ธํ•˜๋Š” ๋กœ์ง
   //  ์ด๊ฒƒ์€ ๊ฐœ๋ฐœ ์ค‘์— ๋ฌธ์ œ๋ฅผ ์กฐ๊ธฐ์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์คŒ
  // ์—ฌ๊ธฐ์„œ๋Š” Java 8์˜ default ๋ฉ”์†Œ๋“œ์™€ ์ •์  ๋ฉ”์†Œ๋“œ๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ๋ฉ”์†Œ๋“œ๋“ค์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
    if (validateEagerly) {
      Platform platform = Platform.get();
      for (Method method : service.getDeclaredMethods()) {
        if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
          loadServiceMethod(method);
        }
      }
    }
  }

์Œ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ Deque๋ฅผ ์‚ฌ์šฉํ•˜๋„ค? ์–ด ๋‹ค์‹œ ๋ณด๋‹ˆ ์ฝ”๋“œ ๋กœ์ง์ด BFS๋„ค?? ์™œ ์ธํ„ฐํŽ˜์ด์Šค ํ•˜๋‚˜๋งŒ ๋„˜๊ฒจ์ฃผ๋Š”๋ฐ Deque ์‚ฌ์ด์ฆˆ๋ฅผ ๊ตณ์ด 1๋กœ ์ดˆ๊ธฐํ™”ํ•ด์„œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋„ฃ์–ด์ฃผ์ง€?

๋ฌด์Šจ ์ด์œ ์ผ๊นŒ?

BFS (Breadth-First Search, ๋„ˆ๋น„ ์šฐ์„  ํƒ์ƒ‰) ์‚ฌ์šฉ ์ด์œ 

  • ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์—ฌ๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†๋ฐ›์„ ์ˆ˜ ์žˆ์Œ

  • ์ฃผ์–ด์ง„ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ทธ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ƒ์†๋ฐ›์€ ๋ชจ๋“  ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•ด BFS ๋ฐฉ์‹์ด ํšจ์œจ์ 

    ์˜ˆ์‹œ: ์ธํ„ฐํŽ˜์ด์Šค A๊ฐ€ B์™€ C๋ฅผ ์ƒ์†๋ฐ›๊ณ , B๊ฐ€ D์™€ E๋ฅผ ์ƒ์†๋ฐ›๋Š” ๊ฒฝ์šฐ, A -> B, C ๋ฐ B -> D, E ์ˆœ์œผ๋กœ ํƒ์ƒ‰

**Deque**์˜ ํ™œ์šฉ

  • Deque๋Š” BFS ๋ฐฉ์‹์˜ ํƒ์ƒ‰์„ ํšจ๊ณผ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

  • ์ฝ”๋“œ: Deque<Class<?>> check = new ArrayDeque<>(1);

    ์ด ์ฝ”๋“œ์—์„œ **ArrayDeque**์˜ ์ดˆ๊ธฐ ํฌ๊ธฐ๋ฅผ 1๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ์ตœ์ ํ™”์˜ ์ผ๋ถ€์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ํ•˜๋‚˜์˜ ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์žˆ์„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ์˜ ํฌ๊ธฐ๋ฅผ 1๋กœ ์‹œ์ž‘ํ•˜์—ฌ ํ•„์š”์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.

BFS ๊ตฌํ˜„

  • ์ฃผ์–ด์ง„ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ์— ์ถ”๊ฐ€: check.add(service);
  • ํ์— ์•„์ดํ…œ์ด ์žˆ์œผ๋ฉด, ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊บผ๋‚ด์–ด ๊ฒ€์‚ฌํ•˜๊ณ , ๊ทธ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์ƒ์†๋ฐ›์€ ๋‹ค๋ฅธ ์ธํ„ฐํŽ˜์ด์Šค๋“ค์„ ๋‹ค์‹œ ํ์— ์ถ”๊ฐ€: Collections.addAll(check, candidate.getInterfaces());

์™œ ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜(์ œ๋„ค๋ฆญ)๋ฅผ ๊ฐ€์ง„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์„๊นŒ?

Retrofit์€ API ์„ ์–ธ์„ ๊ฐ„๋‹จํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ œ๋„ค๋ฆญ์„ ๊ฐ€์ง„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ—ˆ์šฉํ•˜๋ฉด ๋ณต์žก์„ฑ์ด ์ฆ๊ฐ€ํ•˜๋ฉฐ,

๋‚ด๋ถ€์ ์œผ๋กœ ๋ฆฌํ”Œ๋ ‰์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์†Œ๋“œ์™€ ์–ด๋…ธํ…Œ์ด์…˜ ์ •๋ณด๋ฅผ ๋ถ„์„ํ•˜๋Š” ๊ณผ์ •์—์„œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๋‚˜ ๋ณต์žก์„ฑ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ๋„ค๋ฆญ์€ ์ปดํŒŒ์ผ ์‹œ์ ์— ํƒ€์ž…์˜ ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด๋ฏ€๋กœ, ๋Ÿฐํƒ€์ž„์—๋Š” ์ œ๋„ค๋ฆญ ์ •๋ณด๋Š” ๋Œ€๋ถ€๋ถ„ ์ง€์›Œ์ง‘๋‹ˆ๋‹ค. ์ด๊ฒƒ์„ ํƒ€์ž… ์†Œ๊ฑฐ(Type Erasure)๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋Ÿฐํƒ€์ž„์— ์ œ๋„ค๋ฆญ ์ •๋ณด๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ์€ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Retrofit์€ ๋Ÿฐํƒ€์ž„์— ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ทธ ๋ฉ”์†Œ๋“œ๋“ค์˜ ์ •๋ณด๋ฅผ ๋ถ„์„ํ•˜๋ฏ€๋กœ,

์ œ๋„ค๋ฆญ์„ ๊ฐ€์ง„ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด์œ ๊ฐ€ ์ด๋Ÿฌํ•œ ๋ณต์žก์„ฑ์„ ํ”ผํ•˜๋Š” ํ•œ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ถ”์ธก ํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค ๐Ÿค”

๋ฆฌํ”Œ๋ ‰์…˜ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

getGenericReturnType์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ๊ฐ€์ ธ์˜ด


์–ด๋…ธํ…Œ์ด์…˜์˜ ํŒŒ์‹ฑ ๊ตฌํ˜„๋ถ€

RequestFactory.java

private void parseMethodAnnotation(Annotation annotation) {
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } else if (annotation instanceof HEAD) {
        parseHttpMethodAndPath("HEAD", ((HEAD) annotation).value(), false);
      } else if (annotation instanceof PATCH) {
        parseHttpMethodAndPath("PATCH", ((PATCH) annotation).value(), true);
      } else if (annotation instanceof POST) {
        parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
      } else if (annotation instanceof PUT) {
        parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true);
      } else if (annotation instanceof OPTIONS) {
        parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false);
      } else if (annotation instanceof HTTP) {
        HTTP http = (HTTP) annotation;
        parseHttpMethodAndPath(http.method(), http.path(), http.hasBody());
      } else if (annotation instanceof retrofit2.http.Headers) {
        String[] headersToParse = ((retrofit2.http.Headers) annotation).value();
        if (headersToParse.length == 0) {
          throw methodError(method, "@Headers annotation is empty.");
        }
        headers = parseHeaders(headersToParse);
      } else if (annotation instanceof Multipart) {
        if (isFormEncoded) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isMultipart = true;
      } else if (annotation instanceof FormUrlEncoded) {
        if (isMultipart) {
          throw methodError(method, "Only one encoding annotation is allowed.");
        }
        isFormEncoded = true;
      }
    }

RequestFactory ํด๋ž˜์Šค ๋‚ด๋ถ€๋ฅผ ๋“ค์–ด๊ฐ€๋ฉด Path, Headers ๋“ฑ ํŒŒ์‹ฑํ•˜๋Š” ๊ตฌํ˜„๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์กฐ๊ธˆ ๋” ๊นŠ๊ฒŒ ํ™•์ธํ•ด๋ณด๊ณ  ์‹ถ์ง€๋งŒ ์•„์ง ํ• ๊ฒŒ ๋„ˆ๋ฌด๋‚˜๋„ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์—.. ์—ฌ๊ธฐ๊นŒ์ง€ ์•Œ์•„๋ณด์ž

๐Ÿ’ป Projects

๐Ÿค Rules

โ˜€๏ธ Meetings

๐ŸŒต Reviews

1์ฃผ์ฐจ
2์ฃผ์ฐจ
3์ฃผ์ฐจ
4์ฃผ์ฐจ

๐ŸŒˆ Scrums

1์ฃผ์ฐจ
2์ฃผ์ฐจ
3์ฃผ์ฐจ
4์ฃผ์ฐจ

๐Ÿ›  Tech Posts & Mini seminar

๐Ÿ’ช๐Ÿผ [BE]

๐Ÿ›ค [FE]

๐Ÿ›ธ [AOS]


Clone this wiki locally