아래 블로그에 작성한 JAXB HttpClient 코드를 Spring OXM으로 수정해서 돌려보았다. 비교해볼만함

http://knight76.tistory.com/entry/JAXB-Http-Client-Example샘플

 

 

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

 

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

# ROOT.java

package spring;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

# XEmployee.java

 

public class XEmployee {

    private static List<String> employeeList;
    public static List<String> getEmployeeList() {
        return employeeList;
    }
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

여기까지는 바뀌는 것이 없다.  Spring 연동 코드가 바뀌었다. 연동코드는 형광펜으로 칠해놨다.

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/oxm
           http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">

    <oxm:jaxb2-marshaller id="jaxb2Marshaller">
        <oxm:class-to-be-bound name="spring.Root" />
    </oxm:jaxb2-marshaller>
   
    <!-- oxm:jaxb2-marshaller 에서 사용했던 것과 동일한 효과(예전 방식) 
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>spring.Root</value>
            </list>
        </property>
    </bean>
    -->

    <bean id="application" class="spring.CheckEmp">
        <property name="marshaller" ref="jaxb2Marshaller" />
        <property name="unmarshaller" ref="jaxb2Marshaller" />
    </bean>

</beans>

 

main 메서드

package spring;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.transform.stream.StreamSource;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class CheckEmp {
    @Resource(name = "jaxb2Marshaller")
    Unmarshaller unmarshaller;

    @Resource(name = "jaxb2Marshaller")
    Marshaller marshaller;
   
    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }
   
    public static final boolean isSaved = false;
    public static final String URL = "http://aaaaaaaaa";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

       public static void main(String[] args) throws Exception {
        ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        CheckEmp application = (CheckEmp) appContext.getBean("application");
        application.start();
    }

    private void start() throws Exception {
        BufferedReader reader = connectURL();
        if (isSaved) {
            saveFile(reader);
        }

        Root root = getRoot(reader);

         for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for (Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                      found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private Root getRoot(BufferedReader reader) throws Exception {
        StreamSource source = new StreamSource(reader);
        Root root = (Root) unmarshaller.unmarshal(source);
        return root;
    }

    private BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {
        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);

        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024];
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}

 

삽질 방지를 위해서 spring debug log가 출력되게 한다.

log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Threshold" value="DEBUG"/>
        <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
        </layout>
    </appender>
    <appender name="general" class="org.apache.log4j.ConsoleAppender">
       <layout class="org.apache.log4j.PatternLayout">
         <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%F:%L) %m%n"/>
       </layout>
    </appender>

    <logger name="com" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <logger name="org.springframework" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="general"/>
    </logger>

    <root>
        <level value="WARN"/>
        <appender-ref ref="STDOUT"/>
    </root>
</log4j:configuration>

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>test1123</groupId>
    <artifactId>teswt1</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <spring-version>3.1.0.RELEASE</spring-version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-xjc</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>jsr250-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
    </dependencies>

</project>

 

 

# 참조한 내용

Spring OXM 에 대한 자료 (Marshalling XML using O/X Mappers)

http://static.springsource.org/spring-ws/site/reference/html/oxm.html

 

 

# 기타

 

@Resource  annotation은 javax.annotation으로 플랫폼 독립적이며, jsr250에서 DI 의 리더들이 모여서 결정했다. jsr250-api.jar 를 반드시 pom.xml에 넣어야 한다.

<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>

name속성에 자동으로 연결될 빈객체의 이름을 입력한다
@Resource(name="marshaller")
Marshaller marshaller;




# CheckEmp.java 코드를 Spring JUnit으로 만들기

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext.xml"})
public class CheckEmp {

// 코드

 @Test
 public void testMarshaller() throws Exception {

 ...
}
}

Posted by '김용환'
,

 

apache commons의 http client와 jaxb로 어떻게 테스트할지에 대해서 작성한 글이다.

특정 URL을 호출하면 사원 정보가 나온다고 가정한다.

<ROOT>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

<EMPLOYEE>

<id></id>

<nm></nm>

….

</EMPLOYEE>

</ROOT>

 

