سلام! در حالی که مدیریت JavaRush در حال کار بر روی سطوح جدید است، من می خواهم یک سری مقالات آموزشی را در مورد Spring Framework شروع کنم. بله، من می دانم که در حال حاضر مطالب زیادی در مورد این موضوع در اینترنت وجود دارد، اما، همانطور که تمرین نشان می دهد، همه آنها در سطح Hello World هستند. من می خواهم در مورد نحوه صحیح قرار دادن حاشیه نویسی صحبت نکنم، بلکه در مورد نحوه عملکرد همه اینها "زیر کاپوت" صحبت کنم. این مقاله برای کسانی در نظر گرفته شده است که قبلاً با این چارچوب کار کرده اند و با مفاهیم اولیه آشنا هستند.
اولیه سازی زمینه
پس بیایید با اصول اولیه شروع کنیم. به نظر من، یکی از مهم ترین نکات این است که بفهمیم چگونه زمینه تنظیم شده است و beans چگونه مقداردهی می شود. همانطور که می دانید قبل از اینکه 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 ویژگی دیگری و سایر خصوصیات که در xml توضیح داده شده اند مقداردهی اولیه شود. همه دریافتها BeanDefinition
به اضافه میشوند HashMap
، که در آن شناسه نام bean (تنظیم شده توسط شما یا اختصاص داده شده توسط Spring) و BeanDefinition
خود شی است. پس از BeanDefinition
ایجاد همه چیز، یک قهرمان جدید روی صحنه ظاهر می شود - BeanFactory
. این شیء بر روی HashMap’e
s تکرار می شود BeanDefinition
، بر اساس آنها لوبیا ایجاد می کند و آنها را در یک ظرف IoC قرار می دهد. در اینجا یک تفاوت ظریف وجود دارد، در واقع، هنگامی که برنامه شروع می شود، ظرف IoC حاوی دانه هایی است که دارای یک محدوده Singleton هستند (به طور پیش فرض تنظیم شده است)، در حالی که بقیه در صورت نیاز (نمونه اولیه، درخواست، جلسه) ایجاد می شوند. و حالا یک انحراف کوچک، بیایید با یک شخصیت دیگر آشنا شویم.
با BeanPostProcessor آشنا شوید. (BPP)
واقعیت این است که یک لوبیا لزوماً یک کلاس منطق تجاری برای برنامه شما نیست. به لوبیا، دانه زیرساختی نیز گفته می شود. به طور خلاصه، زیرساخت bean کلاسی است که لوبیاهای منطقی کسب و کار شما را پیکربندی می کند (بله، لوبیاهای بسیار زیاد). من در زیر بیشتر در مورد آن به شما خواهم گفت، اما برای اینکه کمی واضح تر شود BPP دقیقاً چه پیکربندی می کند، یک مثال می زنم. آیا همه با خلاصه آشنایی دارند@Autowired
؟ بنابراین، شما AutowiredAnnotationBeanPostProcessor
مسئول هستید که اطمینان حاصل کنید که تمام کلاس های شما در یکدیگر تعبیه شده اند.
بیایید به BeanFactory برگردیم
با دانستن در مورد BPP، لازم است توضیح دهیم که هنگام تکرار بیش ازHashMap
'ها، BeanDefinition
ابتدا همه به طور جداگانه ایجاد و قرار می گیرند (نه در ظرف IoC) BeanPostProcessor
. پس از این، دانههای معمولی منطق تجاری ما ایجاد میشوند، در یک ظرف 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
init
XmlBeanDefinitionReader
فایل پیکربندی xml ما را اسکن می کند.- ایجاد می کند
BeanDefinition
و آنها را در آن قرار می دهدHashMap
. - می آید
BeanFactory
و از اینHashMap
به طور جداگانه همه 'ها را جمع می کندBeanPostProcessor
. BeanDefinition
لوبیاها را از 'ها ایجاد می کند و آنها را در یک ظرف IoC قرار می دهد.- در اینجا BPP ها می آیند و این دانه ها را با استفاده از 2 روش پیکربندی می کنند.
- آماده.
GO TO FULL VERSION