CS/λ””μžμΈ νŒ¨ν„΄

[λ””μžμΈ νŒ¨ν„΄] ν”„λ‘μ‹œ(Proxy) νŒ¨ν„΄

벼리01 2024. 1. 13. 00:58

ν”„λ‘μ‹œ(Proxy)λŠ” μ˜μ–΄λ‘œ λŒ€λ¦¬μžλ₯Ό λœ»ν•œλ‹€. ν”„λ‘μ‹œ νŒ¨ν„΄μ€ ꡬ쑰 νŒ¨ν„΄ 쀑 ν•˜λ‚˜λ‘œ ν΄λΌμ΄μ–ΈνŠΈκ°€ 직접 μ„œλ²„μ— μš”μ²­ν•˜λŠ” 것이 μ•„λ‹ˆλΌ μ–΄λ–€ λŒ€λ¦¬μž 객체λ₯Ό 톡해 κ°„μ ‘μ μœΌλ‘œ μ„œλ²„μ— μš”μ²­ν•˜λŠ” νŒ¨ν„΄μ„ λ§ν•œλ‹€. μ—¬κΈ°μ„œ ν΄λΌμ΄μ–ΈνŠΈλŠ” μ˜λ’°μΈμ„ μ˜λ―Έν•˜λ©° μ„œλ²„λŠ” μ„œλΉ„μŠ€λ‚˜ μƒν’ˆμ„ μ œκ³΅ν•˜λŠ” μ‚¬λžŒ λ˜λŠ” 물건을 λœ»ν•œλ‹€. 즉 μ›Ήμ—μ„œλŠ” ν΄λΌμ΄μ–ΈνŠΈλŠ” μ‚¬μš©μžκ°€ μ ‘μ†ν•œ μ›Ή λΈŒλΌμš°μ €κ°€ 되고, μ„œλ²„λŠ” ν΄λΌμ΄μ–ΈνŠΈμ˜ μš”μ²­μ„ μ²˜λ¦¬ν•œλ‹€. 

 

μ‹€ν–‰ μ‹œ 의쑴 관계

 

 

μΈν„°νŽ˜μ΄μŠ€ 기반 ν”„λ‘μ‹œ 상속 관계
ꡬ체 클래슀 기반 ν”„λ‘μ‹œ 상속 관계

 

 

 

1. 가상 ν”„λ‘μ‹œ(Virtual Proxy) - μ‹€μ œ 객체의 크기가 κ±°λŒ€ν•΄μ„œ 생성과 접근에 λΉ„μš©μ΄ 많이 λ“œλŠ” 경우 ν•„μš”ν•œ μ‹œμ μ—λ§Œ 객체λ₯Ό μƒμ„±ν•˜λ„λ‘ μ§€μ—°λœ μ΄ˆκΈ°ν™”λ₯Ό μˆ˜ν–‰ν•  수 μžˆλ‹€.

2. 보호 ν”„λ‘μ‹œ(Protection Proxy) - μ‹€μ œ 객체에 λŒ€ν•œ 접근을 ν†΅μ œν•˜μ—¬ νŠΉμ • κΆŒν•œμ„ κ°€μ§„ μ‚¬μš©μž(ν΄λΌμ΄μ–ΈνŠΈ)만이 μ ‘κ·Όν•˜λ„λ‘ μ œμ–΄ν•  수 μžˆλ‹€.

 

 

