'Spring MVC'에 해당되는 글 1건

  1. 2011.08.10 Spring MVC 3 Restful 맛보기 (2)








[예제 프로젝트(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 김용환 '김용환'