객체는 Root-List<Employee>의 구조로 생각,

# Root.java 소스

package test;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "ROOT")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
   
    @XmlElement(name="EMPLOYEE")
    private List<Employee> employees;

    public Root() {
        employees = new ArrayList<Employee>();
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

}

 

# Employee.java

package test;

public class Employee {

private String id;
private String nm;

….

… // set, get

}

 

찾고 싶은 사람의 사번을 작성한다.

# XEmployee.java

public class XEmployee {

    private static List<String> employeeList;
   
    public static List<String> getEmployeeList() {
        return employeeList;
    }
   
    static {
        employeeList = new ArrayList<String>();

        employeeList.add("aaa");
         employeeList.add("bbb");
         employeeList.add("ccc");

    }
}

 

찾고 싶은 사람을 찾아서 출력한다.

 

# main 메서드

package test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class CheckEmp {
    public static final boolean isSaved = false;
    public static final String URL = "http://xxxxxxxx";
    public static JAXBContext jaxbContext;
    static {
        try {
            jaxbContext = JAXBContext
                    .newInstance(new Class[] { test.Root.class });
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

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

        // connect url
        BufferedReader reader = connectURL();

        // save file
        if (isSaved) {
            saveFile(reader);
        }
       

        // get object
        Root root = getRoot(reader);

        // find
        for (String retiredEmpID : XEmployee.getEmployeeList()) {
            boolean found = false;
            for(Employee emp : root.getEmployees()) {
                if (emp.getID().trim().equalsIgnoreCase(retiredEmpID)) {
                    found = true;
                }
            }
            if (found == false) {
                System.out.println(retiredEmpID);
            }
        }
    }

    private static Root getRoot(BufferedReader reader) throws JAXBException {
        Root root = (Root) jaxbContext.createUnmarshaller().unmarshal(reader);
        return root;
    }

    private static BufferedReader connectURL() throws IOException,
            ClientProtocolException, UnsupportedEncodingException {

        HttpGet httpget = new HttpGet(URL);
        HttpClient httpclient = new DefaultHttpClient();
        httpclient.getParams().setParameter("http.socket.timeout", 999000);
       
        HttpResponse response = httpclient.execute(httpget);
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new RuntimeException("url error");
        }
        InputStream instream = entity.getContent();
        BufferedReader reader = new BufferedReader(new InputStreamReader(instream, "utf-8"));
        return reader;
    }

    private static void saveFile(BufferedReader reader) throws IOException {
        Writer fstream = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("aaa.txt"), "utf-8"));
        BufferedWriter out = new BufferedWriter(fstream);
        char[] a = new char[1024] ;
        while (reader.read(a) > 0) {
            out.write(a);
        }
        out.flush();
        System.out.println("File created successfully.");
    }

}


# pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>test1123</groupId>
 <artifactId>teswt1</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <dependencies>
  <dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
   <version>4.1.2</version>
  </dependency>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-xjc</artifactId>
   <version>2.2</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.2</version>
  </dependency>
 </dependencies>
</project>
Posted by '김용환'
,

JAXB 잘 사용하기

general java 2011. 12. 21. 16:01

2010년 초에 정리한 줄 알았는데, 과거 메일을 참조로 정리한다.
이 내용은 현재 JAXB 버전과 다를 수 있다.


JAXB에 대해서..
java XML 파서중에 Xstream, JAXB 가 있다. 취향에 따라서 다양하게 쓰기는 하는데..
아직 까지는 잘만 쓴다면 JAXB가 성능이 조금 좋은 것 같았다. JAXB 에 대한 이슈와 오해를 정리하고 어떻게 해야 잘 쓸 수 있는 것인지 확인한다.


JAXB의 초기화
XML 파서인 JAXB는 DOM을 사용해서 상당히 느렸는데, 최근에는 SAX와 STAX를 사용하여 성능이 좋아지고 있으나, 주의해야 하는 것은 JAXBContext를 매번 생성하지 않고 한번 생성된 것을 재사용하는 구조로 써야 한다. QName의 String.intern을 많이 호출하는 성능 상 이슈와 GC상 perm 영역에서 minor GC가 많이 일어난다. 만약 Perm gen 영역을 작게 했을 때는 OOME가 perm gen에서 일어날 수도 있다.

