Spring Method Replace (replaced-method)

Method Replace (매서드 대체)

개요

  • 빈의 소스코드 변경 없이 메서드 구현체를 임의로 수정 할 수 있다.
    • 가령, jar에 포함된 Class파일을 내부 메소드를 변경 하는 경우 유용하다.
  • 내부적으로 replaced-method가 정의된 bean을 cglib가 동적으로 하위 클래스를 생성해 작업 수행
    • cglib가 런타임 시점에서 하위클래스 생성
    • replace 타켓이 되는 메서드가 수행되는 경우 MethodReplacer인터페이스를 구현한 bean을 호출


사용방법

  • MethodReplacer 인터페이스를 구현한 A클래스 생성
  • A클래스에 MethodReplacer인터페이스의 reimplement()메소드 override
  • 일반 클래스B 생성
  • A클래스 bean을 우선 정의하고, B클래스 정의시 <replaced-method name="변경 대상 메소드명" replacer="MethodReplacer빈id"/> 태그 추가
1
2
3
4
5
6
7
8
9
    <!-- MethodReplacer 인터페이스를 구현체 정의-->
    <bean id="replacer" class="com.java.replacer.MyMethodReplacer"/>
    
    <!-- Bean정의시  하위요소 replaced-method 추가-->
    <bean id="replaceBean" class="com.java.pojo.ReplaceTarget">
        <replaced-method name="getString" replacer="replacer">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>
cs


주의사항

  • 하위클래스 동적 생성으로 인한 성능저하 발생 불가피
  • MethodReplacer하나로 다양한 bean에 적용 가능하지만 가급적 권장되지 않음
    • 하나의 클래스에 오버로드된 메서드 단위별로 MethodReplacer구현 할 것
    • MethodReplacer.reimplement() 메소드에서 대체된 메서드가 무엇인지 비교과정 으로 더욱 더 성능저하를 발생


Example


[app-context.xml]

  • replaced-method name="대체 대상 메서드"
  • replaced-method replacer="대신 수행될 메서드내용을 구현한 Bean"
  • arg-type : 동일 메서드 명 오버로드 되는경우 어떤 메서드를 대체할지 매개변수 인자로 결정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 별다른 옵션 없이 평번하게 정의된 Bean -->
    <bean id="normalBean" class="com.java.pojo.ReplaceTarget"/>
    
    
    <!-- 메소드 대체에 사용하게 될 bean. 대상클래스는 MethodReplacer 인터페이스를 구현해야 한다. -->
    <bean id="replacer" class="com.java.replacer.MyMethodReplacer"/>
    
    <!-- 위와 동일한 클래스지만 replaced-method 지정한 Bean -->
    <bean id="replaceBean" class="com.java.pojo.ReplaceTarget">
        <replaced-method name="getString" replacer="replacer">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean>
cs



[ReplaceTarget Class]

평범하게 정의된 Bean 으로 해당 클래스의 getString(String arg) 메소드를 대체할 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.java.pojo;
 
public class ReplaceTarget {
 
    /*
     * 그냥 내비 둘 메소드
     */
    public String getString(int val) {
        return "원본 메시지 " + val;
    }
    
    /*
     * ReplacerTarget이 될 메소드
     */
    public String getString(String str) {
        return "원본 메시지 " + str;
    }
    
}
cs


[MyMethodReplacer Class]

  • Method Replace의 핵심 클래스
  • org.springframework.beans.factory.support.MethodReplacer.MethodReplacer 인터페이스 구현
  • public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable; 오버라이드

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
package com.java.replacer;
 
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
 
public class MyMethodReplacer implements MethodReplacer {
 
    /*
     * DESC 
     *     replace대상으로 지정된 메소드를 수행하는 경우 본 메소드가 수행된다.
     *  <replaced-method>에 name으로 지정된 메소드가 수행되면 reimplement() 가 호출된다. 
     *  내부족으로 <replaced-method>가 위치한 parent bean생성시 Spring이 해당 bean을 상속받아
     *  메소드 내용을 reimplement()의 내용으로 override 한다. 
     *  
     * Params
     *     Object arg0      : 원본메서드를 호출한 bean
     *  Method arg1      : 오라버라이드 할 메서드를 나타내는 Method객체
     *  Object[] arg2 : Method호출시 전달한 인자 배열
     *  
     */
    @Override
    public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable {
        
        // 원본 메서드와 메서드 이름이 맞는지 확인
        // Method 클래스의 인스턴스는 메소드 인자개수, 이름, 반환타입, 인자타입등의 정보를 갖고 있다.
        // 이를 이용해 하나의 MethodReplacer구현체로 여러개의 메소드 replace가 가능하다.
        if("getString".equals(arg1.getName())) return "대체 메시지";
        
        else return "에러";
        
    }
 
}
cs



[MainClass]

ReplaceTarget Class를 메서드 대체를 수행한 Bean (replaceBean)과 수행하지 않은 Bean(normalBean)으로 동일 테스트 수행

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
package com.java;
 
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.util.StopWatch;
 
import com.java.pojo.ReplaceTarget;
 
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();
        
        
        ReplaceTarget normalBean = appCtx.getBean("normalBean", ReplaceTarget.class);
        ReplaceTarget replaceBean = appCtx.getBean("replaceBean", ReplaceTarget.class);
        
        /****************************************
         * normalBean 수행결과                                                *
         * bean의 수행결과 : 원본 메시지 리턴                                *
         * bean의 수행속도 : 빠름                                                 *
         ****************************************/
        System.out.println("*************Normal Bean 테스트*************");
        printInfo(normalBean);
        
 
        /****************************************
         * replaceBean 수행결과                                              *
         * bean의 수행결과 : 대체 메시지 리턴                                *
         * bean의 수행속도 : 느림                                                 *
         ****************************************/
        System.out.println("*************Replace Method 테스트*************");
        printInfo(replaceBean);
    
        appCtx.close();
    }
 
    private static void printInfo(ReplaceTarget target) {
        
        System.out.println(target.getString("테스트"));
        
        
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("normalBean");
        
        for(int i = 0 ; i  < 1000000 ; i++ ) target.getString("테스트");
        stopWatch.stop();
        
        System.out.println(stopWatch.getTotalTimeMillis()+"ms");
    }
}



cs


수행결과


*************Normal Bean 테스트*************

원본 메시지 테스트

105ms

*************Replace Method 테스트*************

대체 메시지

602ms



참고사항

메소드 대체를 사용하지 않은 일반적이 Bean이 약 5배 정도 빠름을 확인.

왠만하면 메소드 대체는 사용하지 않고, 대상 클래스를 상속받고 변경이 필요한 method를 Override하는 쪽이 훨씬 합리적.

이런게 있구나 하고 넘어가고 정말 불가피한 상황에서만 어쩔 수 없이 사용해야 할 듯 합니다.


다운로드

ReplacedMethod.zip


이 글을 공유하기

댓글

Email by JB FACTORY