μ„œλ²„μ™€ ν”„λ‘μ‹œλŠ” 같은 객체λ₯Ό μ‚¬μš©ν•΄μ•Όν•œλ‹€. ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ—κ²Œ μš”μ²­ν•œ 것인지 ν”„λ‘μ‹œμ— μš”μ²­ν•œ 것인지 λͺ°λΌμ•Ό(λŒ€μ²΄ κ°€λŠ₯ν•΄μ•Όλ§Œ) ν”„λ‘μ‹œ 객체라고 ν•  수 μžˆλ‹€. `Client -> Server`μ—μ„œ `Server -> Proxy`둜 의쑴 관계가 λ°”λ€Œλ”λΌλ„, ν΄λΌμ΄μ–ΈνŠΈλŠ” κ·Έ μ½”λ“œλ₯Ό μ „ν˜€ λ³€κ²½ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. 심지어 λ³€κ²½ 사싀쑰차 λͺ¨λ₯Ό 수 μžˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈλŠ” μ„œλ²„ μΈν„°νŽ˜μ΄μŠ€λ§Œμ„ μ˜μ‘΄ν•˜κ³  있으며 μ„œλ²„μ™€ ν”„λ‘μ‹œκ°€ μ„œλ²„ μΈν„°νŽ˜μ΄μŠ€μ— μ˜μ‘΄ν•œλ‹€. 이 λ‚΄μš©μ΄ DI(Dependency Injection: μ˜μ‘΄μ„± μ£Όμž…)의 핡심이닀.

 

 

 

 

πŸ“Œ μ ‘κ·Ό μ œμ–΄λ₯Ό μœ„ν•΄ ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•œ μ˜ˆμ‹œ

ν΄λΌμ΄μ–ΈνŠΈκ°€ μš”μ²­ μ‹œ μœ μ € μœ ν˜• 정보λ₯Ό μ œκ³΅ν•˜κ³  이 μœ ν˜•μ€ String κ°’μœΌλ‘œ "user" λ˜λŠ” "admin"으둜 μ£Όμ–΄μ§„λ‹€κ³  κ°€μ •ν–ˆμ„ λ•Œ, "user"라면 DB 접근을 μ œν•œν•˜κ³  "admin"이라면 DB 접근을 ν—ˆμš©ν•˜λŠ” μš”κ΅¬μ‚¬ν•­μ΄ μžˆλ‹€λ©΄ μ½”λ“œλŠ” λ‹€μŒκ³Ό 같이 μž‘μ„±λ  수 μžˆλ‹€.

 

public class ClientExample {
    public static void main(String[] args) {
        // ν΄λΌμ΄μ–ΈνŠΈλ‘œλΆ€ν„° 받은 정보에 따라 μœ μ € μœ ν˜• μ„€μ •
        // μ—¬κΈ°μ„œλŠ” κ·Έλƒ₯ "admin"으둜 μ„ μ–Έν–ˆμ§€λ§Œ, 웹이라면 request.getParameter("level") λ“±μœΌλ‘œ λ°›μ•„μ˜¬ 수 있음
        String username = "admin"; // λ˜λŠ” "user"

        // νŒ©ν† λ¦¬λ₯Ό 톡해 μ μ ˆν•œ ν”„λ‘μ‹œ 생성
        UserServiceProxyFactory proxyFactory;
        if ("admin".equals(username)) {
            proxyFactory = new AdminUserServiceProxyFactory();
        } else {
            // "user"인 경우 DB 접근이 λΆˆκ°€λŠ₯ν•œ ν”„λ‘μ‹œ 생성
            proxyFactory = new RegularUserServiceProxyFactory();
        }

        // μƒμ„±λœ ν”„λ‘μ‹œλ‘œ UserControllerλ₯Ό 생성
        UserController userController = new UserController(proxyFactory.createServiceProxy());

        // ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­ 처리
        userController.handleRequest();
    }
}

 

 

좔상 νŒ©ν† λ¦¬ νŒ¨ν„΄, 그리고 νŒ©ν† λ¦¬ λ©”μ„œλ“œ νŒ¨ν„΄ λ˜ν•œ μ μš©λ˜μ–΄ μžˆλŠ” 것을 λ³Ό 수 μžˆλ‹€.

각 객체의 상속 κ³„μΈ΅λ„λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

 

 

