อีก slide ที่น่าสนใจจากงาน SpringOne ครับสรุปเรื่องความสามารถใหม่ๆของ Spring 3.0 สามารถ Download Slide ได้ครับ
Posts Tagged ‘spring3’
Spring 3.0; The Next Generation
March 26th, 2010SimpleJdbcTemplate ในสปริง3
November 3rd, 2009วันนี้เราจะมาว่ากันด้วยเรื่องความสามารถใหม่ในเรื่องของการใช้งาน SimpleJdbc คลาสต่างๆในสปริงสามที่จะช่วยลดปริมาณการเขียนโค้ดลงไปได้เยอะพอสมควรเนื่องจากในสปริงสามนั้น มีการดึงข้อมูลพิเศษของฐานข็อมูลเช่นชื่อคอลัมบ์ชนิดของข้อมูล ออกมากจากคอนเน็คชั่นไดร์เวอร์ที่เราต่อเข้ากับฐานข้อมูลของเราแต่ก่อนเราย้อนกลับไปดูที่ spring2.5 กันก่อนว่าถ้าต้องการ SELECT ขอ้มูลออกมาจากตาราง USERS เพื่อแมปเข้ากับออบเจค User เราจะทำอย่างไร จริงสามารถเขียนได้หลายวิธีแต่ผมเลือกการเขียนแบบแยก Extractor, Mapper ออกจากตัว DAO เพราะไม่ต้องการให้มันรกมากครับ ก่อนอื่นเราต้องไปสร้างคลาสที่ทำหน้าที่จับคู่ระหว่างคอลัมบ์กับแอตทริบิวท์ก่อนโดยมันจะต้องอิมพลีเมนท์อินเทอร์เฟส ResultSetExtractor
public class OwnerExtractor implements ResultSetExtractor {
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
Owner owner = new Owner();
owner.setId(rs.getInt("ID"));
owner.setFirstName(rs.getString("FIRSTNAME"));
owner.setLastName(rs.getString("LASTNAME"));
owner.setAddress(rs.getString("ADDRESS"));
owner.setCity(rs.getString("CITY"));
owner.setPhone("PHONE");
return owner;
}
}
จากนั้นเราก็ต้องสร้างคลาสที่อิมพลีเมนท์อินเทอร์เฟส RowMapper
public class OwnerMapper implements RowMapper {
public Object mapRow(ResultSet rs, int i) throws SQLException {
OwnerExtractor ownerExtractor = new OwnerExtractor();
return ownerExtractor.extractData(rs);
}
}
สุดท้านเราก็ต้องไปเขียนรายละเอียดการดึงข้อมูลไว้ที่ DAO จริงๆและเรียกใช้คลาสทั้งสองที่เราเพิ่งสร้างขึ้นมาดังนี้
public class OwnerJdbcDAO implements OwnerDAO {
private final Log log = LogFactory.getLog(getClass());
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource ds) {
dataSource = ds;
}
public Owner loadOwner(Integer id) {
log.debug("OwnerJdbcDAO.loadPet");
JdbcTemplate template = new JdbcTemplate(dataSource);
String sql = "SELECT ID, FIRSTNAME, LASTNAME, ADDRESS, CITY, PHONE FROM OWNERS WHERE ID=?";
return (Owner) template.queryForObject(sql, new Object[]{id}, new OwnerMapper());
}
...
จะเห็นว่าท่าเยอะมากกว่าจะได้มาหนึ่งสเตทเมนท์ซึ่งนี่ก็เป็นข้อเสียของการใช้ SpringJDBC เพราะต้องเขียนอะไรต่อมิอะไรเยอะมาก นี่ยังไม่นับรวมคอนฟิกกูเรชั่นต่างๆที่เราต้องไปเขียนเพิ่มอีก ดังนั้นในสปริง3 จึงมีแพกเกจใหม่ชื่อ org.springframework.jdbc.core.simple โดยทีคลาสต่างๆในแพกเกจนี้จะเข้ามาช่วยเราในการทำงานง่ายๆที่ต้องทำบ่อยๆบนฐานข้อมูลเช่นตัวอย่างต่อไปเราจะทำกระบวนการเดียวกันกับตัวอย่างข้างบนแต่เราจะทำผ่านคลาส SimpleJdbcTemplate แทนดังนั้น DAO ของเราจะมีหน้าตาใหม่เป็นแบบนี้ที่หล่อกว่าเกิมสองร้อยเท่า
public class OwnerJdbcDAO implements OwnerDAO {
private final Log log = LogFactory.getLog(getClass());
@Autowired
private DataSource dataSource;
public void setDataSource(DataSource ds) {
dataSource = ds;
}
public Owner loadOwner(Integer id) {
SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
Owner owner;
try {
owner = (Owner)simpleJdbcTemplate.queryForObject("SELECT ID, FIRSTNAME, LASTNAME, ADDRESS, CITY, PHONE FROM owners WHERE ID=?",
new BeanPropertyRowMapper(Owner.class),
id);
}
catch (EmptyResultDataAccessException ex) {
throw new ObjectRetrievalFailureException(Owner.class, new Integer(id));
}
loadPetsAndVisits(owner);
return owner;
}
...
จะเห็นว่าทั้งสองแบบทำงานได้เหมือนกันแต่ที่ต่างออกไปคือเราไม่ต้องไปนั่งแมป เพราะเราใช้ความสามารถของ BeanPropertyRowMapper ในสปริง3 เข้ามาช่วยวันนี้เอาเรื่อง SELECT ไปก่อนพรุ่งนี้เรามาดูความหล่อในเรื่อง
ของการ INSERT, UPDATE กัน
AtomFeedView ใน SpringMVC 3 (RESTFul)
October 22nd, 2009ตัวอย่างการใช้งาน 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/
เกริ่นนำเรื่อง RESTful ฟังก์ชั่นใน Spring-MVC 3.0 xx
October 20th, 2009A 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 ในเรื่องนี้
