תלות מעגלית באביב

1. מהי תלות מעגלית?

זה קורה כאשר שעועית A תלויה בשעועית אחרת, והשעועית תלויה גם בשעועית A:

שעועית A → שעועית → שעועית A

כמובן, יכול להיות שיש לנו יותר שעועית מרומזת:

שעועית A → שעועית → שעועית C → שעועית D → שעועית E → שעועית A

2. מה קורה באביב

כאשר הקשר האביב טוען את כל השעועית, הוא מנסה ליצור שעועית לפי הסדר הדרוש להם כדי לעבוד לחלוטין. למשל, אם לא הייתה לנו תלות מעגלית, כמו המקרה הבא:

שעועית A → שעועית שעועית → שעועית C

האביב ייצור שעועית C, ואז תיצור שעועית B (ותזריק אליה שעועית C), ואז תיצור שעועית A (ותזריק לה שעועית B).

אך כאשר יש תלות מעגלית, אביב אינו יכול להחליט מי מהשעועית צריכה להיווצר תחילה, מכיוון שהם תלויים זה בזה. במקרים אלה אביב יעלה א BeanCurrentlyInCreationException בזמן טעינת ההקשר.

זה יכול לקרות באביב בשימוש הזרקת קונסטרוקטור; אם אתה משתמש בסוגים אחרים של זריקות, אתה לא אמור למצוא בעיה זו מכיוון שהתלות תוזרק בעת הצורך ולא בעת טעינת ההקשר.

3. דוגמה מהירה

בואו נגדיר שתי שעועית התלויות זו בזו (באמצעות הזרקת קונסטרוקטור):

מחלקה ציבורית @ רכיב ציבורי CircularDependencyA {CircularDependencyB פרטי CircB; @ CircularDependencyA ציבורי אוטומטי (CircularDependencyB circB) {this.circB = circB; }}
@Component class class CircularDependencyB {Private CircularDependencyA circA; @Autowered ציבורי CircularDependencyB (CircularDependencyA circA) {this.circA = circA; }}

כעת נוכל לכתוב מחלקת תצורה למבחנים, נקרא לזה TestConfig, המציין את חבילת הבסיס לסריקת רכיבים. נניח שהשעועית שלנו מוגדרת בחבילה "com.baeldung.circulardependency”:

@Configuration @ComponentScan (basePackages = {"com.baeldung.circulardependency"}) מחלקה ציבורית TestConfig {}

