본문 바로가기

프로그래밍/Spring

Spring Framework의 ContentNegotiatingViewResolver에 대하여... (2)

저번엔 내가 했던 프로젝트의 요구사항과 그로 인한 ContentNegotiatingViewResolver 의 리턴 타입 설정에 대해 얘기를 했었다. 이젠 ContentNegotiatingViewResolver에서 리턴 포맷이 결정된 뒤 이 포맷으로 결과를 만들어주게 될 View에 대해 언급해보도록 하겠다

 

저번에 프로젝트에서 요구했던 결과 포맷은 XML, JSON, 그리고 일반 텍스트라고 말했었다. 그럼 이 부분에 대해 하나씩 언급해보도록 하겠다

 

Spring의 컨트롤러에서 함수가 다음과 같이 객체를 리턴하게 되면 ContentViewResolver는 리턴포맷을 결정하여 그에 따른 작업을 진행하게 된다..

 

public Result<InfoVO> getInfo(HttpServletRequest request, HttpServletResponse response)

 

Result 클래스는 작업 결과 코드와 작업 결과 메시지, 그리고 결과셋을 갖게 되는 클래스로서 다음과 같은 형태로 되어 있다

 

@XmlRootElement(name="result")
@XmlType(name="Result", namespace="myprj.vo.Result", propOrder={"flag", "message", "recordset"})
public class Result<T> { 

    String flag = ""; // 작업 결과 코드 
    String message = ""; // 작업 결과 메시지 
    List<T> recordset; // 결과셋 
    
    public Result(){ 
    
    } 
    
    public String getFlag() { 
        return flag; 
    } 
    
    @XmlElement(name="flag") 
    public void setFlag(String flag) { 
        this.flag = flag; 
    } 
    
    public String getMessage() { 
        return message; 
    } 
    
    @XmlElement(name="message") 
    public void setMessage(String message) { 
        this.message = message; 
    } 
    
    @XmlElementWrapper(name="recordset") 
    @XmlElementRefs(
        { 
            @XmlElementRef(name="record", type=InfoVO.class) 
        }
    ) 
    public List<T> getRecordset() { 
        return recordset; 
    } 
    
    public void setRecordset(List<T> recordset) { 
        this.recordset = recordset; 
    } 
    
    @Override 
    public String toString() { 
        // TODO Auto-generated method stub StringBuffer 
        result = new StringBuffer(); 
        result.append(flag); 
        result.append("__"); 
        result.append(message); 
        if(recordset != null){ 
            if(recordset.size() != 0){ 
                StringBuffer record = new StringBuffer(); 
                record.append("_^"); 
                boolean start = true; 
                for(T item : recordset){ 
                    if(start){ 
                        start = false; 
                    } else { 
                        record.append("#!"); 
                    } 
                    record.append(item); 
                } 
                result.append(record.toString()); 
            } 
        } 
    return result.toString(); 
}

 

그리고 이 Result 클래스의 결과셋으로 들어가는 InfoVO 클래스는 다음과 같다

 

@XmlRootElement(name = "result")
@XmlType(name="Info", namespace="myprj.vo.Info", propOrder={"info_code", "info_name"})
public class InfoVO {
    String info_code;
    String info_name;
    public String getInfo_code() {
        return info_code;
    } 
    
    @XmlElement(name="info_code")
    public void setInfo_code(String info_code){ 
        this.info_code = info_code;
    }
    
    public String getInfo_name() {
        return info_name;
    }
    
    @XmlElement(name="info_name")
    public void setInfo_name(String info_name) {
        this.info_name = info_name;
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        StringBuffer sb = new StringBuffer();
        sb.append(getInfo_code());
        sb.append("__");
        sb.append(getInfo_name());
        return sb.toString();
    }
}

 

순수하게 데이터만 담는 VO 역할 클래스이다. 이제 이 2개의 클래스를 이용해서 View에 대한 얘기들을 하겠다

 

1) XML (MarshallingView)

 

