วันจันทร์ที่ 18 มกราคม พ.ศ. 2559

Spring part 8-2 Spring Annotation Configuration with XML

ต่อจากคราวก่อนที่เรากล่าวถึง Stereotype Annotations, เขาคือผู้ช่วยที่จะทำให้โค้ดฝั่ง XML น้อยลงและเพิ่มโค้ดฝั่งจาวาเพียงเล็กน้อยเท่านั้นผ่านสิ่งที่เรียกว่า annotation ถามว่าสถานที่หรือ layer เช่นไรควรจะวาง @Component หรือวาง @Service หรือวาง @Repository? อ่านตอนท้ายของ part ที่ผ่านมาเรายังสามารถตอบได้ แต่ถ้าถามว่ามันให้ความเจาะจงหรือมีประโยชน์ยิ่งในเรื่องใดจึงต้องมาแบ่งมันออกตาม layer?

เท่าที่ผมทราบมาเขาว่ามันเป็นมากกว่านั้น เป็นมากกว่าการจัดเตรียมและฉีด (injection) ออบเจ็กต์เสียอีก เริ่มจากการแบ่งโค้ดออกเป็น layer จะช่วยให้เพื่อนๆจัดการโค้ดง่ายขึ้น มีสัดส่วนที่อธิบายหน้าที่ของมันอย่างชัดเจน และเมื่อนำ spring stereotype annotations มาใช้กับประดา layer ข้างต้น จะเป็นเหตุให้เราสามารถนำความคิด AOP (Aspect-oriented programming) มาใช้ต่อได้ ซึ่งหนึ่งในเป้าหมายก็คือการทำ pointcut ครับผม (อ่านเอกสาร) แต่ผมไม่ได้ศึกษาเรื่อง AOP ก็ยังคงนำมาเล่าให้ฟังไม่ได้ เอาล่ะ เรามาต่อที่โปรเจ็กค์คราวก่อนกันเถอะ

*** เพื่อนๆควรทราบว่าการไม่ระบุ configure namespaces แก่ spring bean configuration file (applicationContext.xml) เราก็จะไม่สามารถเรียกไปยังคลาสใดๆที่ต้องการได้ ดังขั้นตอนที่ผ่านมานี้ซึ่งเราได้บอกให้ spring bean configuration file รู้จักกับ beans และ context XSD namespaces (อ่านเอกสารว่ามันสำคัญอย่างไร) และด้วยวิธีการนี้ก็เพียงพอแล้วที่ spring จะรับรู้ว่าต่อไปนี้จะมีการใช้ annotation-config ภายในโปรเจ็กต์ พร้อมด้วยการกำหนดพื้นที่ที่จะให้มันมองหาเหล่า annotation ดังกล่าว

>> BookRepositoryImpl นั้นอยู่ใน repository layer เราจึงนำ @Repository ไปวางไว้ ดังนี้ (ยังไม่ต้องตั้งชื่อให้กับมันเหมือนในรูปด้านล่างก็ได้นะครับ เพราะเรายังไม่ได้ใช้)


>> ส่วน BookServiceImpl นั้นอยู่ใน service layer เราจึงนำ @Service ไปวางไว้ ดังนี้ (ตั้งชื่อด้วย เดี๋ยวจะเรียกใช้ที่ main)


>> กลับมาที่ main คลาส Program ครับ เขียนโค้ดทดสอบการเรียกใช้ service ผ่าน context กันเลย

*** จากจาร์ที่เรามี รันปุ๊บต้องพังทันที ให้เพิ่ม spring-aop จาร์เข้าไปอีกตัวครับ

*** โหลดจาก http://mvnrepository.com

>> จาร์ที่โหลดทั้งหมดมีดังนี้
- commons-logging-1.2.jar
- spring-aop-4.2.4.RELEASE.jar
- spring-beans-4.2.4.RELEASE.jar
- spring-context-4.2.4.RELEASE.jar
- spring-core-4.2.4.RELEASE.jar
- spring-expression-4.2.4.RELEASE.jar

>> เสร็จแล้วรันด้วยการคลิกขวาที่คลาส Program แล้วเลือก Run As เลือก Java Application