많은 개발자들이 JAXB를 쓸 때 항상 조심해야 하는 것으로 한번만 JAXBContext 객체를 하라는 내용이 많으나 그 중의 hudson 개발자인 가와구치도 Hudson commitor들에게 얘기한 내용이 있다.

http://markmail.org/message/hy2tmioxev7skqkl#query:+page:1+mid:hy2tmioxev7skqkl+state:results

TIP 1. Never create the same JAXBContext more than once

-------------------------------------------------------
Always, always assign JAXBContext to a static final field. A JAXBContext object can be shared safely by multiple threads. This is a very expensive to create, so create it once, and share it.





JAXB의 Thread Safe

JAXBContext 가 Thread safe하지 않다고 알고 있는 사람이 있기도 하다. Thread safe 하다.다만..
마셜러와 언마셜러와 밸리데이터는 thread safe하지 않기 때문에 잘 써야 한다.  JAXBContext 를 단독으로 쓸 때 잘 유의할 필요는 있다. 그러나 Spring과 연계되어서 쓸 때는 내부적으로 잘 thread safe하게 해서 편하게 쓸 수 있다.

http://jaxb.java.net/guide/Performance_and_thread_safety.html


8.1. Performance and thread-safety
The JAXBContext class is thread safe, but the Marshaller, Unmarshaller, and Validator classes are not thread safe.

For example, suppose you have a multi-thread server application that processes incoming XML documents by JAXB. In this case, for the best performance you should have just one instance of JAXBContext in your whole application like this:

Singleton JAXBContext
class MyServlet extends HttpServlet {   
    static final JAXBContext context = initContext();   
    private static JAXBContext initContext() {       
        return JAXBContext.newInstance(Foo.class,Bar.class);   
    }
}

And each time you need to unmarshal/marshal/validate a document. Just create a new Unmarshaller/Marshaller/Validator from this context, like this:

Thread local Unmarshaller
    public void doGet( HttpServletRequest req, HttpServletResponse ) {       
         Unmarshaller u = context.createUnmarshaller();       
          u.unmarshal(...);   
    }

This is the simplest safe way to use the JAXB RI from multi-threaded applications.

If you really care about the performance, and/or your application is going to read a lot of small documents, then creating Unmarshaller could be relatively an expensive operation. In that case, consider pooling Unmarshaller objects. Different threads may reuse one Unmarshaller instance, as long as you don't use one instance from two threads at the same time.


 




JAXB의 Accessor
jaxb-api, jaxb-impl 2.0 library을 기준으로 사용했고, 다음의 2가지 기준이 맞아야 Permgen영역에서 OOME 가 발생한다.,

JAXB의 plain object는 다음과 같이 사용할 수 있다.




@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder = {"to", "from", "body"})
public class Message2 {
 private String to;

 private String from;

 private String body;
 
 @XmlElement(name = "TO")
 public String getTo() {
  return to;
 }
 public void setTo(String to) {
  this.to = to;
 }
 
 @XmlElement(name = "from")
 public String getFrom() {
  return from;
 }
 
 public void setFrom(String from) {
  this.from = from;
 }

 
 @XmlElement(name = "body")
 public String getBody() {
  return body;
 }
 
 public void setBody(String body) {
  this.body = body;
 }

}


 





원인을 잘 살펴본다.



Accessor의 GetterSetterReflection 인스턴스의 optimize가 호출되면, property에 대해서 getter와 setter 가 있는지 확인합니다.


         @Override
        public Accessor<BeanT,ValueT> optimize(JAXBContextImpl context) {
            if(getter==null || setter==null)
                // if we aren't complete, OptimizedAccessor won't always work
                return this;
            if(context!=null && context.fastBoot)
                // let's not waste time on doing this for the sake of faster boot.
                return this;

            Accessor<BeanT,ValueT> acc = OptimizedAccessorFactory.get(getter,setter);
            if(acc!=null)
                return acc;
            else
                return this;
        }
 


 

