메서드 수행 속도를 나타내는 모듈 만들기 (jar로 제작) (외부 클래스 사용하기)

김지인·2022년 9월 7일
0
post-thumbnail

들어가기 앞서서...

내가 구현하고자 했던 것은 메서드의 수행속도를 측정하는 것을 모듈화 해보고 싶었다. jar로 만들어서 원하는 외부 프로젝트에서도 사용할 수 있게끔 구현할 것이다.

먼저 타겟 메서드의 수정없이 부가기능을 추가하기 위해 프록시를 사용할 것인데, 반복을 없애기 위해 컴파일 시점이 아닌, 런타임 시점에서 프록시 클래스를 만들어주는 동적 프록시를 이용해 AOP(Aspect Oriented Programming)의 느낌으로 순수 자바로 제작 할 것이다.

기능은 간단하게 메서드가 실행 되기 전에 시간초와 실행되고 난뒤에 시간초를 빼서 측정할 것이다.


내가 사용할 것은 JAVA에서 제공해주는 reflection API의 newProxyInstance() 메서드를 사용할 것이다.


public static Object newProxyInstance(ClassLoader loader, -- 1
									  Class<?>[] interfaces, -- 2
                                      InvocationHandler h) -- 3
  • 1 - 프록시 클래스를 만들 클래스로더
  • 2 - 프록시 클래스가 구현할 인터페이스 목록
  • 3 - 메서드가 호출되었을때 실행될 핸들러

그리고 InvocationHandler는 인터페이스 이므로 구현을 해줘야한다.

public interface InvocationHandler {
	public Object invoke(Object proxy, -- 1
    					Method method, -- 2
                        Object[] args) -- 3
                        
}
  • 1 - 프록시 객체
  • 2 - 호출한 메서드 정보
  • 3 - 메서드에 전달된 파라미터

기본 구조를 알아봣으니 이제 제작을 해보자.


public class CheckActionTimeJavaAOP {
	
    public void checkActionTime() {
    	
    }
}
  • 먼저 기능을 할 수 있는 클래스와 메서드를 만들자.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CheckActionTimeJavaAOP {
	
    public void checkActionTime() {
    	Proxy.newProxyInstance(ClassLoader loader,
							   Class<?>[] interfaces,
                               InvocationHandler h) --- 1
    }
}
  • 그다음 Proxy의 newProxyInsance를 호출하자.
  • 그 다음 1위치의 인터페이스 InvocationHandler에서 내가 하고자 하는 기능을 구현해보자.

new InvocationHandler() {
					@Override
    public Object invoke
    (Object proxy, Method method, Object[] args) throws Throwable {
						
		long start = System.currentTimeMillis();
						
		Object ActionTime = method.invoke(obj, args);
						
		long end = System.currentTimeMillis();
						
		System.out.println((end - start) + "ms 시간이 걸렸습니다. ");
						
		return ActionTime;
		}
}
  • 먼저 start 변수에 현재 시간을 나타내는 System.currentTimeMillis()를 실행하고 기능을 하고자 하는 메서드를 실행 후에 end변수에 동일하게 현재 시간을 나타내는 값을 넣고 마지막에 end값에서 start값을 빼는 아주 간단한 기능을 구현했다.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CheckActionTimeJavaAOP {
	
    public void checkActionTime() {
    	Proxy.newProxyInstance(ClassLoader loader,
							   Class<?>[] interfaces,
                               new InvocationHandler()  {
                                      @Override
                                      public Object invoke
                                      (Object proxy, Method method, Object[] args) throws Throwable {

                                          long start = System.currentTimeMillis();

                                          Object ActionTime = method.invoke(obj, args);

                                          long end = System.currentTimeMillis();

                                          System.out.println((end - start) + "ms 시간이 걸렸습니다. ");

                                          return ActionTime;
                                          }
										}); 
    }
}

기본구조


이 클래스는 jar파일로 만들어서 외부 프로젝트에서 사용할 수 있게끔 할 것이다. 근데 여기서 난관을 봉착했는데 외부 클래스를 어떻게 불러와서 사용할까 하는 고민이었다.

(수많은 삽질의 흔적..)

