java의 reflection은 플랫폼을 구성하는 데 효율적으로 쓰일 수 있는 좋은 api이다.

spring의 util에는 ReflectionUtils를 통해 java의 reflection api를 쉽게 사용할 수 있는 api를 제공한다.



예제를 시작한다.


앞으로 사용할 예시를 위해서 사용될 import 이다.

import com.google.common.collect.Maps;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Map;



먼저, MyActivity를 정의했다. 


import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class MyActivity {
public String id;
public String content;
private String writer;
private boolean liked;

public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}



MyActivity 클래스에 ReflectionUtils의 findField, setField, getField, makeAccessible 메소드를 사용하여 내부 변수를 마음대로 바꾼다. 하지만, private 필드를 함부로 수정할 수 없기 때문에 makeAccessible 메소드를 사용한다.

MyActivity myActivity = new MyActivity();

Field idField = ReflectionUtils.findField(MyActivity.class, "id");
System.out.println("field(id) : " + idField);

Field writerField = ReflectionUtils.findField(MyActivity.class, "writer");
System.out.println("field(writer) : " + writerField);

ReflectionUtils.setField(idField, myActivity, "200001");
Object value = ReflectionUtils.getField(idField, myActivity);
System.out.println("value : " + value);

// error - Could not access method: Class org.springframework.util.ReflectionUtils can not access a member of class MyActivity with modifiers "private"
// ReflectionUtils.setField(writerField, myActivity, "Kent");

ReflectionUtils.makeAccessible(writerField);
ReflectionUtils.setField(writerField, myActivity, "Kent");


실행하면, 다음 결과를 얻을 것이다.


field(id) : public java.lang.String MyActivity.id

field(writer) : private java.lang.String MyActivity.writer

value : 200001




참고로, ReflectionUtils.makeAccessible 메소드는 java의 reflection api의 다음 api를 조금 추상화한 것 밖에 없다.

field.setAccessible(true);





예제 2는 조금 길지만, Reflection.doWithFields에 대한 예시이다. ReflectionUtils.FieldCallback()을 이용한 것이라서, 무척 괜찮은 api이다. 간단하게 field를 출력하기 위해 RelfectionUtils.FieldCallback()의 doWith 메소드와 matches 메소드의 사용 방법을 작성했다. 


accessible에 대해서 조심스럽게 써야 할 예시도 추가했으며, matches 메소드 없이도 doWith 메소드로 모두 처리할 수 있다. 




MyActivity myActivity = new MyActivity();
Map<String, Object> properties = Maps.newHashMap();
properties.put("id", "1");
properties.put("content", "sns number one service");

// 먼저 field를 필터하기 위해 new ReflectionUtils.FieldFilter를 한 후
// FieldCallback를 사용하여 필드의 값을 저장한다
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
}, new ReflectionUtils.FieldFilter() {
@Override
public boolean matches(Field field) {
return properties.containsKey(field.getName()) && Modifier.isPublic(field.getModifiers());
}
});
System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
if (field.isAccessible()) {
field.set(myActivity, properties.get(field.getName()));
}
}
});

System.out.println(myActivity);


/*
access 못할 필드에 set하면, FieldCallback를 사용할 때 에러 발생한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
field.set(myActivity, properties.get(field.getName()));
}
});


<결과>
field : public java.lang.String MyActivity.id
field : public java.lang.String MyActivity.content
field : private java.lang.String MyActivity.writer

Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"
java.lang.IllegalStateException: Not allowed to access field 'writer': java.lang.IllegalAccessException: Class ReflectionTest$3 can not access a member of class MyActivity with modifiers "private"

==>


*/

properties.put("writer", "kent");
properties.put("liked", true);

//FieldCallback를 사용할 때 에러가 발생하니, field에 access하게 만들던지, modifier로 구분한다.
ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
ReflectionUtils.makeAccessible(field);
field.set(myActivity, properties.get(field.getName()));
}
});

System.out.println(myActivity);



ReflectionUtils.doWithFields(myActivity.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
System.out.println("field : " + field);
int modifier = field.getModifiers();
if (Modifier.isPrivate(modifier) || Modifier.isFinal(modifier) || Modifier.isStatic(modifier)) {
// do nothing;
} else {
String name = field.getName();
if (name.equals("id") || name.equals("content")) {
field.set(myActivity, properties.get(field.getName()));
}
}
}
});
System.out.println(myActivity);


결과는 다음과 같다.


field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=<null>,liked=false]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]

field : public java.lang.String MyActivity.id

field : public java.lang.String MyActivity.content

field : private java.lang.String MyActivity.writer

field : private boolean MyActivity.liked

MyActivity[id=1,content=sns number one service,writer=kent,liked=true]






또한 필드 뿐만 아니라, 필드에 붙은 Annotation도 처리할 수 있다. Comment 라는 모델과 CommentType이라는 Annotation 결합형에 대해서 ReflectionUtils 처리 코드를 살펴본다.



public class Comment {

public String id;

@CommentType("type1")
public String type;

public String content;


@CommentType("method")
public void createComment() {
System.out.println("create comment");
}
}


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CommentType {
String value();
}



Comment 객체를 생성하고, 필드에 CommentType에 annotation이 있다면, 처리하는 방식이다. AOP 나, 프레임 워크 구축 때 유용할 수 있는 포맷이다. 

Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

ReflectionUtils.doWithFields(Comment.class, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

if (field.getAnnotation(CommentType.class) != null) {
System.out.println("field : " + field);
}
List<Annotation> annotationList = Arrays.asList(field.getAnnotations());
for (Annotation annotation : annotationList) {
System.out.println(annotation);
}
}
});


결과는 다음과 같다.


field : public java.lang.String Comment.type

@CommentType(value=type1)

BUILD SUCCESSFUL

Total time: 2.121 se





다음은 필드가 아닌 메소드 관점으로 살펴보는 메소드, ReflectionUtils.findMethod 메소드 예시이다. 모든 method를 출력하는 예시이다.


반면, 주의해야 할 내용도 있다. 실제 없는 메소드를 호출하면, NullPointerException(NPE)가 발생할 수 있다. 



Comment comment = new Comment();
comment.id="1";
comment.content="hahahah";
comment.type="comment";

Method method = ReflectionUtils.findMethod(Comment.class, "createComment");
ReflectionUtils.invokeMethod(method, comment);

// java.lang.NullPointerException
// Method noMethod = ReflectionUtils.findMethod(Comment.class, "noMethod");
// ReflectionUtils.invokeMethod(noMethod, comment);


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
System.out.println(method.getName());
}
});



결과는 다음과 같다.


create comment

createComment

finalize

wait

wait

wait

equals

toString

hashCode

getClass

clone

registerNatives

notify

notifyAll






ReflectionUtils.doWithMethods를 사용하여 메소드에 연관된 annotation을 발견할 수 있다. 사용방식은 ReflectionUtils.doWithFields 메소드와 동일하다.


Comment comment = new Comment();
comment.id = "1";
comment.content = "hahahah";
comment.type = "comment";


ReflectionUtils.doWithMethods(Comment.class, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {

Annotation annotation = AnnotationUtils.getAnnotation(method, CommentType.class);
if (annotation instanceof CommentType) {
System.out.println("find comment type annotation.");
System.out.println(method.getName());
}

}
});


결과는 다음과 같다.


find comment type annotation.

createComment

Posted by '김용환'
,