Posts Tagged ‘JavaConfig’

แกะ Spring JavaConfig ภาค 1

June 18th, 2009

เคยเขียนเรื่อง 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 แปลกอะไรให้เราดูกันบ้างครับ