만약 getter/setter 메서드 중 하나라도 존재하지 않으면, 그냥 JAXB 내부의 Optimize 작업을 하지 않는다. 클래스 로딩 작업이 없게 된다. 만약 setter, getter가 존재하면, 내부 Optimize작업을 하는데,  Optimize 작업이 바로 내부 클래스를 직접 생성한다.

 OptimizedAccessorFactory 클래스 내부 소스를 보면, 선언된 클래스이름에 JaxbAccessorM_getter이름_setter이름_property타입의 클래스를 생성한다.

 String newClassName = toVMClassName(getter.getDeclaringClass())+"$JaxbAccessorM_"+getter.getName()+'_'+setter.getName()+'_'+typeName;

 

* 참고 : Accessor에 대한 개념 파악에 도움되는 글

Improving field get and set performance with ASM or Javassist

http://stackoverflow.com/questions/3022741/improving-field-get-and-set-performance-with-asm-or-javassist


public class AccessorGenerator { 
 
   
private final ClassPool pool; 
 
   
public PropertyGenerator() { 
        pool
= new ClassPool(); 
        pool
.appendSystemPath(); 
   
} 
 
   
public Map<String, PropertyAccessor> createAccessors(Class<?> klazz) throws Exception { 
       
Field[] fields = klazz.getDeclaredFields(); 
 
       
Map<String, PropertyAccessor> temp = new HashMap<String, PropertyAccessor>(); 
       
for (Field field : fields) { 
           
PropertyAccessor accessor = createAccessor(klazz, field); 
            temp
.put(field.getName(), accessor); 
       
} 
 
       
return Collections.unmodifiableMap(temp); 
   
} 
 
   
private PropertyAccessor createAccessor(Class<?> klazz, Field field) throws Exception { 
       
final String classTemplate = "%s_%s_accessor"; 
       
final String getTemplate = "public Object get(Object source) { return ((%s)source).%s; }"; 
       
final String setTemplate = "public void set(Object dest, Object value) { return ((%s)dest).%s = (%s) value; }"; 
 
       
final String getMethod = String.format(getTemplate,  
                                               klazz
.getName(), 
                                               field
.getName()); 
       
final String setMethod = String.format(setTemplate,  
                                               klazz
.getName(),  
                                               field
.getName(),  
                                               field
.getType().getName()); 
 
       
final String className = String.format(classTemplate, klazz.getName(), field.getName()); 
 
       
CtClass ctClass = pool.makeClass(className); 
        ctClass
.addMethod(CtNewMethod.make(getMethod, ctClass)); 
        ctClass
.addMethod(CtNewMethod.make(setMethod, ctClass)); 
        ctClass
.setInterfaces(new CtClass[] { pool.get(PropertyAccessor.class.getName()) }); 
       
Class<?> generated = ctClass.toClass(); 
       
return (PropertyAccessor) generated.newInstance(); 
   
} 
 
   
public static void main(String[] args) throws Exception { 
       
AccessorGenerator generator = new AccessorGenerator(); 
 
       
Map<String, PropertyAccessor> accessorsByName = generator.createAccessors(PurchaseOrder.class); 
 
       
PurchaseOrder purchaseOrder = new PurchaseOrder("foo", new Customer()); 
 
        accessorsByName
.get("name").set(purchaseOrder, "bar"); 
       
String name = (String) accessorsByName.get("name").get(purchaseOrder); 
       
System.out.println(name); 
   
} 
} 


jaxb가 reflection을 사용하면 속도가 너무 느리기 때문에 Accessor 클래스를 만들어서 속도를 향상시키고 private 멤버도 쉽게 접근하는 구조를 가지고 있다. 따라서 메소드를 확인하는 작업이 줄어들어서 속도를 향상시키는 패턴을 가지고 있다.로그를 보면 구체적으로 알 수 있다.


Plain Object인  Message2 를 만들고 JAXBContext로 클래스를 매번 생성하게 하였다.

@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder = {"to", "from", "body"})
public class Message2 {
 private String to;

