Spring Method Replace (replaced-method)
- 프로그래밍/Spring FWK
- 2015. 12. 18. 00:48
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하는 쪽이 훨씬 합리적.
이런게 있구나 하고 넘어가고 정말 불가피한 상황에서만 어쩔 수 없이 사용해야 할 듯 합니다.
다운로드
'프로그래밍 > Spring FWK' 카테고리의 다른 글
Spring BeanNameAware Interface (1) | 2015.12.28 |
---|---|
Spring Bean Life Cycle (빈 생명주기 관리) (0) | 2015.12.28 |
Spring bean 상속 (0) | 2015.12.27 |
Spring Bean Scope 정리 (0) | 2015.12.27 |
Spring Method LookUp Injection (0) | 2015.12.16 |
Spring Non Singletone (0) | 2015.12.14 |
Spring Collection (List, Map, Properties, Set) 주입 (0) | 2015.12.13 |
Spring 다중 applicationContext 중첩사용 (0) | 2015.12.13 |
이 글을 공유하기