자바와 객체의 변환에 있어 흔히 사용되는 말이 바로 마샬링(Marshalling)과 언마샬링(Unmarshalling)이다. 마샬링은 자바 객체를 XML로 변환하는 작업을 말하고 언마샬링은 그와는 반대로 XML을 자바 객체로 변환하는 작업을 일겉는다. 여기서 설명하고자 하는 것은 Spring Controller에서 자바 객체를 리턴했을때 이를 XML로 변환해서 사용하게 되는 즉 마샬링에 대한 얘기이다.

 

ContentNegotiatingViewResolver에서 마샬링 작업에 사용되는 View 클래스는 org.springframework.web.servlet.view.xml.MarshallingView 클래스이다. 하지만 이것만으로는 자바 객체를 XML로 변환할 수 없다. 자바 객체를 XML로 변환하는 기능을 제공하는 라이브러리는 많이 존재하는데 그중 어떤것을 사용할지를 지정해야 한다. Spring에서도 이를 위해 다양한 XML 변환 기능을 수행하는 클래스, 이른바 마샬러를 제공한다. 그중 내가 사용한것은 org.springframework.oxm.jaxb.Jaxb2Marshaller 이다. 이 Jaxb2Marshaller 를 다음과 같이 MarshallingView 클래스의 생성자를 통하여 전달하도록 한다.

 

<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
	<constructor-arg>
		<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
		</bean>
	</constructor-arg>
</bean>

 

그리고 이 Jaxb2Marshaller 가 어떤 클래스들을 XML로 변환할 것인지, 즉 XML로 변환할 작업 대상을 정해줘야 한다. Spring에서 다뤄지는 사용자가 만든 클래스 모두가 XML로의 변환 대상은 아닐것이다. 그렇기 때문에 Jaxb2Marshaller가 XML로 변환할 클래스를 지정해줘야 한다. 위에서 Result와 InfoVO 클래스를 모두 XML로 변환해야 하기 때문에 Jaxb2Marshaller 클래스의 classesToBeBound 프로퍼티를 이용해서 다음의 설정을 더 추가해준다

 

<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
	<constructor-arg>
		<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
			<property name="classesToBeBound">
			<list>
				<value>myprj.vo.Result</value>
				<value>myprj.vo.Info</value>
			</list>
			</property>
		</bean>
	</constructor-arg>
</bean>

 

이렇게 설정하면 MarshallingView 클래스는 변환하고자 하는 클래스가 지정되어 있는 Jaxb2Marshaller 클래스 객체를 생성자를 통해서 받아 XML 변환을 진행한다.그럼 XML을 변환하는부분에 대해 조금 더 깊게 들어가보도록 한다. 위에서 정의한 Result 클래스와 Info 클래스의 소스를 보면 XML과 관련되어 사용된 어노테이션들이 있다. 이 부분에 대해 설명하고자 한다

 

 어노테이션

 설명

@XMLRootElement

 클래스를 XML로 변환할 때 XML의 Root Element 태그명을 정의하게 된다. Result 클래스를 보면 @XMLRootElement 어노테이션에 name="result" 로 설정되어 있다. 이것은 <result> 태그로 이 클래스를 XML로 변환할때의 Root Element로 한다는 의미이다. 즉 클래스를 XML로 변환할때 사용하게 될 XML의 Root 태그를 정의하는것이다

@XMLType

 클래스를 XML로 변환할때 이 XML의 이름과 namespace를 정의한다. (이 부분의 정확한 역할은 잘 모르겠다. 다만 프로젝트를 하면서 겪었던 경험을 말하자면 두개의 클래스 이상에서 동일한 설정을 할 경우 스프링 구동시 에러가 발생한다. 그래서 name와 namespace 설정을 클래스 이름과 패키지까지 명시한 클래스 이름으로 정의를 하면 한 클래스로 unique한 구성을 할 수 있기 때문에 두개 이상의 클래스가 동일한 설정이 되지 않는다)

propOrder는 XML 표현시 멤버 변수를 표현할 순서를 정의하게 된다. Result 클래스를 보면 propOrder={"flag", "message", "recordset"} 로 설정되어 있는데 이런 설정은 Result 클래스를 XML로 변환했을때 flag, message, recordset 멤버변수 순서대로 XML 태그를 변환하여 보여준다

