Spring Method LookUp Injection

Scope "prototype"의 문제

만약 Singletone으로 생성된 bean이  SomeBean을 참조하는 경우에 문제가 발생한다. prototype으로 지정하였지만, SomeBean역시 Singletone으로 관리가 되는 현상이 발생한다., 이는 싱글톤 객체가 생성되어 소멸하지 않는 상황에서 has-a로 패턴으로 이미 생성된 비싱글톤 객체를 계속 가지고 있기 때문이다.

조금 쉽게 이야기 하면, 아래와 같은 경우이다.

1
2
3
4
5
6
7
    <bean id = "someBean" class = "com.SomeBean" scope="prototype"/>
    
    <bean id = "singleBean" class = "com.SingleTone">
        <property name="mySomeBean">
            <ref bean="someBean"/>
        </property>
    </bean>
cs

Spring은 SinggleTone 객체를 리턴할 때 최초에 한 번 생성된 bean의 인스턴스를 계속해서 리턴한다. 위와 같이 정의한 경우 singleBean은 싱글톤 패턴으로 생성되어 초기에 만들어지고 소멸하지 않기 때문에 singleBean내부에 가지고 있는 someBean역시 최초에 만들어지고 다시 만들어지지 않게 된다.

사실 setter로 인스턴스를 주입받고, getter에서 new 키워드로 신규 인스턴스를 리턴하여 주면 된다. 문제는이건 이미 IoC가 아닌 것이다. 그렇게 하려고 하면 bean으로 관리할 필요가 없다.

IoC를 유지하면서 위에 문제를 해결하는 방법은 Metod Lookup Injection 이다. 


Metod LookUp Injection


사용목적

  • 생명주기가 다른 두 빈에 대한 작업을 할 때 사용. 
  • 싱글턴 빈이 비싱글턴 이여야할 참조를 가지고 있어서, 비싱글턴이여야 하는 객체가 싱글톤으로 동작하는 문제해결.


사용방법

  • 싱글톤 대상 클래스 A가 구현할 Interface IA
    • createB() 등과 같이 신규 인스턴스를 리턴할 메소드 정의. (룩업 메소드 대상)
  • 싱글톤 대상 클래스 A
    • IA를 구현하여 메소드를 생성한다 (대신 abstract 키워드로 추상메서드로 구현한다)
    • bean을 정의할 때 <bean><lookup-method name="createB" bean="beanB"/></bean>
  • 싱글톤 대상 클래스를 B
    • bean 정의시 내부 scope="prototype"으로 지정한다.


주의사항 (필수사항은 아니고, 권장사항 이다)

  • 룩업 대상 메서드는 인터페이스에 선언하여 상속 받을 것.
  • 룩업 대상 메서드를 추상 메서드로 만들 것. 
  • 성능저하 발생하기 때문에 가급적 안쓰는게 좋음.

Example


설정 및 환경관련 사항

[Class Diagram]



[app-context.xml]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <!-- MyPrototype 객체 정의 scope="prototype"으로 지정하여 매번 새로운 인스턴스를 생성하도록 -->
    <bean id = "prototype" class = "com.java.prototype.MyPrototype" scope="prototype"/>
    
 
    <!-- 일반적인 방식으로 setter 주입을 통해  prototype Bean이 주입된다. -->
    <bean id = "stdSingleton" class = "com.java.singletonImpl.StandardSingleton">
        <property name="mPrototype">
            <ref bean="prototype"/>
        </property>    
    </bean>
    
    <!-- lookup-method 주입방식으로 bean이 주입된다.   -->
    <bean id = "absSingleton" class = "com.java.singletonImpl.AbstractSingleton">
        <!-- absSingleton bean의 클래스를 런타임 시점에서 Spring이 상속받고 lookup-method name=""으로
지정된 메소드를 구현하여 prototype 객체를 리턴  -->
        <lookup-method name="getPrototype" bean="prototype"/>
    </bean>
cs

구현사항
[ISingleton]

1
2
3
4
5
6
7
package com.java.singleton;
 
import com.java.prototype.MyPrototype;
 
public interface ISingletone {
    public MyPrototype getPrototype(); 
}
cs


[StandardSingleton]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.java.singletonImpl;
 
import com.java.prototype.MyPrototype;
import com.java.singleton.ISingletone;
 
public class StandardSingleton implements ISingletone {
 
