주요글: 도커 시작하기
반응형

최근 수행중인 프로젝트에서 JPA의 구현체로 하이버네이트를 사용하고 있다. 매핑 설정에 따라 다르겠지만, 이런 JPA 구현체들은 Lazy 로딩을 구현하기 위해 프록시 객체를 사용하고 있는데, 이와 관련해서 한 가지 주의해야 할 것이 있다. 그것은 바로 this를 리턴하는 메서드와 관련된 것이다.


현재 수행중인 프로젝트에는 상속을 매핑한 코드가 있다. 예를 들면 다음과 같은 구조이다.


public class Content { ... 최상위 }

public class VideoContent extends Content { .... }

public class MovieContent extends VideoContent { ... }

public class BookContent extends Content { ... }


그리고, 특정 클래스과 다음과 같이 Content를 *-to-One의 형식으로 레퍼런스하고 있다.


public class Order {

    private Content content;

    public Content getContent() {

        return content;

    }

}


Order 객체를 구한 뒤에 Content 객체에 접근해야 하는 경우가 있으며, 특히 Content 타입이 아닌 실제 타입에 접근해야 할 때가 있다. 이런 경우에 아주 단순하게 생각하면 다음과 같이 Content에 자기 자신을 리턴하는 코드를 넣는 방법을 생각해 볼 수 있을 것이다.


public Content {

    public Content self() {

        return this; // 자기 자신을 리턴

    }

}


Order o = getOrder(xxx);

Content proxy = o.getContent(); // Content는 프록시

Content real = proxy.self(); // self() 메서드는 자기 자신을 리턴하므로 프록시가 아닌 실제 대상 객체???

if (real instanceof MovieContent) { // 하지만, real은 프록시 객체이므로 항상 false

    ...

}


하지만, 위와 같은 방법은 통하지 않는다. 그 이유는 프록시 객체는 대상 객체가 대상 객체 자신을 리턴하는 경우 프록시 객체를 리턴하도록 만들어지기 때문이다. 

  • proxy.self() 메서드는 대상 객체의 self() 메서드를 호출한다.
  • 대상 객체의 self() 메서드는 자기 자신을 리턴한다.
  • proxy.self() 메서드는 대상 객체가 리턴한 객체가 대상 객체와 동일한지 확인한다.
  • 동일하다면 프록시 객체 자신을 리턴한다.
그렇다면, 왜 프록시 객체가 대상 객체를 리턴하지 않고 자기 자신을 리턴하는 것일까? 그 이유는 간단하다. 대상 객체에 직접 접근하게 될 경우 ORM이 제공하는 Dirty Checking이나 Lazy Loading 등이 적용되지 않기 때문이다.

이런 이유로 실제 대상 객체에 접근해서 뭔가를 하고 싶다면, 대상 객체가 자기 자신을 리턴하도록 하지 말고, Double dispatch를 사용해야 한다. Double dispatch란 다음과 같은 것이다.
  • A 객체의 a() 메서드는 B 인터페이스를 파라미터로 갖는다.
  • B 인터페이스의 b() 메서드는 A를 파라미터로 갖는다.
  • A 객체의 a() 메서드는 파라미터로 전달받은 B 인스턴스의 b() 메서드를 호출할 때 자기 자신을 전달한다.
위 내용을 코드로 다시 살펴보면 다음과 같다.
public interface B {
    public void b(A a);
}

public class A {
    public void a(B b) {
        b.b(this);
    }
}

A aobj = new A();
B bobj = new BImpl();
aobj.a(bobj); // 내부적으로 bobj.b(a)가 호출됨

위 코드를 보면 aobj.a() 메서드를 호출하면, a() 메서드는 내부적으로 다시 파라미터로 전달받은 bobj의 b() 메서드를 호출한다. 이렇게 두 객체간의 호출이 두 번 이루어지기 때문에 이를 Double Dispatch라고 표현한다.

그럼, 요놈을 앞서 Content에 적용해 보자. 다음과 같이 변경해 주면 된다.

public interface Accessor {
    public void access(Content c);
}

public class Content {
    public void access(Accessor access) {
        accessor.access(this);
    }
}

위 코드에서 Accessor의 구현체는 Content의 프록시 객체가 아닌 실제 객체에 접근할 수 있게 된다.

Order o = getOrder(xxx);

Content proxy = o.getContent(); // Content는 프록시

proxy.access(new Accessor() {
    public void access(Content c) {
        // c는 proxy의 대상 객체인 실제 객체
        if (c instanceof MovieContent) {
            ...
        }
    }
});

Content 및 Content의 자식 클래스들은 상속 관계에 있으므로 사용하므로 Visitor 패턴을 사용하면 instanceof 연산자를 사용하지 않고 실제 타입으로 바로 접근할 수도 있어 더 보기 좋은 코드를 만들어 낼 수 있을 것이다.



+ Recent posts