@XMLElement

 클래스의 멤버변수를 XML로 변환하여 멤버변수에 들어있는 값을 표현할때 사용한다. @XMLElement 설정은 클래스의 멤버변수를 선언한 부분 또는 멤버변수의 setter 함수에 설정한다. Result 클래스를 보면 flag 변수의 setter 함수인 setFlag 함수에 @XMLElement 어노테이션에 name="flag" 로 설정되어 있다. 이것은 flag 변수의 값을 <flag> 태그의 value 값으로 표현한다는 의미다

@XMLElementWrapper

 다른 XML 변환 클래스를 감싸는 역할을 한다. Result 클래스에서 recordset 변수는 검색 결과를 담는 것이기 때문에 검색 결과 클래스 객체가 List 형태로 들어있다. 이를 XML로 표현할때는 반복적으로 표현하게 되는 데 이런 반복적으로 표현되는 XML을 감싸는 태그가 필요하게 된다. 그때 이 어노테이션을 사용하게 된다. Result 클래스에서는 <recordset>이란 태그를 사용하여 이런 반복적인 결과 출력을 감싸게 된다

@XMLElementRefs, @XMLElementRef

 List 같은 컬렉션 클래스를 XML로 변환할때 컬렉션 클래스에 들어가지는 클래스의 이름을 정의하게 된다. Result 클래스의 recordset 변수의 getter 함수인 getRecordset 함수를 보면 이 어노테이션이 정의되어 있다. 이 recordset의 내부에 InfoVO 클래스 객체가 들어가기 때문에 type에 InfoVO.class를 주게 된다. name은 record로 줌으로써 InfoVO 클래스를 XML로 표현할때 <record> 태그로 감싸게 된다.

 

이렇게 해서 표현되는 Result 클래스와 InfoVO 클래스를 XML로 표현한 예가 다음과 같다.

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<result>
	<flag>0000</flag>
	<message>성공</message>
	<recordset>
		<record>
			<info_code>01</info_code>
			<info_name>마이인포 01</info_name>
		</record>
		<record>
			<info_code>02</info_code>
			<info_name>마이인포 02</info_name>
		</record>
		<record>
			<info_code>03</info_code>
			<info_name>마이인포 03</info_name>
		</record>
		<record>
			<info_code>04</info_code>
			<info_name>마이인포 04</info_name>
		</record>
	</recordset>
</result>

 

하지만 지금과 같은 설정으로 할 경우 위와 같은 형태로 결과가 나오지 않는다. 정확하게 말하자면 XML로 결과가 나오지만 한줄로 쭉 표현이 되기 때문에 위와 같이 알아보기 좋은 형태로 나타나지 않는다. 그래서 위와 같이 알아보기 좋은 형태로 출력할려면 Jaxb2Marshaller 클래스의 marshallerProperties 프로퍼티에 다음의 설정이 필요한다

 

<property name="marshallerProperties">
	<map>
	<entry>
		<key>
		<util:constant static-field="javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT" />
		</key>
		<value type="java.lang.Boolean">false</value>
	</entry>
	</map>
</property>

 

위와 같은 설정을 하면 XML을 알아보기 좋게 출력해준다. 이제 XML을 출력하기 위한 스프링 설정을 종합적으로 정리하면 다음과 같이 정리가 된다.

 

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
	<property name="order" value="1" />
	<property name="favorPathExtension" value="false" />
	<property name="mediaTypes">
		<map>
			<entry key="xml" value="application/xml" />
			<entry key="json" value="application/json" />
			<entry key="text" value="text/plain"/>
		</map>
	</property>
	<property name="ignoreAcceptHeader" value="false" />
	<property name="defaultViews">
		<list>
			<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
				<constructor-arg>
					<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
						<property name="classesToBeBound">
							<list>
								<value>myprj.vo.Result</value>
								<value>myprj.vo.Info</value>
							</list>
						</property>
						<property name="marshallerProperties">
							<map>
								<entry>
									<key>
										<util:constant static-field="javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT" />
									</key>
									<value type="java.lang.Boolean">false</value>
								</entry>
							</map>
						</property>
					</bean>
				</constructor-arg>
			</bean>
		</list>
	</property>
</bean>

 

지금까지 XML 출력에 대해 정리했다. 다음에는 Json 출력에 대해 설명하겠다