1. ภาพรวม
ในบทความนี้เราจะแนะนำแนวคิดของ IoC (Inversion of Control) และ DI (Dependency Injection) จากนั้นเราจะมาดูวิธีการนำสิ่งเหล่านี้ไปใช้ใน Spring framework
2. Inversion of Control คืออะไร?
Inversion of Control เป็นหลักการในวิศวกรรมซอฟต์แวร์ซึ่งการควบคุมวัตถุหรือบางส่วนของโปรแกรมจะถูกถ่ายโอนไปยังคอนเทนเนอร์หรือเฟรมเวิร์ก มักใช้ในบริบทของการเขียนโปรแกรมเชิงวัตถุ
ตรงกันข้ามกับการเขียนโปรแกรมแบบเดิมซึ่งโค้ดที่กำหนดเองของเราทำการเรียกไปยังไลบรารี IoC ช่วยให้เฟรมเวิร์กสามารถควบคุมโฟลว์ของโปรแกรมและโทรไปยังโค้ดที่กำหนดเองของเรา ในการเปิดใช้งานเฟรมเวิร์กจะใช้นามธรรมที่มีพฤติกรรมเพิ่มเติมในตัวหากเราต้องการเพิ่มพฤติกรรมของเราเองเราจำเป็นต้องขยายคลาสของเฟรมเวิร์กหรือปลั๊กอินคลาสของเราเอง
ข้อดีของสถาปัตยกรรมนี้คือ:
- แยกการดำเนินการของงานออกจากการนำไปใช้งาน
- ทำให้ง่ายต่อการสลับระหว่างการใช้งานต่างๆ
- ความเป็นโมดูลาร์ที่มากขึ้นของโปรแกรม
- ง่ายกว่าในการทดสอบโปรแกรมโดยการแยกองค์ประกอบหรือจำลองการอ้างอิงและอนุญาตให้ส่วนประกอบสื่อสารผ่านสัญญา
การผกผันของการควบคุมสามารถทำได้โดยใช้กลไกต่างๆเช่นรูปแบบการออกแบบกลยุทธ์รูปแบบตัวระบุตำแหน่งบริการรูปแบบโรงงานและการฉีดพึ่งพา (DI)
เราจะดู DI ต่อไป
3. Dependency Injection คืออะไร?
Dependency injection เป็นรูปแบบที่จะใช้ IoC โดยที่การควบคุมกลับหัวคือการตั้งค่าการอ้างอิงของวัตถุ
การเชื่อมต่อวัตถุกับวัตถุอื่น ๆ หรือการ "ฉีด" วัตถุไปยังวัตถุอื่นนั้นกระทำโดยผู้ประกอบแทนที่จะทำโดยตัววัตถุเอง
นี่คือวิธีที่คุณจะสร้างการพึ่งพาวัตถุในการเขียนโปรแกรมแบบเดิม:
public class Store { private Item item; public Store() { item = new ItemImpl1(); } }
ในตัวอย่างด้านบนเราจำเป็นต้องสร้างอินสแตนซ์การใช้งานอินเทอร์เฟซItemภายในคลาสStore
ด้วยการใช้ DI เราสามารถเขียนตัวอย่างซ้ำได้โดยไม่ต้องระบุการนำไอเทมที่เราต้องการไปใช้:
public class Store { private Item item; public Store(Item item) { this.item = item; } }
ในส่วนถัดไปเราจะดูว่าเราสามารถให้การใช้งานรายการผ่านข้อมูลเมตาได้อย่างไร
ทั้ง IoC และ DI เป็นแนวคิดที่เรียบง่าย แต่มีผลกระทบอย่างลึกซึ้งในวิธีที่เราจัดโครงสร้างระบบของเราดังนั้นจึงควรค่าแก่การทำความเข้าใจเป็นอย่างดี
4. คอนเทนเนอร์ Spring IoC
คอนเทนเนอร์ IoC เป็นลักษณะทั่วไปของเฟรมเวิร์กที่ใช้ IoC
ในกรอบฤดูใบไม้ผลิภาชนะ IoC เป็นตัวแทนจากอินเตอร์เฟซApplicationContext Spring container มีหน้าที่ในการสร้างอินสแตนซ์กำหนดค่าและประกอบวัตถุที่เรียกว่าbeanตลอดจนการจัดการวงจรชีวิต
Spring framework จัดเตรียมการใช้งานอินเทอร์เฟซApplicationContext - ClassPathXmlApplicationContextและFileSystemXmlApplicationContextสำหรับแอ็พพลิเคชันแบบสแตนด์อโลนและWebApplicationContextสำหรับเว็บแอ็พพลิเคชัน
ในการประกอบ bean คอนเทนเนอร์จะใช้ข้อมูลเมตาของการกำหนดค่าซึ่งอาจอยู่ในรูปแบบของการกำหนดค่า XML หรือคำอธิบายประกอบ
วิธีหนึ่งในการสร้างอินสแตนซ์คอนเทนเนอร์ด้วยตนเองมีดังนี้
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ในการตั้งค่าแอตทริบิวต์รายการในตัวอย่างด้านบนเราสามารถใช้ข้อมูลเมตา จากนั้นคอนเทนเนอร์จะอ่านข้อมูลเมตานี้และใช้เพื่อประกอบ bean ในขณะรันไทม์
Dependency Injection ใน Spring สามารถทำได้ผ่านตัวสร้างตัวตั้งค่าหรือฟิลด์
5. การฉีดขึ้นอยู่กับตัวสร้าง
ในกรณีของการฉีดขึ้นต่อกันตามคอนสตรัคเตอร์คอนเทนเนอร์จะเรียกคอนสตรัคเตอร์ที่มีอาร์กิวเมนต์ซึ่งแสดงถึงการพึ่งพาที่เราต้องการตั้งค่า
Spring แก้ไขอาร์กิวเมนต์แต่ละรายการตามประเภทเป็นหลักตามด้วยชื่อของแอตทริบิวต์และดัชนีสำหรับการลดความสับสน มาดูการกำหนดค่าของ bean และการอ้างอิงโดยใช้คำอธิบายประกอบ:
@Configuration public class AppConfig { @Bean public Item item1() { return new ItemImpl1(); } @Bean public Store store() { return new Store(item1()); } }
@Configurationคำอธิบายประกอบแสดงให้เห็นว่าการเรียนเป็นที่มาของคำจำกัดความถั่ว นอกจากนี้เรายังสามารถเพิ่มลงในคลาสการกำหนดค่าต่างๆได้
@Beanคำอธิบายประกอบที่ใช้ในวิธีการที่จะกำหนดถั่ว หากเราไม่ระบุชื่อที่กำหนดเองชื่อ bean จะเริ่มต้นเป็นชื่อเมธอด
สำหรับ bean ที่มีขอบเขตsingletonดีฟอลต์Spring จะตรวจสอบก่อนว่าอินสแตนซ์ที่แคชของ bean มีอยู่แล้วหรือไม่และจะสร้างขึ้นใหม่ถ้าไม่มี หากเราใช้ขอบเขตต้นแบบคอนเทนเนอร์จะส่งคืนอินสแตนซ์ bean ใหม่สำหรับการเรียกแต่ละวิธี
อีกวิธีหนึ่งในการสร้างการกำหนดค่าของถั่วคือผ่านการกำหนดค่า XML:
6. Setter-Based Dependency Injection
สำหรับ DI ที่ใช้ setter คอนเทนเนอร์จะเรียกเมธอด setter ของคลาสของเราหลังจากเรียกใช้ตัวสร้างที่ไม่มีอาร์กิวเมนต์หรือวิธีโรงงานแบบคงที่ไม่มีอาร์กิวเมนต์เพื่อสร้างอินสแตนซ์ของ bean มาสร้างการกำหนดค่านี้โดยใช้คำอธิบายประกอบ:
@Bean public Store store() { Store store = new Store(); store.setItem(item1()); return store; }
นอกจากนี้เรายังสามารถใช้ XML สำหรับการกำหนดค่าถั่วเดียวกันได้:
Constructor-based and setter-based types of injection can be combined for the same bean. The Spring documentation recommends using constructor-based injection for mandatory dependencies, and setter-based injection for optional ones.
7. Field-Based Dependency Injection
In case of Field-Based DI, we can inject the dependencies by marking them with an @Autowired annotation:
public class Store { @Autowired private Item item; }
While constructing the Store object, if there's no constructor or setter method to inject the Item bean, the container will use reflection to inject Item into Store.
We can also achieve this using XML configuration.
This approach might look simpler and cleaner but is not recommended to use because it has a few drawbacks such as:
- This method uses reflection to inject the dependencies, which is costlier than constructor-based or setter-based injection
- It's really easy to keep adding multiple dependencies using this approach. If you were using constructor injection having multiple arguments would have made us think that the class does more than one thing which can violate the Single Responsibility Principle.
More information on @Autowired annotation can be found in Wiring In Spring article.
8. Autowiring Dependencies
Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.
There are four modes of autowiring a bean using an XML configuration:
- no: the default value – this means no autowiring is used for the bean and we have to explicitly name the dependencies
- byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set
- byType: similar to the byName autowiring, only based on the type of the property. This means Spring will look for a bean with the same type of the property to set. If there's more than one bean of that type, the framework throws an exception.
- constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments
For example, let's autowire the item1 bean defined above by type into the store bean:
@Bean(autowire = Autowire.BY_TYPE) public class Store { private Item item; public setItem(Item item){ this.item = item; } }
We can also inject beans using the @Autowired annotation for autowiring by type:
public class Store { @Autowired private Item item; }
If there's more than one bean of the same type, we can use the @Qualifier annotation to reference a bean by name:
public class Store { @Autowired @Qualifier("item1") private Item item; }
Now, let's autowire beans by type through XML configuration:
Next, let's inject a bean named item into the item property of store bean by name through XML:
We can also override the autowiring by defining dependencies explicitly through constructor arguments or setters.
9. Lazy Initialized Beans
By default, the container creates and configures all singleton beans during initialization. To avoid this, you can use the lazy-init attribute with value true on the bean configuration:
As a consequence, the item1 bean will be initialized only when it's first requested, and not at startup. The advantage of this is faster initialization time, but the trade-off is that configuration errors may be discovered only after the bean is requested, which could be several hours or even days after the application has already been running.
10. Conclusion
In this article, we've presented the concepts of inversion of control and dependency injection and exemplified them in the Spring framework.
You can read more about these concepts in Martin Fowler's articles:
- การผกผันของคอนเทนเนอร์ควบคุมและรูปแบบการฉีดพึ่งพา
- การผกผันของการควบคุม
และคุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับการใช้งาน IoC และ DI ในฤดูใบไม้ผลิได้ในเอกสารอ้างอิง Spring Framework