A Bit of Background
Spring RESTful ถูกเพิ่มเข้าไปในกลไกการทำงานของ Spring-MVC ตรงๆอันเนื่องมาจากเหตุผลทางเทคนิคบางประการนั่นหมายความว่าจะมีบางเรื่องใน Spring RESTful ไปซ้ำเข้ากับ JAX-RS นั่นเป็นเรื่องที่ทางทีมพัฒนาได้พยายามหลีกเลี่ยงอย่างมากแล้วแต่ก็หลีกไม่พ้น แต่สิ่งที่ได้กลับมาคือเราสามารถหรับตัวเข้ากับโมเดลการทำงานแบบนี้ได้อย่างง่ายดายไม่ว่าจะเป็นคนที่เคยทำงานกับ SpringMVC มาแล้วหรือแม้กระทั่งกับนักพัฒนาหน้าใหม่ที่ต้องการลองใช้ SpringMVC เองก็ตาม นอกจากนี้ปัจจุบันเองมีเฟรมเวิร์คหลายตัวที่รองรับการทำงานของ JAX-RS ยกตัวอย่างเช่น Jersey, RESTasy และ Restlet (มันเยอะอยู่แล้วจะเพิ่มไปอีกตัวก็ไม่เกิดประโยชน์อะไร)
ต่อไปเราจะมาดูองค์ประกอบหลักๆที่ทำให้เราสามารถทำ RESTful บน SpringMVC ได้แบบรายตัว
URI Templates
URI เทมเพลทก็คือ URI ที่เป็นสตริงที่เกิดขึ้นจากการรวมเอาชื่อตัวแปรและค่าของมันเข้าไป ซึ่งใน Spring 3.0 M1 มีเริ่มให้มรการใช้งาน @PathVariable annotation ตามตัวอย่างดังนี้
@RequestMapping("/hotels/{hotelId}")
public String getHotel(@PathVariable String hotelId, Model model) {
Hotel hotel = hotelService.getHotel(hotelId);
model.addAttribute("hotels", hotel);
return "hotel";
}
จากโค้ดด้านบนเราจะเห็นได้ว่าเมื่อมีการเรียก URI ที่มีค่า /hotels/1 ค่า 1 จะถูกบายด์เข้ากับ hotelId โอวง่ายมากต่อไปเรามาดูกันว่าถ้าต้องการมีตัวแปรมากกว่าหนึ่งตัวเราจะทำอย่างไร
@RequestMapping(value="/hotels/{hotel}/bookings/{booking}", method=RequestMethod.GET)
public String getBooking(@PathVariable("hotel") long hotelId, @PathVariable("booking") long bookingId, Model model) {
Hotel hotel = hotelService.getHotel(hotelId);
Booking booking = hotel.getBooking(bookingId);
model.addAttribute("booking", booking);
return "booking";
}
จากโค้ดด้านบนเราจะได้ URI ที่มีลักษณะดังนี้ /hotels/1/bookings/2
นอกจากนี้เรายังสามารถใช้วิธีการเขียน URI แบบของ Ant ได้ด้วยเช่น
@RequestMapping(value="/hotels/*/bookings/{booking}", method=RequestMethod.GET)
public String getBooking(@PathVariable("booking") long bookingId, Model model) {
...
}
หรือเราจะไปไกลกว่านั้นด้วยการทำ Data Type Binding ได้ด้วยก็ได้เช่นเราต้องการสร้าง URI แบบนี้ /hotels/1/dates/2009-02-02
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void date(@PathVariable("hotel") String hotel, @PathVariable Date date) {
...
}
Content Negotiation
ในเวอร์ชั่น 2.5 นั้น Spring ให้ @Controller ทำการเลือกวิวที่จะใช้แสดงผลเองตามประเภทวิวของมันเอง( view name, ViewResolver) แต่สำหรับ RESTfule แล้วคนที่เลือกการแสดงผลนั้นกลับกลายเป็นฝั่งไคลแอนท์เองโดยทำผ่าน HTTP เฮดเดอร์ดังนั้นทางฝั่งเซิร์ฟเวอร์เองจะทำการส่งคอนเทนท์ที่ถูกต้องผ่าน Content-Type เฮดเดอร์ซึ่งกระบวนการนี้เรียกว่า Content Navigation
แต่ประเด็นหลักของการทำการสร้างคอนเทนท์นั้นเราไม่สามารถเปลี่ยนประเภทคอนเทนท์ได้ที่เวบเบราส์เซอร์ (HTML) ยกตัวอย่างเช่นใน Firefox นั้นมันจะทำการกำหนดค่าคงที่ให้เป็น
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
ดังนั้นเกิดอะไรขึ้นถ้าเราต้องการอ้างถึง PDF โดยการอ้าง URI แบบนี้ http://example.com/hotels.pdf นั่นคือการแสดงรายชื่อโรงแรมทั้งหมดเป็น PDF เหมือนกับที่ทำใน http://example.com/hotels แต่ให้เปลี่ยนเฮดเดอร์เป็น application/pdf. (นรกตกใส่) ดังนั้นเราจำเป็นต้องมีผู้ช่วยพระเอกที่ชื่อ ContentNegotiatingViewResolver โดยตัวมันจะทำการแรป ViewResolver หลายๆตัวเข้าไว้ด้วยกันและมองดูความเป็นไปได้ของประเภทของคอนเทนท์ที่จะเกิดขึ้นและเลือกใช้ ViewResolver ที่ถุกต้อง
Views
มรการเพิ่มส่วนของการแสดงผลเข้ามาใหม่อีกหลายตัวเช่น
-AbstractAtomFeedView และ AbstractRssFeedView, เพื่อใช้ส่งคอนเทนท์ประเภท Atom และ RSS feed,
-MarshallingView, เพื่อใช้สร้างการแสดงผลที่เป็น XML ซึ่งตัววิวตัวนี้ถูกสร้างบนพื้นฐานของ Object/XML แมปปิ้งโมดูล, ซึ่งได้รับอิทธิพลมาจาก Spring Web Services โดยที่โมดูลนี้ใช้กระบวนการแปลงของเอพีไอชื่อดังเช่น JAXB, Castor, JiBX
-JacksonJsonView, เพื่อใหช้สร้างคอนเทนท์ตสมมาตรฐาน JSON ซึ่งวิวนี้เป็นส่วนหนึ่งของ Spring JavaScript project,
HTTP methos conversion
หัวใจหลักของการทำงานด้วย REST คืดมันจะทำงานบนกลไกของการเชื่อมต่อที่ไม่ซับซ้อนดังนั้นหมายความว่าทุกรีเควสจะต้องสามารถถูกเรียกผ่าน HTTP เมธอดทั้งสี่ได้คือ GET, PUT, POST และ DELETE ซึ่งถ้าเราตามไปดูตามมาตรฐานของ HTTP โปรโตคอลแล้วความหมายของการทำงานของทั้งสี่เมธอดนี้เรียบง่ายมากๆเช่น GET มีไว้ทำงานที่ปลอดภัยที่ไม่มีผลข้างเคียงอะไรกับระบบ, PUT กับ DELETE เป็นเมธอดที่ถูกเรียกวนไปวนมาหลายครั้งหลายหนได้แต่ผลที่ได้ทุกครั้งจะต้องเหมือนเดิม
แต่อย่างไรก็ตามเรามีเมธอดตามมาตรฐานทั้งหมดสี่แต่สำหรับ HTML เราสามารถใช้งานมันได้แค่สองเท่านั้นคือ GET กับ POST แต่เพียงแค่สองมันก็พอให้เราใช้งานได้แล้วเพราะเราสามารถเขียน JavaScript สำหรับการทำ PUT, DELETE หรือ POST ก็ได้โดยทำผ่านพารามิเตอร์พิเศษที่ชื่อ “real” และสำหรับ Spring แล้วการกำหนดค่าให้กับตัวแปร “real” จะถูกทำงานผ่าน HiddenHttpMethodFilter โดยที่ฟิวเตอร์ตัวนี้ถูกสร้างขึ้นมาสำหรับ Spring 3.0 M1 และตัวมันเองก็เป็นแค่ฟิวเตอร์ปกติดังนั้นมันจึงสามารถนำไปใช้กับเวบเฟรมเวิร์คอื่นๆได้อีกหลายตัวไม่จำหัดแค่ SpringMVC โดยการใช้งานนั้นเราก็เพียงแค่เพิ่มฟิวเตอร์เข้าไปใน web.xml, และทำการ POST ค่า hidden _method จากนั้นพารามิเตอร์จะถูกแปลงไปเป็น HTTP เมธอดรีเควสพารามิเตอร์ตามที่ต้องการ
และแน่นอนในเมื่อเราต้องส่งพารามิเตอร์พิเศษนี้ทำให้เราต้องใส่ tag นี่ลงไปในฟอร์มของเราด้วยซึ่ง SpringMVC เองก็เตรียมไว้ให้เรียบร้อยแล้วเช่นกัน
<form:form method="delete">
<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>
สิ่งที่ได้จากการส่งรีเควสนี้คือมันจะทำการส่ง HTTP POST ที่เป็นประเภท delete เพื่อไปเรียกให้เมธอด deletePet ใน @Controller ทำงาน
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}
ETag support
ตัว ETag( entity tag ) ถูกสร้างขึ้นมาเพื่อใช้สำหรับตรวจสอบการเปลี่ยนแปลงของคอนเทนท์บนเซิร์ฟเวอร์โดยที่ Etag เฮดเดอร์ที่เซิร์ฟเวอร์ส่งกลับมานั้นสามารถถุกตรวจสอบได้ ผ่าน If-None-Match เฮดเดอร์ถ้าคอนเทนท์ไม่มีการเปลี่ยนแปลงเซิร์ฟเวอร์จะทำการส่งค่า 304:Not Modified กลับมา
เช่นกันสำหรับ Spring 3.0 M1 นั้นได้มีการสร้าง ShollowETagHeadeFilter เหมือนเดิมมันเป็น Servlet Filter ตามมาตรฐานดังนั้นใครจะเอามันไปใช้งานก็ได้ อย่างไรก็ตามหลักการทำงานของฟิวเตอร์ตัวนี้นั้นไม่มีอะไรซับซ้อนโดยมันจะทำการแคชคอนเทนท์เอาไว้และทำการสร้างแฮช(MD5) ไว้และทำการส่งค่านั้นกลับไปในเรสพอนท์ เมื่อใดก็ตามที่มีการร้องขอคอนเทนท์เดิมกลับมา มันใช้ค่าแฮชที่ได้เก็บไว้เป็น If-None-Match และระบบจะทำการเรนเดอร์คอนเทนท์อีกครั้งและทำการเปรียบเทียบแฮชที่ได้ว่ามีค่าเท่าเดิมหรือไม่ถ้าเท่ากันก็จะส่ง “304″ กลับไป และจากกระบวนการทำงานจะเห็นว่า Filter ตัวนี้จะไปช่วยในเรื่องของการลดการประมวลผลแต่สิ่งที่มันช่วยลดคือแบนด์วิธต่างหาก เพราะคอนเทนท์ที่ซ้ำจะไม่ถูกส่งกลับมาที่ฝั่งไคล์แอนท์
จริงๆแล้วกระบวนการทำงานเชิงลึกของ ETags จะเป็นเรื่องที่ซับซ้อนมาก ในกรณีที่คอนเทนท์ของเราสร้างจากข้อมูลที่ถูกเก็บในระบบฐานข้อมูล และคอนเทนท์จะถูกสร้างใหม่ก็ต่อเมื่อข้อมูลในฐานข้อมูลมีการเปลียนแปลงเท่านั้น ซึ่งกระบวนการนี้ไม่สามารถทำได้ด้วยการใช่ ShollowETagHeadrFilter ธรรมดา เราต้องรอความสามารถใหม่ๆใน Spirng เวอร์ชั่นถัดไปที่จะต้องทำงานร่วมกับ JPA และ AspectJ ในเรื่องนี้

method ที่เห็นใน entry นี้เป็น controller ใช่หรือเปล่าครับ ไม่เคยเขียน spring เป็นงานเป็นการ เลยยังงงๆนะครับ
ถูกต้องครับทุกอย่างทำใน Controller ทั้งหมดครับ เดี๋ยวพรุ่งนี้มีตัวอย่างครับผม