좔상 νŒ©ν† λ¦¬ νŒ¨ν„΄κ³Ό νŒ©ν† λ¦¬ λ©”μ„œλ“œ νŒ¨ν„΄ λ˜ν•œ 적용된 것을 확인할 수 μžˆλ‹€.`UserServiceProxy`λŠ” 내뢀에 `UserServiceImpl`을 κ°€μ§€κ³  있으며 ν•„μš”ν•  λ•Œλ§ˆλ‹€ μ‹€μ œ 객체에 μ ‘κ·Όν•œλ‹€. λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄κ³Ό μœ μ‚¬ν•˜μ§€λ§Œ μ ‘κ·Ό μ œμ–΄λ₯Ό λͺ©μ μœΌλ‘œ ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•œλ‹€λŠ” μ μ—μ„œ ꡬ뢄될 수 μžˆλ‹€. 

 

 

 

 

 

 

πŸ“Œ  캐싱

ν•œ 번의 쑰회 이후에 λ³€ν•˜μ§€ μ•ŠλŠ” λ°μ΄ν„°μ˜ 경우, 처음 μ‘°νšŒν–ˆμ„ λ•Œ κ·Έ 데이터λ₯Ό λ³΄κ΄€ν•΄λ‘μ—ˆλ‹€κ°€ μž¬μš”μ²­ μ‹œ λ³΄κ΄€λœ 데이터λ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ λΆˆν•„μš”ν•œ μžμ› λ‚­λΉ„λ₯Ό κ°μ†Œμ‹œν‚¬ 수 μžˆλ‹€. 이λ₯Ό 캐싱이라고 ν•œλ‹€. 

 

 

import java.util.HashMap;
import java.util.Map;

// μ‹€μ œ 객체의 μΈν„°νŽ˜μ΄μŠ€
interface Component {
    String operation();
}

// μ‹€μ œ 객체
class RealComponent implements Component {
    public String operation() {
        System.out.println("μ‹€μ œ 객체가 λ©”μ„œλ“œ μ‹€ν–‰");
        return "result";
    }
}

 

// ν”„λ‘μ‹œ
class CachingProxy implements Component {
    private RealComponent realComponent;
    private Map<String, String> cache;

    public CachingProxy() {
        this.cache = new HashMap<>();
    }

