แกะ Spring JavaConfig ภาค 1
เคยเขียนเรื่อง Spring JavaConfig ไปแล้วหนึ่งตอนแต่ไม่ได้ลงมือทำเพราะมันไปบ้า Spring Dynamic Module
แต่อ่านไปอ่านมาตัวอย่างมันมี bug เลยขี้เกียจอ่านต่อ เลยไปรื้อ Spring JavaConfig ออกมาใหม่
พบว่าเอกสารเขียนก้าวหน้าไปได้เยอะมากและที่สำคัญ มีตัวอย่างแล้วนั่นคือ PetClinic นั่นเองอย่ารอช้า checkout ออกมาดู
“อ่านไม่รู้เรื่อง” สิ่งแรกที่แว๊บมาในหัว เนื่องจากไม่รู้จะเริ่มจากตรงไหนดี เนื่องจากสมัยก่อนจะไปเปิดดู Application-Context.xml ก่อน
แต่ตอนนี้มันไม่มีแล้ว
แกะอยู่หลายนาทีก็พอคลำได้ แต่พบว่ามันใช้ Annotation แบบพิการไม่สุดตามวิธี Spring คือไม่ยอมใช้ Hibernate Annotation เพราะ
ไม่อยากทำให้ Domain class แปดเปื้อน (แต่ที่อื่นนี่ เละไปด้วย Annotation และ abstract method) นี่ถือเป็นโอกาสที่ดีของผมครับ ที่จะรื้อ
มันออกมาเป็นชิ้นๆ แล้วประกอบเข้าไปใหม่(Japanese Way) เพราะถ้าเราอยากเข้าใจการทำงานของมันก็ต้องลองประกอบเองด้วยมือ
*** สามารถ download sourcecode มาสั่ง unit test ได้ครับที่ http://code.google.com/p/petclinic-java-config/
เรามาเริ่มเรียน Spring JavaConfig กันเลยดีกว่าครับด้วยการ ถอดประกอบตัวอย่าง
ข้อแรกผมต้องหาให้เจอก่อนว่า กระบวนการโหลด Application Context ของมันเริ่มจากตรงไหน ดังนั้นที่ที่ดีที่สุดที่จะไปดูคือ Unit Test (ประโยชน์ข้อแรก
ของการเขียน Unit Test) พบว่ามี Annotation อยู่หนึ่งตัวที่ทำให้ผมพอเดาออก ผมเลยเอามาเขียนใหม่เป็นที่ของผม
@ContextConfiguration(locations = "org.spring66.petclinic.config.JpaPetclinicApplicationConfig", loader = JavaConfigContextLoader.class)
Annotation ตัวนี้จะบอกว่าที่เก็บ Config พื้นฐานของเราทั้งหมดอยู่ที่คลาส “org.spring66.petclinic.config.JpaPetclinicApplicationConfig” และกราจะใช้
JavaConfigContextLoader เป็นตัวโหลด Context ขึ้นมาให้เรา ดังนั้นเราก็ต้องไล่ต่อไปว่าเจ้า JpaPetClinicApplicationConfig ต้องทำอะไรบ้าง
package org.spring66.petclinic.config;
import ... ประมาณสามล้าน อัน
@Configuration
@Import({PetclinicApplicationConfig.class, ExternalDataSourceConfig.class})
@PropertiesValueSource(locations = "classpath:db/jdbc.properties")
public abstract class JpaPetclinicApplicationConfig extends ConfigurationSupport {
// Each of the following @ExternalValues are provided by db/jdbc.properties
abstract @ExternalValue("jpa.showSql") boolean showJpaSql();
abstract @ExternalValue("jpa.database") String databaseType();
abstract @ExternalValue("hibernate.show_sql") boolean showSql();
abstract @ExternalValue("hibernate.hbm2ddl.auto") String hbm2ddlAuto();
abstract @ExternalValue("hibernate.generate_statistics") boolean generateStatistics();
abstract @ExternalValue("hibernate.dialect") String databasePlatform();
/** Provided by {@link EmbeddedDataSourceConfig#dataSource()} */
abstract @ExternalBean DataSource dataSource();
public @Bean Clinic clinic() {
return new EntityManagerClinic();
}
public @Bean EntityManagerFactory entityManagerFactory() {
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.dialect", databasePlatform());
jpaProperties.put("hibernate.show_sql", showSql());
jpaProperties.put("hibernate.generate_statistics", generateStatistics());
jpaProperties.put("hibernate.hbm2ddl.auto", hbm2ddlAuto());
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setJpaProperties(jpaProperties);
em.setDataSource(dataSource());
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setLoadTimeWeaver(loadTimeWeaver());
return this.getObject(em, EntityManagerFactory.class);
}
public @Bean JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(showJpaSql());
adapter.setDatabasePlatform(databasePlatform());
return adapter;
}
public @Bean LoadTimeWeaver loadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
public @Bean PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}
อ้วกเป็นเลือดแต่เราคือโปรแกรมเมอร์มืออาชีพเราต้องอย่าเพิ่งเข้าใจมันหมด เอาที่คุ้นๆก่อน แล้วตรงไหนล่ะ? เริ่มตั้งแต่ตัวแรกเลยละกัน
@Configuration
บอกให้โลกรู้ว่าคลาสนี้จะถูกใช้สำหรับ JavaConfig เพื่อกำหนดและประกาสรายละเอียดเกี่ยวกับ bean ที่เราต้องใช้
@Import
ในกรณีที่เรามี @Configuration มากกว่าหนึ่งคลาสและต้องการใช้งานบีนข้ามไปมาเราสามารถทำได้ด้วยการใช้ @Import ตัวอย่างนี้คือการ
เรียกใช้ bean ชื่อ dataSource() ซึ่งไม่มีอยู่ใน JpaPetclinicApplicationConfig แต่จะถูกประกาสไว้ที่ ExternalDataSourceConfig ครับ
ซึ่งเราก็ Import เข้ามาเรียบร้อยแล้วครับ วิธี Import นี่ดูขัดๆกับแนวคิดแบบเก่าพิกล แต่ก็ช่วยได้เยอะเรื่อง NameSpace ครับ
@PropertiesValueSource
ใช้ในกรณีที่เราต้องการโหลดข้อมูลบางอย่างมาจาก properties ไฟล์เพราะเราไม่ต้องการให้ข้อมูลเหล่านั้นฝังไว้ในคลาสที่นี้เราจะไปอ่านที่
(locations = “classpath:db/jdbc.properties”)
@ExternalValue
จะบอกว่าเราจะเอาค่าจาก properties ตัวไหนมาใส่ให้ property(เป็น abstract method) เราครับ
ต่อไปตัวสำคัญ @Bean แน่นอนเดาไม่ออกสมควรตายมันคือการประกาส bean แบบใหม่แต่ให้ดูการสร้าง EntityManager ของ JPA ก่อนนะครับว่าเขียนแบบใหม่
กับแบบเก่าทำอย่างไร
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
แบบใหม่จะเขียนแบบนี้ครับพี่น้อง ผมไม่รู้ว่ามันดูดีกว่าเดิมหรือดูน่ากลัวกว่าเดิมอันนี้แล้วแต่คนนะครับ
public @Bean EntityManagerFactory entityManagerFactory() {
...
...
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setJpaProperties(jpaProperties);
em.setDataSource(dataSource());
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setLoadTimeWeaver(loadTimeWeaver());
return this.getObject(em, EntityManagerFactory.class);
}
เริ่มชินกันแล้วใช่ไหมครับ ตอนต่อไปจะเป็นเรื่องของการ วนออกไปดูฝั่ง EntityManager จริงๆว่ามี Annoation แปลกอะไรให้เราดูกันบ้างครับ
โอ้วพี่รูฟ …. มันน่ากลัวมากๆ ครับ ขอบอก อิิอิอิ
===================================
…. มี 2 projects ทำด้วย Spring Framework แต่คงได้ใช้แค่ Spring-JDBC ครับ อิอิอิ ….