Wednesday, June 06, 2007

Spring Configuration

โดยปกติ คนที่ใช้ spring มักจะบ่นเรื่อง Configuration file ที่เต็มไปด้วย xml
ถึงกับมี solution จำนวนหนึ่งออกมาทดแทน เช่นไปใช้ groovy หรือไปใช้ annotation

เนื่องจากผมก็มีความเบื่อหน่ายใน xml file เหมือนกัน
ดังนั้นตอนนี้ จึงเริ่มมองหาวิธีที่จะมาทดแทน
สำหรับวิธีการ config สำหรับ spring ที่ผมสนใจตอนนี้ ก็มี
  • Annotation-based configuration
    วิธีนี้จะ bundle มาใน spring 2.1
  • Java Configuration
    แยกเป็น project มาต่างหาก โดยอยู่ใน project JavaConfig

ประเด็นหลักๆที่ต่างกันของ 2 วิธี ก็คือ แนวคิดเรื่องการแยกข้อมูลการ configuration ออกจาก source code
ซึ่งวิธีที่สอง จะดูดีกว่าในแง่นี้ เนื่องจากไม่มี code เกี่ยวกับ configuration ปนอยู่ใน bean เลย

ผมทดลองใช้โจทย์ ใน post ที่เคยเขียนเรื่อง Tapestry 5 IoC
มาลอง config ทั้ง 2 วิธีดู
เริ่มด้วยวิธีแรกก่อน
@Component("indexService")
public class IndexServiceImpl implements IndexService {

public void index(Document document, Long key) {
...
}

}

@Repository
public class RepositoryServiceImpl implements RepositoryService {

public Long persistent(Document document) {
...
return id;
}

}

@Component("uploadService")
public class UploadServiceImpl implements UploadService {

private IndexService indexService;
private RepositoryService repositoryService;
private Map<String, Parser> parserMap = new HashMap<String, Parser>();

public void upload(InputStream input, String type) {
Parser parser = parserMap.get(type);
Document document = parser.parse(input);
Long key = repositoryService.persistent(document);
indexService.index(document, key);
}

@Autowired
public void setIndexService(IndexService indexService) {
this.indexService = indexService;
}

@Autowired
public void setRepositoryService(RepositoryService repositoryService) {
this.repositoryService = repositoryService;
}

@Resource(name="parserMap")
public void setParserMap(Map<String, Parser> parserMap) {
this.parserMap = parserMap;
}
}

จะเห็นมี annotaion อยู่ดังนี้
  • @Component -> เข้าข่ายพวกที่เราเคยประกาศ bean id=".." ใน xml file
  • @Repository -> ต่างกับ Component ตรงที่จะใช้กับพวกที่เป็น Data access layer
    ,เข้าใจว่าตั้งชื่อให้ต่างกัน component เพื่อที่จะได้นำไปใช้อย่างอื่นได้ด้วยเช่น
    เอาไป implement พวก automatic translation of exception
  • @Autowired -> เป็นการ inject โดยใช้ type
  • @Resource -> ใช้ annotation ของ JSR-250 มา inject bean พวกที่ declare ไว้ใน xml file แบบเดิม
    หรือแบบที่ซับซ้อนกว่านั้นเช่น lookup จาก jndi

การใช้วิธีแรกนี้ ยังมีปัญหาไม่สามารถ config พวก map property ผ่านทาง annotation ได้
จึงต้องใช้ xml เข้ามาผสมด้วย
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.1.xsd"
>

<context:component-scan base-package="anno"/>

<bean id="parserMap" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="pdf">
<bean class="anno.impl.PdfParser"/>
</entry>
</map>
</constructor-arg>
</bean>

</beans>

จากข้างบน จะเห็นว่า เราใช้ component-scan เข้ามากำหนดว่า
เพื่อที่จะได้ข้อมูล bean ที่ทำ annotation ไว้, spring ต้อง scan กลุ่ม package ใดบ้าง

เห็นแล้วก็ยังไม่ชอบใจนัก
ลองดูวิธีที่ 2 บ้าง
ในวิธี Java Configuration นี้ เราจะ implement class ขึ้นมาแทน xml file
โดยการกำหนด @Configuration ไว้ที่ class,
ซึ่ง spring ก็จะใช้ class ที่มี annotation นี้ในการ config beans
@Configuration
public class MyConfiguration {

@Bean
public IndexService indexService() {
return new IndexServiceImpl();
}

@Bean
public RepositoryService repositoryService() {
return new RepositoryServiceImpl();
}

@Bean
private Map<String, Parser> parserMap() {
HashMap<String, Parser> map = new HashMap<String, Parser>();
map.put("pdf", new PdfParser());
return map;
}

@Bean
public UploadService uploadService() {
UploadServiceImpl impl = new UploadServiceImpl();
impl.setIndexService(indexService());
impl.setRepositoryService(repositoryService());
impl.setParserMap(parserMap());
return impl;
}

}

