ข้อแตกต่างระหว่าง BeanFactory และ ApplicationContext
By Siros Supavita [ Monday, July 31st, 2006 ]
Question
BeanFactory และ ApplicationContext ต่างกันอย่างไร เมื่อใดควรจะใช้ BeanFactory เมื่อใดควรจะใช้ ApplicationContext
Answer
BeanFactory และ ApplicationContext ต่างก็เป็น interface ของ implementation ที่เป็น bean container ซึ่งโดยทั่วไป application code จะใช้ interface ทั้งสองตัวนี้ ในการ access object ที่สร้างมาจาก bean definition
ข้อแตกต่างของทั้งสองตัวนี้ อยู่ที่ feature ของ implementation โดย ApplicationContext นั้นเป็น interface ที่ขยายต่อเติมออกมาจาก BeanFactory เพื่อเพิ่ม feature หลายๆ อย่างเข้าไป โดยสามารถสรุปเป็นหัวข้อได้ดังนี้
- Default Initialization Mode
- MessageSource
- Event Propagation
- ResourceLoader
- Web Application Integration
- Extension Point
Default Initialization Mode
Bean definition ที่ถูก load โดย BeanFactory จะมี initialization mode โดย default เป็นแบบ lazy นั่นคือ จะยังไม่มีการสร้าง object ของ bean definition ขึ้นมาจนกว่าจะมีความจำเป็นต้องใช้ object นั้น (นั่นคือ เมื่อมีการ call getBean ด้วย id ของ bean definition นั้นๆ หรือ เมื่อมีการสร้าง object ซึ่งมี reference ไปหา bean definition นั้นๆ)
แต่สำหรับ ApplicationContext จะมีค่า default ของ initialization mode ของ bean definition เป็น eager นั่นคือ จะมีการสร้าง object สำหรับ bean definition ที่มี scope เป็น singleton ทันที เมื่อสร้าง instance ของ ApplicationContext
อย่างไรก็ตาม ค่า initialization mode นี้ สามารถกำหนดได้ด้วย attribute ?lazy-init? บน element ?bean? สำหรับ bean definition แต่ละตัว หรือ ด้วย attribute ?default-lazy-init? บน root element ?beans? สำหรับทุกๆ bean definition ภายใต้ root element นั้น
MessageSource
ApplicationContext นั้น extend มาจาก interface MessageSource ซึ่งใช้สำหรับให้บริการด้าน message ซึ่งเป็น internationalization หรือ i18n โดยที่เมื่อสร้าง instance ของ ApplicationContext จะมีการหา instance ของ MessageSource ซึ่งถูก define ไว้ภายใต้ ApplicationContext นั้น จาก id ?messageSource? โดยที่ instance ที่ได้นี้ จะใช้สำหรับให้บริการด้าน message ซึ่งจะ delegate มาจาก ApplicationContext หรืออีกนัยหนึ่ง การ call method ที่เกี่ยวกับ i18n message บน instance ของ ApplicationContext จะทำให้เกิดการ call method บน instance ของ MessageSource นี้อีกทอดหนึ่ง
ตัวอย่างด้านล่างนี้แสดงการเขียน bean definition สำหรับ message source ซึ่งใช้ class ResourceBundleMessageSource โดยที่ class นี้จะใช้การทำงานของ ResourceBundle เพื่อให้บริการ message (สามารถอ่านรายละเอียดเกี่ยวกับการใช้งาน ResourceBundle ได้จาก Java API document)
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource" >
<property name="basenames">
<list>
<value>common</value>
<value>error</value>
</list>
</property>
</bean>
เมื่อเรามี bean definition ที่เป็น message source ใน ApplicationContext แล้ว เราสามารถอ่าน message มาใช้งานได้ โดยใช้ method ?getMessage()? ตัวใดตัวหนึ่งจาก MessageSource ดังแสดงในตัวอย่างด้านล่าง
ApplicationContext ctx; Locale locale = Locale.ENGLISH; ... System.out.println( ctx.getMessage(?mandatory.missing?, new Object(?First Name?), locale));
จากไฟล์ resource bundle นี้ เมื่อตัวอย่าง code ทำงานแล้ว จะได้ผลลัพธ์ดังนี้
Value of field ?First Name? is required.
Event Propagation
ใน ApplicationContext สามารถกำหนดให้ object ที่สร้างขึ้น ทำหน้าที่เป็น event listener ได้ โดยให้ class ของ object นั้น implement interface ApplicationListener ดังแสดงในตัวอย่าง
import org.springframework.context.ApplicationListener;
import org.springframework.context.ApplicationEvent;
public class MyListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
// Handle event
}
}
ApplicationContext จะตรวจสอบว่า object ตัวใดบ้างที่เป็น event listener และเมื่อเกิด event ขึ้น จะ notify event listener ทุกตัวใน Spring เองมี event ด้วยกัน 3 ตัว คือ ContextRefreshedEvent ContextClosedEvent และ RequestHandledEvent นอกจาก event 3 ตัวนี้แล้ว สามารถเขียน code เพื่อสร้าง event ขึ้นมาเองได้ตามต้องการ ดังแสดงตัวอย่างด้านล่าง
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
public class MyEventPublisher implements ApplicationContextAware {
private ApplicationContext ctx;
public void setApplicationContext(ApplicationContext ctx) {
this.ctx = ctx;
}
public void publishMyEvent() {
// Create an instance of ApplicationEvent
ApplicationEvent event = ...
// Publish
ctx.publishEvent(event);
}
}
จากตัวอย่าง class ที่เราเขียนขึ้นมานั้น implement interface ApplicationContextAware เพื่อให้ ApplicationContext ส่ง instance ของตัวมันเองมาเป็น argument ของ method "setApplicationContext()" เพราะเราจำเป็นต้องใช้ instance ของ ApplicationContext เพื่อ call method "publishEvent()"
ResourceLoader
ใน Spring จะมีใช้ interface "Resource" เป็น abstraction API ที่ใช้ access low-level resource ซึ่งได้แก่
- ไฟล์จาก local file system
- ไฟล์จาก classpath
- resource จาก URL
- อื่นๆ
ที่มาของ interface Resource นั้นเนื่องมาจากว่า ยังไม่มี standard Java API ตัวใดที่ทำหน้าที่เป็น abstraction API ของการ access resource ทั้งหมดที่ได้ยกตัวอย่างมาได้ (เช่น class URL ไม่สามารถใช้กับ resource จาก classpath ได้)
ApplicationContext นั้น extend มาจาก interface ResourceLoader ซึ่งทำหน้าที่ในการสร้าง instance ของ Resource ตาม resource path ที่กำหนด ดังนั้น เราจึงสามารถใช้ ApplicationContext ในการ access resource ตามที่เราต้องการได้ ดังตัวอย่างด้านล่าง
ApplicationContext ctx; ... Resource res = ctx.getResource(?classpath:com/xplink/resource.dat?); InputStream in = res.getInputStream();
นอกจากนี้ ApplicationContext ยังมีความสามารถในการหา resource ตาม ?ชนิด? ของ ApplicationContext หากเราไม่กำหนด prefix ใน resource path เช่นในตัวอย่างด้านล่าง ApplicationContext ถูกสร้างขึ้นมาโดยใช้ bean definition จาก classpath เมื่อทำการ access resource ที่ไม่ได้กำหนด prefix มันจึงหา resource นั้นจาก classpath ด้วย
ApplicationContext ctx = new ClassPathXmlApplicationContext(?bean.xml?); Resource res = ctx.getResource(?com/xplink/resource.dat?);
Web Application Integration
โดยปกติแล้ว instance ของ BeanFactory นั้นต้องถูกสร้างขึ้นด้วยการเขียน code เองเสมอ แต่ว่า ApplicationContext นั้นสามารถถูก initialize ได้โดยอัตโนมัติ เมื่ออยู่ใน Java Servlet Container โดยตัว Listener หรือ Servlet ที่ configure ไว้ในไฟล์ web.xml จะทำหน้าที่ในการสร้าง instance ของ ApplicationContext จากไฟล์ bean definition ที่กำหนดด้วย context parameter (ใน web.xml) ชื่อ "contextConfigLocation" ดังแสดงในตัวอย่างด้านล่าง
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml
</param-value>
</context-param>
ค่าของ "contextConfigLocation" นั้นเป็น list ของ location ของไฟล์ bean definition ซึ่งคั่นด้วย space comma หรือ semi-colon ซึ่งค่า context parameter นี้เป็น optional หากไม่มีการระบุค่านี้ในไฟล์ web.xml ไฟล์ "/WEB-INF/applicationContext.xml" จะถูกนำมาใช้สร้าง ApplicationContext
สำหรับ Container ที่ support Listener (Container ที่ implement ตาม Servlet specification version 2.4 หรือ Container ที่ support version 2.3 บางตัว) สามารถใช้ ContextLoaderListener เพื่อ initialize ApplicationContext ได้ดังแสดงในตัวอย่างด้านล่าง
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
สำหรับ Container ที่ไม่ support Listener สามารถใช้ ContextLoaderServlet เพื่อ initialize ApplicationContext ได้แทน โดยใช้ configuration ดังแสดงด้านล่าง
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>
org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
instance ของApplicationContext ที่ถูกสร้างขึ้นนี้จะถูกเก็บไว้ใน ServletContext ภายใต้ชื่อ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE โดยสามารถใช้ method getWebApplicationContext จาก class WebApplicationContextUtils เพื่อ access instance ของ ApplicationContext นั้นๆ ได้ ตามตัวอย่างด้านล่าง
import javax.servlet.ServletRequest;
import javax.servlet.ServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
...
// Inside servlet
ServletContext ctx = this.getServletConfig().getServletContext();
WebApplicationContext webAppCtx =
WebApplicationContextUtils.getWebApplicationContext(ctx);
สำหรับกรณีที่ใช้ web framework ที่สามารถ integrate เข้ากับ Spring ได้นั้น เราอาจไม่จำเป็นต้องเขียน code เพื่อ access ApplicationContext โดยตรงเหมือนในตัวอย่างข้างบน เช่น หากใช้ Spring integrate กับ JSF เราสามารถ inject object จาก Spring bean definition เข้าไปที่ managed bean ของ JSF ได้เลย
Extension Point
ใน BeanFactory และ ApplicationContext นั้นมี extension point อยู่ โดยที่สามารถสร้าง extension เข้าไปทำงานเพิ่มเข้าไป ณ ที่ extension point นั้นได้ เพื่อให้หน้าที่บางอย่างเพิ่มเติมจากสิ่งที่ BeanFactory และ ApplicationContext มีอยู่แล้ว
สำหรับ BeanFactory นั้น จะต้อง register extension ประเภท BeanPostProcessor และ BeanFactoryPostProcessor กับ instance ของ BeanFactory ก่อน extension นั้นจึงจะทำงานได้ ตัวอย่างด้านล่างแสดงการ register BeanPostProcessor กับ BeanFactory
// Create an instance of implementation of ConfigurableBeanFactory ConfigurableBeanFactory factory = ... // Create an instance of implementation of BeanPostProcessor BeanPostProcessor postProcessor = ... // Register BeanPostProcessor to BeanFactory factory.addBeanPostProcessor(postProcessor);
แต่สำหรับ ApplicationContext นั้น ไม่จำเป็นต้องเขียน code เพื่อ register extension เหล่านี้ เพราะ implementation ของ ApplicationContext นั้นสามารถค้นหา bean definition ของตัวเองที่เป็น extension และ register extension เหล่านี้ให้โดยอัตโนมัติ นั่นคือ ไม่จำเป็นต้องเขียน code เพื่อสร้าง instance เหล่านี้ขึ้นมาเพื่อ register กับ ApplicationContext เหมือนใน BeanFactory เพียงแค่สร้าง bean definition ของ extension ที่ต้องการไว้ในใน configuration ที่ใช้สร้าง instance ของ ApplicationContext นั้นๆ extension ที่ถูกสร้างจาก bean definition นั้นก็จะถูก register เข้ากับ ApplicationContext โดยอัตโนมัติเมื่อสร้าง instance ของ extension
Summary
implementation ของ ApplicationContext นั้น ให้ feature ที่เพิ่มเติมไปจาก implementation ของ BeanFactory อยู่หลายตัว สำหรับกรณีทั่วไป ที่ไม่มีประเด็นเรื่อง resource ของ runtime environment ที่มีจำกัดมากๆ ApplicationContext เป็นตัวเลือกที่ค่อนข้างเหมาะสมกับการใช้งานมากกว่า โดยเฉพาะเมื่อพิจารณาถึง feature ที่มีเพิ่มขึ้นมาจาก BeanFactory แต่สำหรับกรณีที่ resource (processor และ memory) มีจำกัดมากๆ BeanFactory อาจจะเป็นตัวเลือกที่ดีกว่า เนื่องจากความเหมาะสมทางด้านการใช้งานทรัพยากร ซึ่งด้วย feature ที่น้อยกว่า implementation ของ BeanFactory จะมีแนวโน้มการใช้ memory ที่น้อยกว่า นอกจากนั้นการใช้ lazy initialization จะช่วยประหยัดทั้ง processing time ขณะ initialization และประหยัด memory ที่ใช้สำหรับเก็บ singleton bean

Leave comment...