>> โปรเจ็กค์นี้เพื่อนๆจะสังเกตได้ว่า แท้จริงเวทมนตร์ของ spring นั้นถูกซ้อนเร้นจากสายตาคล้ายก้อนภูเขาน้ำแข็งใต้น้ำทะเลมิผิดเพี้ยน ระบบของมันเริ่มตั้งแต่การ bootstrap สิ่งที่เรียกว่า annotation scanner ในไฟล์ applicationContext.xml ที่เราได้ประกาศเอาไว้ กระทั่งเราสั่งให้ context เสาะหาชื่อที่ตั้งไว้ว่า bookService ในคลาส Program เจ้า context ก็จะสร้างออบเจ็กต์ของ BookService ให้กับเรา ถึงตอนนี้เราเรียกไปยังเมธอด findAll มันก็จะเข้าไปทำงานและคืนค่าออกมาเป็น List ของ Book นั่นเอง

>> ยังไม่น่าสนใจมากพอเพราะเราต่างรู้จัก Autowire จริงไหม? แล้วไหนล่ะการฉีดแบบไม่พึ่งการเขียน XML configuration?

>> เพื่อนๆทราบอยู่แล้วว่า autowire คือการฉีดออบเจ็กต์ใดๆให้กับคลาสที่ต้องการใช้ออบเจ็กต์นั้น และมีอยู่ 3 แห่งที่เราสามารถเขียน @Autowired ลงไปได้
1. Member variable
       ตัวอย่าง @Autowired private BookRepository bookRepository;

2. Constructor
       ตัวอย่าง @Autowired public BookServiceImpl( ... ) { ... }

3. Setter method
       ตัวอย่าง @Autowired public void setBookRepository( ... ) { ... }

>> เลือกอันแรกก่อนเลย autowire ผ่าน member variable ด้วยการลบหรือ comment คำสั่งให้ค่าที่เขียนว่า new BookRepositoryImpl(); ออกไป (ดูรูปประกอบนะครับ) แล้วรัน

- พังสิครับ เพราะค่าของออบเจ็กต์ repository เป็น null ไปแล้ว, โอเคนะ ใส่เวทมนตร์ได้ (ใส่ @Autowired)

- สำเร็จ!

>> เลือกอันที่สอง autowire ผ่าน constructor จึงต้องเขียน constructor ให้กับคลาส BookServiceImpl เพื่อทดสอบดังนี้

- สำเร็จ!

>> ข้อสุดท้ายแล้ว autowire ผ่าน setter method จึงต้องเขียน setter ให้กับคลาส BookServiceImpl เพื่อทดสอบดังนี้

- ปรากฏว่าพัง... เพราะมันหา default constructor ไม่เจอ และที่ไม่เจอก็เพราะว่าเราได้เขียน constructor ขึ้นมาเอง เป็นเหตุให้ default constructor ตายไปแล้วเรียบร้อย จะแก้ไขอย่างไรล่ะ? ก็เขียน constructor ที่มีหน้าตาเหมือน default constructor ขึ้นมาเพิ่มอีกสิ หรือไม่ก็ลบ constructor ที่มีอยู่ทิ้งไป ในที่นี้ผมเลือกเขียนขึ้นมาเพิ่มแล้วกันนะครับ

- สำเร็จ! และจากความรู้ที่ผ่านมาทำให้เราทราบว่าการ autowire ผ่าน setter method ในตัวอย่างนี้เป็นการ autowire by type เช่นเดียวกับตัวอย่างที่เคยใช้ xml เขียนมาแล้วนั่นเองครับ

