KyungHwan's etc.

java annotation 과 reflection을 사용한 xml mapping 본문

Java

java annotation 과 reflection을 사용한 xml mapping

KyungHwan_0 2018. 5. 31. 16:09

java annotation 과 reflection을 사용한 xml mapping


1. 커스텀 어노테이션을 생성

2. 어노테이션을 찾고 주입하는 컨테이너클래스 생성

3. 어노테이션을 이용하여 사용

스프링 프레임워크를 사용하면, 어노테이션을 이용하여 많은 개발을 하게 된다. 스프링 프레임워크에서 의존성주입(DI)을 위해 @Autowired 어노테이션을 이용하기도 하며, 리플렉션을 통해서 URL을 매핑하기 위한 @RequestMapping 어노테이션등을 사용하게 된다.

어노테이션은 JAVA에서 코드내에 메타데이터(metadata)를 넣기 위해 도입한 개념으로, 특정 지점(ElementType: Type, Method, Field, . . .)에 특정 지점(Retention: Source, Class, Runtime)까지 유지되는 metadata 정보를 남길 수 있다.

Reflection은 class나 method, field 등에 대한 type, signature 정보를 runtime에도 알 수 있도록 코드 구조 정보 등을 메모리에 올려서 접근하게 해주는 기능으로 C/C++같은 native한 언어들을 제외한 대부분의 고급 언어에서 제공해주는 기능이다.

Model class에 적절한 어노테이션으로 어떻게 xml과 연결지을지에 대한 metadata를 기록하고, reflection으로 이를 적절히 읽어서 model과 xml의 상호 변환(mapping)하는 코드를 작성해본다.

Model

public class Customer {

public String name;
public int age;
public String order;

}

Customer class가 xml로 기록되어야 할 때에는 customer라는 노드 이름을 가져야할까? name이라는 field는 xml에 attribute로 기록되어야 할까? 아니면 child-elemnet가 되어야 할까? 그리고 그 이름은 꼭 name이어야 할까?

이러한 정보를 위 model class에 적절히 넣기 위한 가장 좋은 방법이 어노테이션을 사용하는 것이다.

class에 사용할 annotation을 정의한다(import 생략).
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlClassBind {
public String nodeName();
}

특정 ElemnetType이 TYPE이기 때문에 class에만 사용할 수 있는 어노테이션이고, 그정보를 RUTIME 까지 남긴다. 왜냐하면 해당 어노테이션 정보를 reflection으로 읽어서 runtime에 사용해야 하기 때문이다. 그리고 nodeName이라는 어노테이션 값을 하나 같는데, 해당 class가 mapping될 xml elemnet의 이름 정보를 넣기 위해 쓸 것 이다.

**이제 field에 사용할 annotation을 정의한다(import 생략).**
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlAttrBind {
public string nodeName();
public boolean useAttribute();
}

xmlcassBind와 유사한데 ElemnetType을 FIELD로 선언하여 필드에서만 사용할 수 있도록 한다. 그리고 userAttribute라는 Boolean 속성을 추가하여 true이면 attribute로 기록하고 false이면 child-elemnet를 만들도록 할 것이다.

제 정의한 annotation으로 model class에 metadata를 넣어보자.
@XmlClassBind(nodeName="customer")
public class Customer {
@XmlAttrBind(nodeName="name", useAttribute=true)
public String name;
@XmlAttrBind(nodeName="age", useAttribute=true)
public int age;
@XmlAttrBind(nodeName="customer-order", useAttribute=false)
public String order;

/* default constructor, constructor for all of fields, toString method for debugging */
}

Customer class는 xml customer elemnet에 대응될 것이고, name과 age는 attribute로 기록된다. Order 문자열만 child elemnet로 생성되는데 이 때 element 이름은 customer order가 될것이다.

이제 XML과 Mapping하면 된다. JAVA기본 XML Api를 사용하면 코드가 장황해 지기 때문에, 예전부터 사용하던 자체 구현 XmlElemnet을 사용하여 Bind를 수행하는 코드를 작성할 것이다.

먼저 model 객체들을 Xml로 변환하는 코드는 다음과 같다(예외 선언 생략).
public static XmlElement ToXml(List<?> models) {
if (models.isEmpty())
throw new IllegalArgumentException();

// get class type for first element
Class<?> clazz = models.get(0).getClass();

String classNodeName = clazz.getAnnotation(XmlClassBind.class).nodeName();
String listNodeName = classNodeName + "-list";

// convert model to xml-element
XmlElement rootElement = new XmlElement(listNodeName);
for (Object model: models) {
XmlElement classElement = new XmlElement(classNodeName);

// write fields
for (Field field: clazz.getFields()) {
XmlAttrBind attrBind = field.getAnnotation(XmlAttrBind.class);
if (attrBind == null)
continue;

field.setAccessible(true);
String stringValue = String.valueOf(field.get(model));

// attribute or child-element
if (attrBind.useAttribute()) {
classElement.setAttribute(attrBind.nodeName(), stringValue);

} else {
XmlElement childElement = new XmlElement(attrBind.nodeName());
childElement.setTextContent(stringValue);
classElement.appendChild(childElement);
}
}
rootElement.appendChild(classElement);
}
return rootElement;
}

Model이 여럿 들어있는 List를 받아서 그것을 틔띠드둣로 변환해주는 코드이다. List 내에 모두 동일한 type의 model 객체가 있다고 가졍하였고, 해당 model class는 xmlClassBind와 xmlAttrBind와 XmlAttrBind가 적절히 선언되었다고 가정하였다.

reflection으로 class의 xmlClassBind 어노테이션 정보를 얻어서 class에 mapping 될 xml node이름을 얻고, 그 집합에 대한 xml node 이름을 대충 –list 를 붙여서 쓴다.

그리고 모든 XmlAttrBind가 anntated된 filed를 순회하면서, 그 값을 useAttribute에 따라 attribute에 쓰거나 child-elemnet에 기록한다. 다만 값을 가져오기 위한 field가 private, default, protected등 접근하지 못할 수도 있으므로 SetAccssible() 함수를 통해 접근 권한을 먼저 확보하고 접근한다.



Reference


Comments