 private String from;

 private String body;
 
 @XmlElement(name = "x")

 public String getTo() {
  return to;
 }
 public void setTo(String to) {
  this.to = to;
 }

 




jvm 출력 로그는 다음과 같다. AccessroM_ 클래스가 생성되었음을 확인할 수 있다.

[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getBody_setBody_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getBody_setBody_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getFrom_setFrom_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getFrom_setFrom_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getTo_setTo_java_lang_String from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorM_getTo_setTo_java_lang_String from __JVM_DefineClass__]


 

만약 getter/setter 메서드를 없앤 클래스를 테스트해보았다.

@XmlRootElement(name = "message")
@XmlAccessorType(XmlAccessType.FIELD)
public class Message2 {
 public String to;

 public String from;

 public String body;
}




출력되는 jvm 로그는 다음처럼 Accessor가 생기는 것을 볼 수 있다.

Loaded com.google.spring.bean.Message2$JaxbAccessorF_to from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_to from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_from from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_from from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_body from __JVM_DefineClass__]
[Loaded com.google.spring.bean.Message2$JaxbAccessorF_body from __JVM_DefineClass__]

 
JAXB OOME 테스트

매번 JAXBContext를 생성하면 과연 OOME가 일어날까?
jvm 의 perm gen 영역과 heap 영역을 작게 했다.

-XX:PermSize=2500k
-XX:MaxPermSize=2500k
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+TraceClassUnloading
-XX:+TraceClassLoading
-Xms10m
-Xmx10m





모니터링시
생성된 자바 loaded class 가 계속 상승하여 jvm에 부하를 주지만, heap, perm gen 영역은 그리 메모리가 크게 늘어나지 않았다.



아마도 OOME가 나는 이유는 web에서 session 단위로 hashmap으로 jsp로 전달되는 과정에서 (서블릿 스펙상 session은 최소 1초로 사용) 일어난 것으로 생각된다. 일반 Application에서는 큰 영향을 없을 수 있다.


마치며..
JAXB는 성능이 좋아 사람들에게 많이 쓰이고 있다. 매번 클래스를 Reflection 해서 정보를 얻어오기 보다  Accessor클래스를 생성하고 있다. 만약 JAXBContext 를 매번 호출하면 Accessor 클래스 로딩이 호출시마다 일어나 jvm, gc에 부하를 줄 수 있다. JAXBContext는 한번만 초기화하고 객체를 재사용할 필요가 있다.


Posted by '김용환'
,








[예제 프로젝트(maven)]


(데몬 실행)
maven clean compile jetty:run





[PPT 설명]

특징
- REST를 쓰기 위해서 Spring MVC 모델을 그대로 차용, Annotation 이용(Controller)
- JSR 311을 따르지는 않지만, 대부분의 기능 구현 (기존의 코드를 최대한 재사용)
- 하나의 리소스는 여러 개의 Represenation을 가질 수 있도록 함 (JSON/XML/ATOM/RSS)
- 브라우저에서 지원하지 않는 PUT & POST 요청을 처리할 수 있음
- Custom parser 이용 가능
- UTIL(converter..) 클래스 지원
=> REST 관련 Conception만 조금 공부하면 되고, 나머지는 기존 MVC만 알면 되기 까닭에 재사용성이 큼