    public String operation() {
        if (realComponent == null) {
            realComponent = new RealComponent();
        }
        
        // if μΊμ‹œ ν™•μΈν•˜κ³  데이터가 있으면 μΊμ‹œμ— μ €μž₯ν•΄λ‘μ—ˆλ˜ 데이터 λ°˜ν™˜
        // else μΊμ‹œμ— 데이터가 μ—†μœΌλ©΄ μ‹€μ œ 객체 호좜
        if (cache.containsKey("result") {
            System.out.println("ν”„λ‘μ‹œκ°€ 캐싱 데이터 λ°˜ν™˜");
            return cache.get("result");
        } else {
            // μ‹€μ œ 객체 호좜
            String result = realComponent.operation();
            // κ²°κ³Όλ₯Ό μΊμ‹œμ— μ €μž₯
            cache.put("result", result);
            return result;
        }
    }
}

 

// ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œ
public class Client {
    public static void main(String[] args) {
        // ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλŠ” ν”„λ‘μ‹œλ₯Ό 톡해 μ‹€μ œ 객체에 κ°„μ ‘μ μœΌλ‘œ μ ‘κ·Ό
        CachingProxy proxy = new CachingProxy();

        System.out.println(proxy.operation()); // 처음 ν˜ΈμΆœμ‹œ μ‹€μ œ κ°μ²΄μ—μ„œ μ—°μ‚° μˆ˜ν–‰
        System.out.println(proxy.operation()); // 두 번째 ν˜ΈμΆœλΆ€ν„°λŠ” ν”„λ‘μ‹œμ—μ„œ μΊμ‹œ 데이터 λ°˜ν™˜
    }
}

// 좜λ ₯

// μ‹€μ œ 객체가 λ©”μ„œλ“œ μ‹€ν–‰
// result
// ν”„λ‘μ‹œκ°€ 캐싱 데이터 λ°˜ν™˜
// result

 

 

ν΄λΌμ΄μ–ΈνŠΈκ°€ 처음 μš”μ²­ν–ˆμ„ λ•Œλ§Œ μ‹€μ œ 객체에 μ ‘κ·Όν•˜κ³ , κ·Έ μ΄ν›„λ‘œλŠ” μΊμ‹œμ— μ €μž₯된 데이터λ₯Ό μ‚¬μš©ν•œλ‹€. μ‹€ν–‰ μ‹œκ°„μ„ 크게 쀄여 μ„±λŠ₯ ν–₯상에 도움이 λœλ‹€.

 

캐싱을 μ‚¬μš©ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ λ‹€μŒμ˜ μ£Όμ˜μ μ„ κ³ λ €ν•œλ‹€. 

1. 캐싱 λ°μ΄ν„°λŠ” 항상 μ‹€μ œ 데이터와 μΌμΉ˜ν•΄μ•Ό ν•œλ‹€. 데이터가 κ°±μ‹ λœλ‹€λ©΄ 캐싱 데이터 λ˜ν•œ κ°±μ‹ λ˜μ–΄μ•Ό ν•œλ‹€. μ‹€μ œ 데이터와 캐싱 λ°μ΄ν„°μ˜ 일관성이 μœ μ§€λ˜μ§€ μ•ŠμœΌλ©΄ 신뒰성이 깨질 수 μžˆλ‹€. 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ μ μ ˆν•œ 캐싱 만료 기간을 μ„€μ •ν•œλ‹€.

2. 캐싱 λ°μ΄ν„°λŠ” λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ μ£Όμ˜ν•œλ‹€. λ„ˆλ¬΄ λ§Žμ€ 캐싱 데이터λ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ 였히렀 μ„±λŠ₯ μ €ν•˜λ₯Ό μΌμœΌν‚¬ 수 μžˆλ‹€.

3. λ©€ν‹° μ“°λ ˆλ“œ ν™˜κ²½μ—μ„œ 동기화 λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆμœΌλ―€λ‘œ μ£Όμ˜ν•œλ‹€.

4. 캐싱 λ°μ΄ν„°μ˜ λ³΄μ•ˆμ— μ£Όμ˜ν•œλ‹€.

 

 

 

 

 

 

πŸ“Œ  Spring MVC λ‚΄ ν”„λ‘μ‹œ νŒ¨ν„΄ 적용

μ‹€ν–‰ μ‹œ 의쑴 관계

 

 

MVC νŒ¨ν„΄μ—μ„œ`Service`의 κ΅¬ν˜„μ²΄κ°€ `Repository`의 κ΅¬ν˜„μ²΄λ₯Ό 직접 μ°Έμ‘°ν•˜λŠ” λŒ€μ‹  `ServiceImpl`을 μ°Έμ‘°ν•˜λŠ”  `ServiceProxy`κ°€ `RepositoryImpl`을 μ°Έμ‘°ν•˜λŠ” `RepositoryProxy`λ₯Ό ν˜ΈμΆœν•˜λ„λ‘ μ„€κ³„ν•œλ‹€λ©΄ Service 계측이 데이터 μ ‘κ·Ό 계측과 μ™„μ „νžˆ λΆ„λ¦¬λ˜λ―€λ‘œ λ‘˜μ€ λŠμŠ¨ν•˜κ²Œ 결합될 수 μžˆλ‹€. 데이터 μ ‘κ·Ό 계측이 변경됐을 λ•Œ (μƒˆλ‘œμš΄ 데이터 μ†ŒμŠ€λ‚˜ DB μ ‘κ·Ό 방식이 λ„μž…λ˜λ”λΌλ„) μ‹€μ œ 객체인 `RepositoryImpl`을 μ°Έμ‘°ν•˜λŠ” `RepositoryProxy`의 μ½”λ“œλ§Œ λ³€κ²½ν•˜λ©΄ λœλ‹€. μ΄λŠ” 객체 μ§€ν–₯ 5λŒ€ 원칙 쀑 ν•˜λ‚˜μΈ OCP(개방 폐쇄 원칙: Open-closed Principle)에 λΆ€ν•©ν•œλ‹€. 

 

 

1. ν”„λ‘μ‹œμ™€ μ‹€μ œ κ°μ²΄λŠ” ν•˜λ‚˜μ˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œλ‹€.

