דפוסי עיצוב במסגרת האביב

1. הקדמה

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

במדריך זה נבחן ארבעה מדפוסי העיצוב הנפוצים ביותר המשמשים במסגרת האביב:

  1. דפוס סינגלטון
  2. דפוס שיטת המפעל
  3. דפוס פרוקסי
  4. תבנית תבנית

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

2. תבנית סינגלטון

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

2.1. שעועית סינגלטון

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

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

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

2.2. סינגלטונים אוטומטיים

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

ראשית, אנו יוצרים a מאגר ספרים שמנהל את שלנו סֵפֶר אובייקטים בתחום.

לאחר מכן, אנו יוצרים LibraryController, המשתמשת ב- מאגר ספרים להחזרת מספר הספרים בספרייה:

@ RestController מחלקה ציבורית LibraryController {@ מאגר פרטי מאגר BookRepository; @GetMapping ("/ count") ציבורי FindCount ארוך () {System.out.println (מאגר); להחזיר repository.count (); }}

לבסוף, אנו יוצרים BookController, שמתמקד ב סֵפֶרפעולות ספציפיות, כגון מציאת ספר לפי תעודת הזהות שלו:

@RestController בכיתה ציבורית BookController {@ מאגר פרטי BookRepository פרטי; @GetMapping ("/ book / {id}") public book findById (@PathVariable long id) {System.out.println (מאגר); להחזיר repository.findById (id) .get (); }}

לאחר מכן אנו מתחילים יישום זה ומבצעים GET ב- /לספור ו / ספר / 1:

curl -X GET // localhost: 8080 / count curl -X GET // localhost: 8080 / book / 1

בפלט היישום אנו רואים ששניהם מאגר ספרים לאובייקטים אותו מזהה אובייקט:

[דוא"ל מוגן] [דוא"ל מוגן]

ה מאגר ספרים מזהי אובייקט ב- LibraryController ו BookController זהים, מה שמוכיח כי אביב הזריק את אותה שעועית לשני הבקרים.

אנו יכולים ליצור מקרים נפרדים של ה- מאגר ספרים שעועית על ידי שינוי היקף השעועית מ קְלָף בּוֹדֵד ל אב טיפוס משתמש ב @היקף (ConfigurableBeanFactory.SCOPE_PROTOTYPE)ביאור.

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

3. תבנית שיטת המפעל

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

לעתים קרובות, אנו רוצים ליצור אובייקטים שונים המבוססים על הקשר מסוים.

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

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

3.1. הקשר יישום

אביב משתמש בטכניקה זו בשורש מסגרת הזרקת התלות שלה (DI).

בִּיסוֹדוֹ, פינוקים באביבמיכל שעועית כמפעל המייצר שעועית.

לפיכך, אביב מגדיר את BeanFactory ממשק כהפשטה של ​​מיכל שעועית:

ממשק ציבורי BeanFactory {getBean (Class requiredType); getBean (Class requiredType, Object ... args); getBean (שם מחרוזת); // ...]

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

האביב מתארך אז BeanFactory עם ה ApplicationContext ממשק, המציג תצורת יישומים נוספת. Spring משתמש בתצורה זו להפעלת מיכל שעועית המבוסס על תצורה חיצונית כלשהי, כגון קובץ XML או הערות Java.

משתמש ב ApplicationContext יישומים כיתתיים כמו AnnotationConfigApplicationContextלאחר מכן נוכל ליצור שעועית בשיטות המפעל השונות שעוברות בירושה מה- BeanFactory מִמְשָׁק.

ראשית, אנו יוצרים תצורת יישום פשוטה:

@Configuration @ComponentScan (basePackageClasses = ApplicationConfig.class) מחלקה ציבורית ApplicationConfig {}

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

@ מחלקה ציבורית ציבורית של רכיב {}

ואז צרו כיתה נוספת, בָּר, שמקבל טיעון קונסטרוקטור יחיד:

@Component @Scope (ConfigurableBeanFactory.SCOPE_PROTOTYPE) בר מחלקה ציבורית {שם מחרוזת פרטי; סרגל ציבורי (שם מחרוזת) {this.name = שם; } // גטר ...}

לבסוף, אנו יוצרים את השעועית שלנו דרך AnnotationConfigApplicationContext הטמעה של ApplicationContext:

@ מבחן ציבורי בטל כאשר GetSimpleBean_thenReturnConstructedBean () {ApplicationContext context = AnnotationConfigApplicationContext חדש (ApplicationConfig.class); Foo foo = context.getBean (Foo.class); assertNotNull (foo); } @Test הציבור בטל כאשר GetPrototypeBean_thenReturnConstructedBean () {String expectName = "שם כלשהו"; ApplicationContext context = AnnotationConfigApplicationContext חדש (ApplicationConfig.class); סרגל סרגל = context.getBean (Bar.class, expectName); assertNotNull (סרגל); assertThat (bar.getName (), הוא (expectName)); }

משתמש ב getBean בשיטת המפעל, אנו יכולים ליצור שעועית מוגדרת תוך שימוש רק בסוג הכיתה ובמקרה של בָּר - פרמטרים של קונסטרוקטור.

3.2. תצורה חיצונית

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

אם ברצוננו לשנות את היישום של האובייקטים החוטים האוטומטיים ביישום, אנו יכולים להתאים את ApplicationContext יישום בו אנו משתמשים.

לדוגמא, אנו יכולים לשנות את ה- AnnotationConfigApplicationContext למחלקת תצורה מבוססת XML, כגון ClassPathXmlApplicationContext:

@Test הציבור בטל שניתן XmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean () {String expectName = "שם כלשהו"; ApplicationContext context = חדש ClassPathXmlApplicationContext ("context.xml"); // אותה מבחן כמו קודם ...}

4. תבנית פרוקסי

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

4.1. עסקאות

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

לאחר מכן נוכל להשתמש ב- proxy במקום הנושא.

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

@Service בכיתה ציבורית BookManager {@ מאגר פרטי מאגר BookRepository; @ Transactional Public Book create (מחרוזת מחבר) {System.out.println (repository.getClass (). GetName ()); להחזיר repository.create (מחבר); }}

בשלנו BookManager בכיתה, אנו מציינים את לִיצוֹר שיטה עם @ Transactional ביאור. ביאור זה מורה לאביב לבצע באופן אטומי את שלנו לִיצוֹר שיטה. בלי פרוקסי, אביב לא יוכל לשלוט בגישה שלנו מאגר ספרים שעועית ולהבטיח את עקביות העסקאות שלה.

4.2. פרוקסי CGLib

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

כשאנחנו קוראים שלנו BookManager # ליצור שיטה, אנו יכולים לראות את הפלט:

com.baeldung.patterns.proxy.BookRepository $$ EnhancerBySpringCGLIB $$ 3dc2b55c

בדרך כלל היינו מצפים לראות תקן מאגר ספרים מזהה אובייקט; במקום זאת אנו רואים EnhancerBySpringCGLIB מזהה אובייקט.

מאחורי הקלעים, האביב עטף את שלנו מאגר ספרים חפץ בפנים כמו EnhancerBySpringCGLIB לְהִתְנַגֵד. האביב ובכך שולט בגישה שלנו מאגר ספרים אובייקט (הקפדה על עקביות עסקית).

באופן כללי, אביב משתמש בשני סוגים של פרוקסי:

  1. פרוקסי CGLib - משמשים כשיעורי proxying
  2. JDK Proxies דינמי - משמש בעת פרוקסי ממשקים

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

5. תבנית שיטת תבנית

במסגרות רבות, חלק ניכר מהקוד הוא קוד boilerplate.

לדוגמא, בעת ביצוע שאילתה במסד נתונים, יש להשלים את אותה סדרת השלבים:

  1. ליצור קשר
  2. לבצע שאילתה
  3. בצע ניקוי
  4. סגור את החיבור

שלבים אלה הם תרחיש אידיאלי עבור תבנית שיטת התבנית.

5.1. תבניות והתקשרות חוזרת

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

אנו יכולים ליצור תבנית במקרה של שאילתת מסד הנתונים שלנו:

מופשט ציבורי DatabaseQuery {public void execute () {חיבור חיבור = createConnection (); executeQuery (חיבור); closeConnection (חיבור); } חיבור מוגן createConnection () {// התחבר למסד נתונים ...} סגור ריק מוגן חיבור (חיבור חיבור) {// סגור חיבור ...} בטל מופשט ריק ריק executeQuery (חיבור חיבור); }

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

שיטת callback היא שיטה המאפשרת לנבדק לאותת ללקוח כי פעולה כלשהי רצויה הושלמה.

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

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

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

ממשק ציבורי ResultsMapper {מפת T ציבורית (תוצאות תוצאות); }

ואז אנו משנים את שלנו DatabaseQuery בכיתה כדי להשתמש בקריאה חוזרת זו:

מופשט ציבורי DatabaseQuery {ביצוע T ציבורי (שאילתת מחרוזת, מיפוי ResultsMapper) {חיבור קשר = createConnection (); תוצאות תוצאות = executeQuery (חיבור, שאילתה); closeConnection (חיבור); return mapper.map (תוצאות); ] תוצאות מוגנות executeQuery (חיבור חיבור, שאילתת מחרוזת) {// בצע שאילתה ...}}

מנגנון ההתקשרות הזה הוא בדיוק הגישה בה משתמש ספרינג עם ה- JdbcTemplate מעמד.

5.2. JdbcTemplate

ה JdbcTemplate הכיתה מספקת את שאילתא שיטה, המקבלת שאילתה חוּט ו ResultSetExtractor לְהִתְנַגֵד:

מחלקה ציבורית JdbcTemplate {שאילתת T ציבורית (final sql מחרוזת, סופי ResultSetExtractor rse) זורק DataAccessException {// ביצוע שאילתה ...} // שיטות אחרות ...}

ה ResultSetExtractor ממיר את ResultSet אובייקט - המייצג את תוצאת השאילתה - לאובייקט תחום מסוג ט:

@FunctionalInterface ממשק ציבורי ResultSetExtractor {T extractData (ResultSet rs) זורק SQLException, DataAccessException; }

האביב מפחית עוד יותר את קוד הדוד על ידי יצירת ממשקי קריאה ספציפיים יותר.

לדוגמא, ה RowMapper הממשק משמש להמרת שורה בודדת של נתוני SQL לאובייקט תחום מסוג ט.

ממשק ציבורי @FunctionalInterface RowMapper {T mapRow (ResultSet rs, int rowNum) זורק SQLException; }

כדי להתאים את RowMapper ממשק למצופה ResultSetExtractor, האביב יוצר את RowMapperResultSetExtractor מעמד:

מחלקה ציבורית JdbcTemplate {שאילתת רשימה ציבורית (String sql, RowMapper rowMapper) זורקת DataAccessException {תוצאת החזרה (שאילתה (sql, RowMapperResultSetExtractor חדש (rowMapper))); } // שיטות אחרות ...}

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

מחלקה ציבורית BookRowMapper מיישמת את RowMapper {@Override public Book mapRow (ResultSet rs, int rowNum) זורק SQLException {ספר ספר = ספר חדש (); book.setId (rs.getLong ("id")); book.setTitle (rs.getString ("כותרת")); book.setAuthor (rs.getString ("מחבר")); ספר החזרה; }}

בעזרת ממיר זה נוכל לשאול על מסד נתונים באמצעות ה- JdbcTemplate ולמפות כל שורה שנוצרה:

תבנית JdbcTemplate = // צור תבנית ... template.query ("בחר * מתוך ספרים", BookRowMapper חדש ());

מלבד ניהול מסדי נתונים של JDBC, אביב משתמש גם בתבניות ל:

  • שירות הודעות Java (JMS)
  • ממשק API של Java Persistence (JPA)
  • שינה (עכשיו הוצא משימוש)
  • עסקאות

6. מסקנה

במדריך זה בדקנו ארבעה מדפוסי העיצוב הנפוצים ביותר המיושמים במסגרת האביב.

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

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