5 ความคิดเห็น:

  1. คำถามครับ
    1.ทำไมก่อนหน้านี้(ก่อนที่เราจะย้ายมาทำใน spring-tool) เราต้องไปเขียน bean เองก่อน แล้วค่อยมาเขียน java แต่พอมาตอนนี้เรามาเขียนใน java ได้เลย แค่ต้องเพิ่ม namespace-> context ที่ไฟล์ xml เข้าไป ต่อด้วย set package name แล้ว load AOD.jar มาลงเพิ่ม(สงสัยเพิ่มครับ ทำไมตอนแรกถึงไม่ต้องใช้ AOD.jar)

    2.ทำไมตอนแรกการ setter method ต้องตั้งชื่อเป็น setName แต่ตอนนี้เป็น setTheName ครับ

    ตอบลบ
    คำตอบ
    1. เปลี่ยน AOD เป็น AOP ครับ ดูผิด

      ลบ
    2. สวัสดีครับ :) เพื่อน Phruds นอนดึกนะครับเนี่ย และยกนิ้วให้สำหรับความพยายามในการโค้ดและทำความเข้าใจโพสต์เหล่านี้ของผมครับ ข้อสังเกตของเพื่อนมีประโยชน์มากสำหรับผมที่เป็นผู้เริ่มต้นเช่นเดียวกัน ตอบคำถามข้อ 1) ก่อนหน้านี้ทำไมเราต้องไปเขียนบีนก่อน (เข้าใจว่าเขียน config บีนกันใน xml เน๊อะ) แล้วค่อยมาเขียนจาวา (หมายถึงหลังจากขั้นตอน config บีนด้วย xml เสร็จไปแล้ว) คำตอบก็เพราะว่า (อันที่จริงผมก็ไม่ได้ลำดับความคิดขั้นตอนให้ฟังก่อน ก็ผิดที่ผมไม่ได้ชี้แจงครับ ขออภัยจริงๆ) แรกเริ่มเรามีแนวโน้นชีให้เห็นว่าเราจะเริ่มใช้ spring กับ xml ก่อน หลังจากนั้นก็จะลด xml ให้น้อยลงไปจนกระทั่งไม่เห็นไฟล์ xml อีกเลย กล่าวคือเหลือเพียงจาวาโค้ดครับ แนวทางสมัยใหม่นี้นักพัฒนาจะมุ่งเน้นไปที่การจัดการกับธุรกิจหรือความต้องการของโปรแกรมจริงๆ (เรียกว่า business logic) ดังนั้นการ config xml จะกลายเป็นเตรียมผ่าน annotation แทน โดยเฉพาะกับ spring เวอร์ชัน 3.x นี้เป็นต้นไปจ้า

      ลบ
    3. ตอบคำถามข้อที่ 2) ทำไมตอนแรกไม่ใช้จาร์ AOP แต่ตอนหลังกลับต้องใช้มัน คำตอบนั้นมาจากการที่เราต้องการลดวิธี config หรือประกาศบีนด้วย xml นั่นแหละครับ เพื่อลดขั้นตอนนี้จึงให้ context หรือที่เรียกว่าบริบทเป็นตัวช่วย scan ให้, เจ้า context นี้มันสามารถรู้จักบีนได้ทุกตัวผ่านทาง package (context สำหรับผมก็คือสภาพแวดล้อมของโปรเจ็กต์ทั้งโปรเจ็กต์ครับ) นี้จึงเป็นเหตุให้เราต้อง set package name และวิธีการหาบีนที่ context ใช้ก็ต้องอาศัยเครื่องมือในการมองหาบีน (มันก็คือ org.springframework.aop.TargetSource) ซึ่งเป็นหนึ่งใน compile dependencies ที่เจ้าจาร์ context จำเป็นต้องใช้งาน และมันก็อยู่ในจาร์ AOP ครับผม

      ลบ
    4. จากข้อ 2) นี่หมายความว่า หากเพื่อน config หรือประกาศบีนด้วย xml เองทั้งหมด เพื่อนอาจไม่จำเป็นต้องการเครื่องมือในการ scan เหตุนี้ก็ไม่จำเป็นต้อง include หรือโหลดจาร์ AOP เข้ามาเพิ่มนั่นเอง เอาล่ะ ตอบคำถามข้อสุดท้าย ข้อที่ 3) เหตุที่เปลี่ยนชื่อ method จาก setName ปกติเป็น setTheName นั้นเป็นสิ่งที่ผมต้องการแสดงให้เห็นว่า context ใช้วิธีการมองหาบีนและ autowire บีนนี้ด้วยวิธีการเปรียบเทียบที่พารามิเตอร์ เมื่อเปลี่ยนชื่อ setter method ก็ไม่ error อย่างไรล่ะครับ ศึกษาเพิ่มเติมได้ที่

      http://www.mkyong.com/spring/spring-auto-wiring-beans-with-autowired-annotation/

      ลบ