    private MyPrototype mPrototype;
 
    public void setmPrototype(MyPrototype mPrototype) {
        this.mPrototype = mPrototype;
    }
 
    @Override
    public MyPrototype getPrototype() {
        return mPrototype;
    }
    
}
cs

[AbstractSingleton]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.java.singletonImpl;
 
import com.java.prototype.MyPrototype;
import com.java.singleton.ISingletone;
 
public abstract class AbstractSingleton implements ISingletone {
    
    private MyPrototype mPrototype;
    
    public void setmPrototype(MyPrototype mPrototype) {
        this.mPrototype = mPrototype;
    }
    
    @Override
    public abstract MyPrototype getPrototype();
 
}
cs


[MainClass]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.java;
 
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.util.StopWatch;
 
import com.java.prototype.MyPrototype;
import com.java.singleton.ISingletone;
import com.java.singletonImpl.AbstractSingleton;
import com.java.singletonImpl.StandardSingleton;
 
public class MainClass {
 
    public static void main(String[] args) {
        // ApplicationContext 부트스트랩
        GenericXmlApplicationContext appCtx = new GenericXmlApplicationContext();
        appCtx.load("classpath:META-INF/spring/app-context.xml");
        appCtx.refresh();
        
        ISingletone stdSingle = appCtx.getBean("stdSingleton", StandardSingleton.class);
        ISingletone absSingle = appCtx.getBean("absSingleton", AbstractSingleton.class);
        
        stdSingle.getPrototype();
        System.out.println("**********************************************************");
        System.out.println("*** setter bean주입 테스트 수행                          ***");
        System.out.println("*** 예상결과 : 몇 번을 get하더라도 결과는 동일한 인스턴스 리턴 ***");
        System.out.println("*** 수행시간 : 빠름                                     ***");
        System.out.println("**********************************************************");
        printInfo(stdSingle);
        
        System.out.println("");
        System.out.println("");
        System.out.println("**********************************************************");
        System.out.println("*** Method Lookup bean주입 테스트 수행                  ***");
        System.out.println("*** 예상결과 : bean을 get할 때마다 신규 instance를 리턴하여 다른 객체 리턴***");
        System.out.println("*** 수행시간 : 느림                                     ***");
        System.out.println("**********************************************************");
        printInfo(absSingle);
        
        appCtx.close();
    }
 
    private static void printInfo(ISingletone single) {
        MyPrototype proto1 = single.getPrototype();
        MyPrototype proto2 = single.getPrototype();
        
        System.out.println("instance1  == instance2 ? " + (proto1  == proto2) );
        
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("lookUp Demo");
        
        for (int i = 0; i < 100000; i++) {
            MyPrototype proto = single.getPrototype();
        }
        
        stopWatch.stop();
        
        System.out.println("100000 gets took " + stopWatch.getTotalTimeMillis() + "ms");
    }
}
 
cs


<수행결과>


**********************************************************

*** setter bean주입 테스트 수행                              ***

*** 예상결과 : 몇 번을 get하더라도 결과는 동일한 인스턴스 리턴 ***

*** 수행시간 : 빠름                                            ***

**********************************************************

instance1  == instance2 ? true

100000 gets took 2ms



**********************************************************

*** Method Lookup bean주입 테스트 수행                   ***

*** 예상결과 : bean을 get할 때마다 신규 instance를 리턴하여 다른 객체 리턴***

*** 수행시간 : 느림                                             ***

**********************************************************

instance1  == instance2 ? false

100000 gets took 337ms



참고사항

  • 자주쓰이지 않음 (거의 쓰이지 않음)
  • lookup-method로 지정된 메소드를 갖는 Class가 반드시 abstract일 필요는 없음
    • 테스트 해보니 추상클래스가 아니여도 동작
    • 지정된 클래스를 cglib가 런타임에서 클래스 변조하여 상속처리 하고 내부 method내용을 override 하는 구조로 동작하는 듯 함.
  • cglib 라이브러리가 필요함 (spirng 3.2.x 버젼 부터 cglib spring lib에 내장화하여 필요 없음)
  • 어노테이션 방식으로 lookup-method를 지정하는 방식은 책에도 없고 검색에도 없고....의문


다운로드

LookUpMethodInjection.zip

이 글을 공유하기

댓글

Email by JB FACTORY