   1-1. μ‹€μ œ 객체의 μΈν„°νŽ˜μ΄μŠ€κ°€ μ‘΄μž¬ν•˜μ§€ μ•Šμ„ 경우 ν”„λ‘μ‹œ 객체가 μ‹€μ œ 객체λ₯Ό μƒμ†ν•œλ‹€. ν΄λΌμ΄μ–ΈνŠΈ μƒμ„±μžμ˜ 맀개 λ³€μˆ˜ νƒ€μž…μ΄ μ‹€μ œ 객체라면 λ‹€ν˜•μ„±μ˜ μ μš©μ„ λ°›μ•„ μ°Έμ‘°ν•˜λŠ” 객체가 μ‹€μ œ 객체 λ˜λŠ” ν”„λ‘μ‹œ 객체둜 λ³€κ²½λ˜λ”λΌλ„ ν΄λΌμ΄μ–ΈνŠΈμ˜ μ½”λ“œλ₯Ό μˆ˜μ •ν•  ν•„μš”κ°€ μ—†λ‹€. 단, μΈν„°νŽ˜μ΄μŠ€ 기반의 ν”„λ‘μ‹œκ°€ μƒμ†μ˜ μ œμ•½μ΄ μ—†κΈ° λ•Œλ¬Έμ— 더 μœ λ¦¬ν•  수 μžˆλ‹€.

2. ν”„λ‘μ‹œλŠ” 내뢀에 μ‹€μ œ 객체λ₯Ό κ°€μ§€κ³  μžˆλ‹€.

3. μ‹€μ œ 객체 λŒ€μ‹  ν”„λ‘μ‹œ 객체λ₯Ό μŠ€ν”„λ§ 빈으둜 λ“±λ‘ν•˜λŠ” 경우 μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ—λŠ” ν”„λ‘μ‹œ 객체만 λ“±λ‘λœλ‹€. ν”„λ‘μ‹œ 객체가 λ‚΄λΆ€μ—μ„œ μ‹€μ œ 객체λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆλ‹€. 

ν”„λ‘μ‹œ 객체: μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆ, JVM Heap λ©”λͺ¨λ¦¬μ— 올라감

μ‹€μ œ 객체: JVM Heap λ©”λͺ¨λ¦¬μ—λ§Œ 올라감(μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆκ°€ κ΄€λ¦¬ν•˜μ§€ μ•ŠλŠ”λ‹€.)

 

 

 

πŸ“Œ ν”„λ‘μ‹œ νŒ¨ν„΄ vs λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄

ν”„λ‘μ‹œ νŒ¨ν„΄κ³Ό λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄μ€ 맀우 μœ μ‚¬ν•˜λ‹€. λ‘˜μ€ ν”„λ‘μ‹œμ˜ 생성 λͺ©μ μœΌλ‘œ ꡬ뢄될 수 μžˆλ‹€. ν”„λ‘μ‹œ νŒ¨ν„΄μ€ μ ‘κ·Ό μ œμ–΄κ°€ λͺ©μ μ΄λ©° λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄μ€ μƒˆλ‘œμš΄ κΈ°λŠ₯의 μΆ”κ°€κ°€ λͺ©μ μ΄λ‹€. ν”„λ‘μ‹œ νŒ¨ν„΄κ³Ό λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄ λ‘˜ λ‹€ ν”„λ‘μ‹œ, 즉 λŒ€λ¦¬μžλ₯Ό μ‚¬μš©ν•˜μ§€λ§Œ μ˜λ„κ°€ λ‹€λ₯΄λ‹€λŠ” 것이 μ€‘μš”ν•˜λ‹€. ν”„λ‘μ‹œ νŒ¨ν„΄μ€ ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λŠ” λ””μžμΈ νŒ¨ν„΄ 쀑 ν•˜λ‚˜λ‘œ 이름이 ν”„λ‘μ‹œ νŒ¨ν„΄μΌ 뿐 λ°˜λ“œμ‹œ ν”„λ‘μ‹œ νŒ¨ν„΄λ§Œμ΄ ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λŠ” 것은 μ•„λ‹ˆλ‹€. 

 

ν”„λ‘μ‹œ νŒ¨ν„΄: μ ‘κ·Ό μ œμ–΄

λ°μ½”λ ˆμ΄ν„° νŒ¨ν„΄: λΆ€κ°€ κΈ°λŠ₯ μΆ”κ°€

 

 

✨ μž₯점

1. μ‹€μ œ 객체가 κ±°λŒ€ν•œ 경우 ν•„μš”ν•œ μ‹œμ μ—λ§Œ 객체λ₯Ό μƒμ„±ν•˜κΈ° λ•Œλ¬Έμ— λΉ„μš©μ„ μ•„λ‚„ 수 μžˆλ‹€. = μ§€μ—°λœ μ΄ˆκΈ°ν™”(Lazy initialization)

2. ν”„λ‘μ‹œ κ°μ²΄μ—μ„œ 캐싱을 μ‚¬μš©ν•  경우 μš”μ²­λ§ˆλ‹€ κ±°λŒ€ν•œ 객체에 μ ‘κ·Όν•˜μ§€ μ•Šκ³ λ„ 데이터λ₯Ό λ°˜ν™˜ν•  수 μžˆμ–΄ λΉ λ₯Έ 응닡이 κ°€λŠ₯ν•˜λ‹€.

3. ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλ₯Ό 크게 λ³€κ²½ν•˜μ§€ μ•Šκ³  μ ‘κ·Ό μ œμ–΄ μš”κ΅¬ 사항을 λ°˜μ˜ν•  수 μžˆλ‹€. 

 

 

πŸ‘Ž 단점

1. ν”„λ‘μ‹œ 객체의 μΆ”κ°€λ‘œ 클래슀의 양이 λŠ˜μ–΄λ‚˜λ©΄ μ½”λ“œκ°€ λ³΅μž‘ν•΄μ§ˆ 수 μžˆλ‹€.

2. ν”„λ‘μ‹œ 객체가 좔가적인 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κΈ° λ•Œλ¬Έμ— μ„±λŠ₯ μ €ν•˜κ°€ λ°œμƒν•  수 μžˆλ‹€.

3. μ§€μ—°λœ μ΄ˆκΈ°ν™”μ˜ μž₯점은 κ³§ μ‹€μ œ 객체의 생성 μ‹œμ μ΄ 뢈λͺ…ν™•ν•΄μ§„λ‹€λŠ” 단점을 μ˜λ―Έν•œλ‹€. μ–΄λ–€ κ²½μš°μ—λŠ” μ΄ˆκΈ°ν™” μ‹œμ μ΄ λΆ„λͺ…ν•΄μ•Ό ν•˜λŠ” 상황이 μ‘΄μž¬ν•˜λ―€λ‘œ 잘 κ³ λ €ν•˜μ—¬ νŒ¨ν„΄μ„ μ μš©ν•΄μ•Όν•œλ‹€. 

 

 

[λ””μžμΈ νŒ¨ν„΄] λ°μ½”λ ˆμ΄ν„°(Decorator) νŒ¨ν„΄