Note: พวก bean RepositoryServiceImpl, IndexServiceImpl, ...
เขียนแบบ POJO แท้ๆ ไม่ต้องมี Annotation ใดๆ ใส่ไว้ให้รบกวนลูกตาเลย

Note: ให้สังเกตุว่า parserMap เป็น private method ซึ่งเท่ากับว่า bean
นี้ไม่สามารถ access จาก method context.getBean('parserMap') ได้

เราสามารถใช้วิธี Java Configuration ผสมกับ xml ได้เช่นกัน
อย่างสมมติว่า เราต้องการให้ parserMap ถูก config จากข้างนอก
ก็สามารถ เขียนส่วน Configuration ใหม่ดังนี้
        @ExternalBean
public Map<String, Parser> parserMap() {
return null;
}

และไปกำหนด xml ข้างนอกแบบนี้
<beans>

<bean id="parserMap" class="java.util.HashMap">
<constructor-arg>
<map>
<entry key="pdf">
<bean class="anno.impl.PdfParser"/>
</entry>
</map>
</constructor-arg>
</bean>

<bean class="anno.configuration.MyConfiguration"/>

<bean class="org.springframework.config.java.process.ConfigurationPostProcessor"/>

</beans>


ถึงตอนนี้แล้ว ผมออกจะชอบวิธีที่ 2 มากกว่า

Related link from Roti

10 comments:

bunyawat said...
This comment has been removed by the author.
bunyawat said...

ขอขอบคุณสำหรับ ข้อมูลที่คุณอุตสาห์แบ่งปัน
เป็นประโยชน์มากครับ
ผมจะติดตามอ่าน blog ของคุณต่อไปเรื่อยๆครับ

veer said...

ถ้า Spring ตัด XML ออกไปได้บ้างท่าจะทำให้ คนความจำน้อย ขี้เกียจแบบผม เรียนรู้ได้บ้าง แต่ว่าผมลุ่มหลง Tapestry ไปแล้ว :-P

PPhetra said...

pa: ผมไปดู blog ของคุณ pa มาแล้ว
ความสนใจของคุณ pa กับผม มันเหมือนกันยังกับแกะ
,เดือนหน้า จะมีงาน njug แวะมาคุยกันได้นะครับ

veer: veer ลองไล่ โครงสร้างที่ Howard เขาเขียน
tapestry ไว้ดูสิ
มันซับซ้อน แต่ผมชอบนะ
มันเป็น modular ดี

veer said...

พออ่าน Tapestry 101 พร้อมทำแบบฝึกหัดเสร็จ ผมจบต้องเริ่มไล่ดูอะไรบางอย่างแล้วหละครับ แต่ว่าตอนนี้พึ่งถึงบทที่ 3 เอง :-P

bunyawat said...

pok: ครับ ผมยังไม่ถนัดเขียนเท่าไร เลยใช้วิธีตัดแปะเรื่องที่ตัวเองสนใจไปก่อน

ผมเองก็อยากมีโอกาศได้ทำความรู้จักกับคุณเช่นกัน
ไว้เจอกันในงาน njug ครับ

roofimon said...

นัดกันที่ NJUG แล้วยังหาที่ลงไม่ได้เลยอ่ะๆๆๆ

veer said...

หลังจากที่อ่านซุ่มอ่าน + ใช้ Spring อยู่หลายวัน ผมก็พึ่งจะรู้เรื่อง :-P.

ชอบวิธีที่ 2 เหมือนกันครับ ว่าจะลองทำตามดู. จริงๆ XML ก็ดี แต่ว่า refactor ด้วย eclipse ลำบาก. แถมสะกดผิดได้ง่ายๆด้วย ตอนนี้เป็นความผิดพลาดหลักของผมเลย.

อันนี้ใช้กับ 2.0.6 ได้เลยหรือเปล่าครับ? หรือว่าต้องเอา 2.1.x มา?

PPhetra said...

ลองเลย veer
ผมว่าน่าจะใช้กับ 2.0.x ได้นะ

veer said...

ลองใช้ 2.0.6 ก็ได้จริงๆ ครับ :-)
http://gotoknow.org/blog/construction/111426