สวัสดี! ในขณะที่การดูแลระบบ JavaRush กำลังทำงานในระดับใหม่ ฉันต้องการเริ่มบทความการฝึกอบรมเกี่ยวกับ Spring Framework ใช่ ฉันรู้ว่ามีเนื้อหามากมายในหัวข้อนี้บนอินเทอร์เน็ต แต่ดังที่แสดงให้เห็นในทางปฏิบัติ เนื้อหาทั้งหมดอยู่ในระดับ Hello World ฉันต้องการพูดไม่เกี่ยวกับวิธีการวางคำอธิบายประกอบอย่างถูกต้อง แต่เกี่ยวกับวิธีการทำงานทั้งหมด "ภายใต้ประทุน" บทความนี้มีไว้สำหรับผู้ที่เคยทำงานกับกรอบงานนี้ไม่ทางใดก็ทางหนึ่งและคุ้นเคยกับแนวคิดพื้นฐาน
ความจริงก็คือ bean ไม่จำเป็นต้องเป็นคลาสของตรรกะทางธุรกิจสำหรับแอปพลิเคชันของคุณ ถั่วเรียกอีกอย่างว่าถั่วโครงสร้างพื้นฐาน กล่าวโดยสรุปคือ Infrastructure bean คือคลาสที่กำหนดค่า Bean ตรรกะทางธุรกิจของคุณ (ใช่ มี Bean มากเกินไป) ฉันจะบอกคุณเพิ่มเติมเกี่ยวกับเรื่องนี้ด้านล่าง แต่เพื่อให้ชัดเจนขึ้นเล็กน้อยว่าการกำหนดค่า BPP ที่แน่นอนคืออะไร ฉันจะยกตัวอย่าง ทุกคนคุ้นเคยกับบทสรุปหรือไม่
การเริ่มต้นบริบท
เริ่มจากพื้นฐานกันก่อน ในความคิดของฉัน หนึ่งในประเด็นที่สำคัญที่สุดคือการทำความเข้าใจวิธีการตั้งค่าบริบทและการเริ่มต้น bean อย่างที่คุณทราบ ก่อนที่Springจะเริ่มทำงาน จะต้องได้รับการกำหนดค่าก่อน ในสมัยโบราณ สิ่งนี้ทำได้โดยใช้ไฟล์ 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="helloWorld" class="ru.javarush.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
โดยทั่วไปแล้วนี่ก็เพียงพอที่จะสร้างคอนโทรลเลอร์สองสามตัวและเริ่มต้นการเริ่มต้น (ซึ่งจะไม่เกิดขึ้น) แต่การกำหนดค่านี้จะทำให้ Spring ทำงานอย่างไร และนี่คือสิ่งที่น่าสนใจ เพื่อให้ Spring เข้าใจการกำหนดค่าของเรา มีไฟล์XmlBeanDefinitionReader
. นี่คือองค์ประกอบ Spring ภายในที่สแกน (แยกวิเคราะห์) xml และสร้าง ตามสิ่งที่เราเขียนไว้ที่BeanDefinition
นั่น BeanDefinition
เป็นวัตถุที่เก็บข้อมูลเกี่ยวกับถั่ว ซึ่งรวมถึง: ควรสร้างคลาส (bean) จากคลาสใด; ขอบเขต; ไม่ว่าจะติดตั้งการเริ่มต้นแบบขี้เกียจหรือไม่ จำเป็นต้องเริ่มต้นก่อน bean นี้หรือไม่ และคุณสมบัติอื่น ๆ ที่อธิบายไว้ใน xml ที่ได้รับทั้งหมดBeanDefinition
จะถูกเพิ่มเข้าไปHashMap
ใน โดยที่ตัวระบุคือชื่อของ bean (ตั้งค่าโดยคุณหรือกำหนดโดย Spring) และBeanDefinition
ตัววัตถุเอง หลังจากที่ทุกอย่างBeanDefinition
ถูกสร้างขึ้น ฮีโร่คนใหม่ก็ปรากฏตัวบนเวที - BeanFactory
. วัตถุนี้วนซ้ำHashMap’e
s BeanDefinition
สร้าง bean ตามพวกมันและวางลงในคอนเทนเนอร์ IoC ในความเป็นจริงมีความแตกต่างเล็กน้อยที่นี่ เมื่อแอปพลิเคชันเริ่มต้น คอนเทนเนอร์ IoC จะมี bean ที่มีขอบเขต Singleton (ตั้งค่าตามค่าเริ่มต้น) ในขณะที่ส่วนที่เหลือจะถูกสร้างขึ้นเมื่อจำเป็น (ต้นแบบ คำขอ เซสชัน) และตอนนี้เป็นการพูดนอกเรื่องเล็ก ๆ มาทำความรู้จักกับตัวละครอื่นกันดีกว่า
พบกับ BeanPostProcessor (บีพีพี)

