ตัวอย่างการใช้งาน RestFul ใน SpringMVC3.0 คือเราจะสร้างแอพพลิเคชั่นที่สร้าง Content เหมือน Blog ขึ้นมาและเนื้อหาสามารถถูกแปลงจากการแสดงผลแบบ HTML ปกติให้ไปเป็น Atom Feed ได้ด้วยก่อนอื่นเรามาดูการสร้าง
Content ก่อนเพราะเราจะใช้ package de.svenjacobs.loremipsum.LoremIpsum เพื่อสุ่มสร้าง content มาดังนี้
...
import de.svenjacobs.loremipsum.LoremIpsum;
public class SampleContent {
private static final LoremIpsum textGenerator = new LoremIpsum();
private static final Random random = new Random();
private static int idCounter = 0;
private String author;
private Date publicationDate;
private String text;
private int id;
public static SampleContent generateContent(String author, Date date) {
SampleContent content = new SampleContent();
content.author = author;
content.publicationDate = date;
content.id = idCounter++;
content.text = textGenerator.getParagraphs(random.nextInt(9) + 1);
return content;
}
.
.
// get set
.
}
จากนั้นเราจะสร้าง Controller ที่ทำหน้าที่สร้าง Content ที่สร้างวิวแบบ HTML ปกติก่อน
@Controller
public class ContentController {
private List<SampleContent> contentList = new ArrayList<SampleContent>();
@RequestMapping(value="/content", method=RequestMethod.GET)
public ModelAndView getContent() {
ModelAndView mav = new ModelAndView();
mav.setViewName("content");
mav.addObject("sampleContentList", contentList);
return mav;
}
@RequestMapping(value="/content.html", method=RequestMethod.POST)
public String addContent() {
contentList.add(SampleContent.generateContent("Alef Arendsen", new Date()));
return "redirect:content.html";
}
}
การทำงานของ handler ตัวแรกจะทำหน้าที่ลิสท์ไอเท็ม SampleContent ออกมาโดยมันจะรับ request ประเภท GET เข้ามา ส่วน handler ตัวที่สองจะทำหน้าที่เพิ่มคอนเทนท์ SampleContent เข้าไปด้วยการใช้
SampleContent.generateContent() ส่วน handler ตัวนี้จะรับ request ประเภท POST นอกจากนี้เราใช้ @RequestMapping เพื่อระบุ URI ที่เราระบุให้ handler ซึ่งการใช้งาน @RequestMapping นั้นเราต้องไปเพิ่ม Configuration
ชื่อ Component Scanner เข้าไปที่ rest-servlet.xml นอกจากนี้ส่วนสำคัญที่เราต้องใช้ในการทำงาน REST สำหรับ Spring คือ ViewResolver ซึ่งในกรณีนี้เราจะใช้ InternalResourceViewResolver ซึ่งมันจะทำหน้าที่เลือกชนิดของวิวให้ถูกต้อง
(ในกรณีนี้คือ content เพราะเราใช้ getContent() ใน handler)
Implementing the AtomView
สำหรับการสร้าง AtomView เราจะใช้ Rome โปรเจคโดยส่วนของ AtomView นั้นเราจำเป็นต้องสร้างวิวของเราเองเพื่อใช้ในการแสดงผล Atom โดยเราจะสร้าง SampleContentAtomView ที่มีรายละเอียดการทำงานดังนี้
public class SampleContentAtomView extends AbstractAtomFeedView {
@Override
protected void buildFeedMetadata(Map model, Feed feed, HttpServletRequest request) {
System.out.println("SampleContentAtomView.buildFeedMetadata");
feed.setId("tag:springsource.com");
feed.setTitle("Sample Content");
@SuppressWarnings("unchecked")
List<SampleContent> contentList = (List) model.get("sampleContentList");
for (SampleContent content : contentList) {
Date date = content.getPublicationDate();
if (feed.getUpdated() == null || date.compareTo(feed.getUpdated()) > 0) {
feed.setUpdated(date);
}
}
}
@Override
protected List buildFeedEntries(Map model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
@SuppressWarnings("unchecked")
List<SampleContent> contentList = (List) model.get("sampleContentList");
List entries = new ArrayList(contentList.size());
for (SampleContent content : contentList) {
Entry entry = new Entry();
String date = String.format("%1$tY-%1$tm-%1$td", content.getPublicationDate());
// see http://diveintomark.org/archives/2004/05/28/howto-atom-id#other
entry.setId(String.format("tag:springsource.com,%s:%d", date, content.getId()));
entry.setTitle(String.format("On %s, %s wrote", date, content.getAuthor()));
entry.setUpdated(content.getPublicationDate());
Content summary = new Content();
summary.setValue(content.getText());
entry.setSummary(summary);
entries.add(entry);
}
return entries;
}
}
ส่วนสุดท้ายคือการการกำหนด media type เพิ่มสำหรับ Atom เนื่องจาก Atom จะใช้ ‘application/atom+xml’ สำหรับ Html เราจะใช้ ‘text/html’ หรือ ‘text/xhtml’ ซึ่งโดยปกติเบราส์เซอร์จะกำหนดค่ากลุ่มของ media type ที่มันส่งออไปไปพร้อม
กับ HTTP header และไม่มีทางที่เราจะเปลี่ยนประเภทของ media type ได้เลยโดยการใช้ browser ดังนั้นการใช้ประเภทของไฟล์ (*.atom, *.json …) เป็นทางออกที่ดีในการบอกเซิร์ฟเวอร์ว่าประเภทของ representation ที่เราต้องการคืออะไร
Spring 3.0 มี ContentNegotiatingViewResolver นั้นสามารถทำงานได้ทั้ง extension และ accept header และหลังจากที่เรากำหนด media type ที่เราต้องการใช้มันจะทำหน้าที่ส่งงานให้กับ view resolver ตัวที่เหมาะสามให้กับเราโดยในตัวอย่าง
นี้มันจะทำการโยนไปให้ BeanNameViewResolver( resolve ตัว SampleContentAtomView ในกรณีที่ต้องการ ) หรืออาจจะเป็น InternalResourceViewResolver ซึ่งเราจะต้องเปลี่ยน Configuration ให้เป็นแบบด้านล่าง
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="atom" value="application/atom+xml"/>
<entry key="html" value="text/html"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
</bean>
ตัว ContentNegotiatingViewResolver จะทำการหา extension ก่อนว่ามีหรือไม่หลังจากที่ได้รับ Accept Header มาแล้วดังนั้นเราจะต้องแมป extension ให้เหมาะกับ media types ที่เราต้องการได้เช่น .html เราต้องกำหนดให้ใช้ text/html ส่วน
.atom เราจะให้ใช้ application/atom+xml ดังนั้นเมื่อ .atom ถูกส่งเข้ามาตัว ContentNegotiatingViewResolver จะทำการหาวิวที่เหมาะสมกับ application/atom+xml ส่วนในกรณีที่เป็น .html มันจะทำการเปลี่ยนไปหาวิวที่ใช้แสดง text/html
จากนี้เราจะสามารถเรียก
http://localhost:8888/SpringMVC3RestFul-1.0-SNAPSHOT/rest/content.html
เพื่อแสดงและเพิ่ม content ได้
http://localhost:8888/SpringMVC3RestFul-1.0-SNAPSHOT/rest/content.atom
ใช้สำหรับแสดงผลเป็น Atom Feed ครับ
******* source code สามารถ check out ได้ที่ http://code.google.com/p/spring-atomfeed-view/