Annotation 지원 
@Controller : MVC
@RequestMapping : HTTP 메소드, URI, 헤더 처리@RequestMapping(method=RequestMethod.GET, value="/members", 
@PathVariable : 메소드 안에서 파라미터와 매핑하기 위해서 사용public ModelAndView getEmployee(@PathVariable String id) { … } 
@RequestParam : URL 매개변수 이용
@RequestHeader
@RequestBody
@HttpEntity<T>
@ResponseEntity<T> : 정의한대로 response 리턴
@ResponseStatus 
@ExceptionHandler 

public @ResponseBody Employee getEmployeeBy(@RequestParam("name") String name, @RequestHeader("Accept") String accept, @RequestBody String body) {…} 
public ResponseEntity<String> method(HttpEntity<String> entity) {…} 







- @Controller

@Controller
@RequestMapping("/restful")
public class RestfulController {

@RequestMapping(value = "/message/{name}", method = RequestMethod.GET)
public String getMessage(@PathVariable String name, ModelMap model) {
model.addAttribute("message", name);
return "list";
}
}




- Request Parameter Type 
Strong type 형태로 파라미터를 받는다. 

@Controller
@RequestMapping("/restful")
public class RestfulController {

@RequestMapping(value = "/message/{name}", method = RequestMethod.GET)
public String getMessage(@PathVariable String name, ModelMap model) {
model.addAttribute("message", name);
return "list";
}
}





- @PathVariable 
 path를 받는 Annotation

@Controller
@RequestMapping("/restful")
public class RestfulController {

@RequestMapping(value = "/message/{name}", method = RequestMethod.GET)
public String getMessage(@PathVariable String name, ModelMap model) {
model.addAttribute("message", name);
return "list";
}
}




@ResponseBody
Response를 xml형태로 보여줄 수 있다.

@Controller
@RequestMapping("/restful")
public class RestfulController {

@RequestMapping(value = "/messagebody/{message}", method = RequestMethod.GET)
@ResponseBody
public Body getMessageBody(@PathVariable String message, ModelMap model) {
Body body = new Body();
body.setMessage(message);
return body;
}
}




@XmlRootElement(name = "body")
public class Body {
@XmlElement
private String msg;

public String getMessage() {
   return msg;
}

public void setMessage(String message) {
    this.msg = message;
}
}




@ResponseStatus
만약 에러가 나면 특정 Http Status 정보를 response로 보낼 수 있다.


@RequestMapping(value = "/exception", method = RequestMethod.GET)
public String throwException() {
    throw new ResourceNotFoundException();
}


@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(value = HttpStatus.BAD_GATEWAY) 
public void handleNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
     System.out.println("handleNotFoundException:" + ex);
}

…..

class ResourceNotFoundException extends RuntimeException { 


<실행결과>


$ curl -i  localhost:8080/restful/exception
HTTP/1.1 502 Bad Gateway
Content-Length: 0
Server: Jetty(6.1.26)




- Rest 방식과 기존 파라미터 요청 방식 을 같이 사용 가능?? 
사용가능하다. 

@RequestMapping(value = "/test/{name}/id/{id}", method = RequestMethod.GET)
public String getString(@PathVariable String name, @PathVariable int id,                                String email, ModelMap model) {
    model.addAttribute("message", name);
    model.addAttribute("id", id);
    model.addAttribute("email", email);
    return "test";
}




// test.jsp
<html>
<head></head>
<body>
<h1>Test</h1>
<h3>message : ${message}</h3>
<h3>email : ${email}</h3>
</body>
</html>


실행결과



$ curl http://localhost:8080/restful/test/jaja/id/11?email=aaa@google.com
<html>
<head></head>
<body>
        <h1>Test</h1>
        <h3>message : jaja</h3>
        <h3>email : aaa@google.com</h3>
</body>
</html>



- json이나 xml payload 요청하기

Spring MVC가  알아서 deserialization을 해줌!!

@Controller

@RequestMapping("/comm")

@ResponseStatus(value = HttpStatus.ACCEPTED)

public class JsonRequestController {

    @RequestMapping(method = RequestMethod.GET) 

    @ResponseStatus(value=HttpStatus.FOUND)

    public void sendFruitMessage(@RequestBody Message name) {

        System.out.println("name : " + name.getName());

        return ;

    }

}


 


@XmlRootElement(name = "message")

public class Message {

       @XmlElement

private String name;

public String getName() {

return name;

}