ולבסוף נוכל לכתוב מבחן JUnit כדי לבדוק את התלות המעגלית. הבדיקה יכולה להיות ריקה, מכיוון שהתלות המעגלית תתגלה במהלך טעינת ההקשר.

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (class = {TestConfig.class}) class public CircularDependencyTest {@Test public void givenCircularDependency_whenConstructorInjection_thenItFails () {// מבחן ריק; אנחנו רק רוצים שההקשר יטען}}

אם תנסה להריץ את הבדיקה הזו, תקבל את החריג הבא:

BeanCurrentlyInCreationException: שגיאה ביצירת שעועית עם השם 'circularDependencyA': השעועית המבוקשת נמצאת כרגע ביצירה: האם יש התייחסות מעגלית בלתי פתירה?

4. הדרכים לעקיפת הבעיה

אנו נראה כמה מהדרכים הפופולריות ביותר להתמודד עם בעיה זו.

4.1. עיצוב מחדש

כשיש לך תלות מעגלית, סביר להניח שיש לך בעיית עיצוב והאחריות אינה מופרדת היטב. כדאי לנסות לעצב מחדש את הרכיבים כראוי כך שההיררכיה שלהם מתוכננת היטב ואין צורך בתלות מעגלית.

אם אינך יכול לעצב מחדש את הרכיבים (יכולות להיות סיבות רבות אפשריות לכך: קוד מדור קודם, קוד שכבר נבדק ולא ניתן לשנותו, אין מספיק זמן או משאבים לעיצוב מחדש מלא ...), יש כמה דרכים לעקיפת הבעיה.

4.2. להשתמש @עָצֵל

דרך פשוטה לשבור את המחזור היא לומר אביב לאתחל את אחת מהשעועיות בעצלתיים. כלומר: במקום לאתחל את השעועית במלואה, היא תיצור פרוקסי שיזריק אותה לשעועית השנייה. השעועית המוזרקת תיווצר במלואה רק לאחר הצורך בה.

כדי לנסות זאת באמצעות הקוד שלנו, באפשרותך לשנות את התלות המעגלית כדלקמן:

מחלקה ציבורית @ רכיב ציבורי CircularDependencyA {CircularDependencyB פרטי CircB; @Autowired CircularDependencyA (@Lazy CircularDependencyB circB) {this.circB = circB; }}

אם תריץ את הבדיקה עכשיו, תראה שהשגיאה לא מתרחשת הפעם.

4.3. השתמש בזריקה / מגדיר שדה

אחד הדרכים לעקיפת הבעיה הפופולריות ביותר, וגם מה שמציע תיעוד של אביב, הוא שימוש בהזרקת סטרים.

במילים פשוטות אם תשנה את הדרכים המחוברות לשעועית שלך להשתמש בזריקת סטר (או הזרקת שדה) במקום הזרקת קונסטרוקטור - זה אכן מטפל בבעיה. בדרך זו האביב יוצר את השעועית, אך התלות לא מוזרקת עד שנחוצה להן.

בואו נעשה את זה - בואו נשנה את השיעורים שלנו לשימוש בהזרקות סטרים ונוסיף שדה נוסף (הוֹדָעָה) ל תלות מעגלית B. כדי שנוכל לבצע בדיקת יחידה נכונה:

מחלקה ציבורית @ רכיב ציבורי CircularDependencyA {CircularDependencyB פרטי CircB; @Autowired public void setCircB (CircularDependencyB circB) {this.circB = circB; } CircularDependencyB ציבורי getCircB () {return circB; }}
@Component class class CircularDependencyB {Private CircularDependencyA circA; הודעת מחרוזת פרטית = "היי!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } מחרוזת getMessage ציבורית () {הודעה חוזרת; }}

כעת עלינו לבצע כמה שינויים במבחן היחידה שלנו:

@RunWith (SpringJUnit4ClassRunner.class) @ContextConfiguration (מחלקות = {TestConfig.class}) הקשר הציבורי CircularDependencyTest {@Autowired ApplicationContext context; @Bean הציבור CircularDependencyA getCircularDependencyA () {להחזיר CircularDependencyA חדש (); } @ שעועית ציבורי CircularDependencyB getCircularDependencyB () {להחזיר CircularDependencyB חדש (); } @ מבחן חלל ציבורי givenCircularDependency_whenSetterInjection_thenItWorks () {CircularDependencyA circA = context.getBean (CircularDependencyA.class); Assert.assertEquals ("היי!", CircA.getCircB (). GetMessage ()); }}

להלן הסברים על ההערות שנראו לעיל:

@אפונה: כדי לומר למסגרת האביב כי יש להשתמש בשיטות אלה בכדי לאחזר יישום של שעועית להזרקה.

@מִבְחָן: הבדיקה תקבל שעועית CircularDependency מההקשר ותטען כי CircularDependencyB שלה הוזרק כראוי, תוך בדיקת ערך הערך שלה הוֹדָעָה תכונה.

4.4. להשתמש @PostConstruct

דרך נוספת לשבור את המחזור היא הזרקת תלות באמצעות @Autowired על אחת מהשעועית, ואז השתמש בשיטה שמסומנת עליה @PostConstruct כדי לקבוע את התלות האחרת.

לשעועית שלנו יכול להיות הקוד הבא:

@ מחלקה ציבורית רכיב ציבורי CircularDependencyA {@ CircularDependency B פרטי באופן אוטומטי; @PostConstruct init () ריק () {circB.setCircA (זה); } CircularDependencyB ציבורי getCircB () {return circB; }}
@Component class class CircularDependencyB {Private CircularDependencyA circA; הודעת מחרוזת פרטית = "היי!"; בטל ציבורי setCircA (CircularDependencyA circA) {this.circA = circA; } מחרוזת getMessage ציבורית () {הודעה חוזרת; }}

ונוכל לבצע את אותה בדיקה שקיימנו בעבר, לכן אנו בודקים כי חריג התלות המעגלי עדיין לא נזרק וכי התלות מוזרקת כראוי.

4.5. ליישם ApplicationContextAware ו אתחול שעועית

אם אחד מהשעועית מיושם ApplicationContextAware, לשעועית יש גישה להקשר האביב והיא יכולה לחלץ את השעועית השנייה משם. יישום אתחול שעועית אנו מציינים כי שעועית זו צריכה לבצע פעולות לאחר שנקבעו כל תכונותיה; במקרה זה, אנו רוצים להגדיר ידנית את התלות שלנו.

הקוד של השעועית שלנו יהיה:

מחלקה ציבורית @Component CircularDependencyA מיישמת את ApplicationContextAware, InitializingBean {CircularDependencyB privateB circB; הקשר ApplicationContext פרטי; CircularDependency ציבורי getCircB () {להחזיר circB; } @ ביטול ציבורי בטל לאחרPropertiesSet () זורק חריג {circB = context.getBean (CircularDependencyB.class); } @ Override public void setApplicationContext (סופי ApplicationContext ctx) זורק BeansException {context = ctx; }}
@Component class class CircularDependencyB {Private CircularDependencyA circA; הודעת מחרוזת פרטית = "היי!"; @Autowired public void setCircA (CircularDependencyA circA) {this.circA = circA; } מחרוזת getMessage ציבורית () {הודעה חוזרת; }}

שוב נוכל להריץ את המבחן הקודם ולראות שהחריג לא נזרק ושהמבחן פועל כצפוי.

5. לסיכום

ישנן דרכים רבות להתמודד עם תלות מעגלית באביב. הדבר הראשון שיש לקחת בחשבון הוא לעצב מחדש את השעועית שלך כך שלא יהיה צורך בתלות מעגלית: הם בדרך כלל סימפטום של עיצוב שניתן לשפר.

אבל אם אתה בהחלט צריך להיות תלויות מעגליות בפרויקט שלך, אתה יכול לעקוב אחר כמה מהפתרונות המוצעים כאן.

השיטה המועדפת היא באמצעות זריקות סטרים. אבל ישנן חלופות אחרות, שבדרך כלל מבוססות על עצירת ספרינג מלנהל את האתחול והזרקת השעועית, ולעשות זאת בעצמך באמצעות אסטרטגיה זו או אחרת.

את הדוגמאות ניתן למצוא בפרויקט GitHub.


$config[zx-auto] not found$config[zx-overlay] not found