ถึงกับมี 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 มากกว่า