      public void setName(String name) {

this.name = name;

     }

}



<테스트>

XML Payload



<클라이언트>

$ curl -i -H "Content-Type: application/xml" -X get -d '<message><name>Kim Yong Hwan</name></message>' localhost:8080/comm

HTTP/1.1 302 Found

Content-Length: 0

Server: Jetty(6.1.26)


<서버>

name : Kim Yong Hwan


 
JSon Payload


<클라이언트>

$ curl  -i -H "Content-Type: application/json" -X get -d '{"name":"Kim Yong Hwan"}' localhost:8080/comm

HTTP/1.1 302 Found

Content-Length: 0

Server: Jetty(6.1.26)


<서버>

name : Kim Yong Hwan





* 실제 테스트 코드

web.xml

<web-app id="WebApp_ID" version="2.4"

xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


<display-name>Spring Web MVC Rest Demo Application</display-name>


<servlet>

    <servlet-name>mvc-dispatcher</servlet-name>

    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <load-on-startup>1</load-on-startup>

</servlet>


<servlet-mapping>

    <servlet-name>mvc-dispatcher</servlet-name>

    <url-pattern>/</url-pattern>

</servlet-mapping>


<context-param>

    <param-name>contextConfigLocation</param-name>

    <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>

</context-param>


<listener>

    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>


</web-app>





mvc-dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="

        http://www.springframework.org/schema/beans     

        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

        http://www.springframework.org/schema/context 

        http://www.springframework.org/schema/context/spring-context-3.0.xsd

        http://www.springframework.org/schema/mvc

        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">


<context:component-scan base-package="com.google.controller" />


<mvc:annotation-driven />


<bean

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

    <property name="prefix" value="/WEB-INF/pages/" />

    <property name="suffix" value=".jsp" />

</bean>


</beans>





RestfulController.java

@Controller

@RequestMapping("/restful")

public class RestfulController {


@RequestMapping(value = "/message/{name}", method = RequestMethod.GET)

public String getMessage(@PathVariable String name, ModelMap model) {

model.addAttribute("message", name);

return "list";

}


@RequestMapping(value = "/command/{id}/content/{content}", method = RequestMethod.GET)

public String getCommand(@PathVariable("id") String id, @PathVariable("content") long content, ModelMap model) {

model.addAttribute("id", id);

model.addAttribute("content", content);

return "command";

}


@RequestMapping(value = "/link/{id}", method = RequestMethod.DELETE)

public String deleteLink(@PathVariable("id") String id, ModelMap model) {

model.addAttribute("id", id);

return "delete";

}

}




// WEB-INF/pages/list.jsp

<html>

<body>

<h1>Spring MVC Restful Test</h1>

<h3>message : ${message}</h3>

</body>

</html>




// WEB-INF/pages/command.jsp

<html>

<body>

<h1>Spring MVC Restful Test</h1>

    <h3>command id : ${id}</h3>

<h3>content : ${content}</h3>

</body>

</html>




// WEB-INF/pages/delete.jsp

<html>

<body>

<h1>Spring MVC Restful Test</h1>

<h3>deleted id : ${id}</h3>

</body>

</html>




테스트결과
자세한 것은 PPT 또는 프로젝트로 확인


웹콜 테스트

http://localhost:8080/restful/message/reset
http://localhost:8080/restful/command/aa/content/111

시그윈 또는 linux 테스트
curl -X DELETE http://localhost:8080/restful/link/1
curl -X DELETE http://localhost:8080/restful/message/3









Content Negotiation
- HTTP 1.1 스펙에서 정의
- 의미
   media type, 언어, 문자집합, 인코딩 등에 대해 브라우저가 제공한 선호도에 따라 자원의 가장 적합한 표현을 선택. 불완전한 협상 정보를 보내는 브라우저의 요청을 지능적으로 처리하는 기능
- 일반적으로 다른 프로토콜을 쓰려면, request의 “type” 파라미터를 확인하고, 이에 맞는 marshalling 정보를 전달해야 한다.
- 트위터 API가 이렇게 사용되고 있음
- Spring MVC에서는 한번에 해결해주는 클래스 사용 ContentNegotiatingViewResolver
- 요청 데이터 포맷에 맞춰 다양한 MIME(미디어 타입) 이나 content type으로 전달 가능
ATOM, RSS, XML, JSON
예)
http://localhost:8080/fruit/banana.xml
http://localhost:8080/fruit/banana.rss
http://localhost:8080/fruit/banana.html

