룩업 메서드 주입

김종준·2023년 4월 3일
0

공부

목록 보기
6/12

룩업 메서드 주입

룩업 메서드 주입은, 싱글턴 빈이 비싱글턴 빈에 의존하는 상황같이 어떤 빈이 다른 라이프 사이클을 가진 빈에 의존할 때 발생하는 문제를 극복하기 위해 사용한다.

만약 비싱글턴 빈이 싱글턴 빈에 의존한다면 싱글턴빈은 비싱글턴 빈을 싱글턴으로 만들어 버린다.

또는 상황에 따라 싱글턴 빈은 자신에게 필요한 비싱글톤 빈 인스턴스를 매번 새로 생성해 얻고 싶을 수 있다.

사실 프로토타입과 같이 매번 다른 객체가 필요한 경우에는 getter에서 new 키워드를 통해 신규 인스턴스를 리턴해 주면 된다고 한다.

하지만 이는 IoC가 아니다.

즉, 룩업 메서드 주입은 빈 중 프로토타입과 같은 비싱글턴 타입의 빈을 스프링 컨테이너를 통해 DI하기 위해 사용하는 것이라고 할 수 있다.

코드를 통해서 비싱글턴 타입을 DI 하는 경우new 키워드를 사용하는 경우에 대해 조금 더 알아보자.

우선 A라는 스프링 컨테이너에 등록된 객체를 멤버로 가지고 있는 B라는 프로토타입 스코프의 객체가 있다고 생각해보자.

B를 스프링 컨테이너에 빈으로 등록한다면 아래와 같이 A를 DI 받을 수 있을 것이다.

@Component
public class B {
  private A a;

  public B(A a) {
    this.a = a;
  }
}

B가 빈으로 등록하지 않는다면 스프링 컨테이너를 통해 DI를 받지 못한다고 하면 아래와 같이 A를 DI 해야 할 것이다.

A a = (A) applicationContext.getBean("a");
B b = new B(a);

비싱글톤 객체에 대해 알아보았으니 이제 룩업 메서드를 사용하여 이 비싱글톤 객체를 DI 해보자.

이때 룩업 메서드를 사용할 때 주의사항이 아래와 같이 존재한다고 한다.

우선 아래 주의사항을 지키며 룩업 메서드를 사용해보자.

룩업 메서드 사용 시 주의사항 (필수 x, 권장 o)

  • 룩업 대상 메서 드는 인터페이스에 선언하여 상속받을 것.
  • 룩업 대상 메서드를 추상 메서드로 만들 것.
  • 성능저하 발생하기 때문에 가급적 안 쓰는 게 좋음.
// prototype 빈
@Component
@Scope("prototype")
public class ProtoPasswordBean {

    private double password;

    public ProtoPasswordBean() {
        this.password = Math.random();
    }

    public double getPassword() {
        return password;
    }
}

// 인터페이스에 선언하여 상속 받을 룩업 대상 메서드
public interface Locker {

    ProtoPasswordBean getProtoPasswordBean();

    double getPassword();
}

// 추상 메서드로 만든 룩업 대상 메서드
public abstract class AbstractLocker implements Locker {

    public abstract ProtoPasswordBean getProtoPasswordBean();

    @Override
    public double getPassword() {
        return getProtoPasswordBean().getPassword();
    }
}

// AbstractLocker 구현체
@Component
public class PersonalLocker extends AbstractLocker {

    @Override
    @Lookup(value = "protoPasswordBean")
    public ProtoPasswordBean getProtoPasswordBean() {
        return null;
    }
}

위의 코드에서 의문이 들 수 있는 코드는 아마 getProtoPasswordBean() 일 것이다.

어떻게 null을 반환하는데 ProtoPasswordBean을 반환할 수 있을까?

이는 메서드 룩업을 사용하면 스프링이 CGLIB를 사용해 메서드를 동적으로 재정의하는 하의 클래스를 생성하기 때문이다.

테스트 코드를 디버깅하여 확인해보자.

우선 태스트 코드는 아래와 같다.

PersonalLocker locker = (PersonalLocker) applicationContext.getBean("personalLocker");
double password1 = locker.getPassword(); // break point
System.out.println("password1 = " + password1);
double password2 = locker.getPassword();
System.out.println("password2 = " + password2);

위에 주석을 통해 표시한 곳에 break point를 생성하고 디버깅하여보니 실제로 CGLIB를 통해 생성된 객체임을 확인할 수 있었다.

스크린샷 2023-04-03 오후 1 17 57

그리고 password1,2의 결과 역시 아래와 같이 다른 것을 확인할 수 있었다.

password1 = 0.27778628906967284
password2 = 0.8117544517046742

password는 ProtoPasswordBean가 생성될 때 결정되는 값이기에 password가 다르다는 것은 ProtoPasswordBean가 다르다는 것을 뜻할 수 있을 것이다.

이제 성능을 확인해 보자.

성능 확인을 위해 테스트 코드는 아래와 같이 수정하였다.

SingletonLocker singletonLocker = (SingletonLocker) applicationContext.getBean("singletonLocker");
StopWatch singletonStopWatch = new StopWatch();
singletonStopWatch.start("singleton Demo");

for (int i = 0; i < 100000; i++) {
    singletonLocker.getPassword();
}

singletonStopWatch.stop();
System.out.println("singletonLocker#getPassword 100000 gets took " + singletonStopWatch.getTotalTimeMillis() + "ms");

PersonalLocker protoLocker = (PersonalLocker) applicationContext.getBean("personalLocker");
StopWatch protoStopWatch = new StopWatch();
protoStopWatch.start("proto Demo");

for (int i = 0; i < 100000; i++) {
    protoLocker.getPassword();
}

protoStopWatch.stop();
System.out.println("protoLocker#getPassword 100000 gets took " + protoStopWatch.getTotalTimeMillis() + "ms");

그 결과 아래와 같은 결과가 나왔다.

singletonLocker#getPassword 100000 gets took 1ms
protoLocker#getPassword 100000 gets took 414ms

성능 측정 결과를 보니 "성능저하 발생하기 때문에 가급적 안쓰는게 좋음"의 이유를 알 것 같다.

0개의 댓글