Spring을 이용한 RESTful 방식의 서비스를 구현하려다 보니 RESPONSE 데이터 타입에 대한 고민을 하게 되었다.


REST API를 서비스하는 대부분의 사이트(트위터, 페이스북, 포스퀘어 등)의 대부분이 JSON 타입을 기본으로 지원한다.

대부분의 Tutorial, Example 을 보더라도 JSON 형식으로 처리를 하게 끔 되어있다.

아무래도 JSON이 심플하고, 처리 속도도 빠르기 때문에 대세기 때문에 그런듯 하다.


바로 여기서 의문점이 들었다.

다른 타입으로 결과를 만들어서 주고 싶다면 어떻게 해야할까?!

xml, rss, pdf, doc는?!


실제 Twitter REST API 1.1의 샘플을 살펴보니 다음 예와 같이 URI에 따라서 여러가지 형태의 응답결과를 받을 수 있었다.


https://api.twitter.com/1.1/statuses/mentions_timeline.json  (JSON)


https://api.twitter.com/1.1/statuses/mentions_timeline.xml  (XML)


https://api.twitter.com/1.1/statuses/mentions_timeline.rss  (RSS)


그리고


https://api.twitter.com/1.1/statuses/mentions_timeline  (기본 웹 페이지)





Spring MVC 에서는 View단 처리를 하는 Resolver 가 다수 존재하는데 위의 예와 같이 type설정에 따라 응답 결과를 다르게 처리하도록 도와주는 역할을 하는 것이 바로 org.springframework.web.servlet.view.ContentNegotiatingViewResolver 이다.


다음은 spring-context.xml에서 ContentNegotiatingViewResolver 설정 예이다. 

json과 xml을 사용하는 방법에 대해서만 기술한다.


설정후에 서버를 스타트 해보니 로드시 ClassCastException 를 던졌다.

context 파일을 보니 <property name="mediaTypes">에 warning이 떠 있다.


spring-mvc 3.2.x 버전으로 테스트를 하고 있는데 클래스를 찾아가서 mediaTypes와 관련된 메소드를 찾아보니 Deprecated 되어 있다.

ContentNegotiationManager 라는 놈이 3.2부터 생겨 대체되어 있었다.

3.2 공식 문서에는 변경된 내용이 적용이 되어있지 않다. 역시 최신버전을 사용하는건 어렵다...


3.2 이상은 아래와 같이 설정한다.



아래 DTO 모델을 선언할 때 XML 마샬링(Marshalling)을 위해서 아래와 같이 XML 관련 어노테이션을 선언해준다.

@XmlRootElement의 users는 XML생성시에 루트 엘리먼트 이름이 된다. 중복되는 이름이 있을경우에 IllegalAnnotationExceptions 을 던지니 주의해야한다.


마지막으로 Controller 메소드에는 @ResponseBody 어노테이션을 선언하면 리턴하는 객체가 HTTP Response 로 넘어간다.

SampleDTO가 리턴 오브젝트이고 set 해준 값들이 출력된다.

아래는 Controller 선언문이다.




AND





JSONLibTest.java


TestBean.java



Introduction


