소프트웨어 개발/Java - Basic

Annotation 을 이용한 메서드 골라내기

늘근이 2015. 8. 2. 23:51

Annotation은 일반 개발을 하게되면 그다지 신경쓰지 않는 부분일수도 있지만, 어노테이션을 이용해 특정 메서드의 실행을 막는다든지 골라낸다든지 할수있기 때문에 유용하게 쓰일 수 있다. 이를 메타 프로그래밍(Meta) 라고 하는데, 스프링과같은 프레임워크는 이 어노테이션을 통해 구성되어있다.

new file -> enum 일단 어노테이션에서 사용할 ENUM을 하나 만든다.

public enum LangType {
	KOREAN, ENGLISH, SPANISH
}

new file -> annotation 그리고 어노테이션도 하나 만든다.

@Retention(RetentionPolicy.RUNTIME)
public @interface Language {
	LangType type();
}


이제 모든 준비는 끝났다. 저 RETENTION은 어노테이션의 종류를 가르킨다. 내부적으로 ENUM타입을 가지고 있는 첫번째로 CLASS는 기본값으로써 컴파일러에 의해 클래스화 되지만 VM으로부터는 버려진다. SOURCE의 경우 그냥 버린다. 우리가 필요로 하는것은 런타임에서도 동작하는 그러한 강력한 어노테이션이다. 이럴때는 RUNTIME 타입으로 맞춘다.


이제 실제로 여러 메시드를 한번 만들어 보자. 기본적으로 메인 메서드외, 다른 메서드 세개를 더 만들겠다. 매개변수와 리턴타입은 그냥 내마음이다.

	@Language(type=LangType.ENGLISH)
	public String getName(int id) {
		return "HELLO";
	}
	
	@Language(type=LangType.KOREAN)
	protected int[] split(String table, int number) {
		return new int[]{1,2,3};
	}
	
	@Language(type=LangType.SPANISH)
	Boolean isTrue(String predicate) {
		return false;
	}
	
	private void setAge(String[] names) {
		
	}
	


이러한 상태에서 한번 메인메서드에 getMethods() 메서드로 다음과 같이 찍어보자.

		Method list[] = Test.class.getMethods();
		for(Method m : list) {
			System.out.println(m);
		}

결과는 다음과 같다.

public static void anotest.Test.main(java.lang.String[])
public java.lang.String anotest.Test.getName(int)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()


신기한건, public 메서드를 제외한 다른 메서드들은 나오지가 않는다. 그럼 나오려면 어떻게 해야할까? getMethods() 가 아닌 getDeclaredMethods() 메서드를 통해서 해당하는 것들을 다 불러와버리면 된다. 여기서는 이렇게 흔다.

Method list[] = Test.class.getDeclaredMethods();

이제 접근제한자가 아무리 private하더라도 모든 메서드를 다 가져와버릴수 있다. 다만, class내부적으로 만들어진 wait() 과 같은 메서드는 보이지 않는다. 즉, 선언한 메서드만 보이기 때문이다. 이러한 경향으로 사실 getDeclaredMethods가 더 유용한 측면이 있다.

사실 접근제한자의 역할이 깨지는 부분인데, 이러한 리플렉션은 추후에 프레임워크나 IDE를 개발할때 유용하게 쓰일수 있을것이다. 일반용도는 잘 없기 때문에 이거 아는 개발자는 모르는 개발자를 은근히 무시한다... 코딩이란 무엇을 만드는지가 중요하거늘 껄껄껄

이 메서드를 끌러보는 기능은 재밌는것들이 많아 다음의 메서드로 여러가지를 해볼수있다.

   System.out.println(m.getReturnType());
   System.out.println(m.getModifiers());
   System.out.println(m.getParameterTypes());


이제 해당 메서드에 있는 어노테이션을 가지고 오고 싶으면 다음과 같이 표기하면 된다.


	public static void main(String[] args) {
		
		Method list[] = Test.class.getDeclaredMethods();
		for(Method m : list) {
			Language ass = m.getAnnotation(Language.class);
			System.out.println(ass);
		}
		
	}

실행결과는 아래와 같다. main메서드 포함 총 5개의 메서드에 대한 Annotation이 나오고 있다.

null
@anotest.Language(type=ENGLISH)
@anotest.Language(type=KOREAN)
@anotest.Language(type=SPANISH)
null


이제, null 이 아닌 값들 중, ENGLISH인 메서드를 가져와서 실행시키는 코딩을 한번 해본다.





package anotest;

import java.lang.reflect.Method;

public class Test {
	public static void main(String[] args) throws Exception {

		Method list[] = Test.class.getDeclaredMethods();
		for (Method m : list) {
			Language ass = m.getAnnotation(Language.class);
			try {
				if (ass.type() == LangType.ENGLISH) {
					m.invoke(Test.class.newInstance(), 100);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@Language(type = LangType.ENGLISH)
	public String getName(int id) {
		System.out.println("My ID is " + id);
		return "HELLO";
	}

	@Language(type = LangType.KOREAN)
	protected int[] split(String table, int number) {
		return new int[] { 1, 2, 3 };
	}

	@Language(type = LangType.SPANISH)
	Boolean isTrue(String predicate) {
		return false;
	}

	private void setAge(String[] names) {

	}

}

결과는 다음과 같다.


java.lang.NullPointerException
 at anotest.Test.main(Test.java:12)
java.lang.NullPointerException
 at anotest.Test.main(Test.java:12)
My ID is 100


위의 코드의 로직은 다음과 같다. 메서드를 바로 invoke시키기 위해 newInstance()로 바로 객체를 만들어주었다.

그렇지만 만약 static메서드면 어떻게 될까? newInstance()를 생성해줄필요 없이 null값을 인자로 주면 된다.

,..