Introduction
ไม่ recap เรื่อง REST แล้วนะครับเพราะเขียนเรื่องแนวคิดไปสองสามบทความแล้วครับเราเข้าเรื่องกัน เลยว่านอก จากการใช้ RESTEasy, Jersey แล้วเรายังสามารถใช้ SpringMVC3 เพื่อสร้าง RESTFul Service ได้เหมือนกันครับ
ก่อนอื่น sourcecode ทั้งหมดอยู่ที่นี่ครับ https://restfulspringmvc3.googlecode.com/svn/trunk/
Spring 3 REST support
ก่อนที่ Spring จะรองรับการทำ REST เราสามารถเลือกใช้ Framework อื่นๆเช่น Restlet, RestEasy, และ Jersey เพื่อนำมาสร้าง RESTFul ws ขึ้นมาโดยที่ Framework เหล่านี้จะ implement อยู่บนมาตรฐาน JAX-RS (JSR 311)
ปัจจุบัน Spring3 ได้ทำการรวมเอาความสามารถเรื่อง RESTFul WS เข้ามาไว้ในตัวเรียบร้อยแต่วิธีการสร้าง REST ของ Spring3 จะไม่เป็นไปตามมาตรฐาน JAX-RS ซึ่งทีมสร้างเองยืนยันว่า Spring3 มีความสามารถมากกว่า JAX-RS เช่นการ intergrate เข้ากับ SpringMVC แบบ seamless สิ่งที่เราต้องทำมีเพียงแค่ใส่ Annotation เข้าไปที่ Controller เช่น:
- Annotations, @RequestMapping และ @PathVariable, ถูกเตรียมไว้สำหรับการทำ resource identification และ URI mappings
- ContentNegotiatingViewResolver มีไว้สำหรับการสร้าง representations ที่เหมาะสมกับ MIME/content types
- Integrate กับ Spring Web MVC ได้อย่างสวยงาม
เรามาเริ่มกันเลยดีกว่าครับอันดับแรกให้ สร้าง Spring Web Project ด้วย Eclipse ก่อน (หาทำเอาเองนะครับ) อันดับแรกเราจะสร้าง model ก่อนซึ่งในที่นี้คือ class Employee โดยมีรายละเอียดดังนี้
@XmlRootElement(name="employee")
public class Employee {
private long id;
private String name;
private String email;
public Employee() {}
public Employee(long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
.. get/set...
ส่วนต่อไปคือการสร้าง ส่วนที่เรียกว่า Utility Class ที่ทำหน้าที่ื Wrap Employee อีกชั้นหนึ่ง
@XmlRootElement(name="employees")
public class EmployeeList {
private int count;
private List<Employee> employees;
public EmployeeList() {}
public EmployeeList(List<Employee> employees) {
this.employees = employees;
this.count = employees.size();
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@XmlElement(name="employee")
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
เมื่อเราได้ model และ util class แล้วต่อไปเราจะสร้าง service ขึ้นมาครอบสองสิ่งนี้ไว้และเราจะผูก service นี้เข้ากับ controller เพื่อรับหน้าที่จัดการการข้อมูลของ employee ทั้งหมดที่จะมีอยู่
public class EmployeeDS {
private static Map<Long, Employee> allEmployees;
static {
allEmployees = new HashMap<Long, Employee>();
Employee e1 = new Employee(1L, "Huang Yi Ming", "huangyim@cn.ibm.com");
Employee e2 = new Employee(2L, "Wu Dong Fei", "wudongf@cn.ibm.com");
allEmployees.put(e1.getId(), e1);
allEmployees.put(e2.getId(), e2);
}
public void add(Employee e) {
allEmployees.put(e.getId(), e);
}
public Employee get(long id) {
return allEmployees.get(id);
}
public List<Employee> getAll() {
List<Employee> employees = new ArrayList<Employee>();
for( Iterator<Employee> it = allEmployees.values().iterator(); it.hasNext(); ) {
Employee e = it.next();
employees.add(e);
}
return employees;
}
public void remove(long id) {
allEmployees.remove(id);
}
public void update(Employee e) {
allEmployees.put(e.getId(), e);
}
ส่วนต่อมาคือสิ่งที่สำคัญมากๆ เราจะสร้าง Controller ขึ้นมาพื่อทำหน้าที่รองรับ request จากผั่ง client ซึ่งใน application นี้เราออกแบบให้ระบบของเรารองรับการทำงานผ่าน URI ทั้งหมด 5 แบบดังนี้
- GET /enployees = เอาข้อมูล employee ออกมาทั้งหมด
- GET /employee/id = เอาข้อมูล employee ที่มี่ id ตามที่ต้องการออกมา
- PUT /employee/id = แก้ไขข้อมูล employee ที่มี id ตามที่ต้องการ
- POST /employee = สร้าง employee ใหม่
- DELETE /employee/id = ลบข้อมูล employee ที่มี id ตามที่ต้องการ
ดังนั้นเราจะสร้าง controller ขึ้นมาให้มีรายละเอียดการทำงานได้ปรมาณนี้ครับ ซึ่งเราสามารถทำได้ง่ายมาก
@Controller
public class EmployeeController {
private EmployeeDS employeeDS;
public void setEmployeeDS(EmployeeDS ds) {
this.employeeDS = ds;
}
private Jaxb2Marshaller jaxb2Mashaller;
public void setJaxb2Mashaller(Jaxb2Marshaller jaxb2Mashaller) {
this.jaxb2Mashaller = jaxb2Mashaller;
}
private static final String XML_VIEW_NAME = "employees";
@RequestMapping(method=RequestMethod.GET, value="/employee/{id}")
public ModelAndView getEmployee(@PathVariable String id) {
Employee e = employeeDS.get(Long.parseLong(id));
return new ModelAndView(XML_VIEW_NAME, "object", e);
}
@RequestMapping(method=RequestMethod.PUT, value="/employee/{id}")
public ModelAndView updateEmployee(@RequestBody String body) {
Source source = new StreamSource(new StringReader(body));
Employee e = (Employee) jaxb2Mashaller.unmarshal(source);
employeeDS.update(e);
return new ModelAndView(XML_VIEW_NAME, "object", e);
}
@RequestMapping(method=RequestMethod.POST, value="/employee")
public ModelAndView addEmployee(@RequestBody String body) {
Source source = new StreamSource(new StringReader(body));
Employee e = (Employee) jaxb2Mashaller.unmarshal(source);
employeeDS.add(e);
return new ModelAndView(XML_VIEW_NAME, "object", e);
}
@RequestMapping(method=RequestMethod.DELETE, value="/employee/{id}")
public ModelAndView removeEmployee(@PathVariable String id) {
employeeDS.remove(Long.parseLong(id));
List<Employee> employees = employeeDS.getAll();
EmployeeList list = new EmployeeList(employees);
return new ModelAndView(XML_VIEW_NAME, "employees", list);
}
@RequestMapping(method=RequestMethod.GET, value="/employees")
public ModelAndView getEmployees() {
List<Employee> employees = employeeDS.getAll();
EmployeeList list = new EmployeeList(employees);
return new ModelAndView(XML_VIEW_NAME, "employees", list);
}
}
ส่วนที่น่าสนใจสำหรับ Controller คือ @RequestMapping เราจะใช้มันเป็นตัวกำหนดหน้าที่ของแต่ละ function ให้ controller ไม่ว่าจะเป็น HTTP Method หรือ URI ที่จะใช้ร่วมกันซึ่งถึงตรงนี้เราได้องค์ประกอบพื้นฐานทั้งหมดครบแล้วไม่ว่าจะเป็น model, util, controller ต่อไปเราจะต้อง config ทั้งสามสิ่งให้ทำงานร่วมกันผ่าน configuration file ของ spring โดยผมใช้ชื่อ application-context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=...
<bean id="employeeDS" class="dw.spring3.rest.ds.EmployeeDS" />
</beans>
ส่วนต่อไปคือการกำหนดรายละเอียดการทำงานของ controller โดยเราจะกำหนดไฟล์ rest-servlet.xml
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>dw.spring3.rest.bean.Employee</value>
<value>dw.spring3.rest.bean.EmployeeList</value>
</list>
</property>
</bean>
<bean id="employees" class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg ref="jaxbMarshaller" />
</bean>
<bean id="employeeController" class="dw.spring3.rest.controller.EmployeeController">
<property name="employeeDS" ref="employeeDS" />
<property name="jaxb2Mashaller" ref="jaxbMarshaller" />
</bean>
ส่วนที่น่าสนใจมีสามส่วนคือส่วนที่เรากำหนด class สองคลาส Employee, EmployeeList ให้กับ jaxbMarshaller เนื่องจากเราจต้องการทำทั้งการ Bind และ Unbind ทั้งสองนี้ในระหว่างการทำงานตลอดเวลา ส่วนที่สองคือการกำหนด jaxbMarshaller ให้กับ employess และการผูก employeeDS และ jaxb2Mashaller ให้กับ employeeController เมื่อเราทำได้ทั้งหมดนี้แล้วต่อไปคือเราจะทดสอบกันแล้วว่ามันทำงานได้จริงไหมด้วยการเขียน main class ง่ายๆ
public class App {
public static void main(String[] args) throws IOException {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://localhost:8080/service/employee/2");
get.addHeader("accept", "application/xml");
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() != 200) {
throw new RuntimeException("Operation failed: "
+ response.getStatusLine().getStatusCode());
}
System.out.println("Content-Type: "
+ response.getEntity().getContentType().getValue());
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
client.getConnectionManager().shutdown();
}
}
ที่เหลือไปเขียนเอาเองนะครับ
เพราะ work แน่นอน ง่ายจริงๆ