JSON-lib (http://json-lib.sourceforge.net/)는 자바에서 beans, maps, collections, array 그리고 XML을 JSON 으로 변환하기 위해 사용하는 자바 라이브러리이다. 




Download


다음의 경로에서 다운로드 한다.



Dependencies


주의해야할 점은 common-lang 최신 버전(3.3.1)을 사용할 경우 org.apache.commons.lang.exception.NestableRuntimeException 이 발생하게 된다. 위의 경로에서 패키지를 다운 받아 등록하거나 pom.xml에 아래의 Dependency를 추가한다.



How to use json-lib


Array and Collection to JSON : 


Bean and Map to JSON : 


JSON to Beans : 


Json to XML AND XML to JSON : 

XMLSerializer.write()와 XMLSerializer.read() 를 사용하여 XML을 JSON으로 JSON을 XML로 변환할 수 있다.


그런데 위의 소스를 돌려보면 java.lang.NoClassDefFoundError: nu/xom/Element 를 던진다.

json-lib 사이트에서 살펴보면 이와 관련된 언급이 없는 것 같은데...


라이브러리를 하나 추가해야 한다.

XOM(XML Object Model) 라이브러리를 추가해야한다. 여기서 다운로드 할 수 있다.




위의 코드를 돌려보면서 발생할 수 있는 Exception은 다음과 같다.

  • java.lang.NoClassDefFoundError
    • 위의 의존성 패키지에서 누락된게 있는지 확인해본다.
  • org.apache.commons.lang.exception.NestableRuntimeException
    • 최신버전 (commons-lang3-3.1.jar) 사용시에 오류 발생
  • java.lang.NoClassDefFoundError: nu/xom/Element
    • XML을 사용할 때 라이브러리를 추가한다.




끝.



AND




Spring Framework 설정하고 런타임 서버를 올리는 과정에서 발생한 오류이다.

SEVERE: StandardWrapper.

Throwableorg.springframework.beans.factory.xml.XmlBeanDefinitionStoreException : Line 13 in XML document from ServletContext resource [/WEB-INF/dispatcher-servlet.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 13; columnNumber: 63; cvc-complex-type.2.4.c: 일치하는 와일드 카드 문자가 엄격하게 적용되지만 'context:component-scan' 요소에 대한 선언을 찾을 수 없습니다.

 

분명히 dispatcher-servlet.xml 어딘가에 오타가 있나 싶어 찾아보니 별다른 문제점이 없어 보였지만 sample을 찾아 다른게 뭔가 보았더니 역시 오타가 있었다. 아래와 같이 schemaLocation의 URL 뒤에 /가 붙어있어 발생된 오류였다.

 

수정전

...
xsi:schemaLocation="
    http://www.springframework.org/schema/beans/
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context/
    http://www.springframework.org/schema/context/spring-context-3.2.xsd"/>
...

 

수정후

...
xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.2.xsd"/>
...

 

그리고 spring framework의 버전보다 dtd정의에 사용된 버전이 높을 경우에도 발생할 수 있다고 한다.

 

(참고 : http://happytogether.tistory.com/179)



AND









AND




JCA(Java Cryptography Architecture)를 사용하여 암호화/복호화를 구현하려고 한다.





다음 조건과 같은 개발 환경으로 진행하였다.

  • JDK Version : JAVA SE 1.7.x
  • Crypto Algorithm : AES 256bit


그런데 아래와 같은 오류가 발생하였다.


java.security.InvalidKeyException: Illegal key size or default parameters
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1011)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)

구글링을 통해 포스팅된 내용을 보니 AES의 Maximum Keysize가 128bit이기 때문이라고 한다.
정책상 길이가 제한이 걸려있다고 하는데 다음은 암호화 알고리즘별 Maximum Keysize이다.

AlgorithmMaximum Keysize
DES64
DESede*
RC2128
RC4128
RC5128
RSA*
all others

128





그럼 256bit를 사용하기 위해서는 JCE(Java Cryptography Extension)를 설치해주면 되는데...




1. 먼저 아래경로에서 정책파일을 다운로드한다.
  • JDK 6 : http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
  • JDK 7 : http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

2. 다운로드된 파일(UnlimitedJCEPolicyJDKx.zip)의 압축을 풀어 local_policy.jar, US_export_policy.jar 를  
JDK가 설치된 디렉토리의 \jre\bin\security\ 에 넣어준다. (기존 파일은 백업을 받아놓는게 좋을 것 같다.)

3. 끝



Reference




AND




junit 사용중 다음과 같은 오류가 발생.


junit.framework.AssertionFailedError: No tests found in com.mydomain.TestJunit
at junit.framework.Assert.fail(Assert.java:47)
at junit.framework.TestSuite$1.runTest(TestSuite.java:97)
at junit.framework.TestCase.runBare(TestCase.java:134)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:124)
at junit.framework.TestSuite.runTest(TestSuite.java:232)
at junit.framework.TestSuite.run(TestSuite.java:227)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)


테스트 메소드명은 test로 시작되어야 한다.




AND