@Autowired
? ดังนั้นคุณAutowiredAnnotationBeanPostProcessor
มีหน้าที่รับผิดชอบในการดูแลให้ชั้นเรียนทั้งหมดของคุณฝังอยู่ในกันและกัน 
กลับไปที่ BeanFactory กันดีกว่า
เมื่อทราบเกี่ยวกับ BPP แล้ว คุณต้องชี้แจงว่าเมื่อวนซ้ำHashMap
's BeanDefinition
ทั้งหมด ' จะถูกสร้างและวางแยกกันก่อน (ไม่ใช่ในคอนเทนเนอร์ IoC BeanPostProcessor
) หลังจากนี้ Bean ปกติของตรรกะทางธุรกิจของเราจะถูกสร้างขึ้น ใส่ลงในคอนเทนเนอร์ IoC และการกำหนดค่าจะเริ่มโดยใช้ BPP ที่เลื่อนออกไปแยกกัน และนี่คือสิ่งที่เกิดขึ้น แต่ละ BPP มี 2 วิธี:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
วนซ้ำผ่านถังขยะของเราสองครั้ง ครั้งแรกที่เรียกเมธอด และครั้งที่สองที่ เรียกpostProcessorBeforeInitialization
เมธอด postProcessorAfterInitialization
แน่นอนว่ามีคำถามเกิดขึ้นว่าทำไมต้องใช้สองวิธี ให้ฉันอธิบาย ความจริงก็คือในการประมวลผลคำอธิบายประกอบบางอย่าง (เช่น เป็นต้น@Transactional
) bean ของเราถูกแทนที่ด้วยคลาสพร็อกซี เพื่อทำความเข้าใจว่าเหตุใดจึงทำเช่นนี้ คุณจำเป็นต้องทราบวิธีการทำงาน@Transactional
และนี่คือวิธีการทำงาน คุณต้องเพิ่มโค้ดอีกสองสามบรรทัดให้กับวิธีการที่ทำเครื่องหมายด้วยคำอธิบายประกอบนี้ทันที ทำอย่างไร? ถูกต้องโดยการสร้างคลาสพร็อกซีซึ่งภายในโค้ดที่จำเป็นจะถูกเพิ่มลงในวิธีการที่ต้องการ ลองจินตนาการถึงสถานการณ์นี้ เรามีชั้นเรียน:
class A {
@Autowired
private SomeClass someClass;
@Transactional
public void method() {
// модификатор доступа обязательно public
}
}
คลาสนี้มีคำอธิบายประกอบ 2 รายการ@Autowired
และ@Transactional
. คำอธิบายประกอบทั้งสองได้รับการประมวลผลโดย BPP ที่ต่างกัน ถ้ามันได้ผลก่อนAutowiredBPP
ทุกอย่างจะดี แต่ถ้าไม่ได้เราก็จะเจอปัญหานี้ ความจริงก็คือเมื่อมีการสร้างคลาสพร็อกซี ข้อมูลเมตาทั้งหมดจะสูญหายไป กล่าวอีกนัยหนึ่ง จะไม่มีข้อมูลเกี่ยวกับคำอธิบายประกอบ@Autowired
ในคลาสพร็อกซี ดังนั้นจึงAutowiredBPP
ไม่ทำงาน ซึ่งหมายความว่าฟิลด์ของเราsomeClass
จะมีค่าnull
ซึ่งมักจะนำไปสู่ NPE นอกจากนี้ยังควรรู้ด้วยว่าระหว่างการเรียกใช้เมธอดนั้นpostProcessorBeforeInitialization
จะpostProcessorAfterInitialization
มีการเรียก เมธอด init
- ถ้ามี โดยพื้นฐานแล้วนี่คือ Constructor ตัวที่สอง แต่ความแตกต่างก็คือ ในขณะนี้ การขึ้นต่อกันทั้งหมดของเราได้ถูกฝังอยู่ในคลาสแล้ว และเราสามารถเข้าถึงได้จากinit
เมธอด - ดังนั้น อัลกอริธึมการเริ่มต้นบริบทอีกครั้ง:
XmlBeanDefinitionReader
สแกนไฟล์การกำหนดค่า xml ของเรา- สร้าง
BeanDefinition
's และใส่ไว้ในHashMap
. - มา
BeanFactory
และจากนี้HashMap
แยกกันเพิ่มทั้งหมดBeanPostProcessor
's - สร้าง
BeanDefinition
bean จาก 's และวางไว้ในคอนเทนเนอร์ IoC - ที่นี่ BPP มาและกำหนดค่า bean เหล่านี้โดยใช้ 2 วิธี
- พร้อม.
GO TO FULL VERSION