Accept : application/xml
Accept : application/json
Accept : application/html


- 코드

FruitController.java

@Controller

@RequestMapping("/fruit")

public class FruitController {


    @RequestMapping(value="{fruitName}", method = RequestMethod.GET)

    public String getFruit(@PathVariable String fruitName, ModelMap model) {

    Fruit fruit = new Fruit(fruitName, 1000);

    model.addAttribute("model", fruit);

    return "listfruit";

    }

}




 
Fruit.java


@XmlRootElement(name = "fruit")
public class Fruit {
String name;
int quality;

public Fruit() {} 
  
public Fruit(String name, int quality) {
    this.name = name;
    this.quality = quality;
}
 
@XmlElement
public void setName(String name) {
this.name = name;
}
  
@XmlElement
public void setQuality(int quality) {
this.quality = quality;
}
}



mvc-dispatcher-servlet.xml


<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="order" value="1" />
  <property name="mediaTypes">
<map>
   <entry key="json" value="application/json" />
   <entry key="xml" value="application/xml" />
   <entry key="rss" value="application/rss+xml" />
</map>
  </property>
 
  <property name="defaultViews">
<list>
  <!-- JSON View -->
  <bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
  </bean>
 
  <!-- RSS View -->
  <bean class="com.google.rss.RssFeedView" />
 
  <!-- JAXB XML View -->
  <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
   <property name="classesToBeBound">
<list>
   <value>com.google.bean.Fruit</value>
</list>
   </property>
</bean>
</constructor-arg>
  </bean>
 </list>
  </property>
  <property name="ignoreAcceptHeader" value="true" />
 
</bean>
 
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value="2" />
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp" />
</bean>
 



결과

$ curl -H 'Accept: application/xml' localhost:8080/fruit/banana.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><fruit><name>banana</name

><quality>1000</quality></fruit>



 

$ curl -H 'Accept: application/rss' localhost:8080/fruit/banana.rss

<?xml version="1.0" encoding="UTF-8"?>

<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">

  <channel>

    <title>Sample Title</title>

    <link>http://google.com</link>

    <description>Sample Description</description>

    <item>

      <link>http://www.google.com</link>

      <content:encoded>banana1000</content:encoded>

      <author>Test</author>

    </item>

  </channel>

</rss>




$ curl -H 'Accept: application/json' localhost:8080/fruit/banana.json

{"model":{"quality":1000,"name":"banana"}}










웹브라우져에서는 PUT/DELETE를 쓸 수 없다. 

- HTML version 4 과 XHTML 1에서는 HTML  form안의 HTTP 요청은 GET과 POST 방식만 허용. 그동안 put/delete 메소드를 사용하려면, XMLHttpRequest를 이용하였음
- HTTP상에서는 ok! 
- HTML5에서 put/delete는 사용하지 못한다고 나와 있음
    http://www.w3.org/TR/html5-diff/
     Using PUT and DELETE as HTTP methods for the form element is no longer supported.

Spring MVC는 이 것을 가능하게 함

=> 자세한 것은 PPT 참조





클라이언트 API - RestTemplate
기존의 JDBC Template과 비슷.

Map<String, String> vars = new HashMap<String, String>();

vars.put("id", "111");

vars.put("content", "222");

RestTemplate restTemplate = new RestTemplate();

String result = restTemplate.getForObject("http://localhost:8080/restful/command/{id}/content/{content}", String.class, vars);

System.out.println("result : " + result);



결과
 

result : <html>

<body>

<h1>Spring MVC Restful Test</h1>

    <h3>command id : 111</h3>

<h3>content : 222</h3>

</body>

</html>






좋은 자료(레퍼런스)
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html
http://dev.anyframejava.org/docs/anyframe/plugin/restweb/1.0.1/reference/html/index.html
http://www.mkyong.com/spring-mvc/spring-3-mvc-contentnegotiatingviewresolver-example/
http://www.ibm.com/developerworks/web/library/wa-spring3webserv/index.html
http://blog.springsource.com/2010/01/25/ajax-simplifications-in-spring-3-0/












Posted by '김용환'
,