newProxyInstance() 함수는 인자로 클래스와 인터페이스를 받기 때문에 외부 클래스를 인자로 넘겨줫어야 했다. 수많은 삽질과 오류 끝에 구현하는데 성공했다. 이제부터 하나하나 알아가보자.

먼저 생각해야할게 내가 이 메서드에서 어떤 것을 받아올지 생각하는 것이었다. 객체로 받아와서 해당 객체의 클래스를 불러올 수 있게 하는 getClass() 함수를 이용할 것이다.

 public void checkActionTime(Object obj)
// 무엇을 받을지 모르니 최상위 객체인 Object타입의 객체를 인자로 받는다.
...

Proxy.newProxyInstance(obj.getClass().getClassLoder(),
// 받아온 객체에서 getClass()를 통해 해당 클래스를 불러오고 getClassLoder()를 통해 
// 해당 클래스의 ClassLoder를 받아온다.
...

Proxy.newProxyInstance(obj.getClass().getClassLoder(),
					   new Class[] {obj.getClass().getInterfaces()[0]},
// 그 다음 해당 객체의 getClass()를 통해 해당 클래스를 불러오고 getInterfaces()를 통해 해당
// 클래스의 인터페이스 목록을 가져오고 Class<?>[]형태로 반환되기 때문에 0번째 인덱스의 
// 인터페이스를 가져와준다. 

다음은 InvocationHandler()을 구현해보자

new InvocationHandler() {
					
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						
		long start = System.currentTimeMillis();
						
		Object ActionTime = method.invoke(obj, args);
		// 받아온 obj를 invoke인자의 함수로 넘겨준다.				
		long end = System.currentTimeMillis();
						
		System.out.println((end - start) + "ms 시간이 걸렸습니다. ");
						
		return ActionTime;
		}
  }


public class CheckActionTimeJavaAOP {
	
	public Object checkActionTime(Object ob) {
		
		Object proxy = Proxy.newProxyInstance
				(ob.getClass().getClassLoader(),
                new Class[] {ob.getClass().getInterfaces()[0]}, 
				 new InvocationHandler() {...}
				});
         return proxy;
         }
  • 그리고 해당 메서드의 반환값으로 최상위 객체인 Object로 반환하여 형변환이 자유롭게끔 한다.

실행해보기

임의로 들어온 숫자를 계산해주는 클래스를 생성해보자.

public interface Calc {
	public int plusOperator();
	public void printCalc();
}
-------------------------------------------------------------------------------------
package com.test.clac;

public class TestCalc implements Calc{
	private int n1;
	private int n2;
	private int n3;
	private int result;
	
	public TestCalc() {
	}
	
	public TestCalc(int n1, int n2, int n3) {
		this.n1 = n1;
		this.n2 = n2;
		this.n3 = n3;
	}
	
	public int plusOperator() {
		while(n1 < 1000000000) {
			n1++; // 임의로 작업시간을 늘리기
		}
		result = n1 + n2 + n3;
		return result;
		
	}
	
	public void printCalc() {
		System.out.println(result);
	}
	
}

해당 클래스 생성 후 위에서 만든 jar파일을 이용해서 실행해보자.

package com.test.run;

import com.test.clac.Calc;
import com.test.clac.TestCalc;

import checkactiontimejavaAOP.com.CheckActionTimeJavaAOP;

public class TestRun {
	
	
	public static void main(String[] args) {
		CheckActionTimeJavaAOP checkActionTimeJavaAop = new CheckActionTimeJavaAOP();
		Calc t = new TestCalc(1,2,3);
		Calc proxy = (Calc)checkActionTimeJavaAop.checkActionTime(t);
		proxy.plusOperator();
		proxy.printCalc();
	}	
}
---------------
실행결과
---------------
0ms 시간이 걸렸습니다. 
1000000005
0ms 시간이 걸렸습니다. 
  • checkActionTimeJavaAop객체의 함수 checkActionTime()Calc객체 t값을 인자로 넘겨주고 성공적으로 실행되는 것을 볼 수 있다.

처음으로 jar파일로 만들어 외부 프로젝트에서 사용해보았다. 많은 시행착오가 있었지만 외부 프로젝트에서 클래스를 불러오는 등 많은 것을 배우게 된 계기가 된것같다.

profile
에러가 세상